刚学 frida,从网上找了一个题库练练手。
Challenge 0x1
逻辑很简单,就是输入一个数然后判断是不是等于 get_random
这个函数的返回值 * 2 + 4,直接对该返回值 hook
1 2 3 4 5 6 7 8 function hook_1 ( ) { var utils = Java .use ("com.ad2001.frida0x1.MainActivity" ); utils.get_random .implementation = function ( ) { var res = this .get_random (); console .log (res); return res; } }
Challenge 0x2 直接主动调用这个函数即可
1 2 3 4 function hook_2 ( ) { var MainActivity = Java .use ("com.ad2001.frida0x2.MainActivity" ); MainActivity .get_flag (4919 ); }
Challenge 0x3
把这个 code
变量的值改成 512 即可
1 2 3 4 function hook_3 ( ) { var Checker = Java .use ("com.ad2001.frida0x3.Checker" ); Checker .code .value = 512 ; }
Challenge 0x4 主动调用非静态态方法,注意这不是一个 MainActivity 或者其他安卓组件。
主动调用有两种写法 Java.choose
和 $new()
。
Java.choose
适用场景:
当你想要对现有的实例进行操作而不是创建新实例时使用。
应用中可能已经存在多个你需要操作的实例时,这种方法尤为有效。
$new()
适用场景:
当你希望主动创建并控制一个对象实例时使用。
当你需要确保使用的实例是全新的,而不是应用中已有的实例时,这种方法更合适。
那么这道题显然应该用 $new()
,因为我们没有创建对应的实例,不能直接拿来用。
1 2 3 4 5 6 function hook_4 ( ) { let check = Java .use ("com.ad2001.frida0x4.Check" ); let check_obj = check.$new(); let res = check_obj.get_flag (1337 ); console .log ("FLAG " + res); }
Challenge 0x5 这道题需要主动调用 MainActivity
中的 flag 方法,但是如果使用 Challenge 0x4 中的脚本就会报错,原因是像 Activity
这样的 Android 组件依赖于应用程序的上下文(context)来正常运行。而在 Frida 中,你可能缺少所需的上下文。Android 的 UI 组件通常需要一个特定的线程,并且该线程需要关联一个 Looper
。如果你在处理 UI 任务,必须确保你是在主线程上并且有一个活跃的 Looper
。Activity
是 Android 应用程序生命周期的一部分。创建 MainActivity
的实例可能需要应用程序处于特定的状态,通过 Frida 来管理整个生命周期可能并不容易。
1 2 3 4 5 6 7 8 9 function hook_5 ( ) { Java .choose ("com.ad2001.frida0x5.MainActivity" , { onMatch :function (instance ) { instance.flag (1337 ); }, onComplete :function ( ) { } }); }
运行以后UI界面就会出现 flag
Challenge 0x6 也是主动调用 MainActivity
中的 get_flag
方法,不同的是参数是一个 Class,我们的思路是先用 Java.use
找到对应的类,然后新建实例,并给实例中的两个成员变量num1,num2
赋值,最后再主动调用函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 function hook_6 ( ) { Java .choose ("com.ad2001.frida0x6.MainActivity" , { onMatch :function (instance ) { let Checker = Java .use ("com.ad2001.frida0x6.Checker" ); let Checker _obj = Checker .$new(); Checker _obj.num1 .value = 1234 ; Checker _obj.num2 .value = 4321 ; instance.get_flag (Checker _obj); }, onComplete :function ( ) { } }); }
Challenge 0x7 也是要 hook flag,这个方法在onCreate方法中被调用了,而且Checker
类里面有构造器了,之前的 choose
方法直接拿来用即可,注意 $new
加参数
1 2 3 4 5 6 7 8 9 10 11 function hook_7 ( ) { Java .choose ("com.ad2001.frida0x7.MainActivity" , { onMatch :function (instance ) { let Checker = Java .use ("com.ad2001.frida0x7.Checker" ); let Checker _obj = Checker .$new(1234 , 1234 ); instance.flag (Checker _obj); }, onComplete :function ( ) { } }); }
Challenge 0x8 逻辑都在 SO 里面
IDA 看一下
可以看到 s2 就是我们要找的 enc 也即是 flag,同时可以看到有日志信息打印了 s2,那直接 logcat就行了
此外我们也可以 hook strcmp 函数,其第二个参数就是 enc,为了避免把所有的 strcmp 执行结果都输出,我们可以对其第一个参数进行限制,因为第一个参数就是我们输入的 text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function hook_8 ( ) { var strcmpAddr = Module .findExportByName ("libfrida0x8.so" ,"strcmp" ); if (strcmpAddr != null ){ Interceptor .attach (strcmpAddr,{ onEnter : function (args ){ let arg0 = Memory .readUtf8String (args[0 ]); let arg1 = Memory .readUtf8String (args[1 ]); if (arg0.includes ("flag" )) { console .log ("Input: " + arg0); console .log ("enc: " + arg1); } }, onLeave : function (retval ){ } }) } }
Challenge 0x9 hook native 方法返回值
1 2 3 4 5 6 7 8 9 10 11 12 function hook_9 ( ) { let checkAddr = Module .findExportByName ("liba0x9.so" ,"Java_com_ad2001_a0x9_MainActivity_check_1flag" ); if (checkAddr != null ){ Interceptor .attach (checkAddr,{ onEnter : function (args ){ }, onLeave : function (retval ){ retval.replace (1337 ); } }) } }
Challenge 0xA JAVA层啥也看不出来,只知道调用了一个 stringFromJNI
但是该函数只是返回了一个字符串,这里 F5 看不到字符串,有点怪,看汇编可以。
注意到还有一个函数 get_flag
,但是没有交叉引用,看来是 native 主动调用,该函数为 void 型,有两个参数,注意两个参数相加需要为 3,查一下日志就可以获得 flag
1 2 3 4 5 6 function hook_A ( ) { var funcAddr = Module .findBaseAddress ("libfrida0xa.so" ).add (0x1DD60 ); var get_flag = new NativeFunction (funcAddr , 'void' , ['long' , 'int' ]); console .log ("ok" ); get_flag (2 , 1 ); }
challenge 0xB 逻辑全在SO层,但是 get_flag
啥都没有
看一下汇编
把 0xDEADBEEF
赋值给了 W8
寄存器,然后又把该值存到了 X29
帧指针的 var_24
位置,然后又赋值给了 W8
,在减去 0x539
,如果结果不是 0 就跳转到 loc_1532C
,否则到 0x15250
,而这个条件永远是到 loc_1532C
,所以 ida 会自动优化掉该函数
用 Ghidra 打开 so 文件,把这个选项的勾给去掉,就能看到 false 后面的逻辑了
看起来是需要通过 inline hook 修改函数的执行流,然后在日志中找 flag
我们直接给 nop 掉
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 function hook_B ( ) { let codeAddr = Module .findBaseAddress ("libfrida0xb.so" ).add (0x15248 ); let instruction = Instruction .parse (codeAddr); console .log ("Before patching:" , instruction.toString ()); Memory .patchCode (codeAddr, 8 , function (code ) { const writer = new Arm64Writer (code, { pc : codeAddr }); writer.putBytes (hexToBytes ("1F2003D51F2003D5" )); writer.flush (); }); instruction = Instruction .parse (codeAddr); console .log ("After patching:" , instruction.toString ()); function hexToBytes (hex ) { let bytes = []; for (let i = 0 ; i < hex.length ; i += 2 ) { bytes.push (parseInt (hex.substr (i, 2 ), 16 )); } return bytes; } }
还有第二种方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var codeAddr = Module .findBaseAddress ("libfrida0xb.so" ).add (0x15248 ); Memory .protect (codeAddr, 0x1000 , "rwx" );var writer = new Arm64Writer (codeAddr); var target = Module .findBaseAddress ("libfrida0xb.so" ).add (0x1524c ); try { writer.putBImm (target); writer.flush (); } finally { writer.dispose (); }