mirror of
https://github.com/ispointer/RePairip.git
synced 2026-09-14 12:03:39 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19a8b07717 | ||
|
|
978a4b12ce |
Generated
+3
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
+6
@@ -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>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+17
@@ -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>
|
||||
Generated
+8
@@ -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>
|
||||
Generated
+10
@@ -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>
|
||||
Generated
+17
@@ -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
@@ -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.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user