使用Frida自动补全和TypeScript模块化脚本 简介 配合使用集成开发环境编辑器(例如:JetBrains IDEA或Visual Code)编写TypeScript 脚本来规范化、模块化并提供自动补全你的代码,让你的开发效率大大提升。
部署
1 2 3 4 $ git clone git://github.com/oleavr/frida-agent-example.git $ cd frida-agent-example/ $ npm install $ npm run watch
集成开发环境打开frida-agent-example
目录
编辑frida-agent-example/index.ts
文件并保存
npm run watch
会监控index.ts 文件并在项目根目录下生成_agent.js
目标脚本。
1 $ frida -U -f com.example.android --runtime=v8 --no-pause -l _agent.js
遍历Java List requestHeaders
是一个Java的List对象,遍历其内容如下:
1 2 3 log += " request.headers: " + rObject.$methods.find("toString" ).call(rObject, requestHeaders) + "\n" ; for (var i = 0 ; i < requestHeaders.size(); i++) log += " headers[" + i + "]: " + requestHeaders.get(i) + "\n" ;
输出:
1 2 request.headers: [com.sankuai.meituan.retrofit2.Header@a671e8] headers[0]: com.sankuai.meituan.retrofit2.Header@a671e8
Native 静态注册或动态注册的Native函数,第二个参数总是一个jobject
;如果native函数是静态函数,那么该参数是jclass,如果native函数是成员函数,那么该参数是当前类的对象;
例如Java层有以下声明代码:
1 private native String TestNative (int arg1, int arg2, String arg3) ;
在Frida hook时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const nativeHook_TestNative = { onEnter : function (args ) { this .env = args[0 ]; this .arg1 = args[2 ]; this .arg2 = args[3 ]; this .arg3 = args[4 ]; this .tag = "nativeHook_TestNative" ; this .log = "" ; this .log += "> - - - - - - - - <\n" ; this .log += this .tag + " Enter.\n" ; this .log += " JNIEnv: " + this .env + "\n" ; this .log += " arg1: " + this .arg1 + "\n" ; this .log += " arg2: " + this .arg2 + "\n" ; this .log += " arg3: " + this .arg3 + ", " + GetStringUTFChars(this .env, this .arg3, NULL).readCString() + "\n" ; console .log(this .log); }, onLeave : function (ret ) { const tid = gettid(); this .log = this .tag + " Leave.\n" ; this .log += " ret: " + ret + ", str:" + GetStringUTFChars(this .env, ret, NULL).readCString() + "\n" ; this .log += "^ - - - - - - - - ^\n" ; console .log(this .log); } };
华丽的hook代码 以下代码均运行在v8引擎环境中,请使用frida --runtime=v8
来指定
gettid, getpid, getuid 定义gettid, getpid, getuid三个frida.NativeFunction
供外部直接调用。
1 2 3 const gettid = new NativeFunction(Module.getExportByName(null , 'gettid' ), 'uint32' , []);const getpid = new NativeFunction(Module.getExportByName(null , 'getpid' ), 'uint32' , []);const getuid = new NativeFunction(Module.getExportByName(null , 'getuid' ), 'uint32' , []);
Hook libart.RegisterNatives 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function nativeHook_RegisterNatives_onEnter (args ) { let _this = this ; _this.tag = 'RegisterNatives' ; _this.env = args[0 ]; _this.clazz = args[1 ]; _this.methods = args[2 ]; _this.nMethods = parseInt (args[3 ]); let log = `> - - - - - - - - - - - - - - - - - - tid:[${gettid()} ] - - - - - - - - - - - - - - - - - - <\n` ; log += `${_this.tag} Enter.\n` ; log += ` env: ${_this.env} \n` ; log += ` clazz: ${Java.vm.getEnv().getClassName(_this.clazz)} \n` ; log += ` methods: ${_this.methods} \n` ; log += ` nMethods: ${_this.nMethods} \n` ; for (let i = 0 ; i < _this.nMethods; i++) { let methodName = _this.methods.add(i * (Process.pointerSize * 3 )).readPointer().readCString(); let methodSig = _this.methods.add(i * (Process.pointerSize * 3 ) + (Process.pointerSize)).readPointer().readCString(); let methodPtr = _this.methods.add(i * (Process.pointerSize * 3 ) + (Process.pointerSize * 2 )).readPointer(); let methodMod = Process.findModuleByAddress(methodPtr); log += ` ${i + 1 } :\n` ; log += ` methodName: ${methodName} \n` ; log += ` methodSig: ${methodSig} \n` ; log += ` methodPtr: ${methodPtr} , off: ${methodPtr.sub(methodMod.base)} \n` ; log += ` methodLib: ${methodMod.name} , base: ${methodMod.base} \n` ; log += `\n` ; } console .log(log); } function nativeHook_RegisterNatives_onLeave (retval ) { let log = `${this .tag} Leave.\n` ; log += ` retval: ${retval} \n` ; log += `> - - - - - - - - - - - - - - - - - - tid:[${gettid()} ] - - - - - - - - - - - - - - - - - - <\n` ; console .log(log); } setImmediate(function ( ) { Java.perform(function ( ) { let JNIEnv = Java.vm.getEnv().handle; let JNIEnv_vtable = JNIEnv.readPointer(); let registerNativesPtr = JNIEnv_vtable.add(215 * Process.pointerSize).readPointer(); Interceptor.attach(registerNativesPtr, { onEnter : nativeHook_RegisterNatives_onEnter, onLeave : nativeHook_RegisterNatives_onLeave }); }); console .log('end' ); });
给Java.use
增加一些功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 let classCache = {};Function .prototype.before = function (beforeFunction ) { let _this = this ; return function ( ) { this ._continue = true ; let ret = beforeFunction(this , arguments ); if (!ret) { ret = _this.apply(this , arguments ); } return ret; } }; Function .prototype.after = function (afterFunction ) { let _this = this ; return function ( ) { let ret = _this.apply(this , arguments ); return afterFunction(this , ret, arguments ); } }; Java.use = Java.use.before(function (_this, args ) { let className = args[0 ]; if (classCache[className]) { console .log('[+] Java use:' , className, 'is cached.' ); return classCache[className]; } return false ; }) Java.use = Java.use.after(function (_this, ret, args ) { let className = args[0 ]; if (!classCache[className]) { ret.$fields = new class Fields { wrapper; constructor (wrapper ) { this .wrapper = wrapper; } find (name ) { let field; try { field = this .wrapper.getDeclaredField(name); } catch (e) { field = this .wrapper.getField(name); } return field; }; findall ( ) { let fields; fields = this .wrapper.getDeclaredFields(); fields.concat(this .wrapper.getFields()); return fields; }; get (name, obj ) { let notAccessible; let ret; let field = this .find(name); notAccessible = !field.isAccessible(); if (notAccessible) field.setAccessible(true ); ret = field.get.overload('java.lang.Object' ).call(field, obj); if (!notAccessible) field.setAccessible(false ); return ret; }; set (name, obj, val ) { let isAccessible; let field = this .find(name); isAccessible = field.isAccessible(); if (!isAccessible) field.setAccessible(true ); field.set.overload('java.lang.Object' , 'java.lang.Object' ).call(field, obj, val); if (!isAccessible) field.setAccessible(false ); }; }(ret.class); ret.$methods = new class Methods { wrapper; constructor (wrapper ) { this .wrapper = wrapper; } find (name, ...args ) { let method; try { method = this .wrapper.getDeclaredMethod(name, args); } catch (e) { method = this .wrapper.getMethod(name, args); } return function (obj, ...args ) { let isAccessible; let ret; isAccessible = method.isAccessible(); if (!isAccessible) method.setAccessible(true ); ret = method.invoke(obj, args); if (!isAccessible) method.setAccessible(false ); return ret; }; }; }(ret.class); classCache[className] = ret; } return ret; });
打印堆栈 根据指定的tab数量转为空格 1 2 3 4 5 6 7 8 9 function genTableString (tableSize ) { let tableString = '' ; tableSize = Math .max(Math .min(tableSize, 10 ), 0 ); for (let i = 0 ; i < tableSize; i++) { tableString += ' ' ; } return tableString; }
Java层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function nativeThreadTraceToString (traces, tableSize ) { let module ; let table = genTableString(tableSize); const stackTraces = []; for (let j = 0 ; j < traces.length; j++) { if (!traces.hasOwnProperty(j)) continue ; const stackTrace = new class { index; moduleName; moduleBase; offset; address; constructor (index ) { this .index = index; this .moduleName = '' ; this .moduleBase = NULL; this .offset = '' ; this .address = NULL; } toString ( ) { return `idx: ${this .index} ` + `, name: ${this .moduleName} ` + `, base: ${this .moduleBase} ` + `, offset: ${this .offset} ` + `, address: ${this .address} ` ; }; }(j); module = Process.findModuleByAddress(traces[j]); if (module ) { stackTrace.moduleName = module .name; stackTrace.moduleBase = module .base; } stackTrace.address = traces[j]; stackTrace.offset = `0x${(parseInt (stackTrace.address) - parseInt (stackTrace.moduleBase)).toString(16 )} ` ; stackTraces.push(stackTrace); } return `${table} ${stackTraces.join(`\n${table} ` )} ` }
native层
1 2 3 4 5 6 7 8 9 10 function javaThreadTraceToString (thread, tableSize ) { let table = genTableString(tableSize); let tag = `${table} Thread Trace:\n` ; return `${tag} ${table} ${thread.getStackTrace().join(`\n${table} ` )} ` ; } let rThread = Java.use('java.lang.Thread' );let thread = rThread.currentThread();console .log(javaThreadTraceToString(thread, 1 ));