使用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 ));