RE env for inspecting APKs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
9.9 KiB

  1. // Script to gather the shared library from disk and also
  2. // from memory utilizing Frida. After reading the file from
  3. // disk, it will then compare some sections of the file in
  4. // order to hunt and identify potentially modified and hooked
  5. // functions.
  6. //
  7. // Re-written over the ages for usage while
  8. // unpacking Android applications by
  9. // Tim 'diff' Strazzere, <tim -at- corellium.com> <diff -at- protonmail.com>
  10. // Based off older code and concepts from lich4/lichao890427
  11. //
  12. // Corresponding blog https://corellium.com/blog/android-frida-finding-hooks
  13. // Helper function for creating a native function for usage
  14. function getNativeFunction(name, ret, args) {
  15. var mod = Module.findExportByName(null, name);
  16. if (mod === null) {
  17. return null;
  18. }
  19. var func = new NativeFunction(mod, ret, args);
  20. if (typeof func === 'undefined') {
  21. return null;
  22. }
  23. return func;
  24. }
  25. var open_ptr = getNativeFunction('open', 'int', ['pointer', 'int', 'int']);
  26. var read_ptr = getNativeFunction('read', 'int', ['int', 'pointer', 'int']);
  27. var close_ptr = getNativeFunction('close', 'int', ['int']);
  28. var lseek_ptr = getNativeFunction('lseek', 'int', ['int', 'int', 'int']);
  29. function getElfData(module) {
  30. console.log('Processing ', module.path);
  31. if (module.sections) {
  32. return true;
  33. }
  34. var fd = open_ptr(Memory.allocUtf8String(module.path), 0 /* O_RDONLY */, 0);
  35. if (fd == -1) {
  36. return false;
  37. }
  38. // Get elf header
  39. var header = Memory.alloc(64);
  40. lseek_ptr(fd, 0, 0 /* SEEK_SET */);
  41. read_ptr(fd, header, 64);
  42. // Allow for both 32bit and 64bit binaries
  43. var is32 = Memory.readU8(header.add(4)) === 1;
  44. module.is32 = is32;
  45. // Parse section headers
  46. var sectionHeaderOffset = is32 ? Memory.readU32(header.add(32)) : Memory.readU64(header.add(40)).toNumber(); // For some reason this is read as a string
  47. var sectionHeaderSize = is32 ? Memory.readU16(header.add(46)) : Memory.readU16(header.add(58));
  48. var sectionHeaderCount = is32 ? Memory.readU16(header.add(48)) : Memory.readU16(header.add(60));
  49. var sectionHeaderStringTableIndex = is32 ? Memory.readU16(header.add(50)) : Memory.readU16(header.add(62));
  50. var sectionHeaders = Memory.alloc(sectionHeaderSize * sectionHeaderCount);
  51. lseek_ptr(fd, sectionHeaderOffset, 0 /* SEEK_SET */);
  52. read_ptr(fd, sectionHeaders, sectionHeaderSize * sectionHeaderCount);
  53. var stringTableOffset = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 16)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 24)).toNumber();
  54. var stringTableSize = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 20)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 32)).toNumber();
  55. var stringTable = Memory.alloc(stringTableSize);
  56. lseek_ptr(fd, stringTableOffset, 0 /* SEEK_SET */);
  57. read_ptr(fd, stringTable, stringTableSize);
  58. var sections = [];
  59. var dynsym = undefined;
  60. var dynstr = undefined;
  61. var relplt = undefined;
  62. var reldyn = undefined;
  63. for (var i = 0; i < sectionHeaderCount; i++) {
  64. var sectionName = Memory.readUtf8String(stringTable.add(Memory.readU32(sectionHeaders.add(i * sectionHeaderSize))));
  65. var sectionAddress = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 12)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 16)).toNumber();
  66. var sectionOffset = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 16)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 24)).toNumber();
  67. var sectionSize = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 20)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 32)).toNumber();
  68. if (['.text', '.rodata', '.got', '.got.plt'].includes(sectionName)) {
  69. var section = {};
  70. section.name = sectionName;
  71. section.memoryOffset = sectionAddress;
  72. section.fileOffset = sectionOffset;
  73. section.size = sectionSize;
  74. if (sectionSize > 0) {
  75. section.data = Memory.alloc(sectionSize);
  76. lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */);
  77. read_ptr(fd, section.data, sectionSize);
  78. } else {
  79. section.data = undefined;
  80. }
  81. sections.push(section);
  82. } else if (['.dynsym', '.dynstr', '.rel.dyn', '.rel.plt'].includes(sectionName)) {
  83. var section = {};
  84. section.name = sectionName;
  85. section.memoryOffset = sectionAddress;
  86. section.fileOffset = sectionOffset;
  87. section.size = sectionSize;
  88. if (sectionSize > 0) {
  89. section.data = Memory.alloc(sectionSize);
  90. lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */);
  91. read_ptr(fd, section.data, sectionSize);
  92. } else {
  93. console.log('No data section for', section.name);
  94. section.data = undefined;
  95. }
  96. if (section.name === '.dynsym') {
  97. dynsym = section;
  98. }
  99. if (section.name === '.dynstr') {
  100. dynstr = section;
  101. }
  102. if (section.name === '.rel.dyn') {
  103. reldyn = section;
  104. }
  105. if (section.name === '.rel.plt') {
  106. relplt = section;
  107. }
  108. sections.push(section);
  109. }
  110. }
  111. if (!!dynsym && !!dynstr) {
  112. var symbols = [];
  113. var stringTable = module.base.add(dynstr.memoryOffset);
  114. var structSize = is32 ? 16 : 24;
  115. for (var i = 0; i < dynsym.size / structSize; i++) {
  116. var symbolOffset = Memory.readU32(module.base.add(dynsym.memoryOffset).add(structSize * i));
  117. symbols.push(Memory.readUtf8String(stringTable.add(symbolOffset)));
  118. }
  119. module.symbols = symbols;
  120. }
  121. var relmap = new Map();
  122. if (!!reldyn) {
  123. for (var i = 0; i < reldyn.size / 8; i++) {
  124. if ((Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8)) != 0) &&
  125. (Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) {
  126. relmap[Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8;
  127. }
  128. }
  129. }
  130. if (!!relplt) {
  131. for (var i = 0; i < relplt.size / 8; i++) {
  132. if ((Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8)) != 0) &&
  133. (Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) {
  134. relmap[Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8;
  135. }
  136. }
  137. }
  138. module.relmap = relmap;
  139. module.sections = sections;
  140. return true;
  141. }
  142. function findHooks(module) {
  143. if (module.sections === undefined) {
  144. if (!getElfData(module)) {
  145. return undefined;
  146. }
  147. }
  148. module.sections.forEach((section) => {
  149. if (section.size === 0) {
  150. return;
  151. }
  152. // It's important to cast the ArrayBuffer returned by `readByteArray` cannot be referenced incrementally
  153. var file = new Uint8Array(Memory.readByteArray(section.data, section.size));
  154. var memory = new Uint8Array(Memory.readByteArray(module.base.add(section.memoryOffset), section.size));
  155. for (var i = 0; i < section.size;) {
  156. if (['.rodata', '.text'].includes(section.name)) {
  157. if (file[i] != memory[i]) {
  158. console.log('*** Potential variance found at ', DebugSymbol.fromAddress(module.base.add(section.memoryOffset).add(i)));
  159. i += 4;
  160. }
  161. i++
  162. } else if (['.got'].includes(section.name)) {
  163. break;
  164. // It shouldn't be as the got table isn't initialized until execution
  165. if (file[i] != memory[i]) {
  166. // todo compare the symbol to string against what it resolves too
  167. }
  168. i += module.is32 ? 4 : 8;
  169. } else {
  170. // Unscanned sections, to be added as needed
  171. break;
  172. }
  173. }
  174. });
  175. }
  176. // Quick and simple way to get the package name, assumes that the script
  177. // was injected into an APK otherwise it won't work.
  178. function getPackageName() {
  179. var fd = open_ptr(Memory.allocUtf8String('/proc/self/cmdline'), 0 /* O_RDONLY */, 0);
  180. if (fd == -1) {
  181. return 'null';
  182. }
  183. var buffer = Memory.alloc(32);
  184. read_ptr(fd, buffer, 32);
  185. close_ptr(fd);
  186. return Memory.readUtf8String(buffer);
  187. }
  188. // Adjust this as needed, often I don't need to scan anything outside of the
  189. // included shared libraries and a few which are almost always in an apex folder.
  190. // This logic will need to be changed if you're using a pre-apex version of Android
  191. // to ensure it picked up the proper libraries for hunting
  192. //
  193. // While it doesn't hurt to scan everything, it's almost never needed and will just slow
  194. // down the process at a linear scale.
  195. //
  196. // If you already know what you're hunting for, feel free to just return or look for
  197. // libart, libdvm, etc, etc
  198. function getRelevantModules() {
  199. var modules = [];
  200. var packagename = getPackageName();
  201. Process.enumerateModules().forEach((module) => {
  202. if (module.path.includes(packagename)) {
  203. modules.push(module);
  204. console.log('Adding ', module.path);
  205. } else if (module.path.includes('/apex')) {
  206. modules.push(module);
  207. console.log('Adding ', module.path);
  208. } else {
  209. console.log('Skipping ', module.path);
  210. }
  211. })
  212. return modules;
  213. }
  214. var modules = getRelevantModules();
  215. modules.forEach((module) => {
  216. getElfData(module);
  217. findHooks(module);
  218. });