记录一次问题调试过程 -- ptrace + libunwind + malloc hook

整体方案参考自:

https://xz.aliyun.com/t/6883#reply-14067

当前任务是监控malloc等应用层面的调用,获取其输入参数、返回值、调用栈等情况,之前已经完成了参数和返回值的测试;本周主要研究调用栈的获取;

之前测试过通过libunwind来解析调用栈,结果是有时候注入成功,有时候注入失败,与之对比的是不用libunwind时,连续测试超过100次均成功;

  1. 开始怀疑是因为静态链接了libunwind后,需要注入的so过大,导致注入失败,所以想换一种方式,调研了gperftools的当前版本,发现它用了google自己的absl库来解析调用栈的,本想通过移植该模块的方式来达到目的,但经过近2天的代码走读,发现工作量太庞大,主要是absl又用到了其他的模块,牵扯太多,只好暂时放弃该方案;
  2. 回到通过libunwind来解析调用栈的方案上:

怀疑是因为静态链接了libunwind的方式导致注入的so太大,造成最终的注入失败,所以考虑用动态链接的方式来生成注入的so,首先把libunwind由原来的静态编译,修改为动态编译,然后链接到注入的so(hook_so.so),失败,原因不明,现象:编译hook_so.so成功,注入时失败,网上查阅相关资料,尝试设置各种编译参数,一直失败,先放弃;

 

这是之前的测试记录;

A。说明一下背景:有3个程序,分别是hook3.c hook_so.c target.c,完成的工作就是hook3把hook_so注入到target中,libunwind是链接在hook_so.c上;用之前没有连接libunwind的hook_so来执行注入(在前后的有和没有连接libunwind的情况下,hook3和target不变,除非添加部分测试代码),一直注入成功——这导致我把问题可能原因定位在hook_so上,这是很严重的错误——查阅libunwind和动态/静态编译的相关资料,一直分析不出原因;

后来与西罗讨论,他建议先测试一下,看看通过显式调用dlopen的方式来加载链接了libunwind的hook_so.so到target,看看是否成功;随后写了一个测试程序,发现调用成功;他的建议让我开始回顾我原来代码中的注入:在当前方案中,是不能直接调用dlopen的,而是通过调用__libc_dlopen_mode来实现注入,但两者本质上并无区别;只不过这里的注入其实有2步:注入和查询注入后的hook函数的symbol;我原来以为是注入失败,但现在来看可能是查询symbol失败了;

在又一次失败后,进行查询,cat /proc/pid/maps 可以看到hook_so.so已加载成功

 

B。既然注入成功,那就是查询失败了,但是查询为何失败?查询是通过目标程序的link_map链表进行遍历得到的,网上搜了一下dlopen的资料,跟着源码大概看了一下,发现在加载新的so后,会link到link_map中的;首先确定symbol有没有真正导入,因为dlopen加载时,有不同的设置选项,可以不是马上进行解析,但是查看代码发现设置的是马上进行解析,也就是说代码设置是没有问题的;查阅资料发现gdb有info functions的命令,可以看到调试进程的所有函数符号,马上测试,发现的确是有函数符号的:

 

想去搜索一下gdb里是如何实现info functions的,失败;

C。现在问题变成了为何遍历link_map时,查找不到要替换的函数(MyMalloc)呢?回头走读实现代码,没发现什么问题;陷入迷茫,停顿了半天来反思;想着实在没发现问题,那就看看能不能先用其他方式解决这个问题,联想到之前的测试程序:显式调用dlopen,那么再加一步,调用dlsym获取MyMalloc的地址来实现MyMalloc的调用;测试,成功;再进一步测试,通过时间差的方式,来测试,hook3查询不到MyMalloc时,在target里调用dlsym来查询并调用(只是测试,实际中target就等于是客户程序,不能进行任何修改),成功:

 

D。由此想到一个方法:在hook3里通过寄存器设置的方式来使target调用dlsym,然后得到的函数返回值,不就是MyMalloc的地址了?也就达到目的了;修改代码进行测试,失败,发现rax存储的函数返回值跟测试打印出来的MyMalloc的正确地址不一致:

这里简单说一下利用ptrace强行调用dlsym并获取其返回值的实现:通过ptrace读取并保存目标进程的现场,然后设置相关寄存器(包括rip、rdi、rsi、rsp等),使目标进程执行dlsym,在执行完成后,利用汇编基本操作原理,使其产生异常,这时候dlsym的返回值是保存在rax寄存器中的,通过ptrace读取即可;

从代码上来看,整体逻辑没有问题,那是否返回值不是在rax中?写代码遍历所有寄存器,没有在寄存器中发现MyMalloc的地址:

但是通过另外的测试发现,调用__libc_dlopen_modes时得到的rax里,就是期望的hook_so.so的handle:

 

说明函数返回值的确是在rax中,并且代码log中的读取rax的方式也是没有问题的。

E。回去走读代码,跟踪log,突然发现rip的值有问题,按照预期,执行完dlsym后,rip应该是手动设置的0x666,再回去看代码,发现当时为了方便,输入给dlsym的第一个参数是NULL,而不是hook_so.so的句柄,因此dlsym会进行全局搜索,可能会触发异常,导致ptrace提前捕获到而提前回到hook3的处理流程上,修改代码重新测试,发现还是失败;难道dlsym调用的时候有什么特别的设置?写了测试代码,通过gdb查看调用细节:

没有发现特别的地方

F。由于hook3对target进行了ptrace attach的操作,所以这时候不能再对target进行gdb跟踪,只能回去看源码,通过上面的测试已可以确定就是自己添加的操作dlsym的代码有问题;反复查看,发现第二个入参,也就是函数名的传递有问题,因为要替换的函数名MyMalloc正好是8个字节,而在64位程序里,ptrace进行写入时就是以8字节为单位进行写入的,原来的入参是strlen(targetNmae),没有把字符串结束符传入,导致参数解析错误;修改后正常,整个功能实现;

 

总结:

1.分析问题时,最好定位到具体哪一步出了问题,然后再根据问题现象进行推断,本次分析过程中,因为前面把问题定位在注入失败(实际上是解析失败)而浪费了1天多;

2.个人思考陷入困境时,多跟同事请教和交流,在此感谢同事给我的建议,谢谢他跟我走读代码,调试定位;

3.结果不符合预期时,需要多从细节上来进行查看,比如上面的rip错误问题,需要细心,另外也需要多敦实基础,增加代码敏感性;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lqw198421

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值