2 Commits
Author SHA1 Message Date
isPointer 19a8b07717 librarySurgery 2026-09-07 18:47:15 +06:00
isPointer 978a4b12ce librarySurgery 2026-09-07 18:47:07 +06:00
94 changed files with 1043 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/antik" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
This is a resource file for the antik project.
Project: RePairip
Subproject: antik
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.antik.Main
@@ -129,7 +129,7 @@ public class MethodMaker {
public static ImmutableMethod createClinit() {
MethodImplementationBuilder builder = new MethodImplementationBuilder(1);
builder.addInstruction(new BuilderInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference("RePatcher v1.5.20")));
builder.addInstruction(new BuilderInstruction21c(Opcode.CONST_STRING, 0, new ImmutableStringReference("RePatcher v1.6.30")));
builder.addInstruction(new BuilderInstruction35c(Opcode.INVOKE_STATIC, 0, 0, 0, 0, 0, 0, new ImmutableMethodReference(PairipClass.STARTUP_LAUNCHER.type, "launch", null, "V")));
builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
return new ImmutableMethod(PairipClass.APPLICATION.type, "<clinit>", null, "V", AccessFlags.STATIC.getValue() | AccessFlags.CONSTRUCTOR.getValue(), null, null, builder.getMethodImplementation());
+7
View File
@@ -18,6 +18,13 @@ import java.nio.file.Files;
public class Main {
public static void main(String[] args) {
for (String arg : args) {
if ("-p".equals(arg) || "--package".equals(arg)) {
com.antik.librarySurgery.LibSurgery.main(args);
return;
}
}
if (args.length < 2) {
help.help();
return;
@@ -0,0 +1,26 @@
package com.antik.librarySurgery;
import java.io.File;
public class FileUtils {
public static void deleteDirectory(File dir) {
if (dir == null || !dir.exists()) return;
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
deleteDirectory(f);
} else {
if (!f.delete()) {
f.deleteOnExit();
}
}
}
}
boolean deleted = dir.delete();
if (!deleted) {
dir.deleteOnExit();
}
}
}
@@ -0,0 +1,97 @@
package com.antik.librarySurgery;
import com.antik.librarySurgery.memory.Memory;
import com.antik.librarySurgery.memory.MemoryRegion;
import com.antik.librarySurgery.process.ProcessFinder;
import com.antik.ui.banner;
import java.io.File;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
public class LibSurgery {
public static void process(String packageName, String outDirStr) {
banner.banner();
File outDir = new File(outDirStr);
if (!outDir.exists()) {
boolean created = outDir.mkdirs();
if (!created && !outDir.exists()) {
System.err.println("[ERROR] Could not create output directory : " + outDirStr);
}
}
File tmpDir = null;
try {
tmpDir = Files.createTempDirectory("pairip_surgery_").toFile();
List<File> apks = LibraryExtractor.getInstalledApks(packageName);
Map<String, File> extractedLibs = LibraryExtractor.extractArm64Libs(apks, tmpDir);
String pid = ProcessFinder.waitForProcess(packageName);
MemoryRegion<Long> rxRegion = Memory.findRxRegion(pid);
String rxAddr = rxRegion != null ? Long.toHexString(rxRegion.getStartAddress()) : null;
String dumpedPath = rxRegion != null ? rxRegion.getPathname() : "";
String dumpedLibName = !dumpedPath.isEmpty() ? new File(dumpedPath).getName() : "";
byte[] memDumpData = Memory.dumpMemory(pid, rxAddr);
int patchedCount = 0;
for (Map.Entry<String, File> entry : extractedLibs.entrySet()) {
String libName = entry.getKey();
File libPath = entry.getValue();
if ("libpairipcore.so".equals(libName)) {
continue;
}
byte[] data = Files.readAllBytes(libPath.toPath());
byte[] dumpToUse = (dumpedLibName.isEmpty() || libName.equalsIgnoreCase(dumpedLibName) || dumpedLibName.contains(libName))
? memDumpData : null;
PatchResult res = LibraryPatcher.patchSingleLib(data, dumpToUse);
if (res.success()) {
String outName = libName.replace(".so", "") + "_patched.so";
File outPath = new File(outDir, outName);
Files.write(outPath.toPath(), data);
System.out.println("[BUILD] " + libName + " -> Saved " + outName + " (" + data.length + " bytes)");
patchedCount++;
} else {
System.out.println("[SKIP] " + libName + " : " + res.message());
}
}
System.out.println("[BUILD] Process completed (" + patchedCount + " libraries patched)");
} catch (Exception e) {
System.err.println("[ERROR] Process failed : " + e.getMessage());
} finally {
if (tmpDir != null) {
FileUtils.deleteDirectory(tmpDir);
}
}
}
public static void main(String[] args) {
String packageName = null;
String outDir = null;
for (int i = 0; i < args.length; i++) {
if (("-p".equals(args[i]) || "--package".equals(args[i])) && i + 1 < args.length) {
packageName = args[++i];
} else if (("-o".equals(args[i]) || "--outdir".equals(args[i])) && i + 1 < args.length) {
outDir = args[++i];
}
}
if (packageName == null || outDir == null) {
System.out.println("Usage: java -jar RePairip.jar -p <package_name> -o <out_dir>");
System.exit(1);
}
process(packageName, outDir);
}
}
@@ -0,0 +1,76 @@
package com.antik.librarySurgery;
import com.antik.librarySurgery.shell.Shell;
import com.antik.librarySurgery.shell.ShellResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class LibraryExtractor {
public static List<File> getInstalledApks(String packageName) {
ShellResult result = Shell.runRootCmd("pm path " + packageName);
List<File> apks = new ArrayList<>();
if (result.isSuccess() && !result.getStdout().isEmpty()) {
for (String line : result.getStdout().split("\r?\n")) {
String trimmed = line.trim();
if (trimmed.startsWith("package:")) {
String apkPath = trimmed.replace("package:", "").trim();
File f = new File(apkPath);
if (f.exists()) {
apks.add(f);
} else {
File tmpCopy = new File("/data/local/tmp/temp_base.apk");
Shell.runRootCmd("cp " + apkPath + " " + tmpCopy.getAbsolutePath() + " && chmod 666 " + tmpCopy.getAbsolutePath());
if (tmpCopy.exists()) {
apks.add(tmpCopy);
}
}
}
}
}
if (apks.isEmpty()) {
System.err.println("[ERROR] Package not installed or APK paths unavailable : " + packageName);
System.exit(1);
}
System.out.println("[INFO] Found " + apks.size() + " APK path(s) for package : " + packageName);
return apks;
}
public static Map<String, File> extractArm64Libs(List<File> apks, File tmpDir) {
Map<String, File> extractedLibs = new HashMap<>();
for (File apkFile : apks) {
try (ZipFile zip = new ZipFile(apkFile)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String fname = entry.getName();
if (fname.endsWith(".so") && fname.contains("arm64")) {
String libName = new File(fname).getName();
if (!extractedLibs.containsKey(libName)) {
File extPath = new File(tmpDir, libName);
try (InputStream is = zip.getInputStream(entry);
OutputStream os = new FileOutputStream(extPath)) {
is.transferTo(os);
}
extractedLibs.put(libName, extPath);
}
}
}
} catch (Exception e) {
System.err.println("[ERROR] Extracting native libs failed : " + e.getMessage());
}
}
System.out.println("[INFO] Extracted " + extractedLibs.size() + " ARM64 native library/libraries");
return extractedLibs;
}
}
@@ -0,0 +1,129 @@
package com.antik.librarySurgery;
import com.antik.librarySurgery.elf.DynamicEntry;
import com.antik.librarySurgery.elf.Elf;
import com.antik.librarySurgery.elf.ElfConstants;
import com.antik.librarySurgery.elf.ProgramHeader;
import com.antik.librarySurgery.io.ByteReader;
import com.antik.librarySurgery.io.ByteWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LibraryPatcher {
public static PatchResult patchSingleLib(byte[] data, byte[] memDump) {
Elf elf = new Elf(data);
if (!elf.getHeader().isValid()) {
return new PatchResult(false, "Invalid ELF header");
}
ProgramHeader dynamicHeader = elf.getDynamicHeader();
if (dynamicHeader == null) {
return new PatchResult(false, "No PT_DYNAMIC header found");
}
ByteReader reader = elf.getReader();
ByteWriter writer = new ByteWriter(data);
int dynOff = (int) dynamicHeader.getPOffset();
int dynSz = (int) dynamicHeader.getPFileSz();
Long initVa = null;
Long initArrayVa = null;
Long initArraySz = null;
List<DynamicEntry> newEntries = new ArrayList<>();
boolean hasPairip = false;
for (int j = 0; j < dynSz; j += 16) {
int entryPos = dynOff + j;
if (entryPos + 16 > reader.length()) break;
long tag = reader.readUInt64(entryPos);
long val = reader.readUInt64(entryPos + 8);
if (tag == ElfConstants.DT_NULL) {
break;
}
if (tag == ElfConstants.DT_INIT) {
initVa = val;
continue;
}
if (tag == ElfConstants.DT_INIT_ARRAY) {
initArrayVa = val;
continue;
}
if (tag == ElfConstants.DT_INIT_ARRAYSZ) {
initArraySz = val;
continue;
}
if (tag == ElfConstants.DT_NEEDED) {
String name = elf.readStringFromStrtab(val);
if (name.toLowerCase().contains("pairip")) {
hasPairip = true;
continue;
}
}
newEntries.add(new DynamicEntry(tag, val));
}
int posStr = reader.indexOf(ElfConstants.OLD_EXECUTE_PROGRAM);
boolean isPairipProtected = hasPairip || posStr != -1;
if (!isPairipProtected) {
return new PatchResult(false, "Not protected by PairIP");
}
newEntries.add(new DynamicEntry(ElfConstants.DT_NULL, 0L));
for (int k = 0; k < newEntries.size(); k++) {
int pos = dynOff + k * 16;
if (pos + 16 <= reader.length()) {
writer.writeUInt64(pos, newEntries.get(k).getTag());
writer.writeUInt64(pos + 8, newEntries.get(k).getVal());
}
}
writer.zeroRange(dynOff + newEntries.size() * 16, dynOff + dynSz);
if (initVa != null) {
for (ProgramHeader ph : elf.getProgramHeaders()) {
if (ph.getPType() == ElfConstants.PT_LOAD && ph.getPVaddr() <= initVa && initVa < ph.getPVaddr() + ph.getPMemSz()) {
writer.writeUInt32(ph.getHeaderOffset(), ElfConstants.PT_NULL);
}
}
}
if (initArrayVa != null && initArraySz != null) {
Long initArrayOff = elf.vaddrToOffset(initArrayVa);
if (initArrayOff != null) {
int arrayPos = initArrayOff.intValue();
int arraySize = initArraySz.intValue();
if (arrayPos >= 0 && arrayPos + arraySize <= reader.length()) {
writer.zeroRange(arrayPos, arrayPos + arraySize);
}
}
}
if (posStr != -1) {
writer.writeBytes(posStr, ElfConstants.NEW_MEMSET);
}
Long jniOnLoadOffset = elf.findSymbolAddress("JNI_OnLoad");
if (jniOnLoadOffset != null) {
int stubPos = jniOnLoadOffset.intValue();
if (stubPos >= 0 && stubPos + ElfConstants.ARM64_STUB_BYTES.length <= reader.length()) {
writer.writeBytes(stubPos, ElfConstants.ARM64_STUB_BYTES);
}
} else {
System.out.println("[INFO] JNI_OnLoad symbol not found in ELF, skipping stub patch safely.");
}
if (memDump != null && memDump.length > 0) {
int copyLen = Math.min(reader.length(), memDump.length);
writer.writeBytes(0, Arrays.copyOf(memDump, copyLen));
}
return new PatchResult(true, "Successfully patched");
}
}
@@ -0,0 +1,3 @@
package com.antik.librarySurgery;
public record PatchResult(boolean success, String message) {}
@@ -0,0 +1,20 @@
package com.antik.librarySurgery.elf;
public class DynamicEntry {
private final long tag;
private final long val;
public DynamicEntry(long tag, long val) {
this.tag = tag;
this.val = val;
}
public long getTag() {
return tag;
}
public long getVal() {
return val;
}
}
@@ -0,0 +1,120 @@
package com.antik.librarySurgery.elf;
import com.antik.librarySurgery.io.ByteReader;
import java.util.ArrayList;
import java.util.List;
public class Elf {
private final ByteReader reader;
private final ElfHeader header;
private final List<ProgramHeader> programHeaders = new ArrayList<>();
private ProgramHeader dynamicHeader = null;
private Long strtabVa = null;
private Long strtabOff = null;
private Long symtabVa = null;
private Long symtabOff = null;
public Elf(byte[] data) {
this.reader = new ByteReader(data);
this.header = new ElfHeader(reader);
if (header.isValid()) {
parseProgramHeaders();
parseDynamicSection();
}
}
private void parseProgramHeaders() {
long phOff = header.getPhOff();
int phNum = header.getPhNum();
for (int i = 0; i < phNum; i++) {
int offset = (int) (phOff + i * 56L);
if (offset + 56 > reader.length()) break;
ProgramHeader ph = new ProgramHeader(reader, offset);
programHeaders.add(ph);
if (ph.getPType() == ElfConstants.PT_DYNAMIC) {
this.dynamicHeader = ph;
}
}
}
private void parseDynamicSection() {
if (dynamicHeader == null) return;
int dynOff = (int) dynamicHeader.getPOffset();
int dynSz = (int) dynamicHeader.getPFileSz();
for (int j = 0; j < dynSz; j += 16) {
int pos = dynOff + j;
if (pos + 16 > reader.length()) break;
long tag = reader.readUInt64(pos);
long val = reader.readUInt64(pos + 8);
if (tag == ElfConstants.DT_STRTAB) {
this.strtabVa = val;
} else if (tag == ElfConstants.DT_SYMTAB) {
this.symtabVa = val;
}
}
if (strtabVa != null) {
this.strtabOff = vaddrToOffset(strtabVa);
}
if (symtabVa != null) {
this.symtabOff = vaddrToOffset(symtabVa);
}
}
public Long vaddrToOffset(long vaddr) {
for (ProgramHeader ph : programHeaders) {
if (ph.getPType() == ElfConstants.PT_LOAD && ph.getPVaddr() <= vaddr && vaddr < ph.getPVaddr() + ph.getPMemSz()) {
return ph.getPOffset() + (vaddr - ph.getPVaddr());
}
}
return null;
}
public Long findSymbolAddress(String symbolName) {
if (symtabOff == null || strtabOff == null) return null;
int symOff = symtabOff.intValue();
int maxSyms = 3000;
for (int i = 0; i < maxSyms; i++) {
int entryPos = symOff + i * 24;
if (entryPos + 24 > reader.length()) break;
long nameIdx = reader.readUInt32(entryPos);
long value = reader.readUInt64(entryPos + 8);
if (nameIdx > 0) {
String name = reader.readNullTerminatedString((int) (strtabOff + nameIdx));
if (symbolName.equals(name)) {
return vaddrToOffset(value);
}
}
}
return null;
}
public ByteReader getReader() {
return reader;
}
public ElfHeader getHeader() {
return header;
}
public List<ProgramHeader> getProgramHeaders() {
return programHeaders;
}
public ProgramHeader getDynamicHeader() {
return dynamicHeader;
}
public Long getStrtabOff() {
return strtabOff;
}
public String readStringFromStrtab(long offsetVal) {
if (strtabOff == null) return "";
return reader.readNullTerminatedString((int) (strtabOff + offsetVal));
}
}
@@ -0,0 +1,27 @@
package com.antik.librarySurgery.elf;
public class ElfConstants {
public static final byte[] ELF_MAGIC = new byte[]{(byte) 0x7f, 'E', 'L', 'F'};
public static final int PT_NULL = 0;
public static final int PT_LOAD = 1;
public static final int PT_DYNAMIC = 2;
public static final long DT_NULL = 0L;
public static final long DT_NEEDED = 1L;
public static final long DT_STRTAB = 5L;
public static final long DT_SYMTAB = 6L;
public static final long DT_INIT = 12L;
public static final long DT_INIT_ARRAY = 25L;
public static final long DT_INIT_ARRAYSZ = 27L;
public static final byte[] OLD_EXECUTE_PROGRAM = "ExecuteProgram\0".getBytes(java.nio.charset.StandardCharsets.US_ASCII);
public static final byte[] NEW_MEMSET = "memset\0\0\0\0\0\0\0\0\0".getBytes(java.nio.charset.StandardCharsets.US_ASCII);
public static final byte[] ARM64_STUB_BYTES = new byte[]{
(byte) 0xc0, 0x00, (byte) 0x80, 0x52, // mov w0, #0
0x20, 0x00, (byte) 0xa0, 0x72, // movk w0, #0x10, lsl #16 (JNI_VERSION_1_6 = 0x00010000)
(byte) 0xc0, 0x03, 0x5f, (byte) 0xd6 // ret
};
}
@@ -0,0 +1,34 @@
package com.antik.librarySurgery.elf;
import com.antik.librarySurgery.io.ByteReader;
public class ElfHeader {
private final long phOff;
private final int phNum;
private final boolean valid;
public ElfHeader(ByteReader reader) {
if (!reader.checkMagic(ElfConstants.ELF_MAGIC)) {
this.valid = false;
this.phOff = 0L;
this.phNum = 0;
return;
}
this.phOff = reader.readUInt64(0x20);
this.phNum = reader.readUInt16(0x38);
this.valid = true;
}
public boolean isValid() {
return valid;
}
public long getPhOff() {
return phOff;
}
public int getPhNum() {
return phNum;
}
}
@@ -0,0 +1,46 @@
package com.antik.librarySurgery.elf;
import com.antik.librarySurgery.io.ByteReader;
public class ProgramHeader {
private final int headerOffset;
private final int pType;
private final long pOffset;
private final long pVaddr;
private final long pFileSz;
private final long pMemSz;
public ProgramHeader(ByteReader reader, int headerOffset) {
this.headerOffset = headerOffset;
this.pType = (int) reader.readUInt32(headerOffset);
this.pOffset = reader.readUInt64(headerOffset + 8);
this.pVaddr = reader.readUInt64(headerOffset + 16);
this.pFileSz = reader.readUInt64(headerOffset + 32);
this.pMemSz = reader.readUInt64(headerOffset + 40);
}
public int getHeaderOffset() {
return headerOffset;
}
public int getPType() {
return pType;
}
public long getPOffset() {
return pOffset;
}
public long getPVaddr() {
return pVaddr;
}
public long getPFileSz() {
return pFileSz;
}
public long getPMemSz() {
return pMemSz;
}
}
@@ -0,0 +1,70 @@
package com.antik.librarySurgery.io;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
public class ByteReader {
private final byte[] buffer;
private final ByteBuffer byteBuffer;
public ByteReader(byte[] buffer) {
this.buffer = buffer;
this.byteBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
}
public byte[] getBuffer() {
return buffer;
}
public int length() {
return buffer.length;
}
public int readUInt16(int offset) {
if (offset < 0 || offset + 2 > buffer.length) return 0;
return byteBuffer.getShort(offset) & 0xFFFF;
}
public long readUInt32(int offset) {
if (offset < 0 || offset + 4 > buffer.length) return 0L;
return byteBuffer.getInt(offset) & 0xFFFFFFFFL;
}
public long readUInt64(int offset) {
if (offset < 0 || offset + 8 > buffer.length) return 0L;
return byteBuffer.getLong(offset);
}
public String readNullTerminatedString(int offset) {
if (offset < 0 || offset >= buffer.length) return "";
int end = offset;
while (end < buffer.length && buffer[end] != 0) {
end++;
}
return new String(buffer, offset, end - offset, StandardCharsets.US_ASCII);
}
public int indexOf(byte[] target) {
if (target == null || target.length == 0) return -1;
outer:
for (int i = 0; i <= buffer.length - target.length; i++) {
for (int j = 0; j < target.length; j++) {
if (buffer[i + j] != target[j]) {
continue outer;
}
}
return i;
}
return -1;
}
public boolean checkMagic(byte[] magic) {
if (magic == null || buffer.length < magic.length) return false;
for (int i = 0; i < magic.length; i++) {
if (buffer[i] != magic[i]) return false;
}
return true;
}
}
@@ -0,0 +1,51 @@
package com.antik.librarySurgery.io;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ByteWriter {
private final byte[] buffer;
private final ByteBuffer byteBuffer;
public ByteWriter(byte[] buffer) {
this.buffer = buffer;
this.byteBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
}
public byte[] getBuffer() {
return buffer;
}
public void writeUInt16(int offset, int value) {
if (offset >= 0 && offset + 2 <= buffer.length) {
byteBuffer.putShort(offset, (short) value);
}
}
public void writeUInt32(int offset, long value) {
if (offset >= 0 && offset + 4 <= buffer.length) {
byteBuffer.putInt(offset, (int) value);
}
}
public void writeUInt64(int offset, long value) {
if (offset >= 0 && offset + 8 <= buffer.length) {
byteBuffer.putLong(offset, value);
}
}
public void writeBytes(int offset, byte[] data) {
if (data != null && offset >= 0 && offset + data.length <= buffer.length) {
System.arraycopy(data, 0, buffer, offset, data.length);
}
}
public void zeroRange(int startOffset, int endOffset) {
if (startOffset >= 0 && endOffset <= buffer.length && startOffset < endOffset) {
for (int i = startOffset; i < endOffset; i++) {
buffer[i] = 0;
}
}
}
}
@@ -0,0 +1,36 @@
package com.antik.librarySurgery.memory;
import com.antik.librarySurgery.shell.Shell;
import java.io.File;
import java.nio.file.Files;
public class Memory {
public static MemoryRegion<Long> findRxRegion(String pid) {
MemoryMap map = MemoryMap.parseFromProcess(pid);
return map.findRxRegion();
}
public static byte[] dumpMemory(String pid, String rxAddr) {
if (rxAddr == null || rxAddr.isEmpty()) {
return null;
}
String dumpPath = "/data/local/tmp/pairip_dump.bin";
System.out.println("[ROOT] Dumping decrypted .text memory from RAM (Address 0x" + rxAddr + ")");
Shell.runRootCmd("dd if=/proc/" + pid + "/mem of=" + dumpPath + " bs=1024 count=576 skip=$((0x" + rxAddr + "/1024))");
File dumpFile = new File(dumpPath);
byte[] data = null;
if (dumpFile.exists() && dumpFile.length() > 0) {
try {
data = Files.readAllBytes(dumpFile.toPath());
System.out.println("[ROOT] Captured " + data.length + " bytes decrypted memory dump");
} catch (Exception e) {
System.err.println("[ERROR] Reading memory dump failed : " + e.getMessage());
}
}
Shell.runRootCmd("rm -f " + dumpPath);
return data;
}
}
@@ -0,0 +1,58 @@
package com.antik.librarySurgery.memory;
import com.antik.librarySurgery.shell.Shell;
import com.antik.librarySurgery.shell.ShellResult;
import java.util.ArrayList;
import java.util.List;
public class MemoryMap {
private final List<MemoryRegion<Long>> regions = new ArrayList<>();
public static MemoryMap parseFromProcess(String pid) {
MemoryMap map = new MemoryMap();
ShellResult result = Shell.runRootCmd("grep 'r-xp' /proc/" + pid + "/maps");
if (!result.getStdout().isEmpty()) {
for (String line : result.getStdout().split("\r?\n")) {
String trimmed = line.trim();
String[] parts = trimmed.split("\\s+");
if (parts.length >= 1) {
String[] addrs = parts[0].split("-");
if (addrs.length == 2) {
try {
long start = Long.parseUnsignedLong(addrs[0], 16);
long end = Long.parseUnsignedLong(addrs[1], 16);
String perms = parts.length > 1 ? parts[1] : "";
String path = parts.length > 5 ? parts[5] : "";
map.regions.add(new MemoryRegion<>(start, end, perms, path));
} catch (NumberFormatException ignored) {}
}
}
}
}
return map;
}
public List<MemoryRegion<Long>> getRegions() {
return regions;
}
public MemoryRegion<Long> findRxRegion() {
for (MemoryRegion<Long> region : regions) {
String path = region.getPathname();
if (region.isExecutable() && (path.contains("apk") || path.contains("lib"))) {
return region;
}
}
return null;
}
public String findRxAddressString() {
MemoryRegion<Long> region = findRxRegion();
if (region != null) {
return Long.toHexString(region.getStartAddress());
}
return null;
}
}
@@ -0,0 +1,40 @@
package com.antik.librarySurgery.memory;
public class MemoryRegion<T extends Number> {
private final T startAddress;
private final T endAddress;
private final String permissions;
private final String pathname;
public MemoryRegion(T startAddress, T endAddress, String permissions, String pathname) {
this.startAddress = startAddress;
this.endAddress = endAddress;
this.permissions = permissions;
this.pathname = pathname;
}
public T getStartAddress() {
return startAddress;
}
public T getEndAddress() {
return endAddress;
}
public String getPermissions() {
return permissions;
}
public String getPathname() {
return pathname;
}
public boolean isExecutable() {
return permissions != null && permissions.contains("x");
}
public boolean isReadable() {
return permissions != null && permissions.contains("r");
}
}
@@ -0,0 +1,46 @@
package com.antik.librarySurgery.process;
import com.antik.librarySurgery.shell.Shell;
import com.antik.librarySurgery.shell.ShellResult;
public class ProcessFinder {
public static String getPidForPackage(String packageName) {
ShellResult result = Shell.runRootCmd("ps -A | grep " + packageName);
if (!result.isSuccess() || result.getStdout().isEmpty()) {
result = Shell.runRootCmd("pidof " + packageName);
if (result.isSuccess() && !result.getStdout().isEmpty()) {
String[] pids = result.getStdout().trim().split("\\s+");
return pids[0];
}
return null;
}
for (String line : result.getStdout().split("\r?\n")) {
String trimmed = line.trim();
if (trimmed.contains(packageName)) {
String[] parts = trimmed.split("\\s+");
if (parts.length >= 2) {
return parts[1];
}
}
}
return null;
}
public static String waitForProcess(String packageName) {
System.out.println("[INFO] Waiting for package : " + packageName);
System.out.println("[INFO] Please launch the app on your device ");
while (true) {
String pid = getPidForPackage(packageName);
if (pid != null && !pid.isEmpty()) {
System.out.println("[ROOT] App process detected with PID : " + pid);
return pid;
}
try {
Thread.sleep(1500);
} catch (InterruptedException ignored) {}
}
}
}
@@ -0,0 +1,45 @@
package com.antik.librarySurgery.shell;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Shell {
public static ShellResult runRootCmd(String command) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", command});
BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder outSb = new StringBuilder();
for (String line = outReader.readLine(); line != null; line = outReader.readLine()) {
outSb.append(line).append("\n");
}
StringBuilder errSb = new StringBuilder();
for (String line = errReader.readLine(); line != null; line = errReader.readLine()) {
errSb.append(line).append("\n");
}
int exitCode = process.waitFor();
return new ShellResult(exitCode, outSb.toString(), errSb.toString());
} catch (Exception e) {
return new ShellResult(-1, "", e.getMessage());
}
}
public static ShellResult runCmd(String command) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", command});
BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder outSb = new StringBuilder();
for (String line = outReader.readLine(); line != null; line = outReader.readLine()) {
outSb.append(line).append("\n");
}
int exitCode = process.waitFor();
return new ShellResult(exitCode, outSb.toString(), "");
} catch (Exception e) {
return runRootCmd(command);
}
}
}
@@ -0,0 +1,30 @@
package com.antik.librarySurgery.shell;
public class ShellResult {
private final int exitCode;
private final String stdout;
private final String stderr;
public ShellResult(int exitCode, String stdout, String stderr) {
this.exitCode = exitCode;
this.stdout = stdout != null ? stdout.trim() : "";
this.stderr = stderr != null ? stderr.trim() : "";
}
public int getExitCode() {
return exitCode;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
public boolean isSuccess() {
return exitCode == 0;
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ public class banner {
System.out.println("░█▀▄░█▀▀░█▀█░█▀█░▀█▀░█▀▄░▀█▀░█▀█░");
System.out.println("░█▀▄░█▀▀░█▀▀░█▀█░░█░░█▀▄░░█░░█▀▀░");
System.out.println("░▀░▀░▀▀▀░▀░░░▀░▀░▀▀▀░▀░▀░▀▀▀░▀░░░");
System.out.println("Version : 1.5.20");
System.out.println("Version : 1.6.30");
System.out.println("--------------------------------------");
System.out.println("Dev : Antik");
System.out.println("Channel : https://t.me/RevDex");
@@ -6,5 +6,6 @@ public class help {
System.out.println("java -jar RePairip.jar -i <input.apks>");
System.out.println("java -jar RePairip.jar -i <input.apks> -r <lazymod>");
System.out.println("java -jar RePairip.jar -i <merged.apk> -t <pairip.json>");
System.out.println("java -jar RePairip.jar -p <package_name> -o <out_dir>");
}
}