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 *静态注册 
前置了解: 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: