枚举导入表 通过枚举导入表,可以得到出现在so模块导入表中的函数地址
1 2 3 4 5 6 7 枚举导入表 var improts = Module .enumerateImports ("libencryptlib.so" );for (let i = 0 ; i < improts.length ; i++){ console .log (JSON .stringify (improts[i])); console .log (improts[i].name + " " + improts[i].address ); } 上面的方法得到的是函数在内存中的实际加载地址,不是相对地址
枚举导出表 通过枚举导出表,可以得到出现在so模块导出表中的函数地址
1 2 3 4 5 var exports = Module .enumerateExports ("libencryptlib.so" );for (let i = 0 ; i < exports .length ; i++){ console .log (exports [i].name + " " + exports [i].address ); }
枚举符号表 通过枚举符号表,可以得到出现在符号表中的函数地址
1 2 3 4 5 枚举符号表 var symbols = Module .enumerateSymbols ("libencryptlib.so" );for (let i = 0 ; i < symbols.length ; i++){ console .log (symbols[i].name + " " + symbols[i].address ); }
符号表和导出表的区别
exports
解析的SHT_DYNSYM section
,symbols
解析的是SHT_SYMTAB section
SYMTAB
的范围比DYNSYM
更大,不是so运行必须的,一般会被去掉
枚举模块 通过枚举模块,再枚举模块中的导出表(导出表的函数一般会被其他so文件作为导入函数使用),可以快速找到某个导入函数来自哪个so文件
1 2 3 4 var modules = Process .enumerateModules ();console .log (JSON .stringify (modules[0 ].enumerateExports ()[0 ]));会返回模块加载的基址、模块大小、path等信息
Hook导出函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var funcAddr = Module .findExportByName ("libencryptlib.so" , "_ZN7MD5_CTX11MakePassMD5EPhjS0_" );console .log (funcAddr);Interceptor .attach (funcAddr, { onEnter : function (args ) { console .log ("funcAddr onEnter args[1]: " , hexdump (args[1 ])); console .log ("funcAddr onEnter args[2]: " , args[2 ].toInt32 ()); this .args3 = args[3 ]; }, onLeave : function (retval ) { console .log ("funcAddr onLeave args[3]: " , hexdump (this .args3 )); } });
计算函数地址 在导入表、导出表、符号表里找不到的函数,地址需要自己计算
计算方式:so基址+函数在so中的偏移 [+1] (是否加一得看情况)
基址的获取 需要先得到so的基址,也就是模块基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var module1 = Process .findModuleByName ("libencryptlib.so" );console .log ("module1" , module1.base );var module2 = Process .getModuleByName ("libencryptlib.so" );console .log ("module2" , module2.base );var soAddr = Module .findBaseAddress ("libencryptlib.so" );console .log ("soAddr" , soAddr);var modules = Process .enumerateModules ();for (let i = 0 ; i < modules.length ; i++){ if (modules[i].name == "libencryptlib.so" ){ console .log (modules[i].name + " " + modules[i].base ); } } var module = Process .findModuleByAddress (Module .findBaseAddress ("libencryptlib.so" ));console .log ("module " + module .name + " " + module .base );
函数地址的计算 如果是thumb指令,so基址+函数在so中的偏移 +1
如果是arm指令,so基址+函数在so中的偏移
在Android中,32位so中的函数,基本都是thumb指令,64位的so中的函数,基本都是arm指令
可以通过显示汇编指令对应的opcode bytes来判断,options->general->Number of opcode bytes (non-graph) 设置为4
arm指令为4字节,如果函数中有些指令是2个字节(就是4字节和2字节的指令都有的情况下),那么函数地址计算需要+1
其实加不加一都试一下就行
1 2 3 4 5 var soAddr = Module .findBaseAddress ("libencryptlib.so" );var funcAddr = soAddr.add (0x1FA38 );
Hook任意函数 1 2 3 4 5 6 7 8 9 10 11 12 13 var soAddr = Module .findBaseAddress ("libencryptlib.so" );var funcAddr = soAddr.add (0x1FA38 );Interceptor .attach (funcAddr, { onEnter : function (args ) { console .log ("funcAddr onEnter args[1]: " , hexdump (args[1 ])); console .log ("funcAddr onEnter args[2]: " , args[2 ].toInt32 ()); this .args3 = args[3 ]; }, onLeave : function (retval ) { console .log ("funcAddr onLeave args[3]: " , hexdump (this .args3 )); } );
so层hook其实很简单 就是把上面的代码的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 26 27 28 29 30 31 32 33 34 function print_arg (addr ){ var module = Process .findRangeByAddress (addr); if (module != null ) return hexdump (addr) + "\n" ; return ptr (addr) + "\n" ; } function hook_native_addr (funcPtr, paramsNum ){ var module = Process .findModuleByAddress (funcPtr); Interceptor .attach (funcPtr, { onEnter : function (args ){ this .logs = []; this .params = []; this .logs .push ("call " + module .name + "!" + ptr (funcPtr).sub (module .base ) + "\n" ); for (let i = 0 ; i < paramsNum; i++){ this .params .push (args[i]); this .logs .push ("this.args" + i + " onEnter: " + print_arg (args[i])); } }, onLeave : function (retval ){ for (let i = 0 ; i < paramsNum; i++){ this .logs .push ("this.args" + i + " onLeave: " + print_arg (this .params [i])); } this .logs .push ("retval onLeave: " + print_arg (retval) + "\n" ); console .log (this .logs ); } }); } var soAddr = Module .findBaseAddress ("libencryptlib.so" );var funcAddr = soAddr.add (0x1FA38 );hook_native_addr (funcAddr, 4 );
修改函数参数和返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log ("soAddr" , soAddr);var add = soAddr.add (0x165C );Interceptor .attach (add, { onEnter : function (args ) { args[2 ] = ptr (1000 ); console .log (args[2 ].toInt32 ()); console .log (args[3 ]); console .log (args[4 ]); }, onLeave : function (retval ) { retval.replace (1000 ); console .log (retval.toInt32 ()); } });
修改函数字符串参数 1 2 3 4 5 6 7 8 9 10 11 如下方法: 把char *指向的字符串修改掉,新字符串一般不超出原字符串长度 把so中已有的字符串地址传给函数 修改MD5_CTX结构体中存放数据的内容 构建新的字符串,需要注意构建的字符串变量的作用域 替换函数
以如下函数为例
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 __int64 __fastcall Java_com_xiaojianbang_ndk_NativeHelper_md5 (_JNIEnv *a1, __int64 a2, __int64 a3) { __int64 result; int i; const char *v5; size_t v8; char v9[88 ]; char v10[32 ]; __int128 v11[2 ]; char v12; char v13[16 ]; __int64 v14; v14 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3 , 3 , 13 , 0 , 2 )) + 40 ); v5 = (const char *)jstring2cstr(a1, a3); v12 = 0 ; memset (v11, 0 , sizeof (v11)); MD5Init(v9); v8 = strlen (v5); MD5Update(v9, v5, v8); MD5Final(v9, v13); for ( i = 0 ; i <= 15 ; ++i ) { sub_1848(v10, 32LL , "%02x" , (unsigned __int8)v13[i]); __strncat_chk(v11, v10, 2LL , 33LL ); } _JNIEnv::ReleaseStringUTFChars(a1, a3, v5); result = _JNIEnv::NewStringUTF(a1, (const char *)v11); _ReadStatusReg(ARM64_SYSREG(3 , 3 , 13 , 0 , 2 )); return result; }
MD5Update(v9, v5, v8);
,一个参数是CTX,第二个参数是char*
,第三个参数是长度,hook这个函数
方法一:采取把char *
指向的字符串修改掉,新字符串一般不超出原字符串长度的方法,一般不用,因为这个地址的内容可能会被其他代码去访问
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 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; } function hexToString (hexStr ) { var hex = hexStr.toString (); var str = '' ; for (var i = 0 ; i < hex.length ; i += 2 ) str += String .fromCharCode (parseInt (hex.substr (i, 2 ), 16 )); return str; } var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log ("soAddr" , soAddr);var MD5Update = soAddr.add (0x1D68 );Interceptor .attach (MD5Update , { onEnter : function (args ) { if (args[1 ].readCString () == "xiaojianbang" ){ var newStr = "xiaojian" ; args[1 ].writeByteArray (hexToBytes (stringToHex (newStr) + "00" )); console .log (hexdump (args[1 ])); args[2 ] = ptr (newStr.length ); console .log (args[2 ].toInt32 ()); } }, onLeave : function (retval ) { } });
方法二:把so中已有的字符串地址传给函数以及操作C语言结构体,其实也不怎么用,虽然不会破坏指针所指向的内存数据,但是不一定每次都能找到符合条件的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log ("soAddr" , soAddr);var MD5Update = soAddr.add (0x1D68 );Interceptor .attach (MD5Update , { onEnter : function (args ) { this .args0 = args[0 ]; this .args1 = args[1 ]; if (args[1 ].readCString () == "xiaojianbang" ){ args[1 ] = soAddr.add (0x38A1 ); console .log (hexdump (args[1 ])); args[2 ] = ptr (soAddr.add (0x38A1 ).readCString ().length ); console .log (args[2 ].toInt32 ()); } }, onLeave : function (retval ) { if (this .args1 .readCString () == "xiaojianbang" ){ console .log (hexdump (this .args0 .add (24 ).writeByteArray (stringToBytes ("dadajianbang" )))); } } });
方法三:构建新的字符串,需要注意构建的字符串变量的作用域
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 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log ("soAddr" , soAddr);var MD5Update = soAddr.add (0x1D68 );var newStr = "gdfgdhfgjghjgkhjkh;kl;k;" ;var newStrAddr = Memory .allocUtf8String (newStr);Interceptor .attach (MD5Update , { onEnter : function (args ) { this .args0 = args[0 ]; this .args1 = args[1 ]; 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 ) { if (this .args1 .readCString () == "xiaojianbang" ){ console .log (hexdump (this .args0 )); } } });
hook dlopen 有些函数,只在so文件首次被加载的时候才会调用,比如initarray
或者jnionload
等等,就没办法点击按钮之后再去hook了,这个时候需要通过hook来知道so文件什么时候被加载,dlopen
只需要用到第一个参数也就是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 function hook_dlopen (addr, soName, callback ) { Interceptor .attach (addr, { onEnter : function (args ) { var soPath = args[0 ].readCString (); if (soPath.indexOf (soName) != -1 ) this .hook = true ; }, onLeave : function (retval ) { if (this .hook ) callback (); } }); } function hook_func ( ) { var soAddr = Module .findBaseAddress ("libxiaojianbang.so" ); console .log ("soAddr" , soAddr); var MD5Final = soAddr.add (0x3540 ); Interceptor .attach (MD5Final , { onEnter : function (args ) { this .args1 = args[1 ]; }, onLeave : function (retval ) { console .log (hexdump (this .args1 )); } }); } var dlopen = Module .findExportByName ("libdl.so" , "dlopen" );var android_dlopen_ext = Module .findExportByName ("libdl.so" , "android_dlopen_ext" );hook_dlopen (dlopen, "libxiaojianbang.so" , hook_func);hook_dlopen (android_dlopen_ext, "libxiaojianbang.so" , hook_func);0
内存读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );console .log (hexdump (soAddr.add (0x38A1 )));console .log (soAddr.add (0x38A1 ).readCString ());console .log (soAddr.add (0x38A1 ).writeByteArray (stringToBytes ("0123456789abcdef" )));console .log (soAddr.add (0x38A1 ).readByteArray (33 ));var addr = Memory .alloc (13 );addr.writeByteArray (stringToBytes ("xiaojianbang\0" )); console .log (addr.readByteArray (13 ));var str = Memory .allocUtf8String ("xiaojianbang" );console .log ("Memory.allocUtf8String: " , str.readByteArray (13 ));Memory .protect (ptr (libso.base ), libso.size , 'rwx' );
操作汇编代码 arm汇编 32位下传参 ro-r3寄存器,不够的话栈传参,63位下x0-x7传递,不够的话栈传参(x0-x7其实就是w0-w7,都是寄存器)
STR指令保存数据,STR X1, [SP, #0x20+var_10]
,表示把X1的值给放到SP+0x20+var_10
的位置处
LDR指令保存数据,LDR W9,[SP, #0x20+var_11]
,表示把SP, #0x20+var_11
位置处的值赋值给W9
在线ARM与Hex机器码转换 https://armconverter.com
修改汇编指令 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 function changeCode ( ) { var soAddr = Module .findBaseAddress ("libxiaojianbang.so" ); var codeAddr = soAddr.add (0x1684 ); Memory .protect (codeAddr, 4 , 'rwx' ); codeAddr.writeByteArray (hexToBytes ("0001094B" )); console .log (Instruction .parse (codeAddr).toString ()); new Arm64Writer (soAddr.add (0x167C )).putNop (); console .log (Instruction .parse (soAddr.add (0x167C )).toString ()); var codeAddr = soAddr.add (0x1684 ); Memory .patchCode (codeAddr, 4 , function (code ) { var writer = new Arm64Writer (code, { pc : codeAddr }); writer.putBytes (hexToBytes ("0001094B" )); writer.putBytes (hexToBytes ("FF830091" )); writer.putRet (); writer.flush (); }); }
so层主动调用任意函数 声明函数指针
1 2 3 4 文档:https://frida.re/docs/javascript-api/#NativeFunction 语法:new NativeFunction(address, returnType, argTypes[, abi]) abi在Android端一般不写,returnType是函数返回值,参数类型的话,如果有jstring类型,也是pointer
支持的returnType
和argTypes
1 2 void pointer int uint long ulong char uchar float double int8 uint8 int16 uint16 int32 uint32 int64 uint64 bool size_t ssize_t
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function call_so_func ( ) { var soAddr = Module .findBaseAddress ("libxiaojianbang.so" ); var funAddr = soAddr.add (0x124C ); var jstr2cstr = new NativeFunction (funAddr, 'pointer' , ['pointer' ,'pointer' ]); var env = Java .vm .tryGetEnv (); console .log ("env: " , JSON .stringify (env)); var jstring = env.newStringUtf ("xiaojianbang" ); var retval = jstr2cstr (env, jstring); console .log (retval.readCString ()); } ps :如果上面的env获取不到的话,可以试试把函数代码封装到Java .perform 里面去
hook libc读写文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function writeTxt ( ) { var fopenAddr = Module .findExportByName ("libc.so" , "fopen" ); var fputsAddr = Module .findExportByName ("libc.so" , "fputs" ); var fcloseAddr = Module .findExportByName ("libc.so" , "fclose" ); console .log (fopenAddr, fputsAddr, fcloseAddr); var fopen = new NativeFunction (fopenAddr, 'pointer' , ['pointer' , 'pointer' ]); var fputs = new NativeFunction (fputsAddr, 'int' , ['pointer' , 'pointer' ]); var fclose = new NativeFunction (fcloseAddr, 'int' , ['pointer' ]); var fileName = Memory .allocUtf8String ("/data/data/com.xiaojianbang.app/xiaojianbang.txt" ); var openMode = Memory .allocUtf8String ("w" ); var data = Memory .allocUtf8String ("QQ24358757\n" ); var file = fopen (fileName, openMode); console .log (file); fputs (data, file); fclose (file); }
JNI函数的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 26 27 28 29 30 31 32 33 34 35 36 37 function hook_jni ( ) { var symbols = Process .getModuleByName ("libart.so" ).enumerateSymbols (); var newStringUtf = null ; for (let i = 0 ; i < symbols.length ; i++) { var symbol = symbols[i]; if (symbol.name .indexOf ("CheckJNI" ) == -1 && symbol.name .indexOf ("NewStringUTF" ) != -1 ){ console .log (symbol.name , symbol.address ); newStringUtf = symbol.address ; } } Interceptor .attach (newStringUtf, { onEnter : function (args ) { console .log ("newStringUtf args: " , args[1 ].readCString ()); }, onLeave : function (retval ) { console .log ("newStringUtf retval: " , retval); } }); } function hook_jni2 ( ) { var envAddr = Java .vm .tryGetEnv ().handle .readPointer (); var funAddr = envAddr.add (48 ).readPointer (); Interceptor .attach (funAddr, { onEnter : function (args ) { console .log ("FindClass args: " , args[1 ].readCString ()); }, onLeave : function (retval ) { console .log ("FindClass retval: " , retval); } }); }
主动调用jni函数 可以使用封装好的API调用jni函数,比声明NativeFunction
更简单一点
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 function call_jni ( ) { var retval = Java .vm .tryGetEnv ().newStringUtf ("xiaojianbang" ); console .log (retval); console .log (Java .vm .tryGetEnv ().getStringUtfChars (retval).readCString ()); } function call_jni2 ( ) { var symbols = Process .getModuleByName ("libart.so" ).enumerateSymbols (); var newStringUtf = null ; for (let i = 0 ; i < symbols.length ; i++) { var symbol = symbols[i]; if (symbol.name .indexOf ("CheckJNI" ) == -1 && symbol.name .indexOf ("NewStringUTF" ) != -1 ){ console .log (symbol.name , symbol.address ); newStringUtf = symbol.address ; } } var newStringUtf_func = new NativeFunction (newStringUtf, 'pointer' , ['pointer' , 'pointer' ]); var jstring = newStringUtf_func (Java .vm .tryGetEnv ().handle , Memory .allocUtf8String ("xiaojianbang" )); console .log (jstring); var envAddr = Java .vm .tryGetEnv ().handle .readPointer (); var GetStringUTFChars = envAddr.add (0x548 ).readPointer (); var GetStringUTFChars _func = new NativeFunction (GetStringUTFChars , 'pointer' , ['pointer' , 'pointer' , 'pointer' ]); var cstr = GetStringUTFChars _func(Java .vm .tryGetEnv ().handle , jstring, ptr (0 )); console .log (cstr.readCString ()); }
so层打印函数栈 1 console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ('\n' ) + '\n' );
二级指针的构造 函数
1 2 3 void xiugaiStr (char ** str) { strcat (*str, "testtesttest" ); }
demo
1 2 3 4 5 6 7 8 9 10 11 function call_func ( ) { var soAddr = Module .findBaseAddress ("libxiaojianbang.so" ); var xiugaiStr = soAddr.add (0x17D0 ); var xiugaiStr_func = new NativeFunction (xiugaiStr, 'int64' , ['pointer' ]); var strAddr = Memory .allocUtf8String ("dajianbang" ); console .log (hexdump (strAddr)); var finalAddr = Memory .alloc (8 ).writePointer (strAddr); xiugaiStr_func (finalAddr); console .log (hexdump (strAddr)); }
如何确认native函数在哪个so文件 静态分析查看静态代码块中加载的so,并不靠谱
有可能native
函数声明在一个类中,so加载(System.LoadLibrary
函数)在其他的类中,还可以在另外的类中,一次性加载所有的so
或者可以hook系统函数来得到绑定的native函数地址,然后再得到so地址
jni函数动态注册的话,可以hook RegisterNatives
,得到methods
结构体,从而找到函数的地址、函数名等信息,然后定位出模块
jni函数静态注册的话,可以hook dlsym
,通过dlsym
去找对应的函数,静态注册的函数名是不能混淆的,不然app找不到函数
有的app会在某个类中一次性把所有so加载了,而且如果是动态注册,JNI_Onload
函数可能也是加密的,字符串也是加密的,这个时候静态分析就很难
快速定位jni静态注册函数 dlsym第一个参数是dlopen的返回值(用于标识打开的共享库),第二个参数是char*
,即symbol
,通常是函数名,返回值是函数的地址,通过函数的地址判断地址处于哪个so文件的空间中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function hook_dlsym ( ) { var dlsymAddr = Module .findExportByName ("libdl.so" , "dlsym" ); console .log (dlsymAddr); Interceptor .attach (dlsymAddr, { onEnter : function (args ) { this .args1 = args[1 ]; }, onLeave : function (retval ) { var module = Process .findModuleByAddress (retval); if (module == null ) return ; console .log (this .args1 .readCString (), module .name , retval, retval.sub (module .base )); } }); } hook_dlsym ();
快速定位jni动态注册函数 RegisterNatives
是jni函数,首先要找到RegisterNatives
的地址,RegisterNatives
的第三个参数是JNINativeMethod
数组的指针
1 2 3 4 5 typedef struct { const char * name; const char * signature; void * fnPtr; } JNINativeMethod;
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 function hook_RegisterNatives ( ) { var symbols = Process .getModuleByName ("libart.so" ).enumerateSymbols (); var RegisterNatives _addr = null ; for (let i = 0 ; i < symbols.length ; i++) { var symbol = symbols[i]; if (symbol.name .indexOf ("CheckJNI" ) == -1 && symbol.name .indexOf ("RegisterNatives" ) != -1 ) { RegisterNatives _addr = symbol.address ; } } console .log ("RegisterNatives_addr: " , RegisterNatives _addr); Interceptor .attach (RegisterNatives _addr, { onEnter : function (args ) { var env = Java .vm .tryGetEnv (); var className = env.getClassName (args[1 ]); var methodCount = args[3 ].toInt32 (); for (let i = 0 ; i < methodCount; i++) { var methodName = args[2 ].add (Process .pointerSize * 3 * i).readPointer ().readCString (); var signature = args[2 ].add (Process .pointerSize * 3 * i).add (Process .pointerSize ).readPointer ().readCString (); var fnPtr = args[2 ].add (Process .pointerSize * 3 * i).add (Process .pointerSize * 2 ).readPointer (); var module = Process .findModuleByAddress (fnPtr); console .log (className, methodName, signature, fnPtr, module .name , fnPtr.sub (module .base )); } }, onLeave : function (retval ) { } }); } hook_RegisterNatives ();
建议使用--no-pause
启动frida来hook更多函数
inline hook 想hook某一行汇编代码,其实也是基址+偏移的方式去hook,和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 26 27 function inlineHook ( ) { var nativePointer = Module .findBaseAddress ("libxiaojianbang.so" ); var hookAddr = nativePointer.add (0x17BC ); Interceptor .attach (hookAddr, { onEnter : function (args ) { console .log ("onEnter: " , this .context .x8 ); }, onLeave : function (retval ) { console .log ("onLeave: " , this .context .x8 .toInt32 ()); console .log (this .context .x8 & 7 ); } }); var nativePointer = Module .findBaseAddress ("libxiaojianbang.so" ); var hookAddr = nativePointer.add (0x1B70 ); Interceptor .attach (hookAddr, { onEnter : function (args ) { console .log ("onEnter: " , this .context .x1 ); console .log ("onEnter: " , hexdump (this .context .x1 )); }, onLeave : function (retval ) { } }); }
inline hook在32位so下并不稳定,可能会崩溃,32位下的话要么多试几次或者换unidbg
打印so层函数栈 env和env.handle
这两个在一定程度上可以通用,或者可以说会完成自动转换,使用Frida封装的JNI相关API,必须使用env
,当参数需要JNIEnv*
时,可以使用env
和env.handle
1 2 3 var env = Java.vm.tryGetEnv(); // handle可以理解为env的内存地址 env.handle;
打印so层函数栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var updateAddr = soAddr.add (0x21B0 );Interceptor .attach (updateAddr, { onEnter : function (args ) { console .log ("==================================================" ); console .log (soAddr); console .log (Thread .backtrace (this .context , Backtracer .ACCURATE ).map (function (value ) { var symbol = DebugSymbol .fromAddress (value); console .log (symbol.moduleName ); if (symbol.moduleName === "libxiaojianbang.so" ) { return symbol + " offset: " + value.sub (soAddr); } return symbol; }).join ('\n' )); }, onLeave : function (retval ) { } });
注意打印出的函数地址是实际执行的汇编代码的下一行指令的地址
替换函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var soAddr = Module .findBaseAddress ("libxiaojianbang.so" );var addAddr = soAddr.add (0x1A0C );Interceptor .replace (addAddr, new NativeCallback (function ( ) { console .log (100 ); }, 'void' , [])); var add = new NativeFunction (addAddr, 'int' , ['pointer' ,'pointer' , 'int' ,'int' ,'int' ]);Interceptor .replace (addAddr, new NativeCallback (function (a, b, c, d,e ) { console .log (a, b, c, d, e); var oldResult = add (a, b, c, d, e); console .log (oldResult); return 100 ; }, 'int' , ['pointer' , 'pointer' , 'int' ,'int' ,'int' ]));
hexdump方法 打印指定地址的内存空间
1 2 3 4 5 console .log (hexdump (args[1 ], {offset : 6 , length : args[2 ].toUInt32 (), header : false }));
Frida trace tracenatives是IDA中的插件,运行之后会生成一个txt,使用frida trace和该txt结合可以打印出函数栈
加载txt的时候会生成或者加载一些以偏移命名的js文件,可以改这些js文件,让js文件打印出我们想要的结果,比如加上hexdump
这些
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 { onEnter (log, args, state ) { var soAddr = Module .findBaseAddress ("libxiaojianbang.so" ); var funcAddr = soAddr.add (0x21b0 ); log (DebugSymbol .fromAddress (funcAddr).name , hexdump (args[1 ], {length : 12 , header : false })); }, onLeave (log, retval, state ) { } }
内存读写监控 不推荐这种方式,一般用unidbg去监控,了解即可
逻辑就是在首次加载目标so文件的时候调用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 26 27 28 29 30 31 32 function hook_dlopen (addr, soName, callback ) { Interceptor .attach (addr, { onEnter : function (args ) { var soPath = args[0 ].readCString (); if (soPath.indexOf (soName) != -1 ) this .hook = true ; }, onLeave : function (retval ) { if (this .hook ) {callback ()} } }); } var dlopen = Module .findExportByName ("libdl.so" , "dlopen" );var android_dlopen_ext = Module .findExportByName ("libdl.so" , "android_dlopen_ext" );hook_dlopen (dlopen, "libxiaojianbang.so" , set_read_write_break);hook_dlopen (android_dlopen_ext, "libxiaojianbang.so" , set_read_write_break);function set_read_write_break ( ){ Process .setExceptionHandler (function (details ) { console .log (JSON .stringify (details, null , 2 )); console .log ("lr" , DebugSymbol .fromAddress (details.context .lr )); console .log ("pc" , DebugSymbol .fromAddress (details.context .pc )); Memory .protect (details.memory .address , Process .pointerSize , 'rwx' ); console .log (Thread .backtrace (details.context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ('\n' ) + '\n' ); return true ; }); var addr = Module .findBaseAddress ("libxiaojianbang.so" ).add (0x3CFD ); Memory .protect (addr, 8 , '---' ); }