├── .gitignore ├── .vscode └── settings.json ├── README.md ├── _agent.js ├── agent ├── Java │ ├── Context.ts │ ├── JavaUtil.ts │ ├── Theads.ts │ ├── hooks │ │ ├── hook_LoadLibrary.ts │ │ └── include.ts │ └── include.ts ├── android │ ├── Interface │ │ ├── art │ │ │ ├── SizeOfClass.ts │ │ │ ├── include.ts │ │ │ └── mirror │ │ │ │ ├── HeapReference.ts │ │ │ │ ├── IArtMethod.ts │ │ │ │ └── include.ts │ │ └── include.ts │ ├── JSHandle.ts │ ├── Object.ts │ ├── TraceManager.ts │ ├── Utils │ │ ├── ArtMethodHelper.ts │ │ ├── JavaHooker.ts │ │ ├── SymHelper.ts │ │ ├── ThreadHooker.ts │ │ ├── include.ts │ │ ├── libcfuncs.ts │ │ └── syscalls.ts │ ├── ValueHandle.ts │ ├── android.ts │ ├── functions │ │ ├── DefineClass.ts │ │ ├── DexFileManager.ts │ │ ├── ExecuteSwitchImplCpp.ts │ │ ├── LinkerManager.ts │ │ ├── OpenCommon.ts │ │ ├── SymbolManager.ts │ │ ├── dlopen.ts │ │ └── include.ts │ ├── implements │ │ ├── 10 │ │ │ ├── art │ │ │ │ ├── ClassLinker.ts │ │ │ │ ├── GcRoot.ts │ │ │ │ ├── Globals.ts │ │ │ │ ├── Instruction.ts │ │ │ │ ├── Instrumentation │ │ │ │ │ ├── InstructionList.ts │ │ │ │ │ ├── Instrumentation.ts │ │ │ │ │ ├── InstrumentationListener.ts │ │ │ │ │ ├── InstrumentationStackFrame.ts │ │ │ │ │ ├── InstrumentationStackPopper.ts │ │ │ │ │ ├── SmaliWriter.ts │ │ │ │ │ ├── SmalinInlineManager.ts │ │ │ │ │ ├── UnUsedInstruction.ts │ │ │ │ │ ├── enum.ts │ │ │ │ │ └── include.ts │ │ │ │ ├── Oat │ │ │ │ │ ├── MemMap.ts │ │ │ │ │ ├── OatDexFile.ts │ │ │ │ │ ├── OatFile.ts │ │ │ │ │ └── include.ts │ │ │ │ ├── OatQuickMethodHeader.ts │ │ │ │ ├── ObjPtr.ts │ │ │ │ ├── QuickMethodFrameInfo.ts │ │ │ │ ├── ShadowFrame.ts │ │ │ │ ├── StackVisitor │ │ │ │ │ ├── CHAStackVisitor.ts │ │ │ │ │ ├── CatchBlockStackVisitor.ts │ │ │ │ │ ├── StackVisitor.ts │ │ │ │ │ └── include.ts │ │ │ │ ├── Thread.ts │ │ │ │ ├── Thread_Inl.ts │ │ │ │ ├── Type │ │ │ │ │ ├── JValue.ts │ │ │ │ │ ├── JavaString.ts │ │ │ │ │ ├── Throwable.ts │ │ │ │ │ ├── include.ts │ │ │ │ │ └── jobject.ts │ │ │ │ ├── bridge.ts │ │ │ │ ├── dexfile │ │ │ │ │ ├── CodeItemDataAccessor.ts │ │ │ │ │ ├── CodeItemDebugInfoAccessor.ts │ │ │ │ │ ├── CodeItemInstructionAccessor.ts │ │ │ │ │ ├── CompactDexFile.ts │ │ │ │ │ ├── DexFile.ts │ │ │ │ │ ├── DexFileStructs.ts │ │ │ │ │ ├── DexIndex.ts │ │ │ │ │ ├── Header.ts │ │ │ │ │ ├── StandardDexFile.ts │ │ │ │ │ └── include.ts │ │ │ │ ├── include.ts │ │ │ │ ├── interpreter │ │ │ │ │ ├── InstructionHandler.ts │ │ │ │ │ ├── SwitchImplContext.ts │ │ │ │ │ ├── include.ts │ │ │ │ │ └── interpreter.ts │ │ │ │ ├── mirror │ │ │ │ │ ├── ArtClass.ts │ │ │ │ │ ├── ArtMethod.ts │ │ │ │ │ ├── ClassExt.ts │ │ │ │ │ ├── ClassLoader.ts │ │ │ │ │ ├── DexCache.ts │ │ │ │ │ ├── IfTable.ts │ │ │ │ │ └── include.ts │ │ │ │ └── runtime │ │ │ │ │ ├── Runtime.ts │ │ │ │ │ ├── ThreadList.ts │ │ │ │ │ └── include.ts │ │ │ ├── dex2oat │ │ │ │ ├── dex2oat.ts │ │ │ │ └── include.ts │ │ │ └── include.ts │ │ ├── 12 │ │ │ └── include.ts │ │ └── include.ts │ ├── include.ts │ ├── jdwp │ │ ├── JDWPPackage.ts │ │ ├── constant.ts │ │ ├── include.ts │ │ ├── jdb.ts │ │ └── jdwp.ts │ └── machine-code.js ├── doc.ts ├── include.ts ├── main.ts ├── native │ ├── hooks.ts │ └── include.ts └── tools │ ├── StdString.ts │ ├── common.ts │ ├── dlopen.ts │ ├── dump.ts │ ├── enum.ts │ ├── functions.ts │ ├── include.ts │ ├── intercepter.ts │ ├── logger.ts │ └── modifiers.ts ├── imgs ├── dumpDexFiles.png ├── printBackTraceWithSmali.png ├── showOatAsm.png └── showSmali.png ├── oat.ps1 ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | _agent.js 3 | _agent-obfuscated.js 4 | package-lock.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "commentTranslate.targetLanguage": "zh-CN" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 当前测试环境 2 | Pixel XL 3 | android-10 (aosp) 4 | BUILD_ID=QP1A.191005.007.A3 5 | 6 | `_代码中大部分是写死的结构体偏移,不能兼容不同版本的安卓,测试建议用安卓10_` 7 | 8 | # 期望目标 9 | 10 | 根据artmethod指针去得到与之关联的dex源文件,解析dex文件,获取该方法的smali字节码,根据上述打印的代码信息来进行进一步的操作 11 | 12 | 👇 目前考虑的四种大概可行的smali inline trace方式 👇 13 | 14 | 0. Use JDB 😀 15 | 16 | REF: [jvmti doc](https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html) | 17 | REF: [android source jvmti.h](https://cs.android.com/android/platform/superproject/main/+/main:art/openjdkjvmti/include/jvmti.h;l=1002) | 18 | REF: [frida jvmti](https://github.com/frida/frida-java-bridge/blob/a3b0de51451dd38e9dfcbaa1fbc744745bab9579/lib/jvmti.js) | 19 | REF: [how to start jdwp thread](https://github.com/axhlzy/Il2CppHookScripts/blob/fe5ea00c7930135246b37333d63c21786c3fe82b/Il2cppHook/agent/plugin/jdwp/jdwp.ts#L257) | 20 | REF: [jdwp protocol](https://github.com/IOActive/jdwp-shellifier) 21 | 22 | 2. Use Trace Function 😕 23 | 24 | 通过符号以及指令格式的模式匹配定位一些关键的trace函数 25 | 参考源码 [trace.h](https://android.googlesource.com/platform/art/+/refs/tags/android-10.0.0_r42/runtime/trace.h#107) 26 | 27 | 3. Inline Hook Smali 😕 28 | 29 | - 解释执行 30 | 31 | Invoke static 覆盖原字节码调用(跳转到 Java.registerClass注册的js函数,实际就是native java method 对应一个 nativeFunctionCallback),并保存原字节码,进入新的ArtMethod执行流程后,通过 [`ManagedStack`](https://cs.android.com/android/platform/superproject/+/master:art/runtime/art_method.cc;l=379?q=art_method.cc&ss=android%2Fplatform%2Fsuperproject) 拿到上级 `fragment` 并获取 `ShadowFrame` 等同于获取到了当前java函数执行的上下文, 手动去执行我们覆盖的字节码后, 修改[上一贞](https://cs.android.com/android/platform/superproject/+/master:art/runtime/interpreter/shadow_frame.h;l=440)的[寄存器值](https://cs.android.com/android/platform/superproject/+/master:art/runtime/interpreter/shadow_frame.h;l=211),然后执行我们自己定义的static函数,通过这个函数就可以拿到上一级的所有信息, 也就是差不多inlinehook了该java函数指定位置的smail, 关于禁止oat [turbodex](https://github.com/asLody/TurboDex/blob/master/project/turbodex/turbodex/src/main/jni/core/FastLoadDex.cpp#L13) (修改dex以后,还有一些dex缓存需要处理) 32 | 33 | - 快速执行(oat模式) 34 | 35 | 主要工作在于需要解析oat后二进制的符号信息,dump汇编的时候可用借此增加二进制的可读性,至于二进制可行性格式的inlinehook就很普通了 36 | 37 | 4. 自定义smali解释器 38 | 具体的实现可以参考 [vmInterpret](https://github.com/maoabc/nmmp/blob/master/nmmvm/nmmvm/src/main/cpp/vm/InterpC-portable.cpp#L1065C17-L1065C18),或者把它移植过来,像qbdi那样导出一些函数用作frida bridge,完全代理系统原有的art smali解释器以获得最佳的流程控制能力以及跨不同版本的安卓代码兼容性 39 | 40 | 5. node调试执行 41 | 简单的想法是按照frida官网文档中关于调试js/ts的流程为关键js函数下断点 (--runtime=v8 --debug),但是如果我们使用Intercpter.attach以后,断点下在onEnter或者onLeave中,即实现了类似于调试器断点的感觉,这里涉及到另一个问题,如何像lldb一样进行单步调试,我的想法大致分为两种: 42 | 43 | ① 使用 [stalker](https://frida.re/docs/stalker/) CP原汇编并执行 44 | 45 | ② 使用 [QBDI](https://github.com/QBDI/QBDI) 完全代理模拟执行 46 | 47 | ③ 使用大佬现成的方案 [Dwarf](https://github.com/iGio90/Dwarf) 48 | 49 | 上述两种调试器方式我们都可以把断点下的更仔细,实现单步执行的效果,但是实测稳定性欠佳 50 | 51 | 至于 `Dwarf` 我实测也是感觉断点稳定性欠佳 52 | 53 | 54 | --- 55 | 56 | ### 还想做的一些事情 57 | 58 | - 处理一些常见的时机 59 | 1. DefineClass 60 | 2. OpenCommon 61 | 3. OpenMemory 62 | ... 63 | 64 | - 处理一些ART运行时的关键函数 65 | 1. ExecuteMterpImpl / ExecuteSwitchImpl - ExecuteSwitchImplCpp 66 | 2. doInvoke 67 | ... 68 | 69 | - 从调用逻辑上来看 70 | java -> java | 71 | java -> oat | 72 | oat -> java | 73 | oat -> oat | 74 | java -> native | 75 | native -> java 76 | 77 | - 中间顺带处理一下dex2oat对dex优化流程的尝试 78 | 79 | 80 | ### 效果图 81 | 82 | ``` 83 | [AOSP on msm8996::com.xxx.xxx ]-> pathToArtMethod("com.unity3d.player.UnityPlayer.addPhoneCallListener").showSmali() 84 | ↓dex_file↓ 85 | DexFile<0xe8ffe520> 86 | location: /data/app/com.gzcc.xbzc-s_aRcJlPwvVinch43dmvmw==/base.apk!classes4.dex 87 | location_checksum: 545562129 ( 0x20849e11 ) is_compact_dex: false 88 | begin: 0xc771b808 size: 7865800 ( 0x7805c8 ) | data_begin: 0xc771b808 data_size: 7865800 ( 0x7805c8 ) 89 | oat_dex_file_ 0xe8ffe578 90 | 91 | 👉 0xd1413f7c -> protected void com.unity3d.player.UnityPlayer.addPhoneCallListener() 92 | quickCode: 0xef450581 -> art_quick_to_interpreter_bridge @ libart.so | jniCode: null | accessFlags: 0x18080004 | size: 0x1c 93 | 94 | [ 1|0x0 ] 0xc7dcf1ac - 1 - 1210 | const/4 v0, #+1 95 | [ 2|0x2 ] 0xc7dcf1ae - 2 - eb30 0803 | iput-boolean-quick v0, v3, thing@776 96 | [ 3|0x6 ] 0xc7dcf1b2 - 2 - e530 ec02 | iget-object-quick v0, v3, // offset@748 97 | [ 4|0xa ] 0xc7dcf1b6 - 2 - e531 e402 | iget-object-quick v1, v3, // offset@740 98 | [ 5|0xe ] 0xc7dcf1ba - 2 - 1302 2000 | const/16 v2, #+32 99 | [ 6|0x12 ] 0xc7dcf1be - 3 - e930 1401 1002 | invoke-virtual-quick {v0, v1, v2}, // vtable@276 100 | [ 7|0x18 ] 0xc7dcf1c4 - 1 - 7300 | return-void-no-barrier 101 | 102 | // 解析 offset@748 103 | // 解析 vtable@276 104 | ``` 105 | 106 | 107 | 108 | showOatAsm 109 | ![showSmali](https://github.com/axhlzy/ARTHookScripts/blob/master/imgs/showOatAsm.png) 110 | 111 | showSmali 112 | ![showOatAsm](https://github.com/axhlzy/ARTHookScripts/blob/master/imgs/showSmali.png) 113 | 114 | dumpDexFiles 115 | ![dumpDexFiles](https://github.com/axhlzy/ARTHookScripts/blob/master/imgs/dumpDexFiles.png) 116 | 117 | printBackTraceWithSmali 118 | ![printBackTraceWithSmali](https://github.com/axhlzy/ARTHookScripts/blob/master/imgs/printBackTraceWithSmali.png) 119 | 120 | --- 121 | 122 | # Ref 123 | - [frida-smali-trace](https://github.com/SeeFlowerX/frida-smali-trace) 124 | 125 | -------------------------------------------------------------------------------- /agent/Java/Context.ts: -------------------------------------------------------------------------------- 1 | function getApplication(): Java.Wrapper { 2 | let application: Java.Wrapper = null 3 | Java.perform(function () { 4 | application = Java.use("android.app.ActivityThread").currentApplication() 5 | }) 6 | return application 7 | } 8 | 9 | function getPackageName(): string { 10 | let packageName: string = '' 11 | Java.perform(function () { 12 | let currentApplication = Java.use("android.app.ActivityThread").currentApplication() 13 | packageName = currentApplication.getApplicationContext().getPackageName() 14 | }) 15 | return packageName 16 | } 17 | 18 | function getCacheDir(): string { 19 | let path: string = '' 20 | Java.perform(function () { 21 | let currentApplication = Java.use("android.app.ActivityThread").currentApplication() 22 | path = currentApplication.getApplicationContext().getCacheDir().getPath() 23 | }) 24 | return path 25 | } 26 | 27 | function getFilesDir(): string { 28 | let path: string = '' 29 | Java.perform(function () { 30 | let currentApplication = Java.use("android.app.ActivityThread").currentApplication() 31 | path = currentApplication.getApplicationContext().getFilesDir().getPath() 32 | }) 33 | return path 34 | } 35 | 36 | declare global { 37 | var getApplication: () => Java.Wrapper 38 | var getPackageName: () => string 39 | var getCacheDir: () => string 40 | var getFilesDir: () => string 41 | } 42 | 43 | globalThis.getApplication = getApplication 44 | globalThis.getPackageName = getPackageName 45 | globalThis.getCacheDir = getCacheDir 46 | globalThis.getFilesDir = getFilesDir 47 | 48 | export { } -------------------------------------------------------------------------------- /agent/Java/Theads.ts: -------------------------------------------------------------------------------- 1 | /** 2 | #define SIGHUP 1 3 | #define SIGINT 2 4 | #define SIGQUIT 3 5 | #define SIGILL 4 6 | #define SIGTRAP 5 7 | #define SIGABRT 6 8 | #define SIGIOT 6 9 | #define SIGBUS 7 10 | #define SIGFPE 8 11 | #define SIGKILL 9 12 | #define SIGUSR1 10 13 | #define SIGSEGV 11 14 | #define SIGUSR2 12 15 | #define SIGPIPE 13 16 | #define SIGALRM 14 17 | #define SIGTERM 15 18 | #define SIGSTKFLT 16 19 | #define SIGCHLD 17 20 | #define SIGCONT 18 21 | #define SIGSTOP 19 22 | #define SIGTSTP 20 23 | #define SIGTTIN 21 24 | #define SIGTTOU 22 25 | #define SIGURG 23 26 | #define SIGXCPU 24 27 | #define SIGXFSZ 25 28 | #define SIGVTALRM 26 29 | #define SIGPROF 27 30 | #define SIGWINCH 28 31 | #define SIGIO 29 32 | #define SIGPOLL SIGIO 33 | #define SIGPWR 30 34 | #define SIGSYS 31 35 | #define SIGUNUSED 31 36 | #define __SIGRTMIN 32 37 | */ 38 | export enum SIGNAL { 39 | SIGHUP = 1, 40 | SIGINT = 2, 41 | SIGQUIT = 3, 42 | SIGILL = 4, 43 | SIGTRAP = 5, 44 | SIGABRT = 6, 45 | SIGIOT = 6, 46 | SIGBUS = 7, 47 | SIGFPE = 8, 48 | SIGKILL = 9, 49 | SIGUSR1 = 10, 50 | SIGSEGV = 11, 51 | SIGUSR2 = 12, 52 | SIGPIPE = 13, 53 | SIGALRM = 14, 54 | SIGTERM = 15, 55 | SIGSTKFLT = 16, 56 | SIGCHLD = 17, 57 | SIGCONT = 18, 58 | SIGSTOP = 19, 59 | SIGTSTP = 20, 60 | SIGTTIN = 21, 61 | SIGTTOU = 22, 62 | SIGURG = 23, 63 | SIGXCPU = 24, 64 | SIGXFSZ = 25, 65 | SIGVTALRM = 26, 66 | SIGPROF = 27, 67 | SIGWINCH = 28, 68 | SIGIO = 29, 69 | SIGPOLL = 29, 70 | SIGPWR = 30, 71 | SIGSYS = 31, 72 | SIGUNUSED = 31, 73 | __SIGRTMIN = 32, 74 | } 75 | 76 | globalThis.readProcStatus = (tid?: number): string => { 77 | const statusPath = `/proc/${tid == undefined ? 'self' : tid}/status` 78 | const fopenFunc = new NativeFunction(Module.findExportByName("libc.so", 'fopen')!, 'pointer', ['pointer', 'pointer']) 79 | const fopen = fopenFunc(Memory.allocUtf8String(statusPath), Memory.allocUtf8String('r')) 80 | const freadFunc = new NativeFunction(Module.findExportByName("libc.so", 'fread')!, 'int', ['pointer', 'size_t', 'size_t', 'pointer']) 81 | const buf: NativePointer = Memory.alloc(0x1000) 82 | const _fread = freadFunc(buf, 1, 0x1000, fopen) 83 | const fcloseFunc = new NativeFunction(Module.findExportByName("libc.so", 'fclose')!, 'int', ['pointer']) 84 | const _fclose = fcloseFunc(fopen) as number 85 | // (Java as any).api.$delete(buf) 86 | return buf.readCString() 87 | } 88 | 89 | globalThis.readProcTasks = (): string[] => { 90 | const taskPath = '/proc/self/task' 91 | 92 | const opendirFunc = new NativeFunction(Module.findExportByName('libc.so', 'opendir')!, 'pointer', ['pointer']) 93 | const readdirFunc = new NativeFunction(Module.findExportByName('libc.so', 'readdir')!, 'pointer', ['pointer']) 94 | 95 | const dir: NativePointer = opendirFunc(Memory.allocUtf8String(taskPath)) as NativePointer 96 | if (dir.isNull()) { 97 | console.error('Failed to open directory:', taskPath) 98 | return [] 99 | } 100 | const files: string[] = [] 101 | try { 102 | // 读取目录项 103 | let entry = readdirFunc(dir) as NativePointer 104 | while (!entry.isNull()) { 105 | const fileName = entry.readCString() 106 | if (fileName !== '.' && fileName !== '..') { 107 | files.push(fileName) 108 | } 109 | entry = readdirFunc(dir) as NativePointer 110 | } 111 | } finally { 112 | // Memory.writeInt(dir, 0) 113 | dir.writePointer(NULL) 114 | } 115 | return files 116 | }; 117 | 118 | 119 | globalThis.getValueFromStatus = (key: string, tid?: number): string => { 120 | let status: string = readProcStatus(tid) 121 | let reg = new RegExp(key + ".*", "g") 122 | let result = status.match(reg) 123 | if (result == null) return "unknown" 124 | return result[0].split(":")[1].trim() 125 | } 126 | 127 | function getThreadName(tid: number) { 128 | let threadName: string = getValueFromStatus("Name", tid) 129 | 130 | // try { 131 | // const file = new File("/proc/self/task/" + tid + "/comm", "r") 132 | // threadName = (file as any).readLine().toString().trimEnd() 133 | // file.close() 134 | // } catch (e) { throw e } 135 | 136 | // var threadNamePtr: NativePointer = Memory.alloc(0x40) 137 | // var tid_p: NativePointer = Memory.alloc(PointerSize).writePointer(ptr(tid)) 138 | // var pthread_getname_np = new NativeFunction(Module.findExportByName("libc.so", 'pthread_getname_np')!, 'int', ['pointer', 'pointer', 'int']) 139 | // pthread_getname_np(ptr(tid), threadNamePtr, 0x40) 140 | // threadName = threadNamePtr.readCString()! 141 | 142 | return threadName 143 | } 144 | 145 | // Java.scheduleOnMainThread(()=>{LOGD(currentThreadName())}) 146 | // Java.perform(()=>{LOGD(currentThreadName())}) 147 | globalThis.currentThreadName = (): string => { 148 | let tid = Process.getCurrentThreadId() 149 | return getThreadName(tid).toString() 150 | } 151 | 152 | globalThis.listThreads = (maxCountThreads: number = 20) => { 153 | let index_threads: number = 0 154 | let current = Process.getCurrentThreadId() 155 | Process.enumerateThreads() 156 | .sort((a, b) => b.id - a.id) 157 | .slice(0, maxCountThreads) 158 | .forEach((thread: ThreadDetails) => { 159 | let indexText = `[${++index_threads}]`.padEnd(6, " ") 160 | let text = `${indexText} ${thread.id} ${thread.state} | ${getThreadName(thread.id)}` 161 | // let ctx = thread.context 162 | current == thread.id ? LOGE(text) : LOGD(text) 163 | }) 164 | } 165 | 166 | declare global { 167 | var currentThreadName: () => string 168 | var raise: (sign?: SIGNAL) => number 169 | var listThreads: (maxCountThreads?: number) => void 170 | var readProcStatus: (tid?: number) => string 171 | var readProcTasks: () => string[] 172 | var getValueFromStatus: (key: string, tid?: number) => string 173 | } -------------------------------------------------------------------------------- /agent/Java/hooks/hook_LoadLibrary.ts: -------------------------------------------------------------------------------- 1 | export const hookJavaLoadLibrary = () => { 2 | Java.perform(() => { 3 | const SystemClass = Java.use('java.lang.System') 4 | SystemClass.loadLibrary.implementation = function(library) { 5 | console.log('Loading library:' + library) 6 | const result = this.loadLibrary(library) 7 | return result 8 | } 9 | }) 10 | } -------------------------------------------------------------------------------- /agent/Java/hooks/include.ts: -------------------------------------------------------------------------------- 1 | import './hook_LoadLibrary' 2 | 3 | -------------------------------------------------------------------------------- /agent/Java/include.ts: -------------------------------------------------------------------------------- 1 | import './JavaUtil' 2 | import './Context' 3 | import './Theads' 4 | 5 | import './hooks/include' -------------------------------------------------------------------------------- /agent/android/Interface/art/SizeOfClass.ts: -------------------------------------------------------------------------------- 1 | interface SizeOfClass { 2 | get SizeOfClass(): number // return sizeof this class struct 3 | get CurrentHandle(): NativePointer // offset start base parent class 4 | get VirtualClassOffset(): number // return 0 means not virtual class else return PointerSize 5 | get VirtualTableList(): NativePointer[] // return virtual table list 6 | } -------------------------------------------------------------------------------- /agent/android/Interface/art/include.ts: -------------------------------------------------------------------------------- 1 | import './mirror/include' 2 | 3 | import './SizeOfClass' -------------------------------------------------------------------------------- /agent/android/Interface/art/mirror/HeapReference.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../JSHandle" 2 | 3 | // References between objects within the managed heap. 4 | // Similar API to ObjectReference, but not a value type. Supports atomic access. 5 | // template 6 | // class MANAGED HeapReference {} 7 | export class HeapReference extends JSHandle { 8 | 9 | public static readonly Size: number = 0x4 10 | 11 | private _factory: (handle: NativePointer) => T 12 | 13 | constructor(factory: (handle: NativePointer) => T, handle: NativePointer) { 14 | super(handle.readU32()) 15 | this._factory = factory 16 | } 17 | 18 | public static fromRef(factory: (handle: NativePointer) => T, ref: NativePointer): HeapReference { 19 | return new HeapReference(factory, ref) 20 | } 21 | 22 | // mutable mirror::CompressedReference root_; 23 | get root(): T { 24 | return this._factory(this.handle) as T 25 | } 26 | 27 | toString(): string { 28 | return `HeapReference<${this.handle}> [U32]` 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /agent/android/Interface/art/mirror/IArtMethod.ts: -------------------------------------------------------------------------------- 1 | import { GcRoot } from "../../../implements/10/art/GcRoot" 2 | import { ArtClass } from "../../../implements/10/art/mirror/ArtClass" 3 | 4 | export interface IArtMethod { 5 | declaring_class: GcRoot 6 | access_flags: NativePointer 7 | dex_code_item_offset: number 8 | dex_method_index: number 9 | method_index: number 10 | hotness_count: number 11 | } -------------------------------------------------------------------------------- /agent/android/Interface/art/mirror/include.ts: -------------------------------------------------------------------------------- 1 | import './HeapReference' 2 | import './IArtMethod' -------------------------------------------------------------------------------- /agent/android/Interface/include.ts: -------------------------------------------------------------------------------- 1 | import './art/include' -------------------------------------------------------------------------------- /agent/android/JSHandle.ts: -------------------------------------------------------------------------------- 1 | export class JSHandleNotImpl { 2 | 3 | public handle: NativePointer 4 | 5 | constructor(handle: NativePointer | number) { 6 | this.handle = (typeof handle === "number") ? ptr(handle) : handle 7 | } 8 | 9 | toString(): string { 10 | return `JSHandle< ${this.handle} >` 11 | } 12 | 13 | show(): void { LOGD(this.toString()) } 14 | 15 | } 16 | 17 | export class JSHandle extends JSHandleNotImpl implements SizeOfClass { 18 | 19 | get CurrentHandle(): NativePointer { 20 | return this.handle 21 | } 22 | 23 | get SizeOfClass(): number { 24 | return 0 25 | } 26 | 27 | get VirtualClassOffset(): number { 28 | return 0 29 | } 30 | 31 | get VirtualTableList(): NativePointer[] { 32 | if (this.VirtualClassOffset === Process.pointerSize) { 33 | const vtable = this.handle.readPointer() 34 | const vtableList: NativePointer[] = [] 35 | let i = 0 36 | while (true) { 37 | const vtableItem = vtable.add(i * Process.pointerSize).readPointer() 38 | if (vtableItem.isNull()) break 39 | vtableList.push(vtableItem) 40 | i++ 41 | } 42 | return vtableList 43 | } 44 | return [] 45 | } 46 | 47 | public VirtualTablePrint(): void { 48 | this.VirtualTableList.map((item, index) => `[${index}] ${item}`).forEach(LOGD) 49 | } 50 | 51 | toString(): string { 52 | let disp: string = `JSHandle< ${this.handle} >` 53 | return disp 54 | } 55 | } 56 | 57 | declare global { 58 | var setExp: (handler: (exception: ExceptionDetails) => boolean) => void 59 | } 60 | 61 | globalThis.setExp = (handler: (exception: ExceptionDetails) => boolean = (exception: ExceptionDetails) => { 62 | LOGE(`\nCatch Exception:\nTYPE:${exception.type} | NCONTEXT: ${exception.nativeContext} | ADDRESS: ${exception.address} { ${DebugSymbol.fromAddress(exception.address)} }`) 63 | PrintStackTraceNative(exception.context, '', false, true) 64 | return true 65 | }) => { 66 | Process.setExceptionHandler((exception: ExceptionDetails) => { 67 | if (handler(exception)) return false 68 | return true 69 | }) 70 | } -------------------------------------------------------------------------------- /agent/android/Object.ts: -------------------------------------------------------------------------------- 1 | import { HeapReference } from "./Interface/art/mirror/HeapReference" 2 | import { ArtThread } from "./implements/10/art/Thread" 3 | import { StdString } from "../tools/StdString" 4 | import { callSym } from "./Utils/SymHelper" 5 | import { JSHandle } from "./JSHandle" 6 | 7 | class ArtField extends JSHandle { } 8 | 9 | export class ArtObject extends JSHandle implements SizeOfClass { 10 | 11 | // // The Class representing the type of the object. 12 | // HeapReference klass_; 13 | protected klass_: NativePointer = this.handle // 0x4 14 | // // Monitor and hash code information. 15 | // uint32_t monitor_; 16 | protected monitor_: NativePointer = this.klass_.add(0x4) // 0x4 17 | 18 | constructor(handle: NativePointer) { 19 | super(handle) 20 | } 21 | 22 | get CurrentHandle(): NativePointer { 23 | return this.handle.add(super.SizeOfClass) 24 | } 25 | 26 | get SizeOfClass(): number { 27 | return super.SizeOfClass + 0x4 * 2 28 | } 29 | 30 | get klass(): HeapReference { 31 | return new HeapReference((handle) => new ArtClass(handle), ptr(this.klass_.readU32())) 32 | } 33 | 34 | get monitor(): number { 35 | return this.monitor_.readU32() 36 | } 37 | 38 | // caseTo(T: any): T { 39 | // return new T(this.handle) 40 | // } 41 | 42 | simpleDisp(): string { 43 | if (this.handle.isNull()) return `ArtObject<${this.handle}>` 44 | const prettyTypeOf: string = this.PrettyTypeOf() 45 | let disp: string = `ArtObject<${this.handle}> <- ${prettyTypeOf}` 46 | // if (prettyTypeOf == "java.lang.String") disp += ` <- ${JavaString.caseToJavaString(this).value}` 47 | return disp 48 | } 49 | 50 | toString(): string { 51 | let disp: string = `ArtObject< ${this.handle} >` 52 | if (this.handle.isNull()) return disp 53 | // disp += `\n${this.klass.toString()}` 54 | return disp 55 | } 56 | // _ZNK3art11Instruction28SizeInCodeUnitsComplexOpcodeEv 57 | // size_t SizeInCodeUnitsComplexOpcode() const; 58 | sizeInCodeUnitsComplexOpcode(): number { 59 | return callSym( 60 | "_ZNK3art11Instruction28SizeInCodeUnitsComplexOpcodeEv", "libdexfile.so" 61 | , ["pointer"] 62 | , ["pointer"] 63 | , this.handle) 64 | } 65 | 66 | 67 | // static std::string PrettyTypeOf(ObjPtr obj) 68 | // _ZN3art6mirror6Object12PrettyTypeOfENS_6ObjPtrIS1_EE 69 | public static PrettyTypeOf(obj: ArtObject): string { 70 | return StdString.fromPointers(callSym( 71 | "_ZN3art6mirror6Object12PrettyTypeOfENS_6ObjPtrIS1_EE", "libart.so" 72 | , ["pointer", "pointer", "pointer"] 73 | , ["pointer"] 74 | , obj.handle)).toString() 75 | } 76 | 77 | // std::string PrettyTypeOf() 78 | // art::mirror::Object::PrettyTypeOf() 79 | public PrettyTypeOf(): string { 80 | try { 81 | return StdString.fromPointers(callSym( 82 | "_ZN3art6mirror6Object12PrettyTypeOfEv", "libart.so" 83 | , ["pointer", "pointer", "pointer"] 84 | , ["pointer"] 85 | , this.handle)).toString() 86 | } catch (error) { 87 | return 'ERROR -> PrettyTypeOf' 88 | } 89 | } 90 | 91 | // // A utility function that copies an object in a read barrier and write barrier-aware way. 92 | // // This is internally used by Clone() and Class::CopyOf(). If the object is finalizable, 93 | // // it is the callers job to call Heap::AddFinalizerReference. 94 | // static ObjPtr CopyObject(ObjPtr dest, 95 | // ObjPtr src, 96 | // size_t num_bytes) 97 | // REQUIRES_SHARED(Locks::mutator_lock_); 98 | // _ZN3art6mirror6Object10CopyObjectENS_6ObjPtrIS1_EES3_m 99 | public static CopyObject(dest: ArtObject, src: ArtObject, num_bytes: number): ArtObject { 100 | return new ArtObject(callSym( 101 | "_ZN3art6mirror6Object10CopyObjectENS_6ObjPtrIS1_EES3_m", "libart.so" 102 | , ["pointer"] 103 | , ["pointer", "pointer", "int"] 104 | , dest.handle, src.handle, num_bytes)) 105 | } 106 | 107 | // ObjPtr Clone(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) 108 | // _ZN3art6mirror6Object5CloneEPNS_6ThreadE 109 | public Clone(self: ArtThread): ArtObject { 110 | return new ArtObject(callSym( 111 | "_ZN3art6mirror6Object5CloneEPNS_6ThreadE", "libart.so" 112 | , ["pointer"] 113 | , ["pointer", "pointer"] 114 | , this.handle, self.handle)) 115 | } 116 | 117 | // ArtField* FindFieldByOffset(MemberOffset offset) REQUIRES_SHARED(Locks::mutator_lock_); 118 | // _ZN3art6mirror6Object17FindFieldByOffsetENS_12MemberOffsetE 119 | public FindFieldByOffset(offset: number): ArtField { 120 | return new ArtField(callSym( 121 | "_ZN3art6mirror6Object17FindFieldByOffsetENS_12MemberOffsetE", "libart.so" 122 | , ["pointer"] 123 | , ["pointer", "int"] 124 | , this.handle, offset)) 125 | } 126 | 127 | // static uint32_t GenerateIdentityHashCode(); 128 | // _ZN3art6mirror6Object24GenerateIdentityHashCodeEv 129 | public static GenerateIdentityHashCode(): number { 130 | return callSym( 131 | "_ZN3art6mirror6Object24GenerateIdentityHashCodeEv", "libart.so" 132 | , ["int"] 133 | , []) 134 | } 135 | 136 | } 137 | 138 | globalThis.ArtObject = ArtObject -------------------------------------------------------------------------------- /agent/android/TraceManager.ts: -------------------------------------------------------------------------------- 1 | import { ExecuteSwitchImplCppManager } from "./functions/ExecuteSwitchImplCpp" 2 | import { DefineClassHookManager } from "./functions/DefineClass" 3 | import { OpenCommonHookManager } from "./functions/OpenCommon" 4 | import { linkerManager as LinkerManager } from "./functions/LinkerManager" 5 | import { libC, pthread_attr_t, pthread_t } from "./Utils/libcfuncs" 6 | import { waitSoLoad, whenSoLoad } from "./functions/dlopen" 7 | import { PointerSize } from "./implements/10/art/Globals" 8 | import { hookThread } from "./Utils/ThreadHooker" 9 | import { SYSCALL } from "./Utils/syscalls" 10 | import { getSym } from "./Utils/SymHelper" 11 | 12 | export class TraceManager { 13 | 14 | public static startTrace(name: string): void { 15 | console.log("startTrace", name); 16 | } 17 | 18 | public static stopTrace(name: string): void { 19 | console.log("stopTrace", name); 20 | } 21 | 22 | // jetbrains://clion/navigate/reference?project=libart&path=interpreter/interpreter_common.h 23 | public static TraceJava2Java() { 24 | // static ALWAYS_INLINE bool DoInvoke(Thread* self, 25 | // ShadowFrame& shadow_frame, 26 | // const Instruction* inst, 27 | // uint16_t inst_data, 28 | // JValue* result) 29 | 30 | } 31 | 32 | public static TraceJava2Native() { 33 | 34 | } 35 | 36 | public static TraceNative2Java() { 37 | 38 | } 39 | 40 | public static TraceNative2Native() { 41 | 42 | } 43 | 44 | public static Trace_OpenCommon() { 45 | OpenCommonHookManager.getInstance().enableHook() 46 | } 47 | 48 | public static Trace_DefineClass() { 49 | DefineClassHookManager.getInstance().enableHook() 50 | } 51 | 52 | public static Trace_CallConstructors() { 53 | LinkerManager.Hook_CallConstructors() 54 | } 55 | 56 | public static Trace_ExecuteSwitchImplCpp() { 57 | ExecuteSwitchImplCppManager.enableHook() 58 | } 59 | 60 | public static TraceArtMethodInvoke() { 61 | HookArtMethodInvoke() 62 | } 63 | 64 | public static TraceException(){ 65 | logSoLoad() 66 | 67 | Process.setExceptionHandler((exception: ExceptionDetails) => { 68 | LOGE(`TYPE:${exception.type} | NCONTEXT: ${exception.nativeContext} | ADDRESS: ${exception.address} { ${DebugSymbol.fromAddress(exception.address)} }`) 69 | PrintStackTraceNative(exception.context, '', false, true) 70 | }) 71 | } 72 | } 73 | 74 | // LOGD(Process.id) 75 | // libC.sleep(15) 76 | // hookThread() 77 | 78 | setImmediate(() => { 79 | 80 | // logSoLoad() 81 | // hookThread() 82 | 83 | // const addr = Module.findBaseAddress("libJX3_Client.so").add(0x166CEE4) 84 | // Memory.patchCode(addr, Process.pointerSize, (address)=>{ 85 | // const w = new Arm64Writer(address) 86 | // w.putNop() 87 | // w.flush() 88 | // }) 89 | 90 | // Module.load("/data/local/tmp/libinject.so") 91 | 92 | // TraceManager.TraceException() 93 | 94 | // whenSoLoad("libRMS.so", (soName)=>{LOGW(`onEnter ${soName}`)}, ()=>{}) 95 | // // waitSoLoad("libRMS.so", 20) 96 | 97 | // LinkerManager.addOnSoLoadCallback("libRMS.so", (md: Module) => { 98 | // LOGD(`onCalled libRMS.so | md.base = ${md.base}`) 99 | 100 | // const addr0 = Process.findModuleByName("libRMS.so").base.add(0x00003f20) 101 | // LOGE(`addr0 = ${addr0}`) 102 | // Memory.protect(addr0, 4, 'rwx') 103 | // let w = new ArmWriter(addr0) 104 | // w.putRet() 105 | // w.flush() 106 | 107 | // const addr1 = Process.findModuleByName("libRMS.so").base.add(0x00003584) 108 | // LOGE(`addr1 = ${addr1}`) 109 | // Memory.protect(addr1, 4, 'rwx') 110 | // let w1 = new ArmWriter(addr1) 111 | // w1.putNop() 112 | // w1.flush() 113 | 114 | // LOGW(Process.id) 115 | // }) 116 | 117 | // TraceManager.Trace_DefineClass() 118 | // TraceManager.Trace_ExecuteSwitchImplCpp() 119 | // TraceManager.Trace_OpenCommon() 120 | // TraceManager.Trace_CallConstructors() 121 | }) -------------------------------------------------------------------------------- /agent/android/Utils/ArtMethodHelper.ts: -------------------------------------------------------------------------------- 1 | import { ArtMethod } from "../implements/10/art/mirror/ArtMethod" 2 | 3 | export { } 4 | 5 | declare global { 6 | var pathToArtMethod: (path: string) => ArtMethod | null 7 | // alias 8 | var toArtMethod: (path: string) => ArtMethod | null 9 | } 10 | 11 | globalThis.pathToArtMethod = (path: string): ArtMethod | null => { 12 | const index: number = path.lastIndexOf(".") 13 | const className: string = path.substring(0, index) 14 | const methodName: string = path.substring(index + 1) 15 | let retArtMethod: ArtMethod | null = null 16 | Java.perform(() => { 17 | const clazz = Java.use(className) 18 | const method = clazz[methodName] 19 | const handle = method.handle 20 | retArtMethod = new ArtMethod(handle) 21 | }) 22 | return retArtMethod 23 | } 24 | 25 | globalThis.toArtMethod = globalThis.pathToArtMethod -------------------------------------------------------------------------------- /agent/android/Utils/JavaHooker.ts: -------------------------------------------------------------------------------- 1 | interface HookOptions { 2 | before?: (instance: any, args: IArguments) => void 3 | after?: (instance: any, args: IArguments, returnValue?: any) => any 4 | skipOriginal?: boolean 5 | parseValue?: boolean 6 | } 7 | 8 | export interface MethodCallback { 9 | (methodName: string, methodSignature: string, args: IArguments): HookOptions | void 10 | } 11 | 12 | // 如果app本来使用到了gson 就直接用 13 | // 否则就加载gson.dex,如果还是没有就不做解析 14 | var useGson = false 15 | // Java.perform(() => { 16 | // if (Java.available) { 17 | // useGson = testGson() || loadGson() 18 | // function testGson() { 19 | // try { 20 | // Java.use("com.google.gson.Gson") 21 | // return true 22 | // } catch (e) { 23 | // return false 24 | // } 25 | // } 26 | 27 | // function loadGson() { 28 | // try { 29 | // Java.openClassFile("/data/local/tmp/gson.dex").load() 30 | // return testGson() 31 | // } catch (e) { 32 | // return false 33 | // } 34 | // } 35 | // } 36 | // }) 37 | 38 | /** 39 | * @example 40 | * hookJavaClass("com.unity3d.player.UnityWebRequest", null, ['uploadCallback']) 41 | */ 42 | export function hookJavaClass(className: string | Java.Wrapper, callback?: MethodCallback, passMethods: Array = []) { 43 | callback = (callback == undefined || callback == null) ? (_methodName, _methodSignature, _args) => { 44 | return { 45 | skipOriginal: false, 46 | parseValue: true 47 | } 48 | } : callback 49 | Java.perform(() => { 50 | var javaClass: any 51 | try { 52 | if (typeof className === 'string') { 53 | javaClass = Java.use(className) 54 | } else javaClass = className 55 | } catch { 56 | LOGE(`NOT FOUND ${className}`) 57 | return 58 | } 59 | const methods = javaClass.class.getDeclaredMethods() 60 | 61 | methods.forEach((method: any) => { 62 | const methodName: string = method.getName() 63 | if (methodName.includes("$") || methodName.includes('native') || methodName.includes('synchronized')) { 64 | LOGW(`Skip Hook -> ${className}.${methodName}`) 65 | return 66 | } 67 | if (methodName.includes("$") || methodName.includes('init') || methodName.includes('ctor')) { 68 | LOGW(`Skip Hook -> ${className}.${methodName}`) 69 | return 70 | } 71 | if (passMethods.includes(methodName)) return 72 | 73 | LOGW(`Hooking ${className}.${methodName}`) 74 | const methodSignature = method.getParameterTypes().map((t: any) => t.className).join(',') 75 | 76 | javaClass[methodName].overloads.forEach((originalMethod: Java.Method) => { 77 | if (originalMethod) { 78 | originalMethod.implementation = function () { 79 | const hookOptions: HookOptions = callback(methodName, methodSignature, arguments) || {} 80 | 81 | if (hookOptions.before) { 82 | hookOptions.before(this, arguments) 83 | } 84 | 85 | let returnValue: any 86 | if (!hookOptions.skipOriginal) { 87 | returnValue = originalMethod.apply(this, arguments) 88 | } 89 | 90 | if (hookOptions.after) { 91 | returnValue = hookOptions.after(this, arguments, returnValue) 92 | } 93 | 94 | if (hookOptions.parseValue) { 95 | let fullMethodName = `${className}.${methodName}` 96 | if (useGson) { 97 | const Gson = Java.use("com.google.gson.Gson") 98 | const gson = Gson.$new() 99 | let args_str: string = arguments.length == 0 ? '' : Array.prototype.slice.call(arguments).map((arg: any) => { 100 | const json = gson.toJson(arg) 101 | return json 102 | }).join("','") 103 | if (returnValue) { 104 | if (useGson) { 105 | const json = gson.toJson(returnValue) 106 | LOGD(`${fullMethodName}(\x1b[96m'${args_str}'\x1b[0m) => \x1b[93m${json}\x1b[0m`) 107 | } 108 | } else { 109 | LOGD(`${fullMethodName}(\x1b[96m'${args_str}'\x1b[0m)`) 110 | } 111 | 112 | } else { 113 | const args_str: string = arguments.length == 0 ? '' : Array.prototype.slice.call(arguments).map(String).join("','") 114 | if (returnValue) { 115 | LOGD(`${fullMethodName}(\x1b[96m'${args_str}'\x1b[0m) => \x1b[93m${returnValue}\x1b[0m`) 116 | } else { 117 | LOGD(`${fullMethodName}(\x1b[96m'${args_str}'\x1b[0m)`) 118 | } 119 | } 120 | 121 | } 122 | 123 | return returnValue 124 | } 125 | } 126 | }) 127 | 128 | }) 129 | }) 130 | } 131 | 132 | Reflect.set(globalThis, 'hookJavaClass', hookJavaClass) -------------------------------------------------------------------------------- /agent/android/Utils/SymHelper.ts: -------------------------------------------------------------------------------- 1 | import { demangleName_onlyFunctionName as demangleName_ } from "../../tools/functions" 2 | import { SymbolManager } from "../functions/SymbolManager" 3 | import { JSHandle } from "../JSHandle" 4 | 5 | const DEBUG_LOG: boolean = false 6 | 7 | function CallSymLocal(address: NativePointer, retType: NativeType, argTypes: NativeType[], ...args: any[]): T { 8 | try { 9 | return new NativeFunction(address, retType, argTypes)(...args) as T 10 | } catch (error: any) { 11 | throw error 12 | LOGE(`CallSymLocal exception 👇 \n${error.stack}`) 13 | return null 14 | } 15 | } 16 | 17 | type ArgType = NativeType | JSHandle | NativePointer | number 18 | 19 | function transformArgs(args: ArgType[], argTypes: NativeType[]): any[] { 20 | return args.map((arg: any, index: number) => { 21 | if (argTypes[index] == "int") return parseInt(arg.toString()) 22 | if (arg instanceof NativePointer) return arg 23 | if (arg instanceof JSHandle) return arg.handle 24 | if (typeof arg === "number") return arg 25 | if (typeof arg === "string") return Memory.allocUtf8String(arg) 26 | return ptr(arg) 27 | }) 28 | } 29 | 30 | export function callSym(sym: string, md: string, retType: NativeType, argTypes: NativeType[], ...args: any[]): T { 31 | return CallSymLocal(getSym(sym, md), retType, argTypes, ...transformArgs(args, argTypes)) 32 | } 33 | 34 | const Cache: Map = new Map() 35 | export function getSym(symName: string, md: string = "libart.so", checkNotFunction: boolean = false): NativePointer | null { 36 | if (Cache.has(symName)) return Cache.get(symName)! 37 | if (symName == undefined || md == null || symName == "" || md == "") 38 | throw new Error(`Usage: getSym(symName: string, md: string, check: boolean = false)`) 39 | 40 | const module: Module = Process.getModuleByName(md) 41 | if (module == null) throw new Error(`module ${md} not found`) 42 | 43 | let address: NativePointer | null = module.findExportByName(symName) 44 | 45 | // add action to find in symbols 46 | if (address == null) { 47 | let res: ModuleSymbolDetails[] = module.enumerateSymbols().filter((sym: ModuleSymbolDetails) => { 48 | return sym.name == symName && (checkNotFunction ? sym.type == "function" : true) 49 | }) 50 | if (res.length > 1) { 51 | address = res[0].address 52 | LOGW(`find too many symbol, just ret first | size : ${res.length}`) 53 | return address 54 | } else if (res.length == 1) { 55 | address = res[0].address 56 | return address 57 | } 58 | } 59 | 60 | // Use demangle to handle function export names, 61 | // used to deal with the problem of exporting functions with the same name 62 | // but different arguments under arm32 and arm64 63 | if (address == null) { 64 | let sym_ret: ModuleSymbolDetails = SymbolManager.SymbolFilter(null, demangleName_(symName)) 65 | if (DEBUG_LOG) LOGD(`debug -> symbol ${symName} found in ${sym_ret} -> ${sym_ret.address}`) 66 | if (sym_ret.type != "function") throw new Error(`symbol ${sym_ret.name} not a function [ ${sym_ret.type} ]`) 67 | address = sym_ret.address 68 | } 69 | if (DEBUG_LOG) LOGD(`debug -> symbol ${symName} found in ${md} -> ${address}`) 70 | if (address == null) { 71 | throw new Error(`symbol ${symName} not found`) 72 | } 73 | if (checkNotFunction) { 74 | const syms: ModuleSymbolDetails[] = module.enumerateSymbols().filter((sym: ModuleSymbolDetails) => { 75 | return sym.name == symName && sym.type == "object" 76 | }) 77 | if (syms.length == 0) { 78 | throw new Error(`symbol ${symName} not found`) 79 | } else { 80 | // LOGD(`symbol ${symName} found \n ${JSON.stringify(syms[0])}`) 81 | } 82 | } 83 | Cache.set(symName, address) 84 | return address 85 | } 86 | 87 | Reflect.set(globalThis, "getSym", getSym) 88 | Reflect.set(globalThis, "callSym", callSym) 89 | 90 | /** 91 | * Demangles a C++ symbol name using available libraries. 92 | * @param expName The mangled symbol name to demangle. 93 | * @returns The demangled symbol name, or an empty string if demangling failed. 94 | */ 95 | export function demangleName(expName: string) { 96 | let demangleAddress: NativePointer | null = Module.findExportByName("libc++.so", '__cxa_demangle') 97 | if (demangleAddress == null) demangleAddress = Module.findExportByName("libunwindstack.so", '__cxa_demangle') 98 | if (demangleAddress == null) demangleAddress = Module.findExportByName("libbacktrace.so", '__cxa_demangle') 99 | if (demangleAddress == null) demangleAddress = Module.findExportByName(null, '__cxa_demangle') 100 | if (demangleAddress == null) throw Error("can not find export function -> __cxa_demangle") 101 | let demangle: Function = new NativeFunction(demangleAddress, 'pointer', ['pointer', 'pointer', 'pointer', 'pointer']) 102 | let mangledName: NativePointer = Memory.allocUtf8String(expName) 103 | let outputBuffer: NativePointer = NULL 104 | let length: NativePointer = NULL 105 | let status: NativePointer = Memory.alloc(Process.pageSize) 106 | let result: NativePointer = demangle(mangledName, outputBuffer, length, status) as NativePointer 107 | if (status.readInt() === 0) { 108 | let resultStr: string | null = result.readUtf8String() 109 | return (resultStr == null || resultStr == expName) ? "" : resultStr 110 | } else return "" 111 | } 112 | 113 | globalThis.demangleName = demangleName -------------------------------------------------------------------------------- /agent/android/Utils/include.ts: -------------------------------------------------------------------------------- 1 | import './ArtMethodHelper' 2 | import './SymHelper' 3 | import './JavaHooker' 4 | import './libcfuncs' 5 | import './ThreadHooker' 6 | import './syscalls' -------------------------------------------------------------------------------- /agent/android/ValueHandle.ts: -------------------------------------------------------------------------------- 1 | import { PointerSize } from "./implements/10/art/Globals" 2 | 3 | export class ValueHandle { 4 | 5 | protected value_: NativePointerValue = NULL 6 | 7 | constructor(handle: NativePointerValue) { 8 | this.value_ = handle 9 | } 10 | 11 | protected get value(): NativePointerValue { 12 | return this.value_ 13 | } 14 | 15 | protected get Invalid_8(): boolean { 16 | return this.value_ > ptr(0xff) 17 | } 18 | 19 | protected get Invalid_16(): boolean { 20 | return this.value_ > ptr(0xffff) 21 | } 22 | 23 | protected get Invalid_32(): boolean { 24 | return this.value_ > ptr(0xffffffff) 25 | } 26 | 27 | protected get Invalid_64(): boolean { 28 | return this.value_ > ptr(0xffffffffffffffff) 29 | } 30 | 31 | public ReadAs64(): NativePointerValue { 32 | return this.value_ 33 | } 34 | 35 | public ReadAs32(): NativePointerValue { 36 | if (this.Invalid_32) { 37 | return ptr(Memory.alloc(Process.pageSize).writePointer(this.value_).readU32()) 38 | } 39 | return this.value_ 40 | } 41 | 42 | public ReadAs16(): NativePointerValue { 43 | if (this.Invalid_16) { 44 | return ptr(Memory.alloc(Process.pageSize).writePointer(this.value_).readU16()) 45 | } 46 | return this.value_ 47 | } 48 | 49 | public ReadAs8(): NativePointerValue { 50 | if (this.Invalid_8) { 51 | return ptr(Memory.alloc(Process.pageSize).writePointer(this.value_).readU8()) 52 | } 53 | return this.value_ 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /agent/android/functions/DefineClass.ts: -------------------------------------------------------------------------------- 1 | // _ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS_3dex8ClassDefE 2 | // art::ClassLinker::DefineClass(art::Thread*, char const*, unsigned long, art::Handle, art::DexFile const&, art::dex::ClassDef const&) 3 | // ObjPtr ClassLinker::DefineClass(Thread* self, const char* descriptor,size_t hash,Handle class_loader,const DexFile& dex_file,const dex::ClassDef& dex_class_def) 4 | 5 | // https://cs.android.com/android/platform/superproject/+/master:art/runtime/class_linker.cc;l=3388?q=ClassLinker::DefineClass&ss=android%2Fplatform%2Fsuperproject 6 | 7 | import { DexFile } from "../implements/10/art/dexfile/DexFile" 8 | import { DexFileManager } from "./DexFileManager" 9 | 10 | export class DefineClassHookManager extends DexFileManager { 11 | 12 | private static instance: DefineClassHookManager = null 13 | 14 | private constructor() { 15 | super() 16 | } 17 | 18 | public static getInstance(): DefineClassHookManager { 19 | if (DefineClassHookManager.instance == null) { 20 | DefineClassHookManager.instance = new DefineClassHookManager() 21 | } 22 | return DefineClassHookManager.instance 23 | } 24 | 25 | public get defineClassAddress() { 26 | return this.artSymbolFilter(["ClassLinker", "DefineClass", "Thread", "DexFile"]).address 27 | } 28 | 29 | public dexClassFiles: DexFile[] = [] 30 | 31 | public addDexClassFiles(dexFile: DexFile) { 32 | if (this.hasDexFile(dexFile)) return 33 | this.dexClassFiles.push(dexFile) 34 | this.dexClassFiles.forEach((item: DexFile) => { 35 | if (this.dexFiles.some((retItem: DexFile) => retItem.location == item.location)) return 36 | this.dexFiles.push(item) 37 | }) 38 | } 39 | 40 | static MD: CModule = new CModule(` 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | extern void _frida_log(const gchar * message); 47 | 48 | static void frida_log(const char * format, ...) { 49 | gchar * message; 50 | va_list args; 51 | va_start (args, format); 52 | message = g_strdup_vprintf (format, args); 53 | va_end (args); 54 | _frida_log (message); 55 | g_free (message); 56 | } 57 | 58 | extern void gotDexFile(void* dexFile); 59 | 60 | void(*it)(void* dexFile); 61 | 62 | extern GHashTable *ptrHash; 63 | 64 | void IterDexFile(void* callback) { 65 | if (ptrHash == NULL) ptrHash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); 66 | guint size = g_hash_table_size(ptrHash); 67 | if (size == 0) { 68 | frida_log("IterDexFile -> ptrHash is empty"); 69 | } else { 70 | GHashTableIter iter; 71 | gpointer key, value; 72 | g_hash_table_iter_init(&iter, ptrHash); 73 | while (g_hash_table_iter_next(&iter, &key, &value)) { 74 | ((void(*)(void*))callback)(key); 75 | } 76 | } 77 | } 78 | 79 | gboolean filterPtr(void* ptr) { 80 | if (ptrHash == NULL) { 81 | ptrHash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); 82 | } 83 | if (g_hash_table_contains(ptrHash, ptr)) { 84 | // frida_log("Filter PASS -> %p", ptr); 85 | return 0; 86 | } else { 87 | g_hash_table_add(ptrHash, ptr); 88 | return 1; 89 | } 90 | } 91 | 92 | void onEnter(GumInvocationContext *ctx) { 93 | void* dexFile = gum_invocation_context_get_nth_argument(ctx,5); 94 | if (filterPtr(dexFile)) { 95 | gotDexFile(dexFile); 96 | } 97 | } 98 | 99 | `, { 100 | ptrHash: Memory.alloc(Process.pointerSize), 101 | _frida_log: new NativeCallback((message: NativePointer) => { 102 | LOGZ(message.readCString()) 103 | }, 'void', ['pointer']), 104 | gotDexFile: new NativeCallback((dexFile: NativePointer) => { 105 | const dex_file = new DexFile(dexFile) 106 | // LOGD(`gotDexFile -> ${dexFile} | ${dex_file.location}`) 107 | DefineClassHookManager.getInstance().addDexClassFiles(dex_file) 108 | }, 'void', ['pointer']) 109 | }) 110 | 111 | public IterDexFile(callback: (dexFile: NativePointer) => void | NativePointer | undefined) { 112 | let localCallback = NULL 113 | if (callback == undefined || callback == null) { 114 | localCallback = new NativeCallback((dexFile: NativePointer) => { 115 | const dex_file = new DexFile(dexFile) 116 | LOGD(`IterDexFile -> ${dexFile} | ${dex_file.location}`) 117 | DefineClassHookManager.getInstance().addDexClassFiles(dex_file) 118 | }, 'void', ['pointer']) 119 | } else { 120 | localCallback = new NativeCallback(callback, 'void', ['pointer']) 121 | } 122 | new NativeFunction(DefineClassHookManager.MD.IterDexFile, 'void', ['pointer'])(localCallback) 123 | } 124 | 125 | public enableHook(enableLogs: boolean = false) { 126 | if (enableLogs) { 127 | LOGD(`EnableHook -> DefineClassHookManager`) 128 | Interceptor.attach(this.defineClassAddress, { 129 | onEnter: function (this: InvocationContext, args: InvocationArguments) { 130 | const dex_file = new DexFile(args[5]) 131 | DefineClassHookManager.getInstance().addDexClassFiles(dex_file) 132 | if (!enableLogs) return 133 | let disp: string = `ClassLinker::DefineClass(\n` 134 | disp += `\tClassLinker* instance = ${args[0]}\n` 135 | disp += `\tThread* self = ${args[1]}\n` 136 | disp += `\tconst char* descriptor = ${args[2]} | ${args[2].readCString()}\n` 137 | disp += `\tsize_t hash = ${args[3]}\n` 138 | disp += `\tHandle class_loader = ${args[4]}\n` 139 | disp += `\tconst DexFile& dex_file = ${args[5]}\n` 140 | disp += `\tconst dex::ClassDef& dex_class_def = ${args[6]}\n` 141 | disp += `)` 142 | this.passDis = disp 143 | this.needShow = !DefineClassHookManager.getInstance().hasDexFile(dex_file) 144 | }, 145 | onLeave: function (this: InvocationContext, retval: InvocationReturnValue) { 146 | if (!this.needShow || !enableLogs) return 147 | LOGD(this.passDis) 148 | LOGD(`retval [ ObjPtr ] = ${retval}`) 149 | newLine() 150 | } 151 | }) 152 | } else { 153 | Interceptor.attach(this.defineClassAddress, DefineClassHookManager.MD as NativeInvocationListenerCallbacks) 154 | } 155 | 156 | } 157 | 158 | } 159 | 160 | declare global { 161 | var IterDexFile: (callback: (dexFile: NativePointer) => void | NativePointer | undefined) => void 162 | } 163 | 164 | globalThis.IterDexFile = DefineClassHookManager.getInstance().IterDexFile.bind(DefineClassHookManager.getInstance()) -------------------------------------------------------------------------------- /agent/android/functions/DexFileManager.ts: -------------------------------------------------------------------------------- 1 | import { DexFile } from "../implements/10/art/dexfile/DexFile" 2 | import { SymbolManager } from "./SymbolManager" 3 | 4 | export class DexFileManager extends SymbolManager { 5 | 6 | public static dexFiles: DexFile[] = [] 7 | 8 | constructor() { 9 | super() 10 | } 11 | 12 | public get dexFiles() { 13 | return DexFileManager.dexFiles 14 | } 15 | 16 | public addDexFile(dexFile: DexFile) { 17 | if (this.hasDexFile(dexFile)) return 18 | DexFileManager.dexFiles.push(dexFile) 19 | } 20 | 21 | public removeDexFile(dexFile: DexFile) { 22 | DexFileManager.dexFiles = DexFileManager.dexFiles.filter(item => item != dexFile) 23 | } 24 | 25 | public hasDexFile(dexFile: DexFile): boolean { 26 | return this.dexFiles.some(item => item == dexFile) 27 | } 28 | 29 | } 30 | 31 | declare global { 32 | var listDexFiles: (onlyAppDex?: boolean) => void 33 | var dumpDexFiles: (onlyAppDex?: boolean) => void 34 | } 35 | 36 | const showDexFileInner = (dexFile: DexFile, index?: number, dump: boolean = false) => { 37 | LOGD(`[${index == undefined ? "*" : index}] DexFile<${dexFile.handle}>`) 38 | LOGZ(`\tlocation = ${dexFile.location}`) 39 | LOGZ(`\tchecksum = ${dexFile.location_checksum} | is_compact_dex = ${dexFile.is_compact_dex}`) 40 | LOGZ(`\tbegin = ${dexFile.begin} | size = ${dexFile.size} | data_begin = ${dexFile.data_begin} | data_size = ${dexFile.data_size}`) 41 | if (dump && dexFile.location.endsWith(".dex")) dexFile.dump() 42 | newLine() 43 | } 44 | 45 | const iterDexFile = (dump: boolean, onlyAppDex: boolean = true) => { 46 | let count: number = 0 47 | LOGZ(`Waitting for dex files... \nInter ${DexFileManager.dexFiles.length} dex files.`) 48 | DexFileManager.dexFiles.forEach((item: DexFile) => { 49 | if (count == 0) clear() 50 | if (onlyAppDex) if (!item.location.includes("/data/app/")) return 51 | showDexFileInner(item, ++count, dump) 52 | }) 53 | 54 | } 55 | 56 | globalThis.listDexFiles = (onlyAppDex: boolean = true) => { 57 | iterDexFile(false, onlyAppDex) 58 | } 59 | 60 | globalThis.dumpDexFiles = (onlyAppDex: boolean = true) => { 61 | iterDexFile(true, onlyAppDex) 62 | } -------------------------------------------------------------------------------- /agent/android/functions/ExecuteSwitchImplCpp.ts: -------------------------------------------------------------------------------- 1 | 2 | import { InstrumentationEvent, InstrumentationListenerJsProxImpl } from "../implements/10/art/Instrumentation/InstrumentationListener" 3 | import { SwitchImplContext } from "../implements/10/art/interpreter/SwitchImplContext" 4 | import { Instrumentation } from "../implements/10/art/Instrumentation/Instrumentation" 5 | import { interpreter } from "../implements/10/art/interpreter/interpreter" 6 | import { ArtMethod } from "../implements/10/art/mirror/ArtMethod" 7 | import { ArtThread } from "../implements/10/art/Thread" 8 | import { ObjPtr } from "../implements/10/art/ObjPtr" 9 | import { KeyValueStore } from "../../tools/common" 10 | import { getSym } from "../Utils/SymHelper" 11 | 12 | // https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/interpreter/interpreter_switch_impl-inl.h;l=2625 13 | export class ExecuteSwitchImplCppManager { 14 | 15 | private constructor() { } 16 | 17 | // void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*) 18 | // _ZN3art11interpreter20ExecuteSwitchImplCppILb1ELb0EEEvPNS0_17SwitchImplContextE 19 | private static get execute_switch_impl_cpp_1_0() { 20 | return getSym("_ZN3art11interpreter20ExecuteSwitchImplCppILb1ELb0EEEvPNS0_17SwitchImplContextE", "libart.so")! 21 | } 22 | 23 | // void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*) 24 | // _ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb1EEEvPNS0_17SwitchImplContextE 25 | private static get execute_switch_impl_cpp_0_1() { 26 | return getSym("_ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb1EEEvPNS0_17SwitchImplContextE", "libart.so")! 27 | } 28 | 29 | // void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*) 30 | // _ZN3art11interpreter20ExecuteSwitchImplCppILb1ELb1EEEvPNS0_17SwitchImplContextE 31 | private static get execute_switch_impl_cpp_1_1() { 32 | return getSym("_ZN3art11interpreter20ExecuteSwitchImplCppILb1ELb1EEEvPNS0_17SwitchImplContextE", "libart.so")! 33 | } 34 | 35 | // void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*) 36 | // _ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb0EEEvPNS0_17SwitchImplContextE 37 | private static get execute_switch_impl_cpp_0_0() { 38 | return getSym("_ZN3art11interpreter20ExecuteSwitchImplCppILb0ELb0EEEvPNS0_17SwitchImplContextE", "libart.so")! 39 | } 40 | 41 | static onValueChanged(key: string, value: number | string): void { 42 | if (key != "filterThreadId" && key != "filterMethodName") return 43 | LOGZ(`ExecuteSwitchImplCpp Got New Value -> ${key} -> ${value}`) 44 | if (key == "filterThreadId") ExecuteSwitchImplCppManager.filterThreadId = value as number 45 | if (key == "filterMethodName") ExecuteSwitchImplCppManager.filterMethodName = value as string 46 | } 47 | 48 | public static get execute_functions(): NativePointer[] { 49 | return [ 50 | ExecuteSwitchImplCppManager.execute_switch_impl_cpp_1_0, 51 | ExecuteSwitchImplCppManager.execute_switch_impl_cpp_0_1, 52 | ExecuteSwitchImplCppManager.execute_switch_impl_cpp_1_1, 53 | ExecuteSwitchImplCppManager.execute_switch_impl_cpp_0_0, 54 | ].filter(it => it != null) 55 | } 56 | 57 | private static filterThreadId: number = -1 58 | private static filterMethodName: string = '' 59 | 60 | public static enableHook() { 61 | 62 | interpreter.CanUseMterp = true 63 | Instrumentation.ForceInterpretOnly() 64 | 65 | // class method_listeners extends InstrumentationListenerJsProxImpl { 66 | // MethodEntered(thread: ArtThread, this_object: ObjPtr, method: ArtMethod, dex_pc: number): void { 67 | // LOGD(`method_listeners -> MethodEntered: ${method.PrettyMethod(false)}`) 68 | // } 69 | // } 70 | 71 | // Instrumentation.AddListener(new method_listeners(), InstrumentationEvent.kMethodEntered); 72 | 73 | ExecuteSwitchImplCppManager.execute_functions.forEach(hookAddress => { 74 | 75 | // ts impl 76 | Interceptor.attach(hookAddress, { 77 | onEnter: function (args) { 78 | const ctx: SwitchImplContext = new SwitchImplContext(args[0]) 79 | if (!ctx.shadow_frame.method.methodName.includes(ExecuteSwitchImplCppManager.filterMethodName)) return 80 | 81 | // LOGD(ctx.shadow_frame) 82 | // ctx.shadow_frame.printBackTraceWithSmali() 83 | 84 | if (ExecuteSwitchImplCppManager.filterThreadId != -1 && ExecuteSwitchImplCppManager.filterThreadId != Process.getCurrentThreadId()) { 85 | 86 | // ctx.shadow_frame.printBackTraceWithSmali() 87 | 88 | const threadInfo = `${Process.getCurrentThreadId()} ${ctx.self.GetThreadName()}` 89 | const lastMethod: ArtMethod | null = ctx.shadow_frame.link.method 90 | const lastMethodStr: string = lastMethod ? lastMethod.PrettyMethod(false) : "null" 91 | const currentMethod = ctx.self.GetCurrentMethod().PrettyMethod(false) 92 | LOGD(`${threadInfo} \n${lastMethodStr} -> ${currentMethod}`) 93 | 94 | } 95 | 96 | // const dexfile = ctx.self.GetCurrentMethod().GetDexFile() 97 | // LOGD(ctx.accessor) 98 | // let offset = 0 99 | // let allsize = ctx.self.GetCurrentMethod().DexInstructions().insns_size_in_code_units 100 | // while (allsize * 2 > offset) { 101 | // LOGD(ctx.accessor.InstructionAt(offset).dumpString(dexfile)) 102 | // offset += ctx.accessor.InstructionAt(offset).SizeInCodeUnits 103 | // } 104 | 105 | // LOGD(ctx.shadow_frame) 106 | // LOGD(ctx.result_register) 107 | // newLine() 108 | } 109 | }) 110 | 111 | // // cmodule impl 112 | // Interceptor.attach(hookAddress, new CModule(` 113 | // #include 114 | // #include 115 | // #include 116 | // #include 117 | 118 | // extern void _frida_log(const gchar * message); 119 | 120 | // static void frida_log(const char * format, ...) { 121 | // gchar * message; 122 | // va_list args; 123 | // va_start (args, format); 124 | // message = g_strdup_vprintf (format, args); 125 | // va_end (args); 126 | // _frida_log (message); 127 | // g_free (message); 128 | // } 129 | 130 | // `, { 131 | // _frida_log: new NativeCallback((message: NativePointer) => { 132 | // LOGZ(message.readCString()) 133 | // }, 'void', ['pointer']), 134 | // }) as NativeInvocationListenerCallbacks) 135 | }) 136 | } 137 | } 138 | 139 | setImmediate(() => { 140 | KeyValueStore.getInstance().subscribe(ExecuteSwitchImplCppManager) 141 | }) -------------------------------------------------------------------------------- /agent/android/functions/OpenCommon.ts: -------------------------------------------------------------------------------- 1 | 2 | // std::unique_ptr DexFileLoader::OpenCommon( 3 | // std::shared_ptr container, 4 | // const uint8_t* base, 5 | // size_t size, 6 | // const std::string& location, 7 | // std::optional location_checksum, 8 | // const OatDexFile* oat_dex_file, 9 | // bool verify, 10 | // bool verify_checksum, 11 | // std::string* error_msg, 12 | // DexFileLoaderErrorCode* error_code) 13 | 14 | import { DexFileManager } from "./DexFileManager"; 15 | 16 | // https://cs.android.com/android/platform/superproject/+/master:art/libdexfile/dex/dex_file_loader.cc;l=417?q=OpenCommon&ss=android%2Fplatform%2Fsuperproject 17 | 18 | export class OpenCommonHookManager extends DexFileManager { 19 | 20 | private static instance: OpenCommonHookManager = null 21 | 22 | private constructor() { 23 | super() 24 | } 25 | 26 | public static getInstance(): OpenCommonHookManager { 27 | if (OpenCommonHookManager.instance == null) { 28 | OpenCommonHookManager.instance = new OpenCommonHookManager() 29 | } 30 | return OpenCommonHookManager.instance 31 | } 32 | 33 | public get openCommonAddress() { 34 | return this.dexfileSymbolFilter(["DexFileLoader", "OpenCommon"], ["ArtDexFileLoader"]).address 35 | } 36 | 37 | // todo 这里有点问题 晚点结合ida瞅瞅 38 | public enableHook() { 39 | LOGD(`EnableHook -> OpenCommonHookManager`) 40 | Interceptor.attach(this.openCommonAddress, { 41 | onEnter: function (this: InvocationContext, args: InvocationArguments) { 42 | let disp: string = `DexFileLoader::OpenCommon(\n` 43 | disp += `\tDexFileLoader instance = ${args[0]}\n` 44 | disp += `\tstd::shared_ptr container = ${args[1]}\n` 45 | disp += `\tconst uint8_t* base = ${args[2]}\n` 46 | disp += `\tsize_t size = ${args[3]}\n` 47 | disp += `\tconst std::string& location = ${args[4]}\n` 48 | disp += `\tstd::optional location_checksum = ${args[5]}\n` 49 | disp += `\tconst OatDexFile* oat_dex_file = ${args[6]}\n` 50 | disp += `\tbool verify = ${args[7]}\n` 51 | disp += `\tbool verify_checksum = ${args[8]}\n` 52 | disp += `\tstd::string* error_msg = ${args[9]}\n` 53 | disp += `\tDexFileLoaderErrorCode* error_code = ${args[10]}\n` 54 | disp += `)` 55 | this.passDis = disp 56 | }, 57 | onLeave: function (this: InvocationContext, retval: InvocationReturnValue) { 58 | // retval: std::unique_ptr 59 | LOGD(this.passDis) 60 | LOGD(`retval [std::unique_ptr]: ${retval}`) 61 | newLine() 62 | } 63 | }) 64 | 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /agent/android/functions/SymbolManager.ts: -------------------------------------------------------------------------------- 1 | import { DEBUG } from "../../tools/common" 2 | import { LOGW } from "../../tools/logger" 3 | 4 | export class SymbolManager { 5 | 6 | static get artModule(): Module { 7 | return Process.getModuleByName("libart.so")! 8 | } 9 | 10 | static get artBaseAddress(): NativePointer { 11 | return Module.findBaseAddress("libart.so") 12 | } 13 | 14 | static get artSymbol(): ModuleSymbolDetails[] { 15 | return this.artModule.enumerateSymbols() 16 | } 17 | 18 | public artSymbolFilter(filterStrs: string[], excludefilterStrs?: string[]): ModuleSymbolDetails { 19 | return SymbolManager.symbolFilter(SymbolManager.artSymbol, filterStrs, excludefilterStrs) 20 | } 21 | 22 | static get dexDileModule(): Module { 23 | return Process.getModuleByName("libdexfile.so") 24 | } 25 | 26 | static get dexfileAddress(): NativePointer { 27 | return Module.findBaseAddress("libdexfile.so") 28 | } 29 | 30 | static get dexfileSymbol(): ModuleSymbolDetails[] { 31 | return this.dexDileModule.enumerateSymbols() 32 | } 33 | 34 | public dexfileSymbolFilter(filterStrs: string[], excludefilterStrs?: string[]): ModuleSymbolDetails { 35 | return SymbolManager.symbolFilter(SymbolManager.dexfileSymbol, filterStrs, excludefilterStrs) 36 | } 37 | 38 | public static SymbolFilter(moduleName: Module | string | null, filterStrs?: string[], excludefilterStrs?: string[]): ModuleSymbolDetails { 39 | let localMd: Module = null 40 | if (moduleName == null || moduleName == undefined) { 41 | let syms: ModuleSymbolDetails = SymbolManager.symbolFilter(this.artSymbol, filterStrs, excludefilterStrs, false) 42 | if (syms == null) syms = SymbolManager.symbolFilter(this.dexfileSymbol, filterStrs, excludefilterStrs, false) 43 | if (syms == null) throw new Error("can not find symbol") 44 | return syms 45 | } else { 46 | if (typeof moduleName == "string") { 47 | localMd = Process.getModuleByName(moduleName)! 48 | } else if (moduleName instanceof Module) { 49 | localMd = moduleName 50 | } 51 | return SymbolManager.symbolFilter(localMd.enumerateSymbols(), filterStrs) 52 | } 53 | } 54 | 55 | private static symbolFilter(mds: ModuleSymbolDetails[], containfilterStrs: string[], excludefilterStrs: string[] = [], withError: boolean = true): ModuleSymbolDetails { 56 | let ret = mds.filter((item: ModuleSymbolDetails) => { 57 | return containfilterStrs.every((filterStr: string) => { 58 | return item.name.indexOf(filterStr) != -1 59 | }) 60 | }) 61 | ret = ret.filter((item: ModuleSymbolDetails) => { 62 | return excludefilterStrs.every((filterStr: string) => { 63 | return item.name.indexOf(filterStr) == -1 64 | }) 65 | }) 66 | if (ret.length == 0) if (withError) throw new Error("can not find symbol") 67 | if (ret.length > 1) { 68 | if (DEBUG) LOGW(`find too many symbol, just ret first | size : ${ret.length}`) 69 | if (ret.length < 5) { 70 | ret.forEach((item: ModuleSymbolDetails) => { 71 | LOGZ(JSON.stringify(item)) 72 | }) 73 | } 74 | } 75 | return ret[0] 76 | } 77 | } -------------------------------------------------------------------------------- /agent/android/functions/dlopen.ts: -------------------------------------------------------------------------------- 1 | import { PointerSize } from "../implements/10/art/Globals" 2 | import { DEBUG } from "../../tools/common" 3 | import { libC } from "../Utils/libcfuncs" 4 | 5 | export const waitSoLoad = (soName: string, sleepS: number = 10) => { 6 | whenSoLoad(soName, () => { libC.sleep(sleepS) }) 7 | } 8 | 9 | export const logSoLoad = ()=> whenSoLoad(undefined) 10 | 11 | var mapCalled = new Map() 12 | export const whenSoLoad = (soName: string, fun_enter?: Function, fun_leave?: Function, callOnce: boolean = true) => { 13 | 14 | const callback: InvocationListenerCallbacks = { 15 | onEnter: function (args) { 16 | this.path = '' 17 | try { 18 | this.path = String(args[0].readCString()) 19 | LOGD(this.path) 20 | } catch (error) { 21 | if (DEBUG) LOGE(error) 22 | } 23 | if (checkOnce(callOnce, this.path) && this.path != undefined && this.path.includes(soName) && fun_enter != undefined) fun_enter(soName) 24 | }, 25 | onLeave: function (_retval) { 26 | if (checkOnce(callOnce, this.path) && this.path != undefined && this.path.includes(soName) && fun_leave != undefined) fun_leave(soName) 27 | }, 28 | } 29 | 30 | function checkOnce(callOnce: boolean, path: string): boolean { 31 | if (callOnce) { 32 | if (mapCalled.has(path)) return false 33 | else { 34 | mapCalled.set(path, true) 35 | return true 36 | } 37 | } 38 | } 39 | 40 | const dlopen = Module.findExportByName(null, "dlopen") 41 | const android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext") 42 | const MEM = Memory.alloc(PointerSize) 43 | if (dlopen != null) Interceptor.attach(dlopen, callback, MEM.writeInt(0)) 44 | if (android_dlopen_ext != null) Interceptor.attach(android_dlopen_ext, callback, MEM.writeInt(1)) 45 | } 46 | 47 | declare global { 48 | var logSoLoad: ()=>void 49 | var waitSoLoad: (soName: string, sleepS?: number)=>void 50 | var whenSoLoad: (soName: string, fun_enter?: Function, fun_leave?: Function, callOnce?: boolean)=>void 51 | } 52 | 53 | globalThis.logSoLoad = logSoLoad 54 | globalThis.waitSoLoad = waitSoLoad 55 | globalThis.whenSoLoad = whenSoLoad -------------------------------------------------------------------------------- /agent/android/functions/include.ts: -------------------------------------------------------------------------------- 1 | // hook some functions 2 | import './DefineClass' 3 | import './SymbolManager' 4 | import './DefineClass' 5 | import './OpenCommon' 6 | import './LinkerManager' 7 | import './ExecuteSwitchImplCpp' 8 | 9 | import './dlopen' -------------------------------------------------------------------------------- /agent/android/implements/10/art/ClassLinker.ts: -------------------------------------------------------------------------------- 1 | import { callSym } from "../../../Utils/SymHelper" 2 | import { JSHandle } from "../../../JSHandle" 3 | import { PointerSize } from "./Globals" 4 | import { ObjPtr } from "./ObjPtr" 5 | 6 | export class ClassLinker extends JSHandle { 7 | 8 | // std::vector boot_class_path_; 9 | boot_class_path_: NativePointer = this.CurrentHandle 10 | // std::vector> boot_dex_files_; 11 | boot_dex_files_: NativePointer = this.boot_class_path_.add(PointerSize) 12 | // std::list dex_caches_ GUARDED_BY(Locks::dex_lock_); 13 | dex_caches_: NativePointer = this.boot_dex_files_.add(PointerSize) 14 | // std::list class_loaders_ 15 | class_loaders_: NativePointer = this.dex_caches_.add(PointerSize) 16 | // std::unique_ptr boot_class_table_ GUARDED_BY(Locks::classlinker_classes_lock_); 17 | boot_class_table_: NativePointer = this.class_loaders_.add(PointerSize) 18 | // td::vector> new_class_roots_ GUARDED_BY(Locks::classlinker_classes_lock_); 19 | new_class_roots_: NativePointer = this.boot_class_table_.add(PointerSize) 20 | // std::vector new_bss_roots_boot_oat_files_GUARDED_BY(Locks::classlinker_classes_lock_); 21 | new_bss_roots_boot_oat_files_: NativePointer = this.new_class_roots_.add(PointerSize) 22 | // Atomic failed_dex_cache_class_lookups_; 23 | failed_dex_cache_class_lookups_: NativePointer = this.new_bss_roots_boot_oat_files_.add(PointerSize) 24 | // GcRoot> class_roots_; 25 | class_roots_: NativePointer = this.failed_dex_cache_class_lookups_.add(PointerSize) 26 | static kFindArrayCacheSize: NativePointer = ptr(16) 27 | // GcRoot find_array_class_cache_[kFindArrayCacheSize]; 28 | find_array_class_cache_: NativePointer = this.class_roots_.add(0x4) 29 | // size_t find_array_class_cache_next_victim_; 30 | find_array_class_cache_next_victim_: NativePointer = this.find_array_class_cache_.add(0x4) 31 | // bool init_done_; 32 | init_done_: NativePointer = this.find_array_class_cache_next_victim_.add(PointerSize) 33 | // bool log_new_roots_ GUARDED_BY(Locks::classlinker_classes_lock_); 34 | log_new_roots_: NativePointer = this.init_done_.add(PointerSize) 35 | // const bool fast_class_not_found_exceptions_; 36 | fast_class_not_found_exceptions_: NativePointer = this.log_new_roots_.add(PointerSize) 37 | 38 | // const void* quick_resolution_trampoline_; 39 | quick_resolution_trampoline_: NativePointer = this.fast_class_not_found_exceptions_.add(PointerSize) 40 | // const void* quick_imt_conflict_trampoline_; 41 | quick_imt_conflict_trampoline_: NativePointer = this.quick_resolution_trampoline_.add(PointerSize) 42 | // const void* quick_generic_jni_trampoline_; 43 | quick_generic_jni_trampoline_: NativePointer = this.quick_imt_conflict_trampoline_.add(PointerSize) 44 | // const void* quick_to_interpreter_bridge_trampoline_; 45 | quick_to_interpreter_bridge_trampoline_: NativePointer = this.quick_generic_jni_trampoline_.add(PointerSize) 46 | 47 | // PointerSize image_pointer_size_; 48 | image_pointer_size_: NativePointer = this.quick_to_interpreter_bridge_trampoline_.add(PointerSize) 49 | // std::unique_ptr cha_; 50 | cha_: NativePointer = this.image_pointer_size_.add(PointerSize) 51 | 52 | constructor(handle: NativePointer) { 53 | super(handle) 54 | } 55 | 56 | get CurrentHandle(): NativePointer { 57 | return this.handle 58 | } 59 | 60 | get SizeOfClass(): number { 61 | return this.cha_.add(PointerSize).sub(this.handle).toInt32() 62 | } 63 | 64 | toString(): string { 65 | let disp: string = `ClassLinker<${this.handle}>` 66 | return disp 67 | } 68 | 69 | IsReadOnly(): boolean { 70 | return callSym( 71 | "_ZNK3art7DexFile10IsReadOnlyEv", "libdexfile.so", 72 | "bool", ["pointer"], 73 | this.handle) 74 | } 75 | 76 | // ObjPtr LookupClass(Thread* self, 77 | // const char* descriptor, 78 | // ObjPtr class_loader) 79 | // _ZN3art11ClassLinker11LookupClassEPNS_6ThreadEPKcNS_6ObjPtrINS_6mirror11ClassLoaderEEE 80 | LookupClass(descriptor: string, class_loader: ObjPtr): ObjPtr { 81 | return callSym( 82 | "_ZN3art11ClassLinker11LookupClassEPNS_6ThreadEPKcNS_6ObjPtrINS_6mirror11ClassLoaderEEE", "libart.so", 83 | "pointer", ["pointer", "pointer", "pointer"], 84 | this.handle, Memory.allocUtf8String(descriptor), class_loader) 85 | } 86 | 87 | 88 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/GcRoot.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../JSHandle" 2 | import { ArtObject } from "../../../Object" 3 | 4 | // class GcRoot {} 5 | export class GcRoot extends JSHandle { 6 | 7 | public static readonly Size: number = 0x4 8 | 9 | private lsthandle: NativePointer 10 | private _factory: (handle: NativePointer) => T 11 | 12 | constructor(factory: (handle: NativePointer) => T, handle: NativePointer) { 13 | super(handle.readU32()) 14 | this.lsthandle = handle 15 | this._factory = factory; 16 | } 17 | 18 | // mutable mirror::CompressedReference root_; 19 | get root(): T { 20 | return this._factory(this.handle) 21 | } 22 | 23 | toString(): string { 24 | return `GcRoot<(read32)${this.lsthandle} -> ${this.handle}>` 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Globals.ts: -------------------------------------------------------------------------------- 1 | // static constexpr size_t kBitsPerByte = 8; 2 | export const kBitsPerByte: number = 8 3 | 4 | // static constexpr size_t kRegistersSizeShift = 12; 5 | export const kRegistersSizeShift: NativePointer = ptr(12) 6 | // static constexpr size_t kInsSizeShift = 8; 7 | export const kInsSizeShift: NativePointer = ptr(8) 8 | // static constexpr size_t kOutsSizeShift = 4; 9 | export const kOutsSizeShift: NativePointer = ptr(4) 10 | // static constexpr size_t kTriesSizeSizeShift = 0; 11 | export const kTriesSizeSizeShift: NativePointer = ptr(0) 12 | // static constexpr uint16_t kFlagPreHeaderRegisterSize = 0x1 << 0; 13 | export const kFlagPreHeaderRegisterSize: NativePointer = ptr(0x1).shl(0) 14 | // static constexpr uint16_t kFlagPreHeaderInsSize = 0x1 << 1; 15 | export const kFlagPreHeaderInsSize: NativePointer = ptr(0x1).shl(1) 16 | // static constexpr uint16_t kFlagPreHeaderOutsSize = 0x1 << 2; 17 | export const kFlagPreHeaderOutsSize: NativePointer = ptr(0x1).shl(2) 18 | // static constexpr uint16_t kFlagPreHeaderTriesSize = 0x1 << 3; 19 | export const kFlagPreHeaderTriesSize: NativePointer = ptr(0x1).shl(3) 20 | // static constexpr uint16_t kFlagPreHeaderInsnsSize = 0x1 << 4; 21 | export const kFlagPreHeaderInsnsSize: NativePointer = ptr(0x1).shl(4) 22 | // static constexpr size_t kInsnsSizeShift = 5; 23 | export const kInsnsSizeShift: number = 5 24 | // static constexpr size_t kInsnsSizeBits = sizeof(uint16_t) * kBitsPerByte - kInsnsSizeShift; 25 | export const kInsnsSizeBits: number = (16 * (kBitsPerByte)) - (kInsnsSizeShift) 26 | 27 | 28 | export const Arch = Process.arch 29 | export const PointerSize = Process.pointerSize -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/InstrumentationStackFrame.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../../JSHandle" 2 | import { ArtObject } from "../../../../Object" 3 | import { PointerSize } from "../Globals" 4 | import { ArtMethod } from "../mirror/ArtMethod" 5 | 6 | export class InstrumentationStackFrame extends JSHandle { 7 | 8 | // mirror::Object* this_object_; 9 | this_object_: NativePointer = this.CurrentHandle 10 | // ArtMethod* method_; 11 | method_: NativePointer = this.this_object_.add(PointerSize) 12 | // uintptr_t return_pc_; 13 | return_pc_: NativePointer = this.method_.add(PointerSize) 14 | // size_t frame_id_; 15 | frame_id_: NativePointer = this.return_pc_.add(PointerSize) 16 | // bool interpreter_entry_; 17 | interpreter_entry_: NativePointer = this.frame_id_.add(PointerSize) 18 | 19 | constructor(handle: NativePointer) { 20 | super(handle) 21 | } 22 | 23 | toString(): string { 24 | let disp: string = `InstrumentationStackFrame< ${this.handle} >` 25 | if (this.handle.isNull()) return disp 26 | disp += `\n${this.this_object}` 27 | disp += `\n${this.method}` 28 | disp += `\n${this.return_pc}` 29 | disp += `\n${this.frame_id}` 30 | disp += `\n${this.interpreter_entry}` 31 | return disp 32 | } 33 | 34 | get SizeOfClass(): number { 35 | return super.SizeOfClass + (this.interpreter_entry_.add(0x4).sub(this.handle).toInt32()) 36 | } 37 | 38 | get this_object(): ArtObject { 39 | return new ArtObject(this.this_object_.readPointer()) 40 | } 41 | 42 | get method(): ArtMethod { 43 | return new ArtMethod(this.method_.readPointer()) 44 | } 45 | 46 | get return_pc(): NativePointer { 47 | return this.return_pc_.readPointer() 48 | } 49 | 50 | get frame_id(): number { 51 | return this.frame_id_.readU32() 52 | } 53 | 54 | get interpreter_entry(): boolean { 55 | return this.interpreter_entry_.readU8() == 1 56 | } 57 | 58 | // std::string InstrumentationStackFrame::Dump() const { 59 | // std::ostringstream os; 60 | // os << "Frame " << frame_id_ << " " << ArtMethod::PrettyMethod(method_) << ":" 61 | // << reinterpret_cast(return_pc_) << " this=" << reinterpret_cast(this_object_); 62 | // return os.str(); 63 | // } 64 | Dump(): string { 65 | return `Frame ${this.frame_id_} ${this.method.PrettyMethod()}:${this.return_pc_} this=${this.this_object}` 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/InstrumentationStackPopper.ts: -------------------------------------------------------------------------------- 1 | import { Instrumentation } from "./Instrumentation" 2 | import { JSHandle } from "../../../../JSHandle" 3 | import { PointerSize } from "../Globals" 4 | import { ArtThread } from "../Thread" 5 | 6 | export class InstrumentationStackPopper extends JSHandle { 7 | 8 | // Thread* self_; 9 | self_: NativePointer = this.CurrentHandle 10 | // Instrumentation* instrumentation_; 11 | instrumentation_: NativePointer = this.self_.add(PointerSize) 12 | // uint32_t frames_to_remove_; 13 | frames_to_remove_: NativePointer = this.instrumentation_.add(PointerSize) 14 | 15 | constructor(handle: NativePointer) { 16 | super(handle) 17 | } 18 | 19 | toString(): string { 20 | let disp: string = `InstrumentationStackPopper< ${this.handle} >` 21 | if (this.handle.isNull()) return disp 22 | disp += `\n${this.self}` 23 | disp += `\n${this.frames_to_remove}` 24 | return disp 25 | } 26 | 27 | get SizeOfClass(): number { 28 | return super.SizeOfClass + (this.frames_to_remove_.add(0x4).sub(this.handle).toInt32()) 29 | } 30 | 31 | get self(): ArtThread { 32 | return new ArtThread(this.self_.readPointer()) 33 | } 34 | 35 | get frames_to_remove(): number { 36 | return this.frames_to_remove_.readU32() 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/SmalinInlineManager.ts: -------------------------------------------------------------------------------- 1 | import { InstrumentationEvent, InstrumentationListenerJsProxImpl } from "./InstrumentationListener" 2 | import { CodeItemInstructionAccessor } from "../dexfile/CodeItemInstructionAccessor" 3 | import { StandardDexFile_CodeItem } from "../dexfile/StandardDexFile" 4 | import { CompactDexFile_CodeItem } from "../dexfile/CompactDexFile" 5 | import { DexItemStruct } from "../dexfile/DexFileStructs" 6 | import { interpreter } from "../interpreter/interpreter" 7 | import { Instrumentation } from "./Instrumentation" 8 | import { ArtInstruction } from "../Instruction" 9 | import { ArtMethod } from "../mirror/ArtMethod" 10 | import { ShadowFrame } from "../ShadowFrame" 11 | import { SmaliWriter } from "./SmaliWriter" 12 | import { ArtThread } from "../Thread" 13 | import { assert } from "console" 14 | 15 | type InputArtMethod = ArtMethod | NativePointer | number | string 16 | 17 | export class SmaliInlineManager { 18 | 19 | static recoverMap: Map = new Map() 20 | 21 | public static enable() { 22 | SmaliInlineManager.handleException() 23 | } 24 | 25 | private static convertToArtMethod(method: InputArtMethod): ArtMethod { 26 | if (method instanceof NativePointer) method = new ArtMethod(method) 27 | if (typeof method == 'number') method = new ArtMethod(ptr(method)) 28 | if (typeof method == 'string') method = pathToArtMethod(method) 29 | assert(method instanceof ArtMethod, "method must be ArtMethod") 30 | return method 31 | } 32 | 33 | public static traceSingleMethod(method: InputArtMethod) { 34 | method = SmaliInlineManager.convertToArtMethod(method) 35 | if (SmaliInlineManager.recoverMap.has(method)) throw new Error("method already traced") 36 | const newSmali = SmaliInlineManager.Impl_CP(method) 37 | SmaliInlineManager.recoverMap.set(method, { src: method.DexInstructions().CodeItem.insns_start, new: newSmali.newStart }) 38 | method.SetCodeItem(newSmali.newStart) 39 | method.DexInstructions().CodeItem.insns_size_in_code_units = newSmali.insnsSize 40 | } 41 | 42 | public static restoreSingleMethod(method: InputArtMethod) { 43 | method = SmaliInlineManager.convertToArtMethod(method) 44 | if (!SmaliInlineManager.recoverMap.has(method)) return 45 | const { src, new: _newStart } = SmaliInlineManager.recoverMap.get(method)! 46 | method.SetCodeItem(src) 47 | } 48 | 49 | // This implementation has a limitation in that it is not effective when the dex (cdex) cannot be modified permissions (read/write) by mprotect. 50 | // inline way to modify and save smali code 51 | private static Impl_Inline() { 52 | 53 | } 54 | 55 | // cp smali to new memory and reset dexfile codeitem entrypoint 56 | private static Impl_CP(method: InputArtMethod): { newStart: NativePointer, insnsMap: Map, insnsSize: number } { 57 | method = SmaliInlineManager.convertToArtMethod(method) 58 | const st = method.GetCodeItemPack() 59 | const allocSize: number = st.headerSize + st.insnsSize * 2 60 | var new_smali_start: NativePointer = Memory.alloc(allocSize) 61 | Memory.copy(new_smali_start, st.headerStart, st.headerSize) 62 | const sw = new SmaliWriter(new_smali_start.add(st.headerSize)) 63 | sw.putNop() 64 | method.forEachSmali((instruction: ArtInstruction, _codeItem: DexItemStruct) => { 65 | sw.writeInsns(instruction) 66 | sw.putNop() 67 | }) 68 | sw.flush() 69 | 70 | return { newStart: new_smali_start, insnsMap: sw.insnsMap, insnsSize: sw.insnsSize / 2 } 71 | } 72 | 73 | private static handleException() { 74 | 75 | // there's 2 way to impl 76 | // 1. use Instrumentation listener (virtual classs impl) 77 | // class method_listeners extends InstrumentationListenerJsProxImpl { 78 | // MethodEntered(thread: ArtThread, this_object: ObjPtr, method: ArtMethod, dex_pc: number): void { 79 | // LOGD(`method_listeners -> MethodEntered: ${method.PrettyMethod(false)}`) 80 | // } 81 | // } 82 | // Instrumentation.AddListener(new method_listeners(), InstrumentationEvent.kMethodEntered) 83 | 84 | // 2. hook target method and catch exception 85 | interpreter.addMoveToExceptionHandleCalledListener((ret: NativePointerValue, thread: ArtThread, shadowFrame: ShadowFrame, instrumentation: NativePointer) => { 86 | // Exceptions not handled by the original function 87 | if (ret == NULL) { 88 | LOGE(`MoveToExceptionHandleCalledListener -> ${shadowFrame.toString()}`) 89 | } 90 | return ret 91 | }) 92 | 93 | } 94 | 95 | } 96 | 97 | setImmediate(() => { SmaliInlineManager.enable() }) 98 | 99 | Reflect.set(globalThis, "SmaliInlineManager", SmaliInlineManager) -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/UnUsedInstruction.ts: -------------------------------------------------------------------------------- 1 | import { CodeItemInstructionAccessor } from "../dexfile/CodeItemInstructionAccessor" 2 | import { JSHandleNotImpl } from "../../../../JSHandle" 3 | import { R } from "../../../../../tools/intercepter" 4 | import { getSym } from "../../../../Utils/SymHelper" 5 | import { ArtMethod } from "../mirror/ArtMethod" 6 | import { ArtInstruction } from "../Instruction" 7 | import { ShadowFrame } from "../ShadowFrame" 8 | import { DexFile } from "../dexfile/DexFile" 9 | import { Opcode } from "./InstructionList" 10 | 11 | class UnUsedInstructions extends Opcode { } 12 | 13 | export class UnUsedInstructionManager extends JSHandleNotImpl { 14 | 15 | // listenerMap record UNUSED_F6 and listener 16 | private static listenerMap: Map = new Map() 17 | 18 | public static registerListener(opcode: UnUsedInstructions, listener: Function) { 19 | if (!this.listenerMap.has(opcode)) { 20 | this.listenerMap.set(opcode, []) 21 | } 22 | this.listenerMap.get(opcode).push(listener) 23 | } 24 | 25 | public static removeListener(opcode: UnUsedInstructions) { 26 | this.listenerMap.delete(opcode) 27 | } 28 | 29 | public static catchUnexpectedOpcode() { 30 | 31 | // _ZN3art11interpreter16UnexpectedOpcodeEPKNS_11InstructionERKNS_11ShadowFrameE 32 | // art::interpreter::UnexpectedOpcode(art::Instruction const*, art::ShadowFrame const&) 33 | // void UnexpectedOpcode(const Instruction* inst, const ShadowFrame& shadow_frame) 34 | 35 | // Interceptor.attach(getSym("_ZN3art11interpreter16UnexpectedOpcodeEPKNS_11InstructionERKNS_11ShadowFrameE", "libart.so"), { 36 | // onEnter: function (args) { 37 | // const inst: ArtInstruction = new ArtInstruction(args[0]) 38 | // const shadow_frame: ShadowFrame = new ShadowFrame(args[1]) 39 | // if (UnUsedInstruction.listenerMap.has(inst.opcode) && UnUsedInstruction.listenerMap.get(inst.opcode).length > 0) { 40 | // UnUsedInstruction.listenerMap.get(inst.opcode).forEach(listener => { 41 | // listener(inst, shadow_frame) 42 | // }) 43 | // } else { 44 | // shadow_frame.printBackTraceWithSmali() 45 | // LOGD(`UnexpectedOpcode ${inst.toString()} ${shadow_frame.toString()}`) 46 | // ptr(0x7375846512).writeU32(0x0a02) 47 | // const last = shadow_frame.link 48 | // args[1] = last.handle 49 | // } 50 | // } 51 | // }) 52 | 53 | const sym_addr: NativePointer = getSym("_ZN3art11interpreter16UnexpectedOpcodeEPKNS_11InstructionERKNS_11ShadowFrameE", "libart.so") 54 | const src_function = new NativeFunction(sym_addr, 'pointer', ['pointer', 'pointer']) 55 | R(sym_addr, new NativeCallback((inst_: NativePointer, shadow_frame_: NativePointer) => { 56 | let inst = new ArtInstruction(inst_) 57 | let shadow_frame = new ShadowFrame(shadow_frame_) 58 | LOGD(`UnexpectedOpcode ${inst.toString()} ${shadow_frame.toString()}`) 59 | return 60 | if (UnUsedInstructionManager.listenerMap.has(inst.opcode) && UnUsedInstructionManager.listenerMap.get(inst.opcode).length > 0) { 61 | UnUsedInstructionManager.listenerMap.get(inst.opcode).forEach(listener => { 62 | listener(inst, shadow_frame) 63 | }) 64 | } else { 65 | shadow_frame.printBackTraceWithSmali() 66 | UnUsedInstructionManager.mapModify.has(inst_) && inst_.writeByteArray(UnUsedInstructionManager.mapModify.get(inst_)) 67 | shadow_frame.SetDexPC(shadow_frame.GetDexPC()) 68 | } 69 | }, 'pointer', ['pointer', 'pointer'])) 70 | } 71 | 72 | private static mapModify: Map = new Map() 73 | 74 | public static ModSmaliInstruction(methodPath: string = "com.unity3d.player.UnityPlayer.UnitySendMessage", offset: number = 0x42) { 75 | const CurrentMethod: ArtMethod = pathToArtMethod(methodPath) 76 | const dexfile: DexFile = CurrentMethod.GetDexFile() 77 | if (dexfile.is_compact_dex) throw new Error("not support compact dex") 78 | const dexInstructions: CodeItemInstructionAccessor = CurrentMethod.DexInstructions() 79 | const ins_ptr_patch_start: NativePointer = dexInstructions.CodeItem.insns_start.add(offset) 80 | const ins_ptr_patch_len = new ArtInstruction(ins_ptr_patch_start).SizeInCodeUnits 81 | LOGD(`ins_ptr_patch_start ${ins_ptr_patch_start} ins_ptr_patch_len ${ins_ptr_patch_len}`) 82 | const ins_arr: ArrayBuffer = ins_ptr_patch_start.readByteArray(ins_ptr_patch_len) 83 | UnUsedInstructionManager.mapModify.set(ins_ptr_patch_start, ins_arr) 84 | if (dexfile.IsReadOnly()) dexfile.EnableWrite() 85 | ins_ptr_patch_start.writeU32(UnUsedInstructions.UNUSED_3E) 86 | if (ins_ptr_patch_len > 0x1) { 87 | for (let iterP = ins_ptr_patch_start.add(0x1); iterP < ins_ptr_patch_start.add(ins_ptr_patch_len); iterP = iterP.add(0x1)) { 88 | iterP.writeU8(0x0) 89 | } 90 | } 91 | } 92 | 93 | private static newClass(): Java.Wrapper { 94 | const rewardClass = Java.registerClass({ 95 | name: "com.Test.CallbackClass", 96 | superClass: Java.use("java.lang.Object"), 97 | implements: undefined, 98 | methods: { 99 | ['OnCalled']: { 100 | returnType: 'void', 101 | argumentTypes: ['boolean'], 102 | implementation: function (z: boolean) { 103 | LOGW(`called CallbackClass -> OnCalled ${z}`) 104 | } 105 | } 106 | } 107 | }) 108 | return rewardClass 109 | } 110 | 111 | public static test() { 112 | return UnUsedInstructionManager.newClass().OnCalled.handle 113 | } 114 | 115 | } 116 | 117 | setImmediate(() => { UnUsedInstructionManager.catchUnexpectedOpcode() }) 118 | 119 | Reflect.set(globalThis, "UnUsedInstructionManager", UnUsedInstructionManager) 120 | 121 | declare global { 122 | var ModSmaliInstruction: (methodPath: string, offset: number) => void 123 | var test: () => void 124 | var testCallSendMessage 125 | } 126 | 127 | globalThis.ModSmaliInstruction = UnUsedInstructionManager.ModSmaliInstruction 128 | globalThis.test = () => { Java.perform(() => { LOGD(UnUsedInstructionManager.test()) }) } 129 | globalThis.testCallSendMessage = () => { 130 | Java.perform(() => { 131 | var JavaString = Java.use("java.lang.String") 132 | Java.use("com.unity3d.player.UnityPlayer").UnitySendMessage(JavaString.$new("1"), JavaString.$new("2"), JavaString.$new("3")) 133 | }) 134 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/enum.ts: -------------------------------------------------------------------------------- 1 | export enum InterpreterHandlerTable { 2 | kMainHandlerTable = 0, // Main handler table: no suspend check, no instrumentation. 3 | kAlternativeHandlerTable = 1, // Alternative handler table: suspend check and/or instrumentation 4 | // enabled. 5 | kNumHandlerTables 6 | } 7 | 8 | export enum InstrumentationEvent { 9 | kMethodEntered = 0x1, 10 | kMethodExited = 0x2, 11 | kMethodUnwind = 0x4, 12 | kDexPcMoved = 0x8, 13 | kFieldRead = 0x10, 14 | kFieldWritten = 0x20, 15 | kExceptionThrown = 0x40, 16 | kBranch = 0x80, 17 | kWatchedFramePop = 0x200, 18 | kExceptionHandled = 0x400, 19 | } 20 | 21 | export enum InstrumentationLevel { 22 | kInstrumentNothing, // execute without instrumentation 23 | kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs 24 | kInstrumentWithInterpreter // execute with interpreter 25 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Instrumentation/include.ts: -------------------------------------------------------------------------------- 1 | import './enum' 2 | import './SmaliWriter' 3 | import './UnUsedInstruction' 4 | import './InstructionList' 5 | import './SmalinInlineManager' 6 | import './Instrumentation' 7 | import './InstrumentationListener' 8 | import './InstrumentationStackFrame' 9 | import './InstrumentationStackPopper' -------------------------------------------------------------------------------- /agent/android/implements/10/art/Oat/MemMap.ts: -------------------------------------------------------------------------------- 1 | import { StdString } from "../../../../../tools/StdString" 2 | import { JSHandleNotImpl } from "../../../../JSHandle" 3 | import { PointerSize } from "../Globals" 4 | 5 | export class MemMap extends JSHandleNotImpl { 6 | 7 | // std::string name_; 8 | name_: NativePointer = this.handle 9 | // uint8_t* begin_ = nullptr; // Start of data. May be changed by AlignBy. 10 | begin_: NativePointer = this.name_.add(PointerSize * 3) 11 | // size_t size_ = 0u; // Length of data. 12 | size_: NativePointer = this.begin_.add(PointerSize) 13 | 14 | // void* base_begin_ = nullptr; // Page-aligned base address. May be changed by AlignBy. 15 | base_begin_: NativePointer = this.size_.add(PointerSize) 16 | // size_t base_size_ = 0u; // Length of mapping. May be changed by RemapAtEnd (ie Zygote). 17 | base_size_: NativePointer = this.base_begin_.add(PointerSize) 18 | // int prot_ = 0; // Protection of the map. 19 | prot_: NativePointer = this.base_size_.add(PointerSize) 20 | 21 | // // When reuse_ is true, this is just a view of an existing mapping 22 | // // and we do not take ownership and are not responsible for 23 | // // unmapping. 24 | // bool reuse_ = false; 25 | reuse_: NativePointer = this.prot_.add(PointerSize) 26 | 27 | // // When already_unmapped_ is true the destructor will not call munmap. 28 | // bool already_unmapped_ = false; 29 | already_unmapped_: NativePointer = this.reuse_.add(PointerSize) 30 | 31 | // size_t redzone_size_ = 0u; 32 | redzone_size_: NativePointer = this.already_unmapped_.add(PointerSize) 33 | 34 | toString(): string { 35 | let disp: string = `MemMap<${this.handle}>` 36 | if (this.handle.isNull()) return disp 37 | disp += `\n\t name = ${this.name} @ ${this.name_}` 38 | disp += `\n\t begin = ${this.begin} @ ${this.begin_}` 39 | disp += `\n\t size = ${this.size} | ${ptr(this.size)} @ ${this.size_}` 40 | disp += `\n\t base_begin = ${this.base_begin} @ ${this.base_begin_}` 41 | disp += `\n\t base_size = ${this.base_size} | ${ptr(this.base_size)} @ ${this.base_size_}` 42 | disp += `\n\t prot = ${this.prot} | ${ptr(this.prot)} @ ${this.prot_}` 43 | disp += `\n\t reuse = ${this.reuse} @ ${this.reuse_}` 44 | disp += `\n\t already_unmapped = ${this.already_unmapped} @ ${this.already_unmapped_}` 45 | disp += `\n\t redzone_size = ${this.redzone_size} | ${ptr(this.redzone_size)} @ ${this.redzone_size_}` 46 | return disp 47 | } 48 | 49 | get name(): string { 50 | return new StdString(this.name_).toString() 51 | } 52 | 53 | get begin(): NativePointer { 54 | return this.begin_.readPointer() 55 | } 56 | 57 | get size(): number { 58 | return this.size_.toInt32() 59 | } 60 | 61 | get base_begin(): NativePointer { 62 | return this.base_begin_.readPointer() 63 | } 64 | 65 | get base_size(): number { 66 | return this.base_size_.toInt32() 67 | } 68 | 69 | get prot(): number { 70 | return this.prot_.toInt32() 71 | } 72 | 73 | get reuse(): boolean { 74 | return this.reuse_.toInt32() != 0 75 | } 76 | 77 | get already_unmapped(): boolean { 78 | return this.already_unmapped_.toInt32() != 0 79 | } 80 | 81 | get redzone_size(): number { 82 | return this.redzone_size_.toInt32() 83 | } 84 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Oat/OatFile.ts: -------------------------------------------------------------------------------- 1 | import { StdString } from "../../../../../tools/StdString" 2 | import { JSHandle } from "../../../../JSHandle" 3 | import { PointerSize } from "../Globals" 4 | 5 | export class OatFile extends JSHandle { 6 | 7 | // const std::string location_; 8 | private location_ = this.currentHandle 9 | // std::unique_ptr vdex_; 10 | private vdex_ = this.location_.add(PointerSize * 3) 11 | // const uint8_t* begin_; 12 | private begin_ = this.vdex_.add(PointerSize) 13 | // const uint8_t* end_; 14 | private end_ = this.begin_.add(PointerSize) 15 | // const uint8_t* data_bimg_rel_ro_begin_; 16 | private data_bimg_rel_ro_begin_ = this.end_.add(PointerSize) 17 | // const uint8_t* data_bimg_rel_ro_end_; 18 | private data_bimg_rel_ro_end_ = this.data_bimg_rel_ro_begin_.add(PointerSize) 19 | // uint8_t* bss_begin_; 20 | private bss_begin_ = this.data_bimg_rel_ro_end_.add(PointerSize) 21 | // uint8_t* bss_end_; 22 | private bss_end_ = this.bss_begin_.add(PointerSize) 23 | // uint8_t* bss_methods_; 24 | private bss_methods_ = this.bss_end_.add(PointerSize) 25 | // uint8_t* bss_roots_; 26 | private bss_roots_ = this.bss_methods_.add(PointerSize) 27 | // const bool is_executable_; 28 | private is_executable_ = this.bss_roots_.add(PointerSize) 29 | // uint8_t* vdex_begin_; 30 | private vdex_begin_ = this.is_executable_.add(PointerSize) 31 | // uint8_t* vdex_end_; 32 | private vdex_end_ = this.vdex_begin_.add(PointerSize) 33 | // std::vector oat_dex_files_storage_; 34 | private oat_dex_files_storage_ = this.vdex_end_.add(PointerSize) 35 | // using Table = AllocationTrackingSafeMap; 36 | // Table oat_dex_files_; 37 | private oat_dex_files_ = this.oat_dex_files_storage_.add(PointerSize) 38 | // mutable Mutex secondary_lookup_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; 39 | private secondary_lookup_lock_ = this.oat_dex_files_.add(PointerSize) 40 | // mutable Table secondary_oat_dex_files_ GUARDED_BY(secondary_lookup_lock_); 41 | private secondary_oat_dex_files_ = this.secondary_lookup_lock_.add(PointerSize) 42 | // mutable std::list string_cache_ GUARDED_BY(secondary_lookup_lock_); 43 | private string_cache_ = this.secondary_oat_dex_files_.add(PointerSize) 44 | // std::unique_ptr>> uncompressed_dex_files_; 45 | private uncompressed_dex_files_ = this.string_cache_.add(PointerSize) 46 | 47 | constructor(handle: NativePointer) { 48 | super(handle) 49 | } 50 | 51 | get SizeOfClass(): number { 52 | return this.uncompressed_dex_files_.add(PointerSize).sub(this.CurrentHandle).toInt32() 53 | } 54 | 55 | get VirtualClassOffset(): number { 56 | return PointerSize 57 | } 58 | 59 | get currentHandle(): NativePointer { 60 | return this.handle.add(super.SizeOfClass).add(this.VirtualClassOffset) 61 | } 62 | 63 | toString(): string { 64 | let disp: string = `OatFile<${this.handle}>` 65 | disp += `\n\tlocation_: ${this.location} @ ${this.location_}` 66 | disp += `\n\tbegin: ${this.begin} | end: ${this.end}` 67 | disp += `\n\tdata_bimg_rel_ro_begin: ${this.data_bimg_rel_ro_begin} | data_bimg_rel_ro_end: ${this.data_bimg_rel_ro_end}` 68 | disp += `\n\tbss_begin: ${this.bss_begin} | bss_end: ${this.bss_end}` 69 | disp += `\n\tbss_methods: ${this.bss_methods} | bss_roots: ${this.bss_roots}` 70 | disp += `\n\tis_executable: ${this.is_executable}` 71 | disp += `\n\tvdex_begin: ${this.vdex_begin} | vdex_end: ${this.vdex_end}` 72 | disp += `\n\toat_dex_files_storage: ${this.oat_dex_files_storage} | oat_dex_files: ${this.oat_dex_files}` 73 | return disp 74 | } 75 | 76 | get location(): string { 77 | return new StdString(this.location_).toString() 78 | } 79 | 80 | get begin(): NativePointer { 81 | return this.begin_.readPointer() 82 | } 83 | 84 | get end(): NativePointer { 85 | return this.end_.readPointer() 86 | } 87 | 88 | get data_bimg_rel_ro_begin(): NativePointer { 89 | return this.data_bimg_rel_ro_begin_.readPointer() 90 | } 91 | 92 | get data_bimg_rel_ro_end(): NativePointer { 93 | return this.data_bimg_rel_ro_end_.readPointer() 94 | } 95 | 96 | get bss_begin(): NativePointer { 97 | return this.bss_begin_.readPointer() 98 | } 99 | 100 | get bss_end(): NativePointer { 101 | return this.bss_end_.readPointer() 102 | } 103 | 104 | get bss_methods(): NativePointer { 105 | return this.bss_methods_.readPointer() 106 | } 107 | 108 | get bss_roots(): NativePointer { 109 | return this.bss_roots_.readPointer() 110 | } 111 | 112 | get is_executable(): boolean { 113 | return this.is_executable_.readU8() === 1 114 | } 115 | 116 | get vdex_begin(): NativePointer { 117 | return this.vdex_begin_.readPointer() 118 | } 119 | 120 | get vdex_end(): NativePointer { 121 | return this.vdex_end_.readPointer() 122 | } 123 | 124 | get oat_dex_files_storage(): NativePointer { 125 | return this.oat_dex_files_storage_.readPointer() 126 | } 127 | 128 | get oat_dex_files(): NativePointer { 129 | return this.oat_dex_files_.readPointer() 130 | } 131 | 132 | get secondary_lookup_lock(): NativePointer { 133 | return this.secondary_lookup_lock_.readPointer() 134 | } 135 | 136 | get secondary_oat_dex_files(): NativePointer { 137 | return this.secondary_oat_dex_files_.readPointer() 138 | } 139 | 140 | get string_cache(): NativePointer { 141 | return this.string_cache_.readPointer() 142 | } 143 | 144 | get uncompressed_dex_files(): NativePointer { 145 | return this.uncompressed_dex_files_.readPointer() 146 | } 147 | 148 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Oat/include.ts: -------------------------------------------------------------------------------- 1 | import './OatFile' 2 | import './OatDexFile' 3 | import './MemMap' -------------------------------------------------------------------------------- /agent/android/implements/10/art/OatQuickMethodHeader.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../JSHandle" 2 | 3 | // jetbrains://clion/navigate/reference?project=libart&path=oat_quick_method_header.h 4 | 5 | // private: 6 | // static constexpr uint32_t kShouldDeoptimizeMask = 0x80000000; 7 | const kShouldDeoptimizeMask: number = 0x80000000 8 | // static constexpr uint32_t kCodeSizeMask = ~kShouldDeoptimizeMask; 9 | const kCodeSizeMask: number = ~kShouldDeoptimizeMask 10 | 11 | // 12 | // // The offset in bytes from the start of the vmap table to the end of the header. 13 | // uint32_t vmap_table_offset_ = 0u; 14 | // // The code size in bytes. The highest bit is used to signify if the compiled 15 | // // code with the method header has should_deoptimize flag. 16 | // uint32_t code_size_ = 0u; 17 | // // The actual code. 18 | // uint8_t code_[0]; 19 | // OatQuickMethodHeader precedes the raw code chunk generated by the compiler. 20 | // class PACKED(4) OatQuickMethodHeader {} 21 | export class OatQuickMethodHeader extends JSHandle { 22 | 23 | // // The offset in bytes from the start of the vmap table to the end of the header. 24 | // uint32_t vmap_table_offset_ = 0u; 25 | private vmap_table_offset_ = this.handle.add(0) 26 | // // The code size in bytes. The highest bit is used to signify if the compiled 27 | // // code with the method header has should_deoptimize flag. 28 | // uint32_t code_size_ = 0u; 29 | private code_size_ = this.handle.add(4) 30 | // // The actual code. 31 | // uint8_t code_[0]; 32 | private code_ = this.handle.add(8) 33 | 34 | constructor(handle: NativePointer) { 35 | super(handle) 36 | } 37 | 38 | toString(): string { 39 | return `${this.handle} -> vmap_table_offset: ${this.vmap_table_offset} code_size: ${this.code_size} code: ${this.code}` 40 | } 41 | 42 | get vmap_table_offset(): number { 43 | return this.vmap_table_offset_.readU32() 44 | } 45 | 46 | get code_size(): number { 47 | return this.code_size_.readU32() 48 | } 49 | 50 | get code(): NativePointer { 51 | return this.code_.readPointer() 52 | } 53 | 54 | // const uint8_t* GetOptimizedCodeInfoPtr() 55 | GetOptimizedCodeInfoPtr(): NativePointer { 56 | return this.code.sub(this.vmap_table_offset_) 57 | } 58 | 59 | // uint32_t GetCodeSize() 60 | GetCodeSize(): number { 61 | return ptr(this.code_size).and(kCodeSizeMask).toUInt32() 62 | } 63 | 64 | // const uint32_t* GetCodeSizeAddr() 65 | GetCodeSizeAddr(): NativePointer { 66 | return this.code_size_ 67 | } 68 | 69 | // bool IsOptimized() const { 70 | // return GetCodeSize() != 0 && vmap_table_offset_ != 0; 71 | // } 72 | IsOptimized(): boolean { 73 | return this.GetCodeSize() != 0 && this.vmap_table_offset != 0 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/ObjPtr.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../JSHandle" 2 | 3 | // Value type representing a pointer to a mirror::Object of type MirrorType 4 | // Since the cookie is thread based, it is not safe to share an ObjPtr between threads. 5 | // template 6 | // class ObjPtr {} 7 | export class ObjPtr extends JSHandle { 8 | 9 | constructor(handle: NativePointer) { 10 | super(handle) 11 | } 12 | 13 | // uintptr_t reference_; 14 | get value(): NativePointer { 15 | return this.handle.readPointer() 16 | } 17 | 18 | toString(): string { 19 | return `${this.handle} -> ${this.value}` 20 | } 21 | 22 | } 23 | 24 | export class ObjectReference extends JSHandle { 25 | 26 | // The encoded reference to a mirror::Object. 27 | // uint32_t reference_; 28 | private reference_: NativePointer = this.handle 29 | 30 | public static SizeOfClass: number = 0x4 31 | 32 | constructor(handle: NativePointer) { 33 | super(handle) 34 | } 35 | 36 | get reference(): NativePointer { 37 | return ptr(this.reference_.readU32()) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/QuickMethodFrameInfo.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../JSHandle" 2 | 3 | // jetbrains://clion/navigate/reference?project=libart&path=quick/quick_method_frame_info.h 4 | 5 | export class QuickMethodFrameInfo extends JSHandle { 6 | 7 | constructor(handle: NativePointer) { 8 | super(handle) 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/StackVisitor/CHAStackVisitor.ts: -------------------------------------------------------------------------------- 1 | import { StackVisitor } from "./StackVisitor" 2 | 3 | export class CHAStackVisitor extends StackVisitor { 4 | 5 | constructor(handle: NativePointer) { 6 | super(handle) 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/StackVisitor/CatchBlockStackVisitor.ts: -------------------------------------------------------------------------------- 1 | import { StackVisitor } from "./StackVisitor" 2 | 3 | export class CatchBlockStackVisitor extends StackVisitor { 4 | 5 | constructor(handle: NativePointer) { 6 | super(handle) 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/StackVisitor/include.ts: -------------------------------------------------------------------------------- 1 | import './StackVisitor' 2 | import './CHAStackVisitor' 3 | import './CatchBlockStackVisitor' -------------------------------------------------------------------------------- /agent/android/implements/10/art/Thread_Inl.ts: -------------------------------------------------------------------------------- 1 | import { JSHandleNotImpl } from "../../../JSHandle" 2 | import { ShadowFrame } from "./ShadowFrame" 3 | import { PointerSize } from "./Globals" 4 | 5 | export class StateAndFlags extends JSHandleNotImpl { 6 | 7 | private as_struct: { 8 | // volatile uint16_t flags; 9 | flags: NativePointer, 10 | // volatile uint16_t state; 11 | state: NativePointer 12 | } 13 | 14 | // AtomicInteger as_atomic_int; 15 | private as_atomic_int = this.handle.add(0x4) 16 | // volatile int32_t as_int; 17 | private as_int = this.handle.add(0x4) 18 | 19 | constructor(handle: NativePointer) { 20 | super(handle) 21 | this.as_struct = { 22 | flags: this.handle, 23 | state: this.handle 24 | } 25 | } 26 | 27 | toString(): string { 28 | let disp: string = `StateAndFlags<${this.handle}>` 29 | if (this.handle.isNull()) return disp 30 | disp += `\n\t flags=${this.flags}` 31 | disp += `\n\t state=${this.state}` 32 | return disp 33 | } 34 | 35 | get flags(): number { 36 | return this.as_struct.flags.readU16() 37 | } 38 | 39 | get state(): number { 40 | return this.as_struct.state.readU16() 41 | } 42 | 43 | get as_atomic_int_value(): number { 44 | return this.as_atomic_int.readS32() 45 | } 46 | 47 | get as_int_value(): number { 48 | return this.as_int.readS32() 49 | } 50 | 51 | public static get SizeOfClass(): number { 52 | return 0x4 * 3 53 | } 54 | } 55 | 56 | export class RuntimeStats extends JSHandleNotImpl { 57 | 58 | // // Number of objects allocated. 59 | // uint64_t allocated_objects; 60 | allocated_objects: NativePointer = this.handle 61 | // // Cumulative size of all objects allocated. 62 | // uint64_t allocated_bytes; 63 | allocated_bytes: NativePointer = this.allocated_objects.add(0x8) 64 | 65 | // // Number of objects freed. 66 | // uint64_t freed_objects; 67 | freed_objects: NativePointer = this.allocated_bytes.add(0x8) 68 | // // Cumulative size of all freed objects. 69 | // uint64_t freed_bytes; 70 | freed_bytes: NativePointer = this.freed_objects.add(0x8) 71 | 72 | // // Number of times an allocation triggered a GC. 73 | // uint64_t gc_for_alloc_count; 74 | gc_for_alloc_count: NativePointer = this.freed_bytes.add(0x8) 75 | 76 | // // Number of initialized classes. 77 | // uint64_t class_init_count; 78 | class_init_count: NativePointer = this.gc_for_alloc_count.add(0x8) 79 | // // Cumulative time spent in class initialization. 80 | // uint64_t class_init_time_ns; 81 | class_init_time_ns: NativePointer = this.class_init_count.add(0x8) 82 | 83 | toString(): string { 84 | let disp: string = `RuntimeStats<${this.handle}>` 85 | if (this.handle.isNull()) return disp 86 | disp += `\n\t allocated_objects=${this.allocated_objects}` 87 | disp += `\n\t allocated_bytes=${this.allocated_bytes}` 88 | disp += `\n\t freed_objects=${this.freed_objects}` 89 | disp += `\n\t freed_bytes=${this.freed_bytes}` 90 | disp += `\n\t gc_for_alloc_count=${this.gc_for_alloc_count}` 91 | disp += `\n\t class_init_count=${this.class_init_count}` 92 | disp += `\n\t class_init_time_ns=${this.class_init_time_ns}` 93 | return disp 94 | } 95 | 96 | public static get SizeOfClass(): number { 97 | return 0x8 * 7 98 | } 99 | } 100 | 101 | export class ManagedStack extends JSHandleNotImpl { 102 | 103 | // TaggedTopQuickFrame tagged_top_quick_frame_; 104 | private tagged_top_quick_frame_: { 105 | // uintptr_t tagged_sp_; 106 | tagged_sp_: NativePointer 107 | } 108 | // ManagedStack* link_; 109 | private link_: NativePointer 110 | // ShadowFrame* top_shadow_frame_; 111 | private top_shadow_frame_: NativePointer 112 | 113 | constructor(handle: NativePointer) { 114 | super(handle) 115 | this.tagged_top_quick_frame_ = { 116 | tagged_sp_: this.handle 117 | } 118 | this.link_ = this.tagged_top_quick_frame_.tagged_sp_.add(PointerSize) 119 | this.top_shadow_frame_ = this.link_.add(PointerSize) 120 | } 121 | 122 | toString(): string { 123 | let disp: string = `ManagedStack<${this.handle}>` 124 | if (this.handle.isNull()) return disp 125 | disp += `\n\t tagged_sp=${this.tagged_sp}` 126 | disp += `\n\t link=${this.link}` 127 | disp += `\n\t top_shadow_frame=${this.top_shadow_frame}` 128 | return disp 129 | } 130 | 131 | public get link(): ManagedStack { 132 | return new ManagedStack(this.link_.readPointer()) 133 | } 134 | 135 | public get top_shadow_frame(): ShadowFrame { 136 | return new ShadowFrame(this.top_shadow_frame_.readPointer()) 137 | } 138 | 139 | public get tagged_sp(): NativePointer { 140 | return this.tagged_top_quick_frame_.tagged_sp_ 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Type/JValue.ts: -------------------------------------------------------------------------------- 1 | import { JSHandleNotImpl } from "../../../../JSHandle" 2 | import { ArtObject } from "../../../../Object" 3 | 4 | export class JValue extends JSHandleNotImpl { 5 | 6 | // uint8_t z; 7 | private z = this.handle 8 | // int8_t b; 9 | private b = this.z.add(1) 10 | // uint16_t c; 11 | private c = this.b.add(1) 12 | // int16_t s; 13 | private s = this.c.add(2) 14 | // int32_t i; 15 | private i = this.s.add(2) 16 | // int64_t j; 17 | private j = this.i.add(4) 18 | // float f; 19 | private f = this.j.add(8) 20 | // double d; 21 | private d = this.f.add(4) 22 | // mirror::Object* l; 23 | private l = this.d.add(8) 24 | 25 | constructor(handle: NativePointer) { 26 | super(handle) 27 | } 28 | 29 | get z_(): number { 30 | return this.z.readU8() 31 | } 32 | 33 | get b_(): number { 34 | return this.b.readS8() 35 | } 36 | 37 | get c_(): number { 38 | return this.c.readU16() 39 | } 40 | 41 | get s_(): number { 42 | return this.s.readS16() 43 | } 44 | 45 | get i_(): number { 46 | return this.i.readS32() 47 | } 48 | 49 | get j_(): Int64 { 50 | return this.j.readS64() 51 | } 52 | 53 | get f_(): number { 54 | return this.f.readFloat() 55 | } 56 | 57 | get d_(): number { 58 | return this.d.readDouble() 59 | } 60 | 61 | get l_(): ArtObject { 62 | return new ArtObject(this.l.readPointer()) 63 | } 64 | 65 | // mirror::Object** GetGCRoot() { return &l; } 66 | GetGCRoot(): NativePointer { 67 | return this.l 68 | } 69 | 70 | toString(): string { 71 | let disp: string = `JValue<${this.handle}>` 72 | if (this.handle.isNull()) return disp 73 | disp += `\n\t z=${this.z_}` 74 | disp += `\n\t b=${this.b_}` 75 | disp += `\n\t c=${this.c_}` 76 | disp += `\n\t s=${this.s_}` 77 | disp += `\n\t i=${this.i_}` 78 | disp += `\n\t j=${this.j_}` 79 | disp += `\n\t f=${this.f_}` 80 | disp += `\n\t d=${this.d_}` 81 | disp += `\n\t l=${this.l_}` 82 | return disp 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Type/JavaString.ts: -------------------------------------------------------------------------------- 1 | import { ArtObject } from "../../../../Object" 2 | 3 | export class JavaString extends ArtObject { 4 | 5 | // int32_t count_; 6 | private count_: NativePointer = this.CurrentHandle 7 | // uint32_t hash_code_; 8 | private hash_code_: NativePointer = this.count_.add(0x4) 9 | // Compression of all-ASCII into 8-bit memory leads to usage one of these fields 10 | // union { 11 | // uint16_t value_[0]; 12 | // uint8_t value_compressed_[0]; 13 | // }; 14 | public str_data_: { 15 | data_: NativePointer 16 | entry_point_from_quick_compiled_code_: NativePointer 17 | } 18 | 19 | constructor(handle: NativePointer) { 20 | super(handle) 21 | this.str_data_ = { 22 | data_: this.hash_code_.add(0x4), 23 | entry_point_from_quick_compiled_code_: this.hash_code_.add(0x4) 24 | } 25 | } 26 | 27 | toString(): string { 28 | let disp: string = `JavaString<${this.handle}>` 29 | if (this.handle.isNull()) return disp 30 | disp += `\n\t count_=${this.count_} <- ${this.count}` 31 | disp += `\n\t hash_code_=${this.hash_code_} <- ${this.hash_code}` 32 | disp += `\n\t str_data_=${this.str_data_.data_} -> '${this.value}'` 33 | return disp 34 | } 35 | 36 | get count(): number { 37 | return this.count_.readS32() 38 | } 39 | 40 | get hash_code(): number { 41 | return this.hash_code_.readU32() 42 | } 43 | 44 | get value_ptr(): NativePointer { 45 | return this.str_data_.data_ 46 | } 47 | 48 | get value(): string { 49 | return this.value_ptr.readCString() 50 | } 51 | 52 | public static caseToJavaString(artObject: T): JavaString { 53 | return new JavaString(artObject.handle) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Type/Throwable.ts: -------------------------------------------------------------------------------- 1 | import { HeapReference } from "../../../../Interface/art/mirror/HeapReference" 2 | import { ArtObject } from "../../../../Object" 3 | 4 | export class Throwable extends ArtObject { 5 | 6 | // HeapReference backtrace_; 7 | private backtrace_: NativePointer = this.CurrentHandle 8 | // HeapReference cause_; 9 | private cause_: NativePointer = this.backtrace_.add(HeapReference.Size) 10 | // HeapReference detail_message_; 11 | private detail_message_: NativePointer = this.cause_.add(HeapReference.Size) 12 | // HeapReference stack_trace_; 13 | private stack_trace_: NativePointer = this.detail_message_.add(HeapReference.Size) 14 | // HeapReference suppressed_exceptions_; 15 | private suppressed_exceptions_: NativePointer = this.stack_trace_.add(HeapReference.Size) 16 | 17 | toString(): string { 18 | let disp: string = `Throwable<${this.handle}>` 19 | if (this.handle.isNull()) return disp 20 | disp += `\n\t backtrace_=${this.backtrace_} <- ${this.backtrace.root}` 21 | disp += `\n\t cause_=${this.cause_} <- ${this.cause.root}` 22 | disp += `\n\t detail_message_=${this.detail_message_} <- ${this.detail_message.root}` 23 | disp += `\n\t stack_trace_=${this.stack_trace_} <- ${this.stack_trace.root}` 24 | disp += `\n\t suppressed_exceptions_=${this.suppressed_exceptions_} <- ${this.suppressed_exceptions.root}` 25 | return disp 26 | } 27 | 28 | get backtrace(): HeapReference { 29 | return new HeapReference((handle) => new ArtObject(handle), this.backtrace_) 30 | } 31 | 32 | get cause(): HeapReference { 33 | return new HeapReference((handle) => new Throwable(handle), this.cause_) 34 | } 35 | 36 | get detail_message(): HeapReference { 37 | return new HeapReference((handle) => new ArtObject(handle), this.detail_message_) 38 | } 39 | 40 | get stack_trace(): HeapReference { 41 | return new HeapReference((handle) => new ArtObject(handle), this.stack_trace_) 42 | } 43 | 44 | get suppressed_exceptions(): HeapReference { 45 | return new HeapReference((handle) => new ArtObject(handle), this.suppressed_exceptions_) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/Type/include.ts: -------------------------------------------------------------------------------- 1 | import './JValue' 2 | import './jobject' 3 | import './JavaString' 4 | import './Throwable' -------------------------------------------------------------------------------- /agent/android/implements/10/art/Type/jobject.ts: -------------------------------------------------------------------------------- 1 | import { JSHandleNotImpl } from "../../../../JSHandle" 2 | 3 | export class jobject extends JSHandleNotImpl { 4 | 5 | constructor(handle: NativePointer) { 6 | super(handle) 7 | } 8 | 9 | toString(): string { 10 | let disp: string = `jobject<${this.handle}>` 11 | if (this.handle.isNull()) return disp 12 | return disp 13 | } 14 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/bridge.ts: -------------------------------------------------------------------------------- 1 | import { getApi, withRunnableArtThread, ArtStackVisitor, translateMethod } from 'frida-java-bridge/lib/android' 2 | import Java from 'frida-java-bridge' -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/CodeItemDataAccessor.ts: -------------------------------------------------------------------------------- 1 | import { JSHandle } from "../../../../JSHandle" 2 | import { PointerSize } from "../Globals" 3 | import { CodeItemInstructionAccessor } from "./CodeItemInstructionAccessor" 4 | 5 | export class CodeItemDataAccessor extends CodeItemInstructionAccessor implements SizeOfClass { 6 | 7 | // uint16_t registers_size_; 8 | private registers_size_ = this.CurrentHandle.add(0x0) 9 | // uint16_t ins_size_; 10 | private ins_size_ = this.CurrentHandle.add(0x2) 11 | // uint16_t outs_size_; 12 | private outs_size_ = this.CurrentHandle.add(0x2 * 2) 13 | // uint16_t tries_size_; 14 | private tries_size_ = this.CurrentHandle.add(0x2 * 3) 15 | 16 | constructor(insns_size_in_code_units?: number, insns?: NativePointer, registers_size: number = 0, ins_size: number = 0, outs_size: number = 0, tries_size: number = 0) { 17 | if (typeof insns_size_in_code_units == "number") { 18 | super(insns_size_in_code_units, insns) 19 | this.registers_size_.writeU16(registers_size) 20 | this.ins_size_.writeU16(ins_size) 21 | this.outs_size_.writeU16(outs_size) 22 | this.tries_size_.writeU16(tries_size) 23 | } 24 | } 25 | 26 | toString(): string { 27 | let disp: string = `CodeItemDataAccessor<${this.handle}>` 28 | if (this.handle.isNull()) return disp 29 | disp += `\n\t registers_size_: ${this.registers_size}` 30 | disp += `\n\t ins_size_: ${this.ins_size}` 31 | disp += `\n\t outs_size_: ${this.outs_size}` 32 | disp += `\n\t tries_size_: ${this.tries_size}` 33 | return disp 34 | } 35 | 36 | get SizeOfClass(): number { 37 | return CodeItemDataAccessor.SIZE_OF_CodeItemDataAccessor + super.SizeOfClass 38 | } 39 | 40 | get CurrentHandle(): NativePointer { 41 | return this.handle.add(super.SizeOfClass) 42 | } 43 | 44 | get registers_size(): number { 45 | return this.registers_size_.readU16() 46 | } 47 | 48 | get ins_size(): number { 49 | return this.ins_size_.readU16() 50 | } 51 | 52 | get outs_size(): number { 53 | return this.outs_size_.readU16() 54 | } 55 | 56 | get tries_size(): number { 57 | return this.tries_size_.readU16() 58 | } 59 | 60 | set registers_size(registers_size: number) { 61 | this.registers_size_.writeU16(registers_size) 62 | } 63 | 64 | set ins_size(ins_size: number) { 65 | this.ins_size_.writeU16(ins_size) 66 | } 67 | 68 | set outs_size(outs_size: number) { 69 | this.outs_size_.writeU16(outs_size) 70 | } 71 | 72 | set tries_size(tries_size: number) { 73 | this.tries_size_.writeU16(tries_size) 74 | } 75 | 76 | public static fromRef(ref_ptr: NativePointer) { 77 | const accessor: CodeItemDataAccessor = Object.create(CodeItemDataAccessor.prototype) 78 | accessor.handle = ref_ptr 79 | accessor.registers_size_ = ref_ptr.add(0x4 + 0x4 + PointerSize) 80 | accessor.ins_size_ = accessor.registers_size_.add(0x2) 81 | accessor.outs_size_ = accessor.ins_size_.add(0x2) 82 | accessor.tries_size_ = accessor.outs_size_.add(0x2) 83 | Reflect.defineProperty(accessor, 'insns_', ref_ptr.add(0x4 + 0x4)) 84 | Reflect.defineProperty(accessor, 'insns_size_in_code_units_', ref_ptr) 85 | return accessor 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/CodeItemDebugInfoAccessor.ts: -------------------------------------------------------------------------------- 1 | import { CodeItemDataAccessor } from "./CodeItemDataAccessor" 2 | import { PointerSize } from "../Globals" 3 | 4 | export class CodeItemDebugInfoAccessor extends CodeItemDataAccessor implements SizeOfClass { 5 | 6 | // const DexFile* dex_file_ = nullptr; 7 | protected dex_file_ = this.CurrentHandle.add(0x0) 8 | // uint32_t debug_info_offset_ = 0u; 9 | protected debug_info_offset_ = this.CurrentHandle.add(PointerSize) 10 | 11 | constructor(insns_size_in_code_units: number, insns: NativePointer, registers_size?: number, ins_size?: number, outs_size?: number, tries_size?: number, dex_file: NativePointer = NULL, debug_info_offset: number = 0) { 12 | super(insns_size_in_code_units, insns, registers_size, ins_size, outs_size, tries_size) 13 | this.dex_file_.writePointer(dex_file) 14 | this.debug_info_offset_.writeU32(debug_info_offset) 15 | } 16 | 17 | toString(): string { 18 | let disp: string = `CodeItemDebugInfoAccessor< ${this.handle} >` 19 | if (this.handle.isNull()) return disp 20 | disp += `\ndex_file_: ${this.dex_file}` 21 | disp += `\ndebug_info_offset_: ${this.debug_info_offset}` 22 | return disp 23 | } 24 | 25 | get SizeOfClass(): number { 26 | return CodeItemDebugInfoAccessor.SIZE_OF_CodeItemDebugInfoAccessor + super.SizeOfClass 27 | } 28 | 29 | get CurrentHandle(): NativePointer { 30 | return this.handle.add(super.SizeOfClass) 31 | } 32 | 33 | get dex_file(): NativePointer { 34 | return this.dex_file_.readPointer() 35 | } 36 | 37 | get debug_info_offset(): number { 38 | return this.debug_info_offset_.readU32() 39 | } 40 | 41 | set dex_file(dex_file: NativePointer) { 42 | this.dex_file_.writePointer(dex_file) 43 | } 44 | 45 | set debug_info_offset(debug_info_offset: number) { 46 | this.debug_info_offset_.writeU32(debug_info_offset) 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/CodeItemInstructionAccessor.ts: -------------------------------------------------------------------------------- 1 | import { StandardDexFile_CodeItem } from "./StandardDexFile" 2 | import { CompactDexFile_CodeItem } from "./CompactDexFile" 3 | import { JSHandle } from "../../../../JSHandle" 4 | import { ArtMethod } from "../mirror/ArtMethod" 5 | import { ArtInstruction } from "../Instruction" 6 | import { PointerSize } from "../Globals" 7 | import { DexFile } from "./DexFile" 8 | 9 | export class CodeItemInstructionAccessor extends JSHandle implements SizeOfClass { 10 | 11 | protected static readonly SIZE_OF_CodeItemInstructionAccessor: number = 0x4 + PointerSize 12 | protected static readonly SIZE_OF_CodeItemDebugInfoAccessor = PointerSize + 0x4 13 | protected static readonly SIZE_OF_CodeItemDataAccessor = 0x2 * 4 14 | 15 | public CodeItem: CompactDexFile_CodeItem | StandardDexFile_CodeItem 16 | 17 | // size of the insns array, in 2 byte code units. 0 if there is no code item. 18 | // uint32_t insns_size_in_code_units_ = 0; 19 | private insns_size_in_code_units_ = this.CurrentHandle 20 | 21 | // Pointer to the instructions, null if there is no code item. 22 | // const uint16_t* insns_ = nullptr; 23 | private insns_ = this.CurrentHandle.add(0x4) // ref ptr 24 | 25 | constructor(insns_size_in_code_units: number = 0, insns: NativePointer = NULL) { 26 | if (typeof insns_size_in_code_units == "number" && insns_size_in_code_units == 0 && insns.isNull()) { 27 | const needAllocSize: number = CodeItemInstructionAccessor.SIZE_OF_CodeItemInstructionAccessor 28 | + CodeItemInstructionAccessor.SIZE_OF_CodeItemDataAccessor 29 | + CodeItemInstructionAccessor.SIZE_OF_CodeItemDebugInfoAccessor 30 | super(Memory.alloc(needAllocSize)) 31 | this.CurrentHandle.add(0x0).writeU32(insns_size_in_code_units) 32 | this.CurrentHandle.add(0x4).writePointer(insns) 33 | } 34 | } 35 | 36 | toString(): string { 37 | let disp: string = `CodeItemInstructionAccessor<${this.handle}>` 38 | if (this.handle.isNull()) return disp 39 | disp += `\n\tinsns_size_in_code_units: ${this.insns_size_in_code_units} | insns: ${this.insns} | insns_size_in_bytes: ${this.InsnsSizeInBytes()}` 40 | disp += `\n\n\tinsns: ${this.CodeItem.toString().split('\n').map((item, index) => index == 0 ? item : `\n\t${item}`).join('')}` 41 | return disp 42 | } 43 | 44 | public static CodeItem(dexFile: DexFile, dex_pc: NativePointer): CompactDexFile_CodeItem | StandardDexFile_CodeItem { 45 | return dexFile.is_compact_dex ? new CompactDexFile_CodeItem(dex_pc) : new StandardDexFile_CodeItem(dex_pc) 46 | } 47 | 48 | public static fromDexFile(dexFile: DexFile, dex_pc: NativePointer): CodeItemInstructionAccessor { 49 | const accessor = new CodeItemInstructionAccessor() 50 | if (dexFile.is_compact_dex) { 51 | const codeItem: CompactDexFile_CodeItem = CodeItemInstructionAccessor.CodeItem(dexFile, dex_pc) as CompactDexFile_CodeItem 52 | accessor.insns_size_in_code_units = codeItem.insns_size_in_code_units 53 | accessor.insns = codeItem.insns_start 54 | accessor.CodeItem = codeItem 55 | } else { 56 | const codeItem: StandardDexFile_CodeItem = CodeItemInstructionAccessor.CodeItem(dexFile, dex_pc) as StandardDexFile_CodeItem 57 | accessor.insns_size_in_code_units = codeItem.insns_size_in_code_units 58 | accessor.insns = codeItem.insns_start 59 | accessor.CodeItem = codeItem 60 | } 61 | return accessor 62 | } 63 | 64 | public static fromArtMethod(artMethod: ArtMethod): CodeItemInstructionAccessor { 65 | const dexFile: DexFile = artMethod.GetDexFile() 66 | const dex_pc: NativePointer = artMethod.GetCodeItem() 67 | return CodeItemInstructionAccessor.fromDexFile(dexFile, dex_pc) 68 | } 69 | 70 | get SizeOfClass(): number { 71 | return CodeItemInstructionAccessor.SIZE_OF_CodeItemInstructionAccessor + super.SizeOfClass 72 | } 73 | 74 | get CurrentHandle(): NativePointer { 75 | return this.handle.add(super.SizeOfClass) 76 | } 77 | 78 | get insns_size_in_code_units(): number { 79 | return this.insns_size_in_code_units_.readU32() 80 | } 81 | 82 | get insns(): NativePointer { 83 | return this.insns_.readPointer() 84 | } 85 | 86 | set insns_size_in_code_units(insns_size_in_code_units: number) { 87 | this.insns_size_in_code_units_.writeU32(insns_size_in_code_units) 88 | } 89 | 90 | set insns(insns: NativePointer) { 91 | this.insns_.writePointer(insns) 92 | } 93 | 94 | /*****************************************************************/ 95 | 96 | // uint32_t InsnsSizeInBytes() const { 97 | // static constexpr uint32_t kCodeUnitSizeInBytes = 2u; 98 | // return insns_size_in_code_units_ * kCodeUnitSizeInBytes; 99 | // } 100 | InsnsSizeInBytes(): number { 101 | return this.insns_size_in_code_units * 2 102 | } 103 | 104 | // Return the instruction for a dex pc. 105 | // const Instruction& InstructionAt(uint32_t dex_pc) const { 106 | // DCHECK_LT(dex_pc, InsnsSizeInCodeUnits()); 107 | // return *Instruction::At(insns_ + dex_pc); 108 | // } 109 | InstructionAt(dex_pc: number = 0): ArtInstruction { 110 | return ArtInstruction.At(this.insns.add(dex_pc)) 111 | } 112 | 113 | } 114 | 115 | declare global { 116 | var fromDexFile: (dexFile: DexFile, dex_pc: NativePointer) => CodeItemInstructionAccessor 117 | var fromArtMethod: (artMethod: ArtMethod) => CodeItemInstructionAccessor 118 | } 119 | 120 | globalThis.fromDexFile = CodeItemInstructionAccessor.fromDexFile 121 | globalThis.fromArtMethod = CodeItemInstructionAccessor.fromArtMethod -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/CompactDexFile.ts: -------------------------------------------------------------------------------- 1 | import { DexFile, DexFile_CodeItem } from "./DexFile" 2 | import { kInsnsSizeShift } from "../Globals" 3 | 4 | export class CompactDexFile extends DexFile { 5 | 6 | constructor(dexFile: NativePointer) { 7 | super(dexFile) 8 | } 9 | 10 | } 11 | 12 | export class CompactDexFile_CodeItem extends DexFile_CodeItem { 13 | 14 | // uint16_t fields_; 15 | // Packed code item data, 4 bits each: [registers_size, ins_size, outs_size, tries_size] 16 | private fields_ = this.CurrentHandle 17 | // uint16_t insns_count_and_flags_; 18 | // 5 bits for if either of the fields required preheader extension, 11 bits for the number of instruction code units. 19 | private insns_count_and_flags_ = this.fields_.add(0x2) 20 | // uint16_t insns_[1]; 21 | public insns_ = this.insns_count_and_flags_.add(0x2) 22 | 23 | constructor(dex_pc: NativePointer) { 24 | super(dex_pc) 25 | } 26 | 27 | toString(): string { 28 | let disp: string = `CompactDexFile::CodeItem<${this.handle}>` 29 | disp += `\n\tfields: ${this.fields} | insns_count_and_flags: ${this.insns_count_and_flags} | insns: ${this.insns_start}` 30 | disp += `\n\tregisters_size: ${this.registers_size} | ins_size: ${this.ins_size} | outs_size: ${this.outs_size} | tries_size: ${this.tries_size}` 31 | disp += `\n\tinsns_size_in_code_units: ${this.insns_size_in_code_units} | extension_preheader: ${this.extension_preheader}` 32 | return disp 33 | } 34 | 35 | get CurrentHandle(): NativePointer { 36 | return this.handle.add(super.SizeOfClass) 37 | } 38 | 39 | get fields(): NativePointer { 40 | return ptr(this.fields_.readU16()) 41 | } 42 | 43 | get insns_count_and_flags(): NativePointer { 44 | return ptr(this.insns_count_and_flags_.readU16()) 45 | } 46 | 47 | set insns_count_and_flags(insns_count_and_flags: NativePointer) { 48 | this.insns_count_and_flags_.writeU16(insns_count_and_flags.toInt32()) 49 | } 50 | 51 | set fields(fields: NativePointer) { 52 | this.fields_.writeU16(fields.toUInt32()) 53 | } 54 | 55 | public get registers_size(): number { 56 | return (this.fields.toUInt32() >> (4 * 3)) 57 | } 58 | 59 | public get ins_size(): number { 60 | return (this.fields.and(0xF00).toUInt32()) >> (4 * 2) 61 | } 62 | 63 | public set ins_size(ins_size: number) { 64 | this.fields = ptr((ins_size << (4 * 2)) | this.fields.toUInt32()) 65 | } 66 | 67 | public get outs_size(): number { 68 | return (this.fields.toUInt32() & 0xF0) >> (4 * 1) 69 | } 70 | 71 | public get tries_size(): number { 72 | return (this.fields.toUInt32() & 0xF) >> (4 * 0) 73 | } 74 | 75 | public get insns_size_in_code_units(): number { 76 | return this.insns_count_and_flags.shr(kInsnsSizeShift).toUInt32() 77 | } 78 | 79 | public set insns_size_in_code_units(insns_size_in_code_units: number) { 80 | this.insns_count_and_flags = ptr((insns_size_in_code_units << kInsnsSizeShift) | this.insns_count_and_flags.toUInt32()) 81 | } 82 | 83 | // ???? 84 | public get extension_preheader(): number { 85 | return this.insns_count_and_flags.shl(kInsnsSizeShift).toUInt32() 86 | } 87 | 88 | public get header_start(): NativePointer { 89 | return this.CurrentHandle 90 | } 91 | 92 | public get header_size(): number { 93 | return this.insns_.sub(this.CurrentHandle).toUInt32() 94 | } 95 | 96 | public get insns_start(): NativePointer { 97 | return this.insns_ 98 | } 99 | 100 | public get insns_size(): number { 101 | return this.insns_size_in_code_units * 2 102 | } 103 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/DexIndex.ts: -------------------------------------------------------------------------------- 1 | import { ValueHandle } from "../../../../ValueHandle" 2 | 3 | export class DexIndex extends ValueHandle { 4 | 5 | constructor(handle: NativePointer | number) { 6 | handle instanceof NativePointer ? super(handle) : super(ptr(handle)) 7 | } 8 | 9 | toString(): string { 10 | return `DexIndex<${this.value_}>` 11 | } 12 | 13 | } 14 | 15 | export class DexTypeIndex extends DexIndex { 16 | 17 | get index(): number { 18 | return (this.ReadAs16() as NativePointer).toInt32() 19 | } 20 | 21 | static get SizeOfClass(): number { 22 | return 2 23 | } 24 | 25 | toString(): string { 26 | return `TypeIndex<${this.index}>` 27 | } 28 | 29 | } 30 | 31 | export class DexProtoIndex extends DexIndex { 32 | 33 | get index(): number { 34 | return (this.ReadAs16() as NativePointer).toInt32() 35 | } 36 | 37 | static get SizeOfClass(): number { 38 | return 2 39 | } 40 | 41 | toString(): string { 42 | return `ProtoIndex<${this.index}>` 43 | } 44 | 45 | } 46 | 47 | export class DexStringIndex extends DexIndex { 48 | 49 | get index(): number { 50 | return (this.ReadAs32() as NativePointer).toInt32() 51 | } 52 | 53 | static get SizeOfClass(): number { 54 | return 4 55 | } 56 | 57 | toString(): string { 58 | return `StringIndex<${this.index}>` 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/StandardDexFile.ts: -------------------------------------------------------------------------------- 1 | import { DexFile, DexFile_CodeItem } from "./DexFile" 2 | import { DexItemStruct } from "./DexFileStructs" 3 | 4 | // StandardDexFile === DexFile 5 | export class StandardDexFile extends DexFile { } 6 | 7 | export class StandardDexFile_CodeItem extends DexFile_CodeItem implements DexItemStruct { 8 | 9 | // uint16_t registers_size_; 10 | private registers_size_ = this.CurrentHandle 11 | // uint16_t ins_size_; 12 | private ins_size_ = this.CurrentHandle.add(0x2 * 1) 13 | // uint16_t outs_size_; 14 | private outs_size_ = this.CurrentHandle.add(0x2 * 2) 15 | // uint16_t tries_size_; 16 | private tries_size_ = this.CurrentHandle.add(0x2 * 3) 17 | // uint32_t debug_info_off_; 18 | private debug_info_off_ = this.CurrentHandle.add(0x2 * 4) 19 | // uint32_t insns_size_in_code_units_; 20 | private insns_size_in_code_units_ = this.CurrentHandle.add(0x2 * 4 + 0x4 * 1) 21 | // uint16_t insns_[1] 22 | private insns_ = this.CurrentHandle.add(0x2 * 4 + 0x4 * 2) 23 | 24 | constructor(dex_pc: NativePointer) { 25 | super(dex_pc) 26 | } 27 | 28 | toString(): string { 29 | let disp: string = `StandardDexFile::CodeItem<${this.handle}>` 30 | disp += `\n\tregisters_size: ${this.registers_size} | ins_size: ${this.ins_size} | outs_size: ${this.outs_size} | tries_size: ${this.tries_size} | debug_info_off: ${this.debug_info_off} | insns_size_in_code_units: ${this.insns_size_in_code_units} | insns: ${this.insns}` 31 | return disp 32 | } 33 | 34 | public get CurrentHandle(): NativePointer { 35 | return this.handle.add(super.SizeOfClass) 36 | } 37 | 38 | public get registers_size(): number { 39 | return this.registers_size_.readU16() 40 | } 41 | 42 | public get insns(): NativePointer { 43 | return this.insns_ 44 | } 45 | 46 | public get ins_size(): number { 47 | return this.ins_size_.readU16() 48 | } 49 | 50 | public get outs_size(): number { 51 | return this.outs_size_.readU16() 52 | } 53 | 54 | public get tries_size(): number { 55 | return this.tries_size_.readU16() 56 | } 57 | 58 | public get debug_info_off(): number { 59 | return this.debug_info_off_.readU32() 60 | } 61 | 62 | public get insns_size_in_code_units(): number { 63 | return this.insns_size_in_code_units_.readU32() 64 | } 65 | 66 | public set registers_size(registers_size: number) { 67 | this.registers_size_.writeU16(registers_size) 68 | } 69 | 70 | public set ins_size(ins_size: number) { 71 | this.ins_size_.writeU16(ins_size) 72 | } 73 | 74 | public set outs_size(outs_size: number) { 75 | this.outs_size_.writeU16(outs_size) 76 | } 77 | 78 | public set tries_size(tries_size: number) { 79 | this.tries_size_.writeU16(tries_size) 80 | } 81 | 82 | public set debug_info_off(debug_info_off: number) { 83 | this.debug_info_off_.writeU32(debug_info_off) 84 | } 85 | 86 | public set insns_size_in_code_units(insns_size_in_code_units: number) { 87 | this.insns_size_in_code_units_.writeU32(insns_size_in_code_units) 88 | } 89 | 90 | public get header_start(): NativePointer { 91 | return this.CurrentHandle 92 | } 93 | 94 | public get header_size(): number { 95 | return this.insns_.sub(this.CurrentHandle).toUInt32() 96 | } 97 | 98 | public get insns_start(): NativePointer { 99 | return this.insns_ 100 | } 101 | 102 | public get insns_size(): number { 103 | return this.insns_size_in_code_units * 2 104 | } 105 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/dexfile/include.ts: -------------------------------------------------------------------------------- 1 | import './CodeItemInstructionAccessor' 2 | import './CodeItemDebugInfoAccessor' 3 | import './CodeItemDataAccessor' 4 | import './StandardDexFile' 5 | import './CompactDexFile' 6 | import './DexFileStructs' 7 | import './DexIndex' 8 | import './DexFile' 9 | import './Header' -------------------------------------------------------------------------------- /agent/android/implements/10/art/include.ts: -------------------------------------------------------------------------------- 1 | import './ClassLinker' 2 | import './Globals' 3 | import './GcRoot' 4 | import './Instruction' 5 | import './OatQuickMethodHeader' 6 | import './QuickMethodFrameInfo' 7 | import './ObjPtr' 8 | import './Thread' 9 | import './Thread_Inl' 10 | import './ShadowFrame' 11 | import './bridge' 12 | 13 | import './dexfile/include' 14 | import './interpreter/include' 15 | import './mirror/include' 16 | import './Oat/include' 17 | import './runtime/include' 18 | import './StackVisitor/include' 19 | import './Instrumentation/include' 20 | import './Type/include' -------------------------------------------------------------------------------- /agent/android/implements/10/art/interpreter/InstructionHandler.ts: -------------------------------------------------------------------------------- 1 | import { R } from "../../../../../tools/intercepter" 2 | import { JSHandleNotImpl } from "../../../../JSHandle" 3 | import { getSym } from "../../../../Utils/SymHelper" 4 | 5 | export class InstructionHandler extends JSHandleNotImpl { 6 | 7 | // _ZN3art11interpreter18InstructionHandlerILb0ELb1EE45HandlePendingExceptionWithInstrumentationImplEPKNS_15instrumentation15InstrumentationE 8 | // void art::interpreter::InstructionHandler::HandlePendingExceptionWithInstrumentationImpl(art::instrumentation::Instrumentation const*) 9 | public static Hook_InstructionHandler() { 10 | const target: NativePointer = getSym("_ZN3art11interpreter18InstructionHandlerILb0ELb1EE45HandlePendingExceptionWithInstrumentationImplEPKNS_15instrumentation15InstrumentationE") 11 | R(target, new NativeCallback(() => { 12 | LOGD(`Called InstructionHandler()`) 13 | return 14 | }, 'void', [])) 15 | } 16 | 17 | } 18 | 19 | setImmediate(() => { 20 | // InstructionHandler.Hook_InstructionHandler() 21 | }) -------------------------------------------------------------------------------- /agent/android/implements/10/art/interpreter/SwitchImplContext.ts: -------------------------------------------------------------------------------- 1 | import { JSHandleNotImpl } from "../../../../JSHandle" 2 | import { ShadowFrame } from "../ShadowFrame" 3 | import { PointerSize } from "../Globals" 4 | import { JValue } from "../Type/JValue" 5 | import { ArtThread } from "../Thread" 6 | import { CodeItemDataAccessor } from "../dexfile/CodeItemDataAccessor" 7 | 8 | export class SwitchImplContext extends JSHandleNotImpl { 9 | 10 | // Thread* self; 11 | private _self: NativePointer = this.handle 12 | // const CodeItemDataAccessor& accessor; 13 | private _accessor: NativePointer = this._self.add(PointerSize) 14 | // ShadowFrame& shadow_frame; 15 | private _shadow_frame: NativePointer = this._accessor.add(PointerSize) 16 | // JValue& result_register; 17 | private _result_register: NativePointer = this._shadow_frame.add(PointerSize) 18 | // bool interpret_one_instruction; 19 | private _interpret_one_instruction: NativePointer = this._result_register.add(PointerSize) 20 | // JValue result; 21 | private _result: NativePointer = this._interpret_one_instruction.add(PointerSize) 22 | 23 | constructor(handle: NativePointer) { 24 | super(handle) 25 | } 26 | 27 | get self(): ArtThread { 28 | return new ArtThread(this._self.readPointer()) 29 | } 30 | 31 | get accessor(): CodeItemDataAccessor { 32 | return CodeItemDataAccessor.fromRef(this._accessor.readPointer()) 33 | } 34 | 35 | get shadow_frame(): ShadowFrame { 36 | return new ShadowFrame(this._shadow_frame.readPointer()) 37 | } 38 | 39 | // JValue& result_register; 40 | get result_register(): JValue { 41 | return new JValue(this._result_register.readPointer()) 42 | } 43 | 44 | get interpret_one_instruction(): boolean { 45 | return !!this._interpret_one_instruction.readU8() 46 | } 47 | 48 | // JValue result; 49 | get result(): JValue { 50 | return new JValue(this._result.readPointer()) 51 | } 52 | 53 | toString(): string { 54 | let disp: string = `SwitchImplContext<${this.handle}>` 55 | if (this.handle.isNull()) return disp 56 | disp += `\n\t self=${this._self.readPointer()} <- ${this._self}` 57 | disp += `\n\t accessor_=${this._accessor.readPointer()} <- ${this._accessor}` 58 | disp += `\n\t shadow_frame_=${this._shadow_frame.readPointer()} <- ${this._shadow_frame}` 59 | disp += `\n\t result_register=${this.result_register} <- ${this._result_register}` 60 | disp += `\n\t interpret_one_instruction=${this.interpret_one_instruction} <- ${this._interpret_one_instruction}` 61 | disp += `\n\t result_=${this._result.readPointer()} <- ${this._result}` 62 | return disp 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/interpreter/include.ts: -------------------------------------------------------------------------------- 1 | import './interpreter' 2 | import './SwitchImplContext' 3 | import './InstructionHandler' -------------------------------------------------------------------------------- /agent/android/implements/10/art/interpreter/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueStore } from "../../../../../tools/common" 2 | import { JSHandleNotImpl } from "../../../../JSHandle" 3 | import { getSym } from "../../../../Utils/SymHelper" 4 | import { R } from "../../../../../tools/intercepter" 5 | import { ShadowFrame } from "../ShadowFrame" 6 | import { ArtThread } from "../Thread" 7 | 8 | type MoveToExceptionHandleCalledListener = (ret: NativePointerValue, thread: ArtThread, shadowFrame: ShadowFrame, instrumentation: NativePointer) => NativePointerValue 9 | 10 | export class interpreter extends JSHandleNotImpl { 11 | 12 | private static _CanUseMterp: boolean = false 13 | 14 | static get CanUseMterp() { 15 | return interpreter._CanUseMterp 16 | } 17 | 18 | static set CanUseMterp(value: boolean) { 19 | interpreter._CanUseMterp = value 20 | } 21 | 22 | // _ZN3art11interpreter11CanUseMterpEv 23 | // bool CanUseMterp(); 24 | public static Hook_CanUseMterp() { 25 | const target: NativePointer = getSym("_ZN3art11interpreter11CanUseMterpEv") 26 | Interceptor.attach(target, { 27 | onLeave(retval) { 28 | // LOGD(`Called CanUseMterp() => ${retval}`) 29 | retval.replace(interpreter._CanUseMterp ? ptr(1) : NULL) 30 | } 31 | }) 32 | } 33 | 34 | // _ZN3art11interpreter17AbortTransactionFEPNS_6ThreadEPKcz 35 | // void AbortTransaction(art::Thread*, char const*, ...) 36 | public static Hook_AbortTransaction() { 37 | const target: NativePointer = getSym("_ZN3art11interpreter17AbortTransactionFEPNS_6ThreadEPKcz") 38 | try { 39 | R(target, new NativeCallback(() => { 40 | LOGD(`Called AbortTransaction()`) 41 | return 42 | }, 'void', [])) 43 | } catch (error) { 44 | // LOGE(`Hook_AbortTransaction ${error}`) 45 | } 46 | } 47 | 48 | // _ZN3art11interpreter17AbortTransactionVEPNS_6ThreadEPKcSt9__va_list 49 | // void AbortTransactionV(art::Thread*, char const*, std::__va_list) 50 | public static Hook_AbortTransactionV() { 51 | const target: NativePointer = getSym("_ZN3art11interpreter17AbortTransactionVEPNS_6ThreadEPKcSt9__va_list") 52 | try { 53 | R(target, new NativeCallback(() => { 54 | LOGD(`Called AbortTransactionV()`) 55 | return 56 | }, 'void', [])) 57 | } catch (error) { 58 | // LOGE(`Hook_AbortTransactionV ${error}`) 59 | } 60 | } 61 | 62 | // _ZN3art11interpreter22MoveToExceptionHandlerEPNS_6ThreadERNS_11ShadowFrameEbb 63 | // _ZN3art11interpreter22MoveToExceptionHandlerEPNS_6ThreadERNS_11ShadowFrameEPKNS_15instrumentation15InstrumentationE 64 | // bool MoveToExceptionHandler(art::Thread* self, art::ShadowFrame& shadow_frame, art::instrumentation::Instrumentation const* instrumentation) 65 | public static Hook_MoveToExceptionHandler() { 66 | const target: NativePointer = getSym("_ZN3art11interpreter22MoveToExceptionHandlerEPNS_6ThreadERNS_11ShadowFrameEPKNS_15instrumentation15InstrumentationE") 67 | const target_SrcCall = new NativeFunction(target, 'pointer', ['pointer', 'pointer', 'pointer']) 68 | try { 69 | R(target, new NativeCallback((self: NativePointer, shadow_frame: NativePointer, instrumentation: NativePointer) => { 70 | let ret = target_SrcCall(self, shadow_frame, instrumentation) 71 | // const thread = new ArtThread(self) 72 | const shadowFrame = new ShadowFrame(shadow_frame) 73 | // if (!shadowFrame.dex_instructions.handle.isNull()) { 74 | // LOGD(`Called MoveToExceptionHandler() -> ${shadowFrame.method.methodName} -> ${ret}`) 75 | // } 76 | // const instrumentation_ = instrumentation 77 | // interpreter.moveToExceptionHandleCalledListeners.forEach(listener => { 78 | // const lis_ret: NativePointerValue = listener(ret, thread, shadowFrame, instrumentation_) 79 | // if (lis_ret) ret = lis_ret 80 | // }) 81 | return ret 82 | }, 'pointer', ['pointer', 'pointer', 'pointer'])) 83 | } catch (error) { 84 | // LOGE(`Hook_MoveToExceptionHandler ${error}`) 85 | } 86 | } 87 | 88 | static onValueChanged(key: string, value: boolean): void { 89 | if (key != "CanUseMterp") return 90 | LOGZ(`ArtInterpreter Got New Value -> ${key} -> ${value}`) 91 | if (key == "CanUseMterp") interpreter.CanUseMterp = value 92 | } 93 | 94 | private static moveToExceptionHandleCalledListeners: MoveToExceptionHandleCalledListener[] = [] 95 | 96 | public static addMoveToExceptionHandleCalledListener(listener: MoveToExceptionHandleCalledListener) { 97 | interpreter.moveToExceptionHandleCalledListeners.push(listener) 98 | } 99 | 100 | } 101 | 102 | setImmediate(() => { 103 | KeyValueStore.getInstance().subscribe(interpreter) 104 | }) 105 | 106 | setImmediate(() => { 107 | try { 108 | interpreter.Hook_CanUseMterp() // by defult 109 | // interpreter.Hook_AbortTransaction() 110 | // interpreter.Hook_AbortTransactionV() 111 | interpreter.Hook_MoveToExceptionHandler() // by defult 112 | } catch (error) { 113 | // LOGE(error) 114 | } 115 | }) 116 | 117 | Reflect.set(globalThis, "ArtInterpreter", interpreter) -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/ArtClass.ts: -------------------------------------------------------------------------------- 1 | import { HeapReference } from "../../../../Interface/art/mirror/HeapReference" 2 | import { StdString } from "../../../../../tools/StdString" 3 | import { ArtObject } from "../../../../Object" 4 | import { ArtClassLoader } from "./ClassLoader" 5 | import { ClassExt } from "./ClassExt" 6 | import { DexCache } from "./DexCache" 7 | import { IfTable } from "./IfTable" 8 | 9 | export class ArtClass extends ArtObject implements SizeOfClass { 10 | 11 | isVirtualClass: boolean = false 12 | 13 | // HeapReference class_loader_; 14 | private class_loader_ = this.CurrentHandle 15 | // HeapReference component_type_; 16 | private component_type_ = this.class_loader_.add(HeapReference.Size) 17 | // HeapReference dex_cache_; 18 | private dex_cache_ = this.component_type_.add(HeapReference.Size) 19 | // HeapReference ext_data_; 20 | private ext_data_ = this.dex_cache_.add(HeapReference.Size) 21 | // HeapReference iftable_; 22 | private iftable_ = this.ext_data_.add(HeapReference.Size) 23 | // HeapReference name_; 24 | private name_ = this.iftable_.add(HeapReference.Size) 25 | // HeapReference super_class_; 26 | private super_class_ = this.name_.add(HeapReference.Size) 27 | // HeapReference vtable_; 28 | private vtable_ = this.super_class_.add(HeapReference.Size) 29 | // ArtFields are allocated as a length prefixed ArtField array, and not an array of pointers to ArtFields. 30 | // uint64_t ifields_; => instance fields 31 | private ifields_ = this.vtable_.add(HeapReference.Size) 32 | // uint64_t methods_; 33 | private methods_ = this.ifields_.add(0x8) 34 | // uint64_t sfields_; 35 | private sfields_ = this.methods_.add(0x8) 36 | // uint32_t access_flags_; 37 | private access_flags_ = this.sfields_.add(0x8) 38 | // uint32_t class_flags_; 39 | private class_flags_ = this.access_flags_.add(0x4) 40 | // uint32_t class_size_; 41 | private class_size_ = this.class_flags_.add(0x4) 42 | // pid_t clinit_thread_id_; 43 | private clinit_thread_id_ = this.class_size_.add(0x4) 44 | // int32_t dex_class_def_idx_; 45 | private dex_class_def_idx_ = this.clinit_thread_id_.add(0x8) 46 | 47 | constructor(handle: NativePointer) { 48 | super(handle) 49 | } 50 | 51 | get SizeOfClass(): number { 52 | return (this.dex_class_def_idx_).add(0x4).sub(this.CurrentHandle).add(this.super_class.SizeOfClass).toInt32() 53 | } 54 | 55 | get CurrentHandle(): NativePointer { 56 | return this.handle.add(super.SizeOfClass).add(this.VirtualClassOffset) 57 | } 58 | 59 | toString(): string { 60 | let disp: string = `ArtClass< ${this.handle} >` 61 | if (this.handle.isNull()) return disp 62 | disp += `\n\t class_loader: ${this.class_loader} -> ArtClassLoader< ${this.class_loader.root.handle} >` 63 | disp += `\n\t component_type: ${this.component_type} -> ArtClass< ${this.component_type.root.handle} >` 64 | disp += `\n\t dex_cache: ${this.dex_cache} -> DexCache` 65 | disp += `\n\t ext_data: ${this.ext_data}` 66 | disp += `\n\t iftable: ${this.iftable}` 67 | disp += `\n\t name: ${this.name} -> ${this.name_str}` 68 | disp += `\n\t super_class: ${this.super_class} -> ArtClass< ${this.super_class.root.handle} >` 69 | disp += `\n\t vtable: ${this.vtable}` 70 | disp += `\n\t ifields: ${this.ifields} | ${ptr(this.ifields)}` 71 | disp += `\n\t methods: ${this.methods} | ${ptr(this.methods)}` 72 | disp += `\n\t sfields: ${this.sfields} | ${ptr(this.sfields)}` 73 | disp += `\n\t access_flags: ${this.access_flags} -> ${this.access_flags_string}` 74 | disp += `\n\t class_flags: ${this.class_flags}` 75 | disp += `\n\t class_size: ${this.class_size}` 76 | disp += `\n\t clinit_thread_id: ${this.clinit_thread_id}` 77 | disp += `\n\t dex_class_def_idx: ${this.dex_class_def_idx}` 78 | return disp 79 | } 80 | 81 | get class_loader(): HeapReference { 82 | return new HeapReference((handle) => new ArtClassLoader(handle), this.class_loader_) 83 | } 84 | 85 | get component_type(): HeapReference { 86 | return new HeapReference((handle) => new ArtClass(handle), this.component_type_) 87 | } 88 | 89 | get dex_cache(): HeapReference { 90 | return new HeapReference((handle) => new DexCache(handle), this.dex_cache_) 91 | } 92 | 93 | get ext_data(): HeapReference { 94 | return new HeapReference((handle) => new ClassExt(handle), this.ext_data_) 95 | } 96 | 97 | get iftable(): HeapReference { 98 | return new HeapReference((handle) => new IfTable(handle), this.iftable_) 99 | } 100 | 101 | get name(): HeapReference { 102 | return new HeapReference((handle) => new StdString(handle), this.name_) 103 | } 104 | 105 | private name_str(): string { 106 | return StdString.from(this.name.root.handle) 107 | } 108 | 109 | get super_class(): HeapReference { 110 | return new HeapReference((handle) => new ArtClass(handle), this.super_class_) 111 | } 112 | 113 | get vtable(): HeapReference { 114 | return new HeapReference((handle) => new NativePointer(handle), this.vtable_) 115 | } 116 | 117 | get ifields(): number { 118 | return this.ifields_.readU64().toNumber() 119 | } 120 | 121 | get methods(): number { 122 | return this.methods_.readU64().toNumber() 123 | } 124 | 125 | get sfields(): number { 126 | return this.sfields_.readU64().toNumber() 127 | } 128 | 129 | get access_flags(): number { 130 | return this.access_flags_.readU32() 131 | } 132 | 133 | get access_flags_string(): string { 134 | return PrettyAccessFlags(this.access_flags) 135 | } 136 | 137 | get class_flags(): number { 138 | return this.class_flags_.readU32() 139 | } 140 | 141 | get class_size(): number { 142 | return this.class_size_.readU32() 143 | } 144 | 145 | get clinit_thread_id(): number { 146 | return this.clinit_thread_id_.readS32() 147 | } 148 | 149 | get dex_class_def_idx(): number { 150 | return this.dex_class_def_idx_.readS32() 151 | } 152 | 153 | // todo ... 154 | } 155 | 156 | declare global { 157 | var ArtClass: any 158 | } 159 | 160 | globalThis.ArtClass = ArtClass -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/ClassExt.ts: -------------------------------------------------------------------------------- 1 | import { ArtObject } from "../../../../Object" 2 | 3 | // C++ mirror of dalvik.system.ClassExt 4 | // class MANAGED ClassExt : public Object {} 5 | export class ClassExt extends ArtObject { 6 | 7 | constructor(handle: NativePointer) { 8 | super(handle) 9 | } 10 | 11 | toString(): string { 12 | return `ClassExt<${this.handle}>` 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/ClassLoader.ts: -------------------------------------------------------------------------------- 1 | import { HeapReference } from "../../../../Interface/art/mirror/HeapReference" 2 | import { ArtObject } from "../../../../Object" 3 | 4 | // C++ mirror of java.lang.ClassLoader 5 | // class MANAGED ClassLoader : public Object {} 6 | export class ArtClassLoader extends ArtObject { 7 | 8 | // // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses". 9 | // HeapReference packages_; 10 | private packages_: NativePointer = this.CurrentHandle 11 | // HeapReference parent_; 12 | private parent_: NativePointer = this.packages_.add(HeapReference.Size) 13 | // HeapReference proxyCache_; 14 | private proxyCache_: NativePointer = this.parent_.add(HeapReference.Size) 15 | // // Native pointer to class table, need to zero this out when image writing. 16 | // uint32_t padding_ ATTRIBUTE_UNUSED; 17 | private padding_: NativePointer = this.proxyCache_.add(HeapReference.Size) 18 | // uint64_t allocator_; 19 | private allocator_: NativePointer = this.padding_.add(0x4) 20 | // uint64_t class_table_; 21 | private class_table_: NativePointer = this.allocator_.add(0x8) 22 | 23 | constructor(handle: NativePointer) { 24 | super(handle) 25 | } 26 | 27 | get SizeOfClass(): number { 28 | return super.SizeOfClass + this.class_table_.add(0x8).sub(this.handle).toInt32() 29 | } 30 | 31 | toString(): string { 32 | let idsp: string = `ClassLoader<${this.handle}>` 33 | if (this.handle.isNull()) return idsp 34 | idsp += `\n\t packages_: ${this.packages} | parent_: ${this.parent} | proxyCache_: ${this.proxyCache}` 35 | idsp += `\n\t allocator_: ${this.allocator} | class_table_: ${this.class_table}` 36 | return idsp 37 | } 38 | 39 | get packages(): HeapReference { 40 | return new HeapReference((handle) => new ArtObject(handle), this.packages_) 41 | } 42 | 43 | get parent(): HeapReference { 44 | return new HeapReference((handle) => new ArtClassLoader(handle), this.parent_) 45 | } 46 | 47 | get proxyCache(): HeapReference { 48 | return new HeapReference((handle) => new ArtObject(handle), this.proxyCache_) 49 | } 50 | 51 | get allocator(): HeapReference { 52 | return new HeapReference((handle) => new ArtObject(handle), this.allocator_) 53 | } 54 | 55 | get class_table(): NativePointer { 56 | return ptr(this.class_table_.readU32()) 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/DexCache.ts: -------------------------------------------------------------------------------- 1 | 2 | import { HeapReference } from "../../../../Interface/art/mirror/HeapReference" 3 | import { StdString } from "../../../../../tools/StdString" 4 | import { ArtObject } from "../../../../Object" 5 | import { DexFile } from "../dexfile/DexFile" 6 | 7 | // C++ mirror of java.lang.DexCache. 8 | // class MANAGED DexCache final : public Object {} 9 | export class DexCache extends ArtObject implements SizeOfClass { 10 | 11 | // HeapReference location_; // 0x4 12 | private location_ = this.currentHandle.add(0) 13 | // uint32_t num_preresolved_strings_; // 0x4 14 | private num_preresolved_strings_ = this.currentHandle.add(0x4) 15 | // uint64_t dex_file_; // const DexFile* 16 | private dex_file_ = this.currentHandle.add(0x4 * 2) 17 | // uint64_t preresolved_strings_; // GcRoot array with num_preresolved_strings elements. 18 | private preresolved_strings_ = this.currentHandle.add(0x4 * 2 + 0x8 * 1) 19 | // uint64_t resolved_call_sites_; // GcRoot* array with num_resolved_call_sites_ elements. 20 | private resolved_call_sites_ = this.currentHandle.add(0x4 * 2 + 0x8 * 2) 21 | // uint64_t resolved_fields_; // std::atomic*, array with num_resolved_fields_ elements. 22 | private resolved_fields_ = this.currentHandle.add(0x4 * 2 + 0x8 * 3) 23 | // uint64_t resolved_methods_; // ArtMethod*, array with num_resolved_methods_ elements. 24 | private resolved_methods_ = this.currentHandle.add(0x4 * 2 + 0x8 * 4) 25 | // uint64_t resolved_types_; // TypeDexCacheType*, array with num_resolved_types_ elements. 26 | private resolved_types_ = this.currentHandle.add(0x4 * 2 + 0x8 * 5) 27 | // uint64_t strings_; // std::atomic*, array with num_strings_ elements. 28 | private strings_ = this.currentHandle.add(0x4 * 2 + 0x8 * 6) 29 | // uint32_t num_resolved_call_sites_; // Number of elements in the call_sites_ array. 30 | private num_resolved_call_sites_ = this.currentHandle.add(0x4 * 2 + 0x8 * 7) 31 | // uint32_t num_resolved_fields_; // Number of elements in the resolved_fields_ array. 32 | private num_resolved_fields_ = this.currentHandle.add(0x4 * 3 + 0x8 * 7) 33 | // uint32_t num_resolved_method_types_; // Number of elements in the resolved_method_types_ array. 34 | private num_resolved_method_types_ = this.currentHandle.add(0x4 * 4 + 0x8 * 7) 35 | // uint32_t num_resolved_methods_; // Number of elements in the resolved_methods_ array. 36 | private num_resolved_methods_ = this.currentHandle.add(0x4 * 5 + 0x8 * 7) 37 | // uint32_t num_resolved_types_; // Number of elements in the resolved_types_ array. 38 | private num_resolved_types_ = this.currentHandle.add(0x4 * 6 + 0x8 * 7) 39 | // uint32_t num_strings_; // Number of elements in the strings_ array. 40 | private num_strings_ = this.currentHandle.add(0x4 * 7 + 0x8 * 7) 41 | 42 | constructor(handle: NativePointer) { 43 | super(handle) 44 | } 45 | 46 | toString(): string { 47 | let disp: string = `DexCache` 48 | if (this.handle.isNull()) return disp 49 | disp += `\n\t location: ${this.location_str} @ ${this.location.root.handle}` 50 | disp += `\n\t preresolved_strings_: ${this.preresolved_strings} | num_preresolved_strings_: ${this.num_preresolved_strings} | resolved_call_sites_: ${this.resolved_call_sites}` 51 | disp += `\n\t resolved_fields_: ${this.resolved_fields} | resolved_methods_: ${this.resolved_methods} | resolved_types_: ${this.resolved_types}` 52 | disp += `\n\t strings_: ${this.strings} | num_resolved_call_sites_: ${this.num_resolved_call_sites} | num_resolved_fields_: ${this.num_resolved_fields}` 53 | disp += `\n\t num_resolved_method_types_: ${this.num_resolved_method_types} | num_resolved_methods_: ${this.num_resolved_methods} | num_resolved_types_: ${this.num_resolved_types}` 54 | disp += `\n\t num_strings_: ${this.num_strings}` 55 | return disp 56 | } 57 | 58 | get SizeOfClass(): number { 59 | return super.SizeOfClass + this.num_strings_.add(0x4).sub(this.CurrentHandle).toInt32() 60 | } 61 | 62 | get currentHandle(): NativePointer { 63 | return this.handle.add(super.SizeOfClass).add(this.VirtualClassOffset) 64 | } 65 | 66 | // HeapReference contains StdString -> The Third Pointer don't need read 67 | get location(): HeapReference { 68 | return new HeapReference(handle => new StdString(handle), this.location_) 69 | } 70 | 71 | private get location_str(): string { 72 | return StdString.from(this.location.root.handle) 73 | } 74 | 75 | get num_preresolved_strings(): number { 76 | return this.num_preresolved_strings_.readU32() 77 | } 78 | 79 | get dex_file(): DexFile { 80 | return new DexFile(this.dex_file_.readPointer()) 81 | } 82 | 83 | get preresolved_strings(): NativePointer { 84 | return this.preresolved_strings_.readPointer() 85 | } 86 | 87 | get resolved_call_sites(): NativePointer { 88 | return this.resolved_call_sites_.readPointer() 89 | } 90 | 91 | get resolved_fields(): NativePointer { 92 | return this.resolved_fields_.readPointer() 93 | } 94 | 95 | get resolved_methods(): NativePointer { 96 | return this.resolved_methods_.readPointer() 97 | } 98 | 99 | get resolved_types(): NativePointer { 100 | return this.resolved_types_.readPointer() 101 | } 102 | 103 | get strings(): NativePointer { 104 | return this.strings_.readPointer() 105 | } 106 | 107 | get num_resolved_call_sites(): number { 108 | return this.num_resolved_call_sites_.readU32() 109 | } 110 | 111 | get num_resolved_fields(): number { 112 | return this.num_resolved_fields_.readU32() 113 | } 114 | 115 | get num_resolved_method_types(): number { 116 | return this.num_resolved_method_types_.readU32() 117 | } 118 | 119 | get num_resolved_methods(): number { 120 | return this.num_resolved_methods_.readU32() 121 | } 122 | 123 | get num_resolved_types(): number { 124 | return this.num_resolved_types_.readU32() 125 | } 126 | 127 | get num_strings(): number { 128 | return this.num_strings_.readU32() 129 | } 130 | 131 | } 132 | 133 | Reflect.set(globalThis, "DexCache", DexCache) -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/IfTable.ts: -------------------------------------------------------------------------------- 1 | import { ArtObject } from "../../../../Object" 2 | 3 | // class MANAGED IfTable final : public ObjectArray {} 4 | export class IfTable extends ArtObject { 5 | 6 | constructor(handle: NativePointer) { 7 | super(handle) 8 | } 9 | 10 | toString(): string { 11 | return `IfTable<${this.handle}>` 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /agent/android/implements/10/art/mirror/include.ts: -------------------------------------------------------------------------------- 1 | import './ArtClass' 2 | import './ArtMethod' 3 | import './ClassExt' 4 | import './ClassLoader' 5 | import './IfTable' -------------------------------------------------------------------------------- /agent/android/implements/10/art/runtime/Runtime.ts: -------------------------------------------------------------------------------- 1 | import { callSym } from "../../../../Utils/SymHelper" 2 | import { JSHandle } from "../../../../JSHandle" 3 | import { StdString } from "../../../../../tools/StdString" 4 | 5 | export class ArtRuntime extends JSHandle { 6 | 7 | private constructor(handle: NativePointer) { 8 | super(handle) 9 | } 10 | 11 | public static getInstance() { 12 | return new ArtRuntime(((Java as any).api.artRuntime as NativePointer)) 13 | } 14 | 15 | // std::string Runtime::GetCompilerExecutable() const { 16 | // if (!compiler_executable_.empty()) { 17 | // return compiler_executable_; 18 | // } 19 | // std::string compiler_executable(GetAndroidRoot()); 20 | // compiler_executable += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat"); 21 | // return compiler_executable; 22 | // } 23 | // _ZNK3art7Runtime21GetCompilerExecutableEv 24 | GetCompilerExecutable(): string { 25 | return StdString.fromPointers(callSym( 26 | "_ZNK3art7Runtime21GetCompilerExecutableEv", "libart.so", 27 | ['pointer', 'pointer', 'pointer'], ['pointer'], 28 | this.handle)) 29 | } 30 | 31 | } 32 | 33 | Reflect.set(global, 'ArtRuntime', ArtRuntime) -------------------------------------------------------------------------------- /agent/android/implements/10/art/runtime/include.ts: -------------------------------------------------------------------------------- 1 | import './Runtime' 2 | import './ThreadList' -------------------------------------------------------------------------------- /agent/android/implements/10/dex2oat/dex2oat.ts: -------------------------------------------------------------------------------- 1 | import { StdString } from "../../../../tools/StdString" 2 | import { R } from "../../../../tools/intercepter" 3 | import { getSym } from "../../../Utils/SymHelper" 4 | 5 | type int = number 6 | 7 | export class dex2oat { 8 | 9 | // https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/exec_utils.cc;l=33 10 | // int ExecAndReturnCode(std::vector& arg_vector, std::string* error_msg) 11 | // _ZN3art17ExecAndReturnCodeERNSt3__16vectorINS0_12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEENS5_IS7_EEEEPS7_ 12 | static hook_ExecAndReturnCode() { 13 | const target: NativePointer = getSym("_ZN3art17ExecAndReturnCodeERNSt3__16vectorINS0_12basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEENS5_IS7_EEEEPS7_", "libart.so") 14 | const srcCall: (arg_vector: NativePointer, error_msg: NativePointer) => int = new NativeFunction(target, 'int', ['pointer', 'pointer']) as any 15 | // Interceptor.attach(target, { 16 | // onEnter: function(args) { 17 | // const arg_vector = args[0] 18 | // const error_msg = new StdString(args[1]) 19 | // LOGD(`ExecAndReturnCode arg_vector: ${arg_vector} error_msg: ${error_msg.toString()}`) 20 | // } 21 | // }) 22 | R(target, new NativeCallback((arg_vector_: NativePointer, error_msg_: NativePointer) => { 23 | const error_msg = new StdString(error_msg_[1]) 24 | LOGD(`ExecAndReturnCode arg_vector: ${arg_vector_} error_msg: ${error_msg.toString()}`) 25 | // return srcCall(arg_vector, error_msg) 26 | return ptr(-1) 27 | }, 'pointer', ['pointer', 'pointer'])) 28 | } 29 | 30 | // ref https://github.com/asLody/TurboDex/blob/master/project/turbodex/turbodex/src/main/jni/core/FastLoadDex.cpp#L27 31 | // #define DEX2OAT_BIN "/system/bin/dex2oat" 32 | private static DEX2OAT_BIN = "/system/bin/dex2oat" 33 | // int execv(const char* __path, cha __argr* const*v); 34 | // int execve(const char* __file, char* const* __argv, char* const* __envp); 35 | static hook_exec() { 36 | const target_execv: NativePointer = getSym("execv", "libc.so") 37 | const srcCall_execv: (path: NativePointer, argv: NativePointer) => int = new NativeFunction(target_execv, 'int', ['pointer', 'pointer']) as any 38 | R(target_execv, new NativeCallback((path: NativePointer, argv: NativePointer) => { 39 | const pathStr = path.readCString() 40 | const argvStr = argv.readCString() 41 | LOGD(`execv path: ${pathStr} argv: ${argvStr}`) 42 | if (pathStr.includes(dex2oat.DEX2OAT_BIN)) return ptr(-1) 43 | return srcCall_execv(path, argv) 44 | }, 'pointer', ['pointer', 'pointer'])) 45 | 46 | const target_execve: NativePointer = getSym("execve", "libc.so") 47 | const srcCall_execve: (file: NativePointer, argv: NativePointer, envp: NativePointer) => int = new NativeFunction(target_execve, 'int', ['pointer', 'pointer', 'pointer']) as any 48 | R(target_execve, new NativeCallback((file: NativePointer, argv: NativePointer, envp: NativePointer) => { 49 | const fileStr = file.readCString() 50 | const argvStr = argv.readCString() 51 | const envpStr = envp.readCString() 52 | LOGD(`execve file: ${fileStr} argv: ${argvStr} envp: ${envpStr}`) 53 | if (fileStr.includes(dex2oat.DEX2OAT_BIN)) return ptr(-1) 54 | return srcCall_execve(file, argv, envp) 55 | }, 'pointer', ['pointer', 'pointer', 'pointer'])) 56 | 57 | } 58 | 59 | } 60 | 61 | Reflect.set(globalThis, 'dex2oat', dex2oat) -------------------------------------------------------------------------------- /agent/android/implements/10/dex2oat/include.ts: -------------------------------------------------------------------------------- 1 | import './dex2oat' -------------------------------------------------------------------------------- /agent/android/implements/10/include.ts: -------------------------------------------------------------------------------- 1 | import "./art/include" 2 | import './dex2oat/include' -------------------------------------------------------------------------------- /agent/android/implements/12/include.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhlzy/ARTHookScripts/d2858d88ead83b0511f10261537cc4c41e6908bd/agent/android/implements/12/include.ts -------------------------------------------------------------------------------- /agent/android/implements/include.ts: -------------------------------------------------------------------------------- 1 | import "./10/include" 2 | // import "./12/include" -------------------------------------------------------------------------------- /agent/android/include.ts: -------------------------------------------------------------------------------- 1 | import './Object' 2 | import './JSHandle' 3 | import './android' 4 | import './ValueHandle' 5 | import './machine-code' 6 | import './TraceManager' 7 | 8 | import './implements/include' 9 | import './Interface/include' 10 | import './Utils/include' 11 | import './functions/include' 12 | import './jdwp/include' -------------------------------------------------------------------------------- /agent/android/jdwp/JDWPPackage.ts: -------------------------------------------------------------------------------- 1 | // https://ioactive.com/hacking-java-debug-wire-protocol-or-how/ 2 | 3 | import { assert } from "console" 4 | import { CommandType, JDB } from "./constant" 5 | 6 | var _nextID: number = 0 7 | function nextID() { 8 | return ++_nextID 9 | } 10 | 11 | // PackageHeader(4+4+1+1+1=11) 12 | export class JDWPPackage { 13 | protected d_src: ArrayBuffer 14 | protected v_src: DataView 15 | 16 | protected constructor(buffer: ArrayBuffer = new ArrayBuffer(JDB.HEADERLEN)) { 17 | this.d_src = buffer 18 | this.v_src = new DataView(this.d_src) 19 | this.length = JDB.HEADERLEN 20 | this.id = nextID() 21 | return this 22 | } 23 | 24 | set buffer(buffer: ArrayBuffer) { 25 | this.d_src = buffer 26 | this.v_src = new DataView(this.d_src) 27 | } 28 | 29 | get buffer(): ArrayBuffer { 30 | return this.d_src 31 | } 32 | 33 | set length(len: number) { 34 | this.v_src.setUint32(0, len, false) 35 | } 36 | 37 | get length() { 38 | return this.v_src.getUint32(0, false) 39 | } 40 | 41 | set id(id: number) { 42 | this.v_src.setUint32(4, id, false) 43 | } 44 | 45 | get id() { 46 | return this.v_src.getUint32(4, false) 47 | } 48 | 49 | set flags(flags: JDB.Flag) { 50 | this.v_src.setUint8(8, flags) 51 | } 52 | 53 | get flags(): JDB.Flag { 54 | return this.v_src.getUint8(8) 55 | } 56 | 57 | set data(buffer: ArrayBuffer) { 58 | const newTotalLength = JDB.HEADERLEN + buffer.byteLength 59 | const newBuffer = new ArrayBuffer(newTotalLength) 60 | const newView = new DataView(newBuffer) 61 | const oldHeader = new Uint8Array(this.d_src, 0, JDB.HEADERLEN) 62 | new Uint8Array(newBuffer, 0, JDB.HEADERLEN).set(oldHeader) 63 | const newData = new Uint8Array(buffer) 64 | new Uint8Array(newBuffer, JDB.HEADERLEN).set(newData) 65 | this.d_src = newBuffer 66 | this.v_src = newView 67 | this.length = newTotalLength 68 | } 69 | 70 | get data(): ArrayBuffer { 71 | return this.v_src.buffer.slice(JDB.HEADERLEN) as ArrayBuffer 72 | } 73 | 74 | static get Handshake(): ArrayBuffer { 75 | return str2ab('JDWP-Handshake') 76 | } 77 | 78 | toString() { 79 | return arrayBufferToHex(this.buffer) 80 | } 81 | 82 | } 83 | 84 | // Request Package 85 | // length(4) id(4) flags(1) CommandSet(1) Command(1) Data(...) 86 | export class RequestPackage extends JDWPPackage { 87 | 88 | constructor(id: number, flags: number, commandSet: number, command: number, data: ArrayBuffer = new ArrayBuffer(0)) { 89 | super() 90 | this.id = id 91 | this.flags = flags 92 | this.commandSet = commandSet 93 | this.command = command 94 | this.data = data 95 | this.length += data.byteLength 96 | } 97 | 98 | set commandSet(cmd: number) { 99 | this.v_src.setUint8(9, cmd) 100 | } 101 | 102 | get commandSet(): number { 103 | return this.v_src.getUint8(9) 104 | } 105 | 106 | set command(cmd: number) { 107 | this.v_src.setUint8(10, cmd) 108 | } 109 | 110 | get command(): number { 111 | return this.v_src.getUint8(10) 112 | } 113 | 114 | static from(cmd: CommandType, data: ArrayBuffer = new ArrayBuffer(0)): RequestPackage { 115 | if (cmd.description !== undefined && data.byteLength == 0) throw new Error(`Check Arguments:\n\t${cmd.description}`) 116 | if (cmd.description === undefined) { 117 | if (data.byteLength != 0) { 118 | data = new ArrayBuffer(0) 119 | LOGW('This command does not need data, force to set data to empty') 120 | } 121 | } 122 | return new RequestPackage(nextID(), JDB.Flag.REQUEST_PACKET_TYPE, cmd.commandSet, cmd.command, data) 123 | } 124 | } 125 | 126 | // Replay Package 127 | // length(4) id(4) flags(1) ErrorCode(2) Data(...) 128 | export class ReplyPackage extends JDWPPackage { 129 | 130 | constructor(id: number = nextID(), flags: number = JDB.Flag.REPLY_PACKET_TYPE, errorCode: number = 0, data: ArrayBuffer = new ArrayBuffer(0)) { 131 | super() 132 | this.id = id 133 | this.flags = flags 134 | this.errorCode = errorCode 135 | this.data = data 136 | this.length += data.byteLength 137 | } 138 | 139 | set errorCode(code: number) { 140 | this.v_src.setUint16(8, code) 141 | } 142 | 143 | get errorCode(): number { 144 | return this.v_src.getUint16(8) 145 | } 146 | 147 | static from(buffer: ArrayBuffer): ReplyPackage { 148 | const retPkg = new ReplyPackage() 149 | retPkg.buffer = buffer 150 | return retPkg 151 | } 152 | 153 | } 154 | 155 | // str -> ArrayBuffer 156 | export function str2ab(str: string): ArrayBuffer { 157 | const buf = new ArrayBuffer(str.length) 158 | const view = new Uint8Array(buf) 159 | for (let i = 0, l = str.length; i < l; i++) { 160 | view[i] = str.charCodeAt(i) & 0xFF 161 | } 162 | return buf 163 | // return Memory.allocUtf8String(str).readByteArray(str.length)! 164 | } 165 | 166 | // ArrayBuffer -> str 167 | export function ab2str(buf: ArrayBuffer): string { 168 | return Memory.alloc(buf.byteLength).writeByteArray(buf).readCString()! 169 | } 170 | 171 | // ArrayBuffer -> hexString 172 | var cachePtr: NativePointer = NULL 173 | export function arrayBufferToHex(buffer: ArrayBuffer, cached: boolean = true) { 174 | const tempMem = Memory.alloc(buffer.byteLength) 175 | tempMem.writeByteArray(buffer) 176 | if (cached) cachePtr = tempMem 177 | return hexdump(tempMem, { length: buffer.byteLength }) 178 | } 179 | 180 | export function arrayBufferToHex2(buffer: ArrayBuffer) { 181 | const byteArray = new Uint8Array(buffer) 182 | let hexString: string = '' 183 | byteArray.forEach(byte => hexString += byte.toString(16).padStart(2, '0') + ' ') 184 | return hexString.trim() 185 | } 186 | 187 | export function buildString(str: string): ArrayBuffer { 188 | const strBuffer = str2ab(str) 189 | const newBuffer = new ArrayBuffer(strBuffer.byteLength + 4) 190 | const newView = new DataView(newBuffer) 191 | newView.setUint32(0, strBuffer.byteLength) 192 | new Uint8Array(newBuffer, 4).set(new Uint8Array(strBuffer)) 193 | return newBuffer 194 | } 195 | 196 | declare global { 197 | var ab2str: (buf: ArrayBuffer) => string 198 | var str2ab: (str: string) => ArrayBuffer 199 | // var buildString: (str:string) => ArrayBuffer 200 | var arrayBufferToHex: (buffer: ArrayBuffer) => string 201 | var arrayBufferToHex2: (buffer: ArrayBuffer) => string 202 | } 203 | 204 | globalThis.ab2str = ab2str 205 | globalThis.str2ab = str2ab 206 | // globalThis.buildString = buildString 207 | globalThis.arrayBufferToHex = arrayBufferToHex 208 | globalThis.arrayBufferToHex2 = arrayBufferToHex2 -------------------------------------------------------------------------------- /agent/android/jdwp/include.ts: -------------------------------------------------------------------------------- 1 | import './constant' 2 | import './JDWPPackage' 3 | import './jdb' 4 | import './jdwp' -------------------------------------------------------------------------------- /agent/android/jdwp/jdb.ts: -------------------------------------------------------------------------------- 1 | import { arrayBufferToHex, ReplyPackage as ReplPkg, RequestPackage as ReqPkg } from "./JDWPPackage" 2 | import { JDB } from "./constant" 3 | 4 | function interaction(port = Process.id, host = "127.0.0.1") { 5 | LOGD(`Connection -> ${host}:${port}`) 6 | Socket.connect({ "family": 'ipv4', 'host': host, 'port': port }) 7 | .then((connection: SocketConnection) => { 8 | 9 | // JDWP-Handshake 10 | connection.output 11 | .write(ReqPkg.Handshake) 12 | 13 | connection.input 14 | .read(JDB.HANDSHAKE.length) 15 | .then(function (buff: ArrayBuffer) { 16 | if (ab2str(buff) == JDB.HANDSHAKE) LOGD("Handshake Success") 17 | else LOGE(arrayBufferToHex(buff)) 18 | }) 19 | .catch(LOGE) 20 | 21 | setTimeout(() => { 22 | connection.output.write(ReqPkg.from(JDB.CommandSet.VirtualMachine.Version).buffer) 23 | 24 | setTimeout(() => { 25 | connection.input.read(JDB.HEADERLEN).then((buffer: ArrayBuffer) => { 26 | const totalLength:number = ReplPkg.from(buffer).length 27 | const additionalDataLen = totalLength - JDB.HEADERLEN 28 | if (additionalDataLen > 0) { 29 | const maxDispLen = 0x1000 30 | const fixDispLen = additionalDataLen > maxDispLen ? maxDispLen : additionalDataLen 31 | connection.input.read(fixDispLen).then((data: ArrayBuffer) => { 32 | const fullPacket = new ArrayBuffer(fixDispLen + JDB.HEADERLEN) 33 | new Uint8Array(fullPacket).set(new Uint8Array(buffer), 0) 34 | new Uint8Array(fullPacket).set(new Uint8Array(data), JDB.HEADERLEN) 35 | processPacket(ReplPkg.from(fullPacket)) 36 | }) 37 | } else { 38 | processPacket(ReplPkg.from(buffer)) 39 | } 40 | }).catch(LOGE) 41 | }, 200); 42 | 43 | }, 200) 44 | }) 45 | .catch((e)=>{ 46 | LOGE(e) 47 | LOGE("Check to see if you've run the `startJdwpThread()`") 48 | }) 49 | 50 | function processPacket(packet: ReplPkg) { 51 | LOGD(packet) 52 | } 53 | } 54 | 55 | export { } 56 | 57 | declare global { 58 | var jdbTest: (port?: number, host?: string) => void 59 | } 60 | 61 | globalThis.jdbTest = interaction -------------------------------------------------------------------------------- /agent/android/machine-code.js: -------------------------------------------------------------------------------- 1 | function parseInstructionsAt (address, tryParse, { limit }) { 2 | let cursor = address; 3 | let prevInsn = null; 4 | 5 | for (let i = 0; i !== limit; i++) { 6 | const insn = Instruction.parse(cursor); 7 | 8 | const value = tryParse(insn, prevInsn); 9 | if (value !== null) { 10 | return value; 11 | } 12 | 13 | cursor = insn.next; 14 | prevInsn = insn; 15 | } 16 | 17 | return null; 18 | } 19 | 20 | module.exports = { 21 | parseInstructionsAt 22 | }; 23 | -------------------------------------------------------------------------------- /agent/doc.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/frida/frida-java-bridge/blob/main/lib/class-factory.js#L2072-L2073 2 | // Java.Field 3 | // this._p[0] => holder 4 | // this._p[1] => fieldType 5 | // this._p[2] => fieldReturnType 6 | // this._p[3] => id ( same to handle or art field pointer) 7 | // this._p[4] => getValue ===> using getValue function to call with id (fieldType == 1 ? static : instance) 8 | // this._p[5] => setValue ===> using setValue function to call with id (fieldType == 1 ? static : instance) 9 | 10 | 11 | // const clazz: Java.Wrapper = Java.use('com.google.firebase.MessagingUnityPlayerActivity') 12 | 13 | // // protected void com.google.firebase.MessagingUnityPlayerActivity.onNewIntent(android.content.Intent) 14 | // const method: Java.Method = clazz['onCreate'] 15 | 16 | // // private static final String EXTRA_FROM = "google.message_id"; 17 | // const field: Java.Field = clazz['EXTRA_FROM'] 18 | 19 | // // 判断是否有字段 _p 20 | // Object.getOwnPropertyNames(clazz['EXTRA_FROM']).includes("_p") 21 | 22 | // [MI 8::xxx ]-> var clazz = Java.use('com.google.firebase.MessagingUnityPlayerActivity') 23 | // [MI 8::xxx ]-> clazz['EXTRA_FROM'] 24 | // { 25 | // "_p": [ 26 | // "", 27 | // 1, 28 | // { 29 | // "className": "java.lang.String", 30 | // "defaultValue": "0x0", 31 | // "name": "Ljava/lang/String;", 32 | // "size": 1, 33 | // "type": "pointer" 34 | // }, 35 | // "0xa220d1dc", 36 | // "0x7ae0c04a08", 37 | // "0x7ae0c07ce8" 38 | // ] 39 | // } 40 | 41 | // this._p[0] => methodName 42 | // this._p[1] => holder 43 | // this._p[2] => type 44 | // this._p[3] => handle 45 | // this._p[4] => returnType 46 | // this._p[5] => argumentTypes -------------------------------------------------------------------------------- /agent/include.ts: -------------------------------------------------------------------------------- 1 | import './android/include' 2 | import './Java/include' 3 | import './tools/include' 4 | import './native/include' -------------------------------------------------------------------------------- /agent/main.ts: -------------------------------------------------------------------------------- 1 | import "./include" 2 | import { ArtMethod } from "./android/implements/10/art/mirror/ArtMethod" 3 | import { DexFile } from "./android/implements/10/art/dexfile/DexFile" 4 | import { hookJavaClass } from "./android/Utils/JavaHooker" 5 | 6 | globalThis.testArtMethod = () => { 7 | 8 | // Java.perform(()=>{ 9 | // console.log(Java.openClassFile("/data/local/tmp/dex_androidx.dex")) 10 | // }) 11 | 12 | Java.perform(() => { 13 | 14 | let method: ArtMethod = pathToArtMethod("com.unity3d.player.UnityPlayer.UnitySendMessage") 15 | let dexFile: DexFile = method.GetDexFile() 16 | method.show() 17 | 18 | for (let i = dexFile.NumStringIds(); i > dexFile.NumStringIds() - 20; i--) { 19 | LOGD(dexFile.StringDataByIdx(i).toString()) 20 | } 21 | 22 | for (let i = dexFile.NumTypeIds(); i > dexFile.NumTypeIds() - 20; i--) { 23 | LOGD(dexFile.GetTypeDescriptor(i)) 24 | } 25 | // test parse dexFile 26 | newLine() 27 | LOGD(dexFile.StringDataByIdx(8907).str) 28 | dexFile.PrettyMethod(41007) 29 | // LOGD(dexFile.GetFieldId(10)) 30 | // LOGD(dexFile.GetTypeId(617)) 31 | // LOGD(dexFile.GetProtoId(10)) 32 | // LOGD(dexFile.GetMethodId(10)) 33 | // LOGD(dexFile.GetClassDef(10)) 34 | }) 35 | } 36 | 37 | globalThis.sendMessage = (a: string = "test_class", b: string = "test_function", c: string = "test_value") => { 38 | Java.perform(() => { 39 | const JavaString = Java.use("java.lang.String") 40 | Java.use("com.unity3d.player.UnityPlayer").UnitySendMessage(JavaString.$new(a), JavaString.$new(b), JavaString.$new(c)) 41 | }) 42 | } 43 | 44 | declare global { 45 | var testArtMethod: () => void 46 | var sendMessage: (a?: string, b?: string, c?: string) => void 47 | } 48 | 49 | // HookArtMethodInvoke() -------------------------------------------------------------------------------- /agent/native/hooks.ts: -------------------------------------------------------------------------------- 1 | import { get } from "http" 2 | 3 | export { } 4 | 5 | declare global { 6 | var hookNativeMethod: (start: NativePointer, end: NativePointer) => void 7 | } 8 | 9 | // typedef struct { 10 | // const char* name; 11 | // const char* signature; 12 | // void* fnPtr; 13 | // } JNINativeMethod; 14 | class JNINativeMethod { 15 | address: NativePointer 16 | 17 | constructor(address: NativePointer) { 18 | this.address = address 19 | } 20 | 21 | getAddress(): NativePointer { 22 | return this.address 23 | } 24 | 25 | getMethodName(): string { 26 | return this.address.readCString() 27 | } 28 | 29 | getMethodSignature(): string { 30 | return this.address.add(Process.pointerSize * 1).readCString() 31 | } 32 | 33 | getMethodImplementation(): NativePointer { 34 | return this.address.add(Process.pointerSize * 2).readPointer() 35 | } 36 | 37 | toString(): string { 38 | return `fnPtr: ${this.getMethodImplementation()} | name: ${this.getMethodName()} | signature: ${this.getMethodSignature()}` 39 | } 40 | 41 | hook(): void { 42 | LOGZ(`Hooking : ${this.getMethodName()} @ ${this.getMethodImplementation()}`) 43 | { 44 | let func = () => { 45 | let thisObj = this 46 | return Interceptor.attach(thisObj.getMethodImplementation(), { 47 | onEnter: function (args) { 48 | let currentImp = thisObj.getMethodImplementation() 49 | let md = Process.findModuleByAddress(currentImp) 50 | let extInfo = '' 51 | if (md != null) extInfo += `| ${currentImp.sub(md.base)} @ ${md.name}` 52 | LOGD(`Called : ${thisObj.getMethodName()} @ ${currentImp} ${extInfo}`) 53 | LOGZ(`\targs : ${args[0]} ${args[1]} ${args[2]} ${args[3]}`) 54 | } 55 | }) 56 | }; func() 57 | } 58 | } 59 | 60 | next(): NativePointer { 61 | return this.address.add(Process.pointerSize * 3).readPointer() 62 | } 63 | } 64 | 65 | globalThis.hookNativeMethod = (start, end) => { 66 | LOGD(`looking from ${start} to ${end}`) 67 | let count = 0 68 | for (let local = start; local < end; local = local.add(Process.pointerSize * 3)) { 69 | let method = new JNINativeMethod(local) 70 | LOGD(method.toString()) 71 | method.hook() 72 | count++ 73 | } 74 | LOGD(`Total methods found: ${count}`) 75 | } -------------------------------------------------------------------------------- /agent/native/include.ts: -------------------------------------------------------------------------------- 1 | import './hooks' -------------------------------------------------------------------------------- /agent/tools/StdString.ts: -------------------------------------------------------------------------------- 1 | export class StdString { 2 | 3 | private static STD_STRING_SIZE = 3 * Process.pointerSize 4 | 5 | handle: NativePointer 6 | 7 | constructor(mPtr: NativePointer = Memory.alloc(StdString.STD_STRING_SIZE)) { 8 | this.handle = mPtr 9 | } 10 | 11 | private dispose(): void { 12 | const [data, isTiny] = this._getData() 13 | if (!isTiny) (Java as any).api.$delete(data) 14 | } 15 | 16 | static fromPointer(ptrs: NativePointer): string { 17 | return StdString.fromPointers([ptrs, ptrs.add(Process.pointerSize), ptrs.add(Process.pointerSize * 2)]) 18 | } 19 | 20 | static fromPointers(ptrs: NativePointer[]): string { 21 | if (ptrs.length != 3) return '' 22 | return StdString.fromPointersRetInstance(ptrs).disposeToString() 23 | } 24 | 25 | static from(pointer: NativePointer) { 26 | try { 27 | return pointer.add(Process.pointerSize * 2).readCString() 28 | } catch (error) { 29 | // LOGE("StdString.from ERROR" + error) 30 | return 'ERROR' 31 | } 32 | } 33 | 34 | private static fromPointersRetInstance(ptrs: NativePointer[]): StdString { 35 | if (ptrs.length != 3) return new StdString() 36 | const stdString = new StdString() 37 | stdString.handle.writePointer(ptrs[0]) 38 | stdString.handle.add(Process.pointerSize).writePointer(ptrs[1]) 39 | stdString.handle.add(2 * Process.pointerSize).writePointer(ptrs[2]) 40 | return stdString 41 | } 42 | 43 | disposeToString(): string { 44 | const result = this.toString() 45 | this.dispose() 46 | return result 47 | } 48 | 49 | toString(): string { 50 | try { 51 | const data: NativePointer = this._getData()[0] as NativePointer 52 | return data.readUtf8String() 53 | } catch (error) { 54 | return StdString.from(this.handle.add(Process.pointerSize * 2)) 55 | } 56 | } 57 | 58 | private _getData(): [NativePointer, boolean] { 59 | const str = this.handle 60 | const isTiny = (str.readU8() & 1) === 0 61 | const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer() 62 | return [data, isTiny] 63 | } 64 | } 65 | 66 | Reflect.set(globalThis, 'StdString', StdString) -------------------------------------------------------------------------------- /agent/tools/common.ts: -------------------------------------------------------------------------------- 1 | export const DEBUG :boolean = false 2 | 3 | globalThis.clear = () => console.log('\x1Bc') 4 | 5 | globalThis.cls = () => clear() 6 | 7 | globalThis.seeHexA = (addr: NativePointer | number, length: number = 0x40, header: boolean = true, color: any | undefined) => { 8 | let localAddr: NativePointer = NULL 9 | if (typeof addr == "number") { 10 | localAddr = ptr(addr) 11 | } else { 12 | localAddr = addr 13 | } 14 | LOG(hexdump(localAddr, { 15 | length: length, 16 | header: header, 17 | }), color == undefined ? LogColor.WHITE : color) 18 | } 19 | 20 | declare global { 21 | var clear: () => void 22 | var cls: () => void 23 | var seeHexA: (addr: NativePointer, length?: number, header?: boolean, color?: any | undefined) => void 24 | } 25 | 26 | export interface Subscriber { 27 | onValueChanged: (key: K, value: V) => void 28 | } 29 | 30 | export class KeyValueStore { 31 | private static instance: KeyValueStore 32 | private subscribers: Subscriber[] = [] 33 | private store: Map = new Map() 34 | 35 | private constructor() { } 36 | 37 | public static getInstance(): KeyValueStore { 38 | if (!this.instance) { 39 | this.instance = new KeyValueStore() 40 | } 41 | return this.instance 42 | } 43 | 44 | public subscribe(subscriber: Subscriber): void { 45 | this.subscribers.push(subscriber) 46 | } 47 | 48 | public unsubscribe(subscriber: Subscriber): void { 49 | const index = this.subscribers.indexOf(subscriber) 50 | if (index !== -1) { 51 | this.subscribers.splice(index, 1) 52 | } 53 | } 54 | 55 | public update(key: K, value: V): void { 56 | this.store.set(key, value) 57 | for (const subscriber of this.subscribers) { 58 | subscriber.onValueChanged(key, value) 59 | } 60 | } 61 | 62 | public get(key: K): V | undefined { 63 | return this.store.get(key) 64 | } 65 | } 66 | 67 | class globalValueStore { 68 | 69 | // filterThreadId 70 | public static set filterThreadId(value: number) { 71 | KeyValueStore.getInstance().update('filterThreadId', value) 72 | } 73 | 74 | public static get filterThreadId(): number { 75 | return KeyValueStore.getInstance().get('filterThreadId') || -1 76 | } 77 | 78 | // filterTimes 79 | public static set filterTimes(value: number) { 80 | KeyValueStore.getInstance().update('filterTimes', value) 81 | } 82 | 83 | public static get filterTimes(): number { 84 | return KeyValueStore.getInstance().get('filterTimes') || 5 85 | } 86 | 87 | // CanUseMterp 88 | public static set CanUseMterp(value: boolean) { 89 | KeyValueStore.getInstance().update('CanUseMterp', value) 90 | } 91 | 92 | public static get CanUseMterp(): boolean { 93 | return KeyValueStore.getInstance().get('CanUseMterp') || false 94 | } 95 | 96 | // filterMethodName 97 | public static set filterMethodName(value: string) { 98 | KeyValueStore.getInstance().update('filterMethodName', value) 99 | } 100 | 101 | public static get filterMethodName(): string { 102 | return KeyValueStore.getInstance().get('filterMethodName') || "" 103 | } 104 | 105 | // // Interceptors 106 | // public static get Interceptors(): InvocationListener[] { 107 | // return KeyValueStore.getInstance().get('Interceptors') || [] 108 | // } 109 | 110 | // public static set Interceptors(value: InvocationListener[]) { 111 | // KeyValueStore.getInstance().update('Interceptors', value) 112 | // } 113 | 114 | } 115 | 116 | var arrayMap = new Map() 117 | export const filterDuplicateOBJ = (objstr: string | String, maxCount: number = 10) => { 118 | if (arrayMap.has(objstr)) { 119 | arrayMap.set(objstr, arrayMap.get(objstr) + 1) 120 | if (arrayMap.get(objstr) > maxCount) { 121 | arrayMap.delete(objstr) 122 | return false 123 | } 124 | } else arrayMap.set(objstr, 1) 125 | return true 126 | } 127 | 128 | setImmediate(() => { 129 | globalValueStore.filterThreadId = -1 130 | globalValueStore.filterTimes = 5 131 | globalValueStore.CanUseMterp = false 132 | globalValueStore.filterMethodName = '' 133 | }) 134 | 135 | Reflect.set(globalThis, "globalValueStore", globalValueStore) 136 | 137 | globalThis.setfilterThreadId = (value: number) => globalValueStore.filterThreadId = value 138 | globalThis.setfilterTimes = (value: number) => globalValueStore.filterTimes = value 139 | globalThis.setCanUseMterp = (value: boolean) => globalValueStore.CanUseMterp = value 140 | globalThis.setfilterMethodName = (value: string) => globalValueStore.filterMethodName = value -------------------------------------------------------------------------------- /agent/tools/dlopen.ts: -------------------------------------------------------------------------------- 1 | // #define RTLD_LOCAL 0 2 | // #define RTLD_LAZY 0x00001 3 | // #define RTLD_NOW 0x00002 4 | // #define RTLD_NOLOAD 0x00004 5 | // #define RTLD_GLOBAL 0x00100 6 | // #define RTLD_NODELETE 0x01000 7 | const MAP_RTLD: { [key: number]: string } = { 8 | 0: "RTLD_LOCAL", 9 | 0x00001: "RTLD_LAZY ", 10 | 0x00002: "RTLD_NOW ", 11 | 0x00004: "RTLD_NOLOAD ", 12 | 0x00100: "RTLD_GLOBAL ", 13 | 0x01000: "RTLD_NODELETE" 14 | } 15 | 16 | export class dlopenManager { 17 | 18 | private static needLog_G: boolean = true 19 | 20 | public static init(needLog: boolean) { 21 | dlopenManager.needLog_G = needLog 22 | 23 | // void* dlopen(const char* filename, int flag) 24 | // https://cs.android.com/android/platform/superproject/+/master:bionic/libdl/libdl.cpp;l=86?q=libdl%20dlopen&sq=&ss=android%2Fplatform%2Fsuperproject 25 | Interceptor.attach(Module.findExportByName(null, "dlopen"), { 26 | onEnter: function (args) { 27 | this.name_p = args[0].readCString() 28 | this.flag_p = args[1].toInt32() 29 | }, 30 | onLeave: function (retval: NativePointer) { 31 | dlopenManager.onSoLoad(this.name_p, this.flag_p, retval) 32 | } 33 | }) 34 | 35 | // void* android_dlopen_ext(const char* __filename, int __flags, const android_dlextinfo* __info) 36 | // https://cs.android.com/android/platform/superproject/+/master:prebuilts/vndk/v32/x86_64/include/generated-headers/bionic/libc/libc/android_vendor.32_x86_64_shared/gen/include/android/dlext.h;l=185?q=android_dlopen_ext&ss=android%2Fplatform%2Fsuperproject 37 | Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { 38 | onEnter: function (args) { 39 | if (args[0] !== undefined && args[0] != null) { 40 | this.name_p = args[0].readCString() 41 | this.flag_p = args[1].toInt32() 42 | this.info_p = args[2].toInt32() 43 | } 44 | }, 45 | onLeave: function (retval: NativePointer) { 46 | dlopenManager.onSoLoad(this.name_p, this.flag_p, retval, this.info_p) 47 | } 48 | }) 49 | } 50 | 51 | public static onSoLoad(soName: string, flag: number, retval: NativePointer, _info?: NativePointer) { 52 | if (dlopenManager.needLog_G) LOGD(`dlopen: ${flag} -> ${MAP_RTLD[flag]} | ${soName}`) 53 | // if (dlopenManager.needLog_G) LOGD(`dlopen: ${retval} | ${flag} -> ${MAP_RTLD[flag]} | ${soName}`) 54 | if (dlopenManager.callbacks == undefined) return 55 | dlopenManager.callbacks.forEach((actions: (() => void)[], name: string) => { 56 | if (soName.indexOf(name) !== -1) { 57 | actions.forEach(action => { 58 | action() 59 | }) 60 | } 61 | dlopenManager.callbacks.delete(name) 62 | }) 63 | } 64 | 65 | private static callbacks: Map void)[]> = new Map void)[]>() 66 | 67 | public static registSoLoadCallBack(soName: string, action: () => void) { 68 | if (dlopenManager.callbacks.has(soName)) { 69 | dlopenManager.callbacks.get(soName).push(action) 70 | } else { 71 | dlopenManager.callbacks.set(soName, [action]) 72 | } 73 | } 74 | 75 | } 76 | 77 | setImmediate(() => { 78 | dlopenManager.init(false) 79 | }) 80 | 81 | declare global { 82 | var addSoLoadCallBack: (soName: string, action: () => void) => void 83 | } 84 | 85 | globalThis.addSoLoadCallBack = (soName: string, action: () => void) => { 86 | dlopenManager.registSoLoadCallBack(soName, action) 87 | } -------------------------------------------------------------------------------- /agent/tools/dump.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 内存 dump so 3 | * @param soName 指定so名称 4 | */ 5 | function dump_so(soName: string = "libil2cpp.so") { 6 | const module = Process.getModuleByName(soName) 7 | LOGE(getLine(30)) 8 | LOGW("[name]:" + module.name) 9 | LOGW("[base]:" + module.base) 10 | LOGW("[size]:" + module.size) 11 | LOGW("[path]:" + module.path) 12 | LOGE(getLine(30)) 13 | const fileName = `${module.name}_${module.base}_${ptr(module.size)}.so` 14 | dump_mem(module.base, module.size, fileName) 15 | } 16 | 17 | /** 18 | * 内存 dump 19 | * @param from 从哪里开始 20 | * @param length 长度 21 | * @param fileName 保存的文件名 22 | * @returns 23 | */ 24 | function dump_mem(from: NativePointer, length: number, fileName?: string, path?: string, withLog: boolean = true) { 25 | if (length <= 0) return 26 | 27 | let savedPath: string = path == undefined ? getFilesDir() : path 28 | if (fileName == undefined) { 29 | savedPath += `/${from}_${length}.bin` 30 | } else { 31 | savedPath += '/' + fileName 32 | } 33 | 34 | const file_handle = new File(savedPath, "wb") 35 | if (file_handle && file_handle != null) { 36 | Memory.protect(from, length, 'rwx') 37 | let libso_buffer = from.readByteArray(length)! 38 | file_handle.write(libso_buffer) 39 | file_handle.flush() 40 | file_handle.close() 41 | if (withLog) { 42 | LOGZ(`\nDump ${length} bytes from ${from} to ${from.add(length)}`) 43 | LOGD(`Saved to -> ${savedPath}\n`) 44 | } 45 | } 46 | } 47 | 48 | declare global { 49 | var dumpSo: (soName: string) => void 50 | var dumpMem: (from: NativePointer, length: number, fileName?: string, path?: string, withLog?: boolean) => void 51 | } 52 | 53 | globalThis.dumpSo = dump_so 54 | globalThis.dumpMem = dump_mem 55 | 56 | export { } -------------------------------------------------------------------------------- /agent/tools/enum.ts: -------------------------------------------------------------------------------- 1 | // enum InvokeType : uint32_t { 2 | // kStatic, // <> 3 | // kDirect, // <> 4 | // kVirtual, // <> 5 | // kSuper, // <> 6 | // kInterface, // <> 7 | // kPolymorphic, // <> 8 | // kCustom, // <> 9 | // kMaxInvokeType = kCustom 10 | // }; 11 | export class InvokeType { 12 | 13 | static kStatic = 0 14 | static kDirect = 1 15 | static kVirtual = 2 16 | static kSuper = 3 17 | static kInterface = 4 18 | static kPolymorphic = 5 19 | static kCustom = 6 20 | static kMaxInvokeType = 6 21 | 22 | static toString(type: number): string { 23 | switch (type) { 24 | case InvokeType.kStatic: 25 | return "kStatic" 26 | case InvokeType.kDirect: 27 | return "kDirect" 28 | case InvokeType.kVirtual: 29 | return "kVirtual" 30 | case InvokeType.kSuper: 31 | return "kSuper" 32 | case InvokeType.kInterface: 33 | return "kInterface" 34 | case InvokeType.kPolymorphic: 35 | return "kPolymorphic" 36 | case InvokeType.kCustom: 37 | return "kCustom" 38 | case InvokeType.kMaxInvokeType: 39 | return "kMaxInvokeType" 40 | default: 41 | return "unknown" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /agent/tools/functions.ts: -------------------------------------------------------------------------------- 1 | export function demangleName(expName: string) { 2 | let demangleAddress: NativePointer | null = Module.findExportByName("libc++.so", '__cxa_demangle') 3 | if (demangleAddress == null) demangleAddress = Module.findExportByName("libunwindstack.so", '__cxa_demangle') 4 | if (demangleAddress == null) demangleAddress = Module.findExportByName("libbacktrace.so", '__cxa_demangle') 5 | if (demangleAddress == null) demangleAddress = Module.findExportByName(null, '__cxa_demangle') 6 | if (demangleAddress == null) throw Error("can not find export function -> __cxa_demangle") 7 | let demangle: Function = new NativeFunction(demangleAddress, 'pointer', ['pointer', 'pointer', 'pointer', 'pointer']) 8 | let mangledName: NativePointer = Memory.allocUtf8String(expName) 9 | let outputBuffer: NativePointer = NULL 10 | let length: NativePointer = NULL 11 | let status: NativePointer = Memory.alloc(Process.pageSize) 12 | let result: NativePointer = demangle(mangledName, outputBuffer, length, status) as NativePointer 13 | if (status.readInt() === 0) { 14 | let resultStr: string | null = result.readUtf8String() 15 | return (resultStr == null || resultStr == expName) ? "" : resultStr 16 | } else return "" 17 | } 18 | 19 | export const demangleName_onlyFunctionName = (expName: string): string[] => demangleName(expName).split("::").map(item => item.split("(")[0]) 20 | 21 | globalThis.demangleName = demangleName 22 | 23 | declare global { 24 | var demangleName: (expName: string) => string 25 | } -------------------------------------------------------------------------------- /agent/tools/include.ts: -------------------------------------------------------------------------------- 1 | import './StdString' 2 | import './dlopen' 3 | import './dump' 4 | import './common' 5 | import './enum' 6 | import './logger' 7 | import './modifiers' 8 | import './intercepter' -------------------------------------------------------------------------------- /agent/tools/intercepter.ts: -------------------------------------------------------------------------------- 1 | import { getSym } from "../android/Utils/SymHelper" 2 | 3 | export const bsym = (name: string, lib: string = "libart.so") => { 4 | 5 | const target: NativePointer = getSym(name, lib, false) 6 | const demangleStr: string = demangleName(name) 7 | const targetName: string = `${demangleStr.length == 0}` ? name : demangleStr 8 | LOGD(`Hooking [ ${targetName} ] from ${lib} => ${target}`) 9 | 10 | Interceptor.attach(target, { 11 | onEnter(args) { 12 | this.args = args 13 | }, 14 | onLeave(retval) { 15 | LOGD(`\nCalled ${name}(${this.args.map((arg) => arg.toString()).join(', ')}) => ${retval}`) 16 | } 17 | }) 18 | } 19 | 20 | export const nop = (functionAddress: NativePointer | number) => R(functionAddress, new NativeCallback(() => { LOGD(`Called NOP -> ${functionAddress}`); return }, 'void', [])) 21 | 22 | export const R = (functionAddress: NativePointer | number | string, callback: NativeCallback) => { 23 | if (typeof functionAddress == 'number') functionAddress = ptr(functionAddress) 24 | if (typeof functionAddress == "string") functionAddress = getSym(functionAddress) 25 | const debugInfo = DebugSymbol.fromAddress(functionAddress) 26 | try { 27 | Interceptor.replace(functionAddress, callback) 28 | // LOGD(`Enable Replace -> ${debugInfo.name} | ${debugInfo.address}`) 29 | } catch (error) { 30 | if (error.message.indexOf("already hooked") != -1) { 31 | LOGE(`Enable Replace -> ${debugInfo.name} | ${debugInfo.address} <- already replaced , try to rehook`) 32 | Interceptor.revert(functionAddress) 33 | Interceptor.replace(functionAddress, callback) 34 | } else { 35 | LOGE(`Enable Replace -> ${debugInfo.name} | ${debugInfo.address} <- ${error}`) 36 | } 37 | } 38 | } 39 | 40 | const listeners: InvocationListener[] = [] 41 | export const A = (functionAddress: NativePointer | number | string, callbacksOrProbe: InvocationListenerCallbacks | InstructionProbeCallback, data?: NativePointerValue) => { 42 | if (typeof functionAddress == 'number') functionAddress = ptr(functionAddress) 43 | if (typeof functionAddress == "string") functionAddress = getSym(functionAddress) 44 | const debugInfo = DebugSymbol.fromAddress(functionAddress) 45 | try { 46 | listeners.push(Interceptor.attach(functionAddress, callbacksOrProbe, data)) 47 | LOGD(`Enable Attach -> ${debugInfo.name} | ${debugInfo.address}`) 48 | } catch (error) { 49 | LOGE(`Enable Attach -> ${debugInfo.name} | ${debugInfo.address} <- ${error}`) 50 | } 51 | } 52 | 53 | Reflect.set(global, 'bsym', bsym) 54 | Reflect.set(global, 'nop', nop) 55 | Reflect.set(global, 'R', R) -------------------------------------------------------------------------------- /agent/tools/modifiers.ts: -------------------------------------------------------------------------------- 1 | // jetbrains://clion/navigate/reference?project=libart&path=~/bin/aosp/art/libdexfile/dex/modifiers.h 2 | export class ArtModifiers { 3 | 4 | static kAccPublic = 0x0001; // class, field, method, ic 5 | static kAccPrivate = 0x0002; // field, method, ic 6 | static kAccProtected = 0x0004; // field, method, ic 7 | static kAccStatic = 0x0008; // field, method, ic 8 | static kAccFinal = 0x0010; // class, field, method, ic 9 | static kAccSynchronized = 0x0020; // method (only allowed on natives) 10 | static kAccSuper = 0x0020; // class (not used in dex) 11 | static kAccVolatile = 0x0040; // field 12 | static kAccBridge = 0x0040; // method (1.5) 13 | static kAccTransient = 0x0080; // field 14 | static kAccVarargs = 0x0080; // method (1.5) 15 | static kAccNative = 0x0100; // method 16 | static kAccInterface = 0x0200; // class, ic 17 | static kAccAbstract = 0x0400; // class, method, ic 18 | static kAccStrict = 0x0800; // method 19 | static kAccSynthetic = 0x1000; // class, field, method, ic 20 | static kAccAnnotation = 0x2000; // class, ic (1.5) 21 | static kAccEnum = 0x4000; // class, field, ic (1.5) 22 | 23 | static kAccJavaFlagsMask = 0xffff; // bits set from Java sources (low 16) 24 | 25 | static kAccConstructor = 0x00010000; // method (dex only) <(cl)init> 26 | static kAccDeclaredSynchronized = 0x00020000; // method (dex only) 27 | static kAccClassIsProxy = 0x00040000; // class (dex only) 28 | // Set to indicate that the ArtMethod is obsolete and has a different DexCache + DexFile from its 29 | 30 | // declaring class. This flag may only be applied to methods. 31 | static kAccObsoleteMethod = 0x00040000; // method (runtime) 32 | // Used by a method to denote that its execution does not need to go through slow path interpreter. 33 | static kAccSkipAccessChecks = 0x00080000; // method (runtime, not native) 34 | // Used by a class to denote that the verifier has attempted to check it at least once. 35 | static kAccVerificationAttempted = 0x00080000; // class (runtime) 36 | static kAccSkipHiddenapiChecks = 0x00100000; // class (runtime) 37 | // This is set by the class linker during LinkInterfaceMethods. It is used by a method to represent 38 | // that it was copied from its declaring class into another class. All methods marked kAccMiranda 39 | // and kAccDefaultConflict will have this bit set. Any kAccDefault method contained in the methods_ 40 | // array of a concrete class will also have this bit set. 41 | static kAccCopied = 0x00100000; // method (runtime) 42 | 43 | // ... 44 | 45 | public static PrettyAccessFlags = (access_flags: NativePointer | number): string => { 46 | let access_flags_local: NativePointer = NULL 47 | if (typeof access_flags === "number") { 48 | access_flags_local = ptr(access_flags) 49 | } else { 50 | access_flags_local = access_flags 51 | } 52 | if (access_flags_local.isNull()) throw new Error("access_flags is null") 53 | let result: string = "" 54 | if (!(access_flags_local.and(ArtModifiers.kAccPublic)).isNull()) { 55 | result += "public " 56 | } 57 | if (!(access_flags_local.and(ArtModifiers.kAccProtected)).isNull()) { 58 | result += "protected " 59 | } 60 | if (!(access_flags_local.and(ArtModifiers.kAccPrivate)).isNull()) { 61 | result += "private " 62 | } 63 | if (!(access_flags_local.and(ArtModifiers.kAccFinal)).isNull()) { 64 | result += "final " 65 | } 66 | if (!(access_flags_local.and(ArtModifiers.kAccStatic)).isNull()) { 67 | result += "static " 68 | } 69 | if (!(access_flags_local.and(ArtModifiers.kAccAbstract)).isNull()) { 70 | result += "abstract " 71 | } 72 | if (!(access_flags_local.and(ArtModifiers.kAccInterface)).isNull()) { 73 | result += "interface " 74 | } 75 | if (!(access_flags_local.and(ArtModifiers.kAccTransient)).isNull()) { 76 | result += "transient " 77 | } 78 | if (!(access_flags_local.and(ArtModifiers.kAccVolatile)).isNull()) { 79 | result += "volatile " 80 | } 81 | if (!(access_flags_local.and(ArtModifiers.kAccSynchronized)).isNull()) { 82 | result += "synchronized " 83 | } 84 | return result 85 | } 86 | } 87 | 88 | declare global { 89 | var PrettyAccessFlags: (access_flags: NativePointer | number) => string 90 | } 91 | 92 | globalThis.PrettyAccessFlags = (access_flags: NativePointer | number) => ArtModifiers.PrettyAccessFlags(access_flags) 93 | 94 | Reflect.set(globalThis, "ArtModifiers", ArtModifiers) -------------------------------------------------------------------------------- /imgs/dumpDexFiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhlzy/ARTHookScripts/d2858d88ead83b0511f10261537cc4c41e6908bd/imgs/dumpDexFiles.png -------------------------------------------------------------------------------- /imgs/printBackTraceWithSmali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhlzy/ARTHookScripts/d2858d88ead83b0511f10261537cc4c41e6908bd/imgs/printBackTraceWithSmali.png -------------------------------------------------------------------------------- /imgs/showOatAsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhlzy/ARTHookScripts/d2858d88ead83b0511f10261537cc4c41e6908bd/imgs/showOatAsm.png -------------------------------------------------------------------------------- /imgs/showSmali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axhlzy/ARTHookScripts/d2858d88ead83b0511f10261537cc4c41e6908bd/imgs/showSmali.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frida-art-hook-agent", 3 | "version": "1.0.0", 4 | "description": "ART Hook Scripts", 5 | "private": false, 6 | "main": "agent/main.ts", 7 | "scripts": { 8 | "prepare": "npm run build", 9 | "build": "frida-compile agent/main.ts -o _agent.js -c", 10 | "watch": "frida-compile agent/main.ts -o _agent.js -w", 11 | "obfuscator": "javascript-obfuscator _agent.js --compact true --self-defending false --unicode-escape-sequence true", 12 | "fridaTs": "tsc --allowJs --declaration node_modules\\frida-java-bridge\\index.js" 13 | }, 14 | "devDependencies": { 15 | "@types/frida-gum": "^16.2.0", 16 | "@types/node": "^14.14.10", 17 | "frida-compile": "^10.0.0", 18 | "javascript-obfuscator": "^4.1.0" 19 | }, 20 | "dependencies": { 21 | "crypto-js": "^4.2.0", 22 | "dts-gen": "^0.8.2", 23 | "frida-java-bridge": "^6.2.6", 24 | "log4js": "^6.9.1", 25 | "rxjs": "^7.8.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "es2020" 6 | ], 7 | "resolveJsonModule": true, 8 | "experimentalDecorators": true, 9 | "module": "commonjs", 10 | "allowJs": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "strict": false, 14 | "removeComments": true, 15 | "moduleResolution": "node", 16 | // "baseUrl": "./", 17 | // "paths": { 18 | // }, 19 | "skipLibCheck": true, 20 | "allowSyntheticDefaultImports": true, 21 | "forceConsistentCasingInFileNames": true 22 | } 23 | } --------------------------------------------------------------------------------