《九阴真经: iOS黑客攻防秘籍》新书发布,干货满满,快来看看吧!

iOS 安全论坛 - 专注于研究 iOS 安全

 找回密码
 立即注册
查看: 3394|回复: 5

【Frida 实战】如何拦截 sub_xxxx 这种函数

[复制链接]

119

主题

580

帖子

2607

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2607
发表于 2020-5-31 21:54:54 | 显示全部楼层 |阅读模式
本文是 Frida 实战系列教程的第三篇,讲解如何拦截 sub_xxxx 这种函数,以及如何替换和调用原始函数。
在 IDA 里看到的 sub_xxxx 这种类型的函数是因为没有符号,所以 IDA 解析时就显示 sub_xxxx,其中 xxxx 是 IDA 读取到的函数地址。在第二篇教程中,我们初步学习了拦截器(Interceptor)的使用,知道了怎么拦截函数,但是如果是自定义函数,去掉符号信息可能就没有名称,我们该怎么拦截呢?下面我们来实际操作,自定义一个静态函数,名称是 add,功能就是将第一个参数和第二个参数相加,返回相加的结果,然后调用 add 函数时第一个参数和第二个参数都传入 1,执行之后会打印出 “1 + 1 = 2",具体代码如下:
  1. static int add(int num1, int num2){

  2.     int sum = num1 + num2;
  3.     return sum;
  4. }

  5. int sum = add(1, 1);
  6. NSLog(@"1 + 1 = %d", sum);
复制代码
编译成功之后,执行下面的命令即可去掉符号。这时使用 IDA 查看可执行文件只会显示 sub_xxxx 这类名称,没有函数的真实名称。
  1. strip -x .../Products/Debug-iphoneos/CrackMe.app/CrackMe
复制代码
接下来开始编写 frida 脚本,get_func_addr 是我们定义的一个函数用来获取函数地址的,需要传入两个参数,第一个参数是模块的名称,第二个参数是在 IDA 里看到的函数地址。查看这个地址的方法,我一般是在 IDA 里将基址设为 0,然后定位到目标函数,此时的地址就是函数地址。得到地址之后就可以使用 Interceptor.attach() 拦截函数。
  1. function get_func_addr(module, offset) {

  2.    var base_addr = Module.findBaseAddress(module);
  3.    console.log("base_addr: " + base_addr);

  4.    console.log(hexdump(ptr(base_addr), {
  5.             length: 16,
  6.             header: true,
  7.             ansi: true
  8.         }))

  9.    var func_addr = base_addr.add(offset);
  10.    if (Process.arch == 'arm')
  11.       return func_addr.add(1);  //如果是32位地址+1
  12.    else
  13.       return func_addr;
  14. }

  15. var func_addr = get_func_addr('CrackMe', 0x6684);
  16. console.log('func_addr: ' + func_addr);

  17. console.log(hexdump(ptr(func_addr), {
  18.             length: 16,
  19.             header: true,
  20.             ansi: true
  21.         }))

  22. Interceptor.attach(ptr(func_addr), {
  23.    onEnter: function(args) {

  24.       console.log("onEnter");
  25.       var num1 = args[0];
  26.       var num2 = args[1];

  27.       console.log("num1: " + num1);
  28.       console.log("num2: " + num2);

  29.    },
  30.    onLeave: function(retval) {

  31.       console.log("onLeave");
  32.       retval.replace(3);  //返回值替换成3
  33.    }
  34. });
复制代码
执行 frida 附加进程并加载 test.js 脚本
  1. frida -U -l test.js CrackMe
复制代码
有时候目标函数是在进程启动后马上就触发,使用附加的方式可能来不及注入脚本,函数就已经触发了。这种情况可以先退出进程,使用 -f 参数直接启动进程,可以在启动时马上注入脚本。需要注意的是进程名称填写的是包名。
  1. frida -U -l test.js -f net.ioshacker.CrackMe
复制代码
此时进入交互模式,然后输入 %resume,让进程继续运行,不然过一会进程会被强制退出,如果不想每次都输入 %resume,就添加 --no-pause 参数,这样在运行进程时不会暂停。
  1. frida -U -l test2.js -f net.ioshacker.CrackMe --no-pause
