Frida 练习——Frida-labs 这个项目可以从看 frida 相关视频过渡到实战,没有 ctf 这么难,感觉不看 frida 相关视频直接打这个练习也行。
项目地址: https://github.com/DERE-ad2001/Frida-Labs/tree/main
frida_0x1 从应用的界面看这是要你输入一个数字
查看源代码,很容易的看出这是要你找到这个随机数 i
下面的声明的 obj 是要在 app 界面中输入的数字
然后把存有随机数 i 与输入的数字 obj 传到 check 函数中进行检查
在 check 函数中发现输入的 obj 也就是 check 函数声明的 i2
判断条件是(i*2)+4==i2
所以我们要输入的 obj 应该是随机数 i*2+4
这里有两个解法:
解法一:hook get_random 这里我们可以直接 hook 掉 get_random 来查看 get_random 生成的随机数
也可以直接返回一个特定值然后按照计算式输入,但是随机数是在程序初始化生成的,所以要在程序初始化时进行注入。
1 frida -U -f com.ad2001.frida0x1 -l .\hook.js
查看 get_random exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function hookTest1 ( ) { var utils = Java .use ("com.ad2001.frida0x1.MainActivity" ); utils.get_random .implementation = function ( ) { var ret = this .get_random (); console .log ("get_random: " , ret); return ret; }; } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
返回特定数字 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 function hookTest1 ( ) { var utils = Java .use ("com.ad2001.frida0x1.MainActivity" ); utils.get_random .implementation = function ( ) { return 0 ; }; } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
然后输入 4 即可进入下一个界面
解法二:hook check check 函数是在程序初始化之后才运行的,所以不必在初始化的时候进行 hook
1 frida -U "Frida 0x1" -l ./hook.js
这样 hook 之后随便输入一串数字之后就可以进到下一个页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function hookTest1 ( ) { var utils = Java .use ("com.ad2001.frida0x1.MainActivity" ); utils.check .overload ("int" , "int" ).implementation = function (a, b ) { this .check (a, a * 2 + 4 ); return ; }; } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
frida_0x2 感觉这个题没有上一个难,hook 静态方法。
打开软件,发现只有一个“HOOK ME!”
查看源码
发现 main 函数下面有一个 get_flag 函数
这串代码是把下面的经过 base64 加密过的 aes 密文进行解密
而且这是个静态函数,有静态标识符,直接按照静态函数 hook 模板 hook 就行
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 function hookTest1 ( ) { Java .perform (function ( ) { var ClassName = Java .use ("com.ad2001.frida0x2.MainActivity" ); ClassName .get_flag (4919 ); }); } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
frida_0x3 这题也是 hook 一个静态方法,但是这次的静态方法不在 Mainactivity
而是在 Checker 函数中
先打开这个 App 界面,如下图,点击完 click me 会提示 try again
查看源码
发现判断条件是 Checker.code == 512
跟进 Checker.code
发现只是个静态函数,并且有个 increase 函数每次加 2,所以我们只需要用让这个 increase 函数循环 256 次就能得到 flag
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function hookTest1 ( ) { Java .perform (function ( ) { var ClassName = Java .use ("com.ad2001.frida0x3.Checker" ); for (var i = 0 ; i < 256 ; i++) { ClassName .increase (); } }); } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
frida_0x4 这道题是要 hook 一个没有被在 MainActivity 调用的类中的普通方法、
查看 MainActivity 发现其中并没有任何与 flag 有关的类或者方法
查看 Check 类,发现加密在 Check 类中的 get_flag 方法
但是 MainActivity 并没有调用过 Check,
所以我们要先创建实例,然后再进行调用 hook
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function hookTest1 ( ) { Java .perform (function ( ) { var a = Java .use ("com.ad2001.frida0x4.Check" ); var check = a.$new(); var flag = check.get_flag (1337 ); console .log ("flag: " + flag); }); } function main ( ) { Java .perform (function ( ) { hookTest1 (); }); } setImmediate (main);
frida_0x5 这个题是要 hook 在 MainActivity 中的普通方法
因为是在 MainActivity 之中所以就没有必要手动进行实例化
所以我们只需要获得这个实例就可以了
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function hookTest2 ( ) { var ret = null ; Java .perform (function ( ) { Java .choose ("com.ad2001.frida0x5.MainActivity" ,{ onMatch :function (instance ){ ret=instance.flag (1337 ); }, onComplete :function ( ){ } }); }) } function main ( ) { Java .perform (function ( ){ hookTest2 (); }); } setImmediate (main);frida -U -F -l .\hook.js
frida_0x6 这道是 hook 一个在 MainActivity 中的普通方法
但是这个普通方法中的参数是一个未被实例化的类中的两个参数
要 hook 这个方法的话只需要调用方法之前将 Check 类进行实例化以及对里面的变量进行赋值就可以了
exp: 这里有个注意点,因为 check.num1 它只是一个 frida 代理的一个字段对象,直接进行赋值是赋不了的,
要给其所代理的进行赋值是要用 check.num1.value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function hookTest2 ( ) { var ret = null ; Java .perform (function ( ) { Java .choose ("com.ad2001.frida0x6.MainActivity" , { onMatch : function (instance ) { var checker = Java .use ("com.ad2001.frida0x6.Checker" ); var check = checker.$new(); check.num1 .value = 1234 ; check.num2 .value = 4321 ; instance.get_flag (check); }, onComplete : function ( ) { }, }); }); }
frida_0x7 这题和上面 0x6 差不多,
先是查看 MainActivity,,发现 Check 已经被实例化了
但是这里可以有两种解法
解一: 和上体一样直接当作普通类进行 hook,但是这里有构造方法
需要按照构造方法的传参进行 hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function hookTest2 ( ) { var ret = null ; Java .perform (function ( ) { Java .choose ("com.ad2001.frida0x7.MainActivity" ,{ onMatch : function (instance ) { var checker = Java .use ("com.ad2001.frida0x7.Checker" ); var a = 513 ; var b = 513 ; var check = checker.$new(a,b); instance.flag (check); }, onComplete :function ( ){ } }); }) } frida -U -F -l .\hook.js
解二:hook 构造方法
因为 Check 类中声明了构造方法,所以我们可以直接 hook 构造方法
1 2 3 4 5 6 7 function hookTest3 ( ){ var utils = Java .use ("com.ad2001.frida0x7.Checker" ); utils.$init .overload ('int' ,'int' ).implementation = function (a,b ){ this .$init(513 ,513 ); } } frida -U -f com.ad2001 .frida0x7 -l .\frida0x7.js
这里的 overload(‘int’,’int’)可以省略,但是为了重构的准确性还是加上为好
function(a,b),这里对应着原来的参数个数
frida_0x8 这道题是要 hook *静态注册 *的 JNI native 层的函数
前置了解: https://www.52pojie.cn/thread-1840174-1-1.html
要先了解一下导入表,导出表,以及 so 的基址的获取方法
打印导入表 1 2 3 4 5 6 7 8 9 10 var imports = Module .enumerateImports ("libfrida0x8.so" );console .log ("导入表:\n" , JSON .stringify (imports, null , 2 ));
打印导出表 1 2 3 4 5 6 7 var exports = Module .enumerateExports ("libfrida0x8.so" );console .log ("导出表:\n" , JSON .stringify (exports , null , 2 ));
打印.so 基址 1 2 var baseAddr = Module .getBaseAddress ("libfrida0x8.so" );console .log ("Base Address of libfrida0x8.so:" , baseAddr);
寻找打印指定 so 中 export 库中的函数地址 这里有两个 api 方法
Module.findExportByName(lib,exportName)
从 lib 中寻找指定函数的地址,若不知道 lib 名称可以传 NULL,找不到函数会抛出异常
Module.getExportByName(lib,exportName)
功能与 getExportByName 相同,找不到返回 null
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 cmpstr_addr = Module .getExportByName ( "libfrida0x8.so" , "Java_com_ad2001_frida0x8_MainActivity_cmpstr" ); var strcmp_addr = Module .getExportByName ("libc.so" , "strcmp" );var strcmp_null_addr = Module .getExportByName (null , "strcmp" );console .log ("\ncmpstr_addr:" , cmpstr_addr);console .log ("strcmp_addr:" , strcmp_addr);console .log ("strcmp_null_addr:" , strcmp_null_addr);var Closure _addr = Module .getExportByName (null , "Closure" );console .log ("Closure_addr:" , Closure _addr);var strcmp_ad = Module .findExportByName ("libc.so" , "strcmp" );var strcmp_null_ad = Module .findExportByName (null , "strcmp" );console .log ("\ncmpstr_ad:" , strcmp_ad);console .log ("strcmp_null_ad:" , strcmp_null_ad);var Closure _ad = Module .findExportByName ("libfrida0x8.so" , "Closure" );console .log ("Closure_addr:" , Closure _ad);
解题过程: 看 MainActivity 发现有一个 native 层的函数 cmpstr
并且 flag 的判断条件也是这个函数
所以我们要在函数被调用前进行 hook
用 ida 查看 frida0x8.so
找到 Java_com_ad2001_frida0x8_MainActivity_cmpstr
这个命名规则和 java 层的包名是一样的,只要把包名的.换成_就可以了
根据伪代码,我们可以清晰的看到我们实际上要 hook 的是 cmpstr 中的 strcmp 函数,
因此这道题的 hook 目标就是 strcmp
其中参数 s1 是我们输入的字符串,s2 是 flag
我们可以查看先看.so 的导入表然后再获取 strcmp 的地址进行 hook
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function hookTest ( ) { Java .perform (function ( ) { var imp = Module .enumerateImports ("libfrida0x8.so" ); console .log ("导入表:\n" , JSON .stringify (imp, null , 2 )); var strcmp_add = Module .getExportByName ("libc.so" , "strcmp" ); console .log ("strcmp_addr:" , strcmp_add); var out_put; Interceptor .attach (strcmp_add, { onEnter : function (args ) { var arg0 = Memory .readUtf8String (args[0 ]); var arg1 = Memory .readUtf8String (args[1 ]); out_put = arg1; if (arg0.includes ("tttt" )) { console .log ("args0:" , arg1); } }, }); }); }
frida 执行之后需要在 app 输入框中输入包含 tttt 的文本
然后点击提交,这样 s2 包含的 flag 就会打印在终端之中
frida_0x9 和 0x8 一样也是要 hook native 层的函数,
但是这个题的 hook 点是在函数执行后
查看.so 中的函数发现只是的那会一个 1
那么我们只要使得函数结束后返回 1337 就可以了
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function hookTest ( ) { Java .perform (function ( ) { var imp = Module .enumerateImports ("liba0x9.so" ); console .log ("导入表:\n" , JSON .stringify (imp, null , 2 )); var expr = Module .enumerateExports ("liba0x9.so" ); console .log ("导出表:\n" , JSON .stringify (expr, null , 2 )); var check_1flag_add = Module .getExportByName ( "liba0x9.so" , "Java_com_ad2001_a0x9_MainActivity_check_1flag" ); console .log ("check_1flag_addr:" , check_1flag_add); Interceptor .attach (check_1flag_add, { onEnter : function (args ) {}, onLeave : function (retval ) { retval.replace (1337 ); }, }); }); }
frida_0xA 这一关也是 hook 一个静态注册的 native 层
但是和上一关不一样的是这关的获得 flag 的函数没有被 java 层的 MainActivity 调用
需要在 frida 之中进行 native 层的声明实例化。
我们发现了声明了 native 层的 stringFromJNI 方法
查看 native 层的 stringFromJNI 函数
没啥有用的,看下函数导出表,搜了一下 flag,发现一个 get_flag 的函数
跟进查看,发现是一个 flag 的自解密程序
最后在日志中进行打印(__android_log_print)
这里要先知道一下__android_log_print 这个函数,
它是 Android NDK 提供的日志函数,它的作用类似 printf
,但输出到 adb logcat
。
ida 中这句具体函数中的各参数的意思是
3LL
→ **日志级别 ANDROID_LOG_DEBUG
**(等同于 ANDROID_LOG_DEBUG = 3
)。
"FLAG"
→ 日志 Tag ,用于 logcat
过滤日志。
"Decrypted Flag: %s"
→ 格式化字符串 ,输出 "Decrypted Flag: v4"
。
v4
→ 要打印的字符串 (char*
类型)。
所以在 hook 完这个函数之后要在日志中过滤查找 flag
但是直接查找这个函数名的地址时会发现找不到
回到 ida 的汇编界面才发现这个函数的名字不是 get_flag
而是_Z8get_flagii
这样就能找到 get_flag 的地址了
然后需要进行实例化调用,之后进行 hook,最后在日志中查找
exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function hookTest ( ) { Java .perform (function ( ) { var get_flag_add = Module .getExportByName ( "libfrida0xa.so" , "_Z8get_flagii" ); console .log ("get_flag_add_addr:" , get_flag_add); var get_flag_ptr = new NativePointer (get_flag_add); const get_flag = new NativeFunction (get_flag_ptr, "void" , ["int" , "int" ]); get_flag (1 , 2 ); console .log (get_flag (1 , 2 )); }); }
frida_0xB hook
静态的so
层,但是需要用Frida
的硬编码工具修改硬编码
查看java
层主类,发现调用了so
层的getflag
函数
查看so
层的getflag
函数,发现并不能正常反编译
原来是 rax 这里有个比较,但是赋值和比较值不一样所以且恒为 0,所以 ida 会自动不编译
把 jnz 改成 jz 之后就可以正常查看伪代码了,
伪代码就是一个简单异或
综上这里有两个方法
方法一:修改硬编码,hook
改jnz
为jz
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 hookTest7 ( ) { Java .perform (function ( ) { var libc_base = Module .getBaseAddress ("libfrida0xb.so" ); var jnz = libc_base.add (0x170ce ); console .log ("libc_base : " , libc_base); console .log ("jnz : " , jnz); Memory .protect (jnz, 0x1000 , "rwx" ); var writer = new X86Writer (jnz); var size = 0x30 ; var instructionBytes = Memory .readByteArray (jnz, size); console .log ("instructionBytes :\n" , instructionBytes); var instructions = Instruction .parse (jnz, instructionBytes); console .log ("instructions :" , instructions); try { writer.putBytes ([0x0f , 0x84 ]); writer.flush (); } finally { writer.dispose (); } var instructionBytes = Memory .readByteArray (jnz, size); console .log ("new instructionBytes :\n" , instructionBytes); var instructions = Instruction .parse (jnz, instructionBytes); console .log ("new instructions :" , instructions); }); }
方法二:修改上面的比较,把长的数字改成短的,使得比较结果恒为 1 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 Java .perform (function ( ) { var libc_base = Module .getBaseAddress ("libfrida0xb.so" ); var DEADBEEF = libc_base.add (0x170c0 ); console .log ("libc_base : " , libc_base); console .log ("DEADBEEF : " , DEADBEEF ); Memory .protect (DEADBEEF , 0x1000 , "rwx" ); var writer = new X86Writer (DEADBEEF ); var size = 0x30 ; var instructionBytes = Memory .readByteArray (DEADBEEF , size); console .log ("instructionBytes :" , instructionBytes); var instructions = Instruction .parse (DEADBEEF , instructionBytes); console .log ("instructions :" , instructions); try { writer.putBytes ([0xc7 , 0x45 , 0xdc , 0x39 , 0x05 , 0x00 , 0x00 ]); writer.flush (); } finally { writer.dispose (); } var instructionBytes = Memory .readByteArray (DEADBEEF , size); console .log ("new instructionBytes :" , instructionBytes); var instructions = Instruction .parse (DEADBEEF , instructionBytes); console .log ("new instructions :" , instructions); });
exp: