|
|
// Script to gather the shared library from disk and also
// from memory utilizing Frida. After reading the file from
// disk, it will then compare some sections of the file in
// order to hunt and identify potentially modified and hooked
// functions.
//
// Re-written over the ages for usage while
// unpacking Android applications by
// Tim 'diff' Strazzere, <tim -at- corellium.com> <diff -at- protonmail.com>
// Based off older code and concepts from lich4/lichao890427
//
// Corresponding blog https://corellium.com/blog/android-frida-finding-hooks
// Helper function for creating a native function for usage
function getNativeFunction(name, ret, args) { var mod = Module.findExportByName(null, name); if (mod === null) { return null; }
var func = new NativeFunction(mod, ret, args); if (typeof func === 'undefined') { return null; }
return func; }
var open_ptr = getNativeFunction('open', 'int', ['pointer', 'int', 'int']); var read_ptr = getNativeFunction('read', 'int', ['int', 'pointer', 'int']); var close_ptr = getNativeFunction('close', 'int', ['int']); var lseek_ptr = getNativeFunction('lseek', 'int', ['int', 'int', 'int']);
function getElfData(module) { console.log('Processing ', module.path); if (module.sections) { return true; }
var fd = open_ptr(Memory.allocUtf8String(module.path), 0 /* O_RDONLY */, 0); if (fd == -1) { return false; }
// Get elf header
var header = Memory.alloc(64); lseek_ptr(fd, 0, 0 /* SEEK_SET */); read_ptr(fd, header, 64);
// Allow for both 32bit and 64bit binaries
var is32 = Memory.readU8(header.add(4)) === 1; module.is32 = is32;
// Parse section headers
var sectionHeaderOffset = is32 ? Memory.readU32(header.add(32)) : Memory.readU64(header.add(40)).toNumber(); // For some reason this is read as a string
var sectionHeaderSize = is32 ? Memory.readU16(header.add(46)) : Memory.readU16(header.add(58)); var sectionHeaderCount = is32 ? Memory.readU16(header.add(48)) : Memory.readU16(header.add(60)); var sectionHeaderStringTableIndex = is32 ? Memory.readU16(header.add(50)) : Memory.readU16(header.add(62));
var sectionHeaders = Memory.alloc(sectionHeaderSize * sectionHeaderCount);
lseek_ptr(fd, sectionHeaderOffset, 0 /* SEEK_SET */); read_ptr(fd, sectionHeaders, sectionHeaderSize * sectionHeaderCount);
var stringTableOffset = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 16)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 24)).toNumber(); var stringTableSize = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 20)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 32)).toNumber();
var stringTable = Memory.alloc(stringTableSize); lseek_ptr(fd, stringTableOffset, 0 /* SEEK_SET */); read_ptr(fd, stringTable, stringTableSize); var sections = [];
var dynsym = undefined; var dynstr = undefined; var relplt = undefined; var reldyn = undefined; for (var i = 0; i < sectionHeaderCount; i++) { var sectionName = Memory.readUtf8String(stringTable.add(Memory.readU32(sectionHeaders.add(i * sectionHeaderSize)))); var sectionAddress = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 12)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 16)).toNumber(); var sectionOffset = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 16)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 24)).toNumber(); var sectionSize = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 20)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 32)).toNumber();
if (['.text', '.rodata', '.got', '.got.plt'].includes(sectionName)) { var section = {}; section.name = sectionName; section.memoryOffset = sectionAddress; section.fileOffset = sectionOffset; section.size = sectionSize; if (sectionSize > 0) { section.data = Memory.alloc(sectionSize); lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */); read_ptr(fd, section.data, sectionSize); } else { section.data = undefined; } sections.push(section); } else if (['.dynsym', '.dynstr', '.rel.dyn', '.rel.plt'].includes(sectionName)) { var section = {}; section.name = sectionName; section.memoryOffset = sectionAddress; section.fileOffset = sectionOffset; section.size = sectionSize; if (sectionSize > 0) { section.data = Memory.alloc(sectionSize); lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */); read_ptr(fd, section.data, sectionSize); } else { console.log('No data section for', section.name); section.data = undefined; }
if (section.name === '.dynsym') { dynsym = section; } if (section.name === '.dynstr') { dynstr = section; } if (section.name === '.rel.dyn') { reldyn = section; } if (section.name === '.rel.plt') { relplt = section; } sections.push(section); } }
if (!!dynsym && !!dynstr) { var symbols = []; var stringTable = module.base.add(dynstr.memoryOffset); var structSize = is32 ? 16 : 24; for (var i = 0; i < dynsym.size / structSize; i++) { var symbolOffset = Memory.readU32(module.base.add(dynsym.memoryOffset).add(structSize * i)); symbols.push(Memory.readUtf8String(stringTable.add(symbolOffset))); }
module.symbols = symbols; }
var relmap = new Map(); if (!!reldyn) { for (var i = 0; i < reldyn.size / 8; i++) { if ((Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8)) != 0) && (Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) { relmap[Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8; } } }
if (!!relplt) { for (var i = 0; i < relplt.size / 8; i++) { if ((Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8)) != 0) && (Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) { relmap[Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8; } } } module.relmap = relmap;
module.sections = sections; return true; }
function findHooks(module) { if (module.sections === undefined) { if (!getElfData(module)) { return undefined; } }
module.sections.forEach((section) => { if (section.size === 0) { return; }
// It's important to cast the ArrayBuffer returned by `readByteArray` cannot be referenced incrementally
var file = new Uint8Array(Memory.readByteArray(section.data, section.size)); var memory = new Uint8Array(Memory.readByteArray(module.base.add(section.memoryOffset), section.size)); for (var i = 0; i < section.size;) { if (['.rodata', '.text'].includes(section.name)) { if (file[i] != memory[i]) { console.log('*** Potential variance found at ', DebugSymbol.fromAddress(module.base.add(section.memoryOffset).add(i))); i += 4; } i++ } else if (['.got'].includes(section.name)) { break; // It shouldn't be as the got table isn't initialized until execution
if (file[i] != memory[i]) { // todo compare the symbol to string against what it resolves too
} i += module.is32 ? 4 : 8; } else { // Unscanned sections, to be added as needed
break; } } }); }
// Quick and simple way to get the package name, assumes that the script
// was injected into an APK otherwise it won't work.
function getPackageName() { var fd = open_ptr(Memory.allocUtf8String('/proc/self/cmdline'), 0 /* O_RDONLY */, 0); if (fd == -1) { return 'null'; }
var buffer = Memory.alloc(32); read_ptr(fd, buffer, 32); close_ptr(fd);
return Memory.readUtf8String(buffer); }
// Adjust this as needed, often I don't need to scan anything outside of the
// included shared libraries and a few which are almost always in an apex folder.
// This logic will need to be changed if you're using a pre-apex version of Android
// to ensure it picked up the proper libraries for hunting
//
// While it doesn't hurt to scan everything, it's almost never needed and will just slow
// down the process at a linear scale.
//
// If you already know what you're hunting for, feel free to just return or look for
// libart, libdvm, etc, etc
function getRelevantModules() { var modules = []; var packagename = getPackageName();
Process.enumerateModules().forEach((module) => { if (module.path.includes(packagename)) { modules.push(module); console.log('Adding ', module.path); } else if (module.path.includes('/apex')) { modules.push(module); console.log('Adding ', module.path); } else { console.log('Skipping ', module.path); } })
return modules; }
var modules = getRelevantModules();
modules.forEach((module) => { getElfData(module); findHooks(module); });
|