复制代码
脚本执行后会打印出基址、基址内存中的数据、add 函数地址、add 函数内存中的数据,信息如下:
  1. base_addr: 0x100a14000
  2.             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
  3. 100a14000  cf fa ed fe 0c 00 00 01 00 00 00 00 02 00 00 00  ................
  4. func_addr: 0x100a1a684
  5.             0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
  6. 100a1a684  ff 43 00 d1 e0 0f 00 b9 e1 0b 00 b9 e0 0f 40 b9  .C............@.
复制代码
为什么要将可执行文件的基址数据和 add 函数内存中数据打印出来呢?这是为了验证我们获取到的地址是否正确,比如基址内存中的数据是 cf fa ed fe,说明是 Mach-O 头部,确实是可执行文件。打印出 add 函数内存中的数据也是为了验证结果,查看函数内存中的数据与 IDA 里看到的十六进制机器码对比是否一致,如果不一致说明获取到的函数地址是错误的,如果一致代表正确。IDA 里看到的十六进制机器码数据如下图所示,图中的原本是显示的函数名是 sub_6684,为了演示方便,我没有去掉符号,所以显示是函数原始名称 add。
当 add 函数触发时,在调用 add 函数时会打印出 “1 + 1 = 3”,因为返回值被替换成 3。
Interceptor.attach() 拦截到相应的函数时不能阻止原始函数的执行,比如有些情况,我们不想执行原始函数,或者是判断参数达到某个条件时才执行原始函数,否则不执行,这种情况可以使用 Interceptor.replace() 来替换原始函数,比如下面这段代码可以替换掉 add 函数, NativeFunction 里有三个 int,分别代表是返回值、第一个参数、第二个参数。
  1. var addPtr = get_func_addr('CrackMe', 0x6684);
  2. var add = new NativeFunction(addPtr, 'int', ['int', 'int']);

  3. // 进行替换
  4. Interceptor.replace(add, new NativeCallback(function(num1, num2) {

  5.     if((num1 == 1) && (num2 == 1)){
  6.         console.log("1+1");
  7.     }
  8.     // 调用原函数
  9.     return add(num1, num2);
  10.     //return add(2, 3); //将参数替换掉
  11. }, 'int', ['int', 'int']));
复制代码
有些情况我们需要拦截系统的函数,比如要拦截 open 函数,可以使用下面的代码。
  1. var openPtr = Module.getExportByName(null, 'open');
  2. var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

  3. Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {
  4.   var path = pathPtr.readUtf8String();
  5.   console.log('Opening "' + path + '"');
  6.   var fd = open(pathPtr, flags);
  7.   console.log('Got fd: ' + fd);
  8.   return fd;
  9. }, 'int', ['pointer', 'int']));
复制代码

想了解最新的iOS安全资讯、技术干货请关注 iOS安全论坛(ioshacker.net)微信公众号

回复

使用道具 举报

2

主题

7

帖子

62

积分

注册会员

Rank: 2

积分
62
发表于 2020-6-1 17:04:03 | 显示全部楼层
膜拜大佬!能不能出个ios frida rpc的文章?感谢!
回复

使用道具 举报

119

主题

580

帖子

2607

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2607
 楼主| 发表于 2020-6-2 20:30:33 | 显示全部楼层
周小魚 发表于 2020-6-1 17:04
膜拜大佬!能不能出个ios frida rpc的文章?感谢!

好的,估计下周会更新。
回复

使用道具 举报

2

主题

7

帖子

62

积分

注册会员

Rank: 2

积分
62
发表于 2020-6-2 20:44:03 | 显示全部楼层
exchen 发表于 2020-6-2 20:30
好的,估计下周会更新。

谢谢陈大,期待!
回复

使用道具 举报

119

主题

580

帖子

2607

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2607
 楼主| 发表于 2020-6-7 21:51:35 | 显示全部楼层
周小魚 发表于 2020-6-2 20:44
谢谢陈大,期待!

已更新,第五篇:远程过程调用(RPC)
https://www.ioshacker.net/thread-401-1-1.html
回复

使用道具 举报

0

主题

1

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2021-5-17 16:13:25 | 显示全部楼层
楼主,想问一个问题,像这个自己写的c函数,都是编译器绑定了调用地址的。 而不是想动态库的函数一样进行动态绑定地址的。 那么frida是如何做到替换的?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|iOSHacker

GMT+8, 2021-10-18 08:15 , Processed in 0.019964 second(s), 19 queries .

iOS安全论坛

© 2017-2020 iOS Hacker Inc. 京ICP备17074153号-2

快速回复 返回顶部 返回列表