title:So层Hook So层Hook 示例应用的so文件加载时机是在一些按钮被点击后才加载,注意开启应用后注入,然后点击按钮,才会输出相关信息
像那种一开始就有加载so的代码,先点击CADD(其他能加载so的按钮也行)一下,再注入,防止找不到so文件。
最基本的,加载so
1 2 3 4 var module = Process .findModuleByName ("libxiaojianbang.so" );if (module !=NULL ){ console .log (JSON .stringify (module )); }
这里也可以用getModuleByName,不过该函数如果找不到对应的模块会抛出异常,前者找不到会返回NULL。据说frida里,find和get开头的一样的函数都是这样。
Module对象的属性可以通过JSON.stringfy方法来打印
通过地址获取模块;主要通过以下的api
1 2 findModuleByAddress (addres :NativePointerValue ):Module | NULL ;getModuleByAddress (addres :NativePointerValue ):Module ;
枚举导入表 1 2 var imports = Process .getModuleByName ("libxiaojianbang.so" ).enumerateImports ();console .log (JSON .stringfy (imports[0 ]));
得到模块中指定函数的地址,进行一个遍历
1 2 3 4 5 6 7 8 9 10 var improts = Process .findModuleByName ("libxiaojianbang.so" ).enumerateImports ();var sprintf_addr = null ;for (let i = 0 ; i < improts.length ; i++){ let _import = improts[i]; if (_import.name .indexOf ("sprintf" ) != -1 ){ sprintf_addr = _import.address ; break ; } } console .log ("sprintf_addr: " , sprintf_addr);
枚举导出表 1 2 var exports = Process .getModuleByName ("libxiaojianbang.so" ).enumerateExports ();console .log (JSON .stringify (exports [0 ]));
同样的,得到模块中指定函数的地址,进行一个遍历
1 2 3 4 5 6 7 8 9 10 var exports = Process .findModuleByName ("libxiaojianbang.so" ).enumerateExports ();var MD5Final _addr = null ;for (let i = 0 ; i < exports .length ; i++){ let _export = exports [i]; if (_export.name .indexOf ("_Z8MD5FinalP7MD5_CTXPh" ) != -1 ){ MD5Final _addr = _export.address ; break ; } } console .log ("MD5Final_addr: " , MD5Final _addr);
枚举模块中的符号表 将导入表导出表都给遍历了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function findFuncInWitchSo (funcName ) { var modules = Process .enumerateModules (); for (let i = 0 ; i < modules.length ; i++) { let module = modules[i]; let _symbols = module .enumerateSymbols (); for (let j = 0 ; j < _symbols.length ; j++) { let _symbol = _symbols[i]; if (_symbol.name == funcName){ return module .name + " " + JSON .stringify (_symbol); } } let _exports = module .enumerateExports (); for (let j = 0 ; j < _exports.length ; j++) { let _export = _exports[j]; if (_export.name == funcName){ return module .name + " " + JSON .stringify (_export); } } } return null ; } console .log (findFuncInWitchSo ('strcat' ));
Module的源码声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 declare class Module { name : string; base : NativePointer ; size : number; path : string; enumerateImports (): ModuleImportDetails []; enumerateExports (): ModuleExportDetails []; enumerateSymbols (): ModuleSymbolDetails []; findExportByName (exportName : string): NativePointer | null ; getExportByName (exportName : string): NativePointer ; static load (name : string): Module ; static findBaseAddress (name : string): NativePointer | null ; static getBaseAddress (name : string): NativePointer ; static findExportByName (moduleName : string | null , exportName : string): NativePointer | null ; static getExportByName (moduleName : string | null , exportName : string): NativePointer ; }
正式开始Hook so函数 第一步,得到目标函数的地址 除了上述方法外,用Frida的API也可以
首先是静态方法和实例方法
静态方法可以直接通过 类名.方法名 的方式来访问,并传入两个参数,第一个是string类型的模块名,第二个是string类型的导出函数名(以汇编界面显示的名称位准,如上文的MD5_Final)
实例方法可以先获取Module对象,再通过 对象.方法名的方式来访问,只需要传入string类型的导出函数名即可,并返回NativePointer类型的函数地址。得到NativePointer类型的函数地址后,可以使用Interceptor的attach函数来进行Hook,也可以使用Interceptor的detachAll函数来解除Hook。
1 2 3 4 5 6 declare namespace Interceptor { function attach (target:NativePointerValue,callbacksOrProbe:InvocationListenerCallbacks | InstructionProbeCallback,data?:NativePointerValue ): InvocationListener ; function detatchAll ( ):void ; .... }
可以看到,detachAll不需要任何参数,而attach则需要传入函数地址和被Hook函数触发时执行的回调函数。
Interceptor通过inlinehook的方式拦截代码执行,会修改被Hook处的16个字节
需要注意的时,Java native层的函数,前两个参数是 JNIEnv*,jcalss(静态)/jobject(实例),后面的参数就是Java层中声明时对应的函数。
arm64中使用x0-x7来传递参数,如果参数数量多于8个,则需要从栈中获取(实际上传参还得考虑浮点寄存器,w开头的32位寄存器;另外arm32中是使用r0-r3寄存器来传递参数,超出的同样入栈)
arm64使用x0或w0寄存器存放函数返回值,arm32使用r0,r0如果放不下,则会使用r1
接下来来看看hexdump的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 declare function hexdump (target: ArrayBuffer | NativePointerValue, options?: HexdumpOptions ): string; interface HexdumpOptions { offset?: number; length?: number; header?: boolean; ansi?: boolean; } var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var data = hexdump (soAddr, {length : 16 , header : false });console .log (data);var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var data = hexdump (soAddr, {offset : 4 , length : 16 , header : false });console .log (data);
Hook so层任意函数 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 declare class Module { ...... static findBaseAddress (name : string): NativePointer | null ; static getBaseAddress (name : string): NativePointer ; } var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log (soAddr);var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var funcAddr = soAddr.add (0x1ACC ); declare class NativePointer { constructor (v: string | number | UInt64 | Int64 | NativePointerValue ); add (v : NativePointerValue | UInt64 | Int64 | number | string): NativePointer ;...... } var soAddr = 0x77ab999000 ; console .log ( ptr (soAddr).add (0x1A0C ) ); var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var sub_1A0C = soAddr.add (0x1ACC );Interceptor .attach (sub_1ACC, { onEnter : function (args ) { console .log ("sub_1ACC onEnter args[0]: " , args[0 ]); console .log ("sub_1ACC onEnter args[1]: " , args[1 ]); console .log ("sub_1ACC onEnter args[2]: " , args[2 ]); console .log ("sub_1ACC onEnter args[3]: " , args[3 ]); console .log ("sub_1ACC onEnter args[4]: " , args[4 ]); }, onLeave : function (retval ) { console .log ("sub_1ACC onLeave retval: " , retval); } });
当然了,函数地址也是可以手算的:
thumb指令下:so文件基址+函数地址相对so文件的偏移量+1
ARM指令下:so文件基址+函数地址相对so文件的偏移量
如何判断是thumb还是arm:
thumd的opcode是两个字节,arm是4个字节
当然了,其实32位的基本是thumb,64位基本是arm
获取指针参数返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var MD5Final = soAddr.add (0x3A78 );Interceptor .attach (MD5Final , { onEnter : function (args ) { this .args1 = args[1 ]; }, onLeave : function (retval ) { console .log (hexdump (this .args1 )); } });
Frida inlineHook获取函数执行结果(hook精确到某一条指令,这个需要找到在so文件中的偏移量) 两个实例
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 var hookAddr = Module .findBaseAddress ("libxiaojianbang.so" ).add (0x1AF4 );Interceptor .attach (hookAddr, { onEnter : function (args ) { console .log ("onEnter x8: " , this .context .x8 .toInt32 ()); console .log ("onEnter x9: " , this .context .x9 .toInt32 ()); }, onLeave : function (retval ) { console .log ("onLeave x0: " , this .context .x0 .toInt32 ()); } }); var hookAddr = Module .findBaseAddress ("libxiaojianbang.so" ).add (0x1FF4 );Interceptor .attach (hookAddr, { onEnter : function (args ) { console .log ("onEnter: " , this .context .x1 ); console .log ("onEnter: " , hexdump (this .context .x1 )); }, onLeave : function (retval ) { } });
Frida修改函数参数与返回值
修改函数参数和返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var addFunc = soAddr.add (0x1ACC );Interceptor .attach (addFunc, { onEnter : function (args ) { args[2 ] = ptr (100 ); console .log (args[2 ].toInt32 ()); }, onLeave : function (retval ) { console .log (retval.toInt32 ()); retval.replace (100 ); } });
需要注意的是args[2] = ptr(100);这一条语句,因为args是NativePointer类型的数组,故只接受NativePinter类型的值,除次方法外,也可以用this.context.x2=100的直接修改寄存器的值方式来修改。
onLeave中新增了replace方法,直接修改返回值
字符串修改
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 127 128 129 var MD5Update = Module .findExportByName ("libxiaojianbang.so" , "_Z9MD5UpdateP7MD5_CTXPhj" );Interceptor .attach (MD5Update , { onEnter : function (args ) { console .log (hexdump (args[1 ])); console .log (args[2 ].toInt32 ()); }, onLeave : function (retval ) { } }); function stringToBytes (str ){ return hexToBytes (stringToHex (str)); } function stringToHex (str ) { return str.split ("" ).map (function (c ) { return ("0" + c.charCodeAt (0 ).toString (16 )).slice (-2 ); }).join ("" ); } function hexToBytes (hex ) { for (var bytes = [], c = 0 ; c < hex.length ; c += 2 ) bytes.push (parseInt (hex.substr (c, 2 ), 16 )); return bytes; } var MD5Update = Module .findExportByName ("libxiaojianbang.so" , "_Z9MD5UpdateP7MD5_CTXPhj" );Interceptor .attach (MD5Update , { onEnter : function (args ) { if (args[1 ].readCString () == "xiaojianbang" ){ let newStr = "xiaojian\0" ; args[1 ].writeByteArray (stringToBytes (newStr)); console .log (hexdump (args[1 ])); args[2 ] = ptr (newStr.length - 1 ); console .log (args[2 ].toInt32 ()); } }, onLeave : function (retval ) { } }); var MD5Update = Module .findExportByName ("libxiaojianbang.so" , "_Z9MD5UpdateP7MD5_CTXPhj" );var strAddr = Module .findBaseAddress ("libxiaojianbang.so" ).add (0x3CFD );Interceptor .attach (MD5Update , { onEnter : function (args ) { if (args[1 ].readCString () == "xiaojianbang" ){ args[1 ] = strAddr; console .log (hexdump (args[1 ])); args[2 ] = ptr (strAddr.readCString ().length ); console .log (args[2 ].toInt32 ()); } }, onLeave : function (retval ) { } }); var MD5Update = Module .findExportByName ("libxiaojianbang.so" , "_Z9MD5UpdateP7MD5_CTXPhj" );Interceptor .attach (MD5Update , { onEnter : function (args ) { this .args0 = args[0 ]; this .args1 = args[1 ]; }, onLeave : function (retval ) { if (this .args1 .readCString () == "xiaojianbang" ){ let newStr = "jianbang" ; this .args0 .add (24 ).writeByteArray (stringToBytes (newStr)); console .log (hexdump (this .args0 .writeInt (newStr.length * 8 ))); } } }); var MD5Update = Module .findExportByName ("libxiaojianbang.so" , "_Z9MD5UpdateP7MD5_CTXPhj" );var newStr = "xiaojianbang&liruyi" ;var newStrAddr = Memory .allocUtf8String (newStr);Interceptor .attach (MD5Update , { onEnter : function (args ) { if (args[1 ].readCString () == "xiaojianbang" ){ args[1 ] = newStrAddr; console .log (hexdump (args[1 ])); args[2 ] = ptr (newStr.length ); console .log (args[2 ].toInt32 ()); } }, onLeave : function (retval ) { } });
readCString是NativePointer的方法,读取C字符串,返回JavaScript的string类型的字符串,可以传入一个参数作为指定读取的字节数,没有的话就读到结束标识符”\0”