librarySurgery

This commit is contained in:
isPointer
2026-09-07 18:47:07 +06:00
parent 5f9c833473
commit 978a4b12ce
4 changed files with 446 additions and 0 deletions
+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,340 @@
package com.antik.librarySurgery;
import com.antik.ui.banner;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class LibSurgery {
public record PatchResult(boolean success, String message) {}
public static List<File> getInstalledApks(String packageName) {
String out = Memory.runRootCmd("pm path " + packageName);
List<File> apks = new ArrayList<>();
for (String l : out.split("\r?\n")) {
String trimmed = l.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");
Memory.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;
}
public static PatchResult patchSingleLib(byte[] data, byte[] memDump) {
if (data.length < 0x40 || data[0] != 0x7f || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') {
return new PatchResult(false, "Invalid ELF header");
}
ByteBuffer buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
long ePhOff = buf.getLong(0x20);
int ePhNum = buf.getShort(0x38) & 0xFFFF;
Long dynOff = null;
Long dynSz = null;
for (int i = 0; i < ePhNum; i++) {
int o = (int) (ePhOff + i * 56L);
if (o + 56 > data.length) break;
int pType = buf.getInt(o);
if (pType == 2) { // PT_DYNAMIC
dynOff = buf.getLong(o + 8);
dynSz = buf.getLong(o + 32);
break;
}
}
if (dynOff == null) {
return new PatchResult(false, "No PT_DYNAMIC header found");
}
Long strtabVa = null;
int dynOffInt = dynOff.intValue();
int dynSzInt = dynSz.intValue();
for (int j = 0; j < dynSzInt; j += 16) {
int entryPos = dynOffInt + j;
if (entryPos + 16 > data.length) break;
long t = buf.getLong(entryPos);
long v = buf.getLong(entryPos + 8);
if (t == 5) { // DT_STRTAB
strtabVa = v;
break;
}
}
Long strtabOff = null;
if (strtabVa != null) {
for (int i = 0; i < ePhNum; i++) {
int o = (int) (ePhOff + i * 56L);
if (o + 56 > data.length) break;
int pt = buf.getInt(o);
long pVa = buf.getLong(o + 16);
long pFsz = buf.getLong(o + 32);
if (pt == 1 && pVa <= strtabVa && strtabVa < pVa + pFsz) { // PT_LOAD
long pOff = buf.getLong(o + 8);
strtabOff = pOff + (strtabVa - pVa);
break;
}
}
}
Long initVa = null;
List<long[]> newEntries = new ArrayList<>();
boolean hasPairip = false;
for (int j = 0; j < dynSzInt; j += 16) {
int entryPos = dynOffInt + j;
if (entryPos + 16 > data.length) break;
long t = buf.getLong(entryPos);
long v = buf.getLong(entryPos + 8);
if (t == 0) { // DT_NULL
break;
}
if (t == 12) { // DT_INIT
initVa = v;
continue;
}
if (t == 1 && strtabOff != null) { // DT_NEEDED
int strPos = (int) (strtabOff + v);
String name = readNullTerminatedString(data, strPos);
if (name.toLowerCase().contains("pairip")) {
hasPairip = true;
continue;
}
}
newEntries.add(new long[]{t, v});
}
if (!hasPairip && initVa == null) {
return new PatchResult(false, "Not protected by PairIP");
}
newEntries.add(new long[]{0, 0});
for (int k = 0; k < newEntries.size(); k++) {
int pos = dynOffInt + k * 16;
if (pos + 16 <= data.length) {
buf.putLong(pos, newEntries.get(k)[0]);
buf.putLong(pos + 8, newEntries.get(k)[1]);
}
}
for (int k = newEntries.size() * 16; k < dynSzInt; k += 16) {
int pos = dynOffInt + k;
if (pos + 16 <= data.length) {
buf.putLong(pos, 0L);
buf.putLong(pos + 8, 0L);
}
}
if (initVa != null) {
for (int i = 0; i < ePhNum; i++) {
int o = (int) (ePhOff + i * 56L);
if (o + 56 > data.length) break;
int pt = buf.getInt(o);
long pVa = buf.getLong(o + 16);
long pMsz = buf.getLong(o + 40);
if (pt == 1 && pVa <= initVa && initVa < pVa + pMsz) {
buf.putInt(o, 0); // PT_NULL
}
}
}
byte[] oldStr = "ExecuteProgram\0".getBytes(StandardCharsets.US_ASCII);
byte[] newStr = "memset\0\0\0\0\0\0\0\0\0".getBytes(StandardCharsets.US_ASCII);
int posStr = indexOf(data, oldStr);
if (posStr != -1 && posStr + newStr.length <= data.length) {
System.arraycopy(newStr, 0, data, posStr, newStr.length);
}
int pos4af0 = 0x4af0;
byte[] arm64Stub = new byte[] {
(byte) 0xc0, 0x00, (byte) 0x80, 0x52,
0x20, 0x00, (byte) 0xa0, 0x72,
(byte) 0xc0, 0x03, 0x5f, (byte) 0xd6
};
if (data.length > pos4af0 + arm64Stub.length) {
System.arraycopy(arm64Stub, 0, data, pos4af0, arm64Stub.length);
}
if (memDump != null && memDump.length > 0) {
int copyLen = Math.min(data.length, memDump.length);
System.arraycopy(memDump, 0, data, 0, copyLen);
}
return new PatchResult(true, "Successfully patched");
}
private static String readNullTerminatedString(byte[] data, int offset) {
if (offset < 0 || offset >= data.length) return "";
int end = offset;
while (end < data.length && data[end] != 0) {
end++;
}
return new String(data, offset, end - offset, StandardCharsets.US_ASCII);
}
private static int indexOf(byte[] array, byte[] target) {
if (target.length == 0) return 0;
outer:
for (int i = 0; i <= array.length - target.length; i++) {
for (int j = 0; j < target.length; j++) {
if (array[i + j] != target[j]) {
continue outer;
}
}
return i;
}
return -1;
}
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 = getInstalledApks(packageName);
Map<String, File> extractedLibs = extractArm64Libs(apks, tmpDir);
String pid = Memory.waitForProcess(packageName);
String rxAddr = Memory.findRxAddress(pid);
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 = (libName.contains("mjpg") || libName.contains("unity") || libName.contains("il2cpp"))
? memDumpData : null;
PatchResult res = 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) {
deleteDirectory(tmpDir);
}
}
}
private static void deleteDirectory(File dir) {
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();
}
}
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,98 @@
package com.antik.librarySurgery;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.file.Files;
public class Memory {
public static String runRootCmd(String cmd) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", cmd});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder sb = new StringBuilder();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
sb.append(line).append("\n");
}
process.waitFor();
return sb.toString().trim();
} catch (Exception e) {
return "";
}
}
public static String getPidForPackage(String packageName) {
String pidOut = runRootCmd("ps -A | grep " + packageName);
if (pidOut.isEmpty()) {
pidOut = runRootCmd("pidof " + packageName);
if (!pidOut.isEmpty()) {
String[] pids = pidOut.trim().split("\\s+");
return pids[0];
}
return null;
}
for (String line : pidOut.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) {}
}
}
public static String findRxAddress(String pid) {
String mapsOut = runRootCmd("grep 'r-xp' /proc/" + pid + "/maps");
for (String line : mapsOut.split("\r?\n")) {
if (line.contains("apk") || line.contains("lib")) {
String[] parts = line.trim().split("\\s+");
if (parts.length > 0) {
return parts[0].split("-")[0];
}
}
}
return null;
}
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 + ")");
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());
}
}
runRootCmd("rm -f " + dumpPath);
return data;
}
}
@@ -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> [-s <device_serial>]");
}
}