// 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, // 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); });