首页
社区
课程
招聘
[原创]零长度hook续
发表于: 2024-5-7 23:33 3796

[原创]零长度hook续

2024-5-7 23:33
3796

引言

这是一个基于异常hook的工具,可以做到指令级别的jhook,对代码所做的修改仅仅是一条指令,绝大多数的inline hook,都会污染超过一条指令的代码,在arm 以及arm64中是两条,thumb就不确定了,那两条通常是ldr pc,[nex_addr],紧接着第二条存的是地址,改地址保存shellcode,负责处理接管控制权。这种hook方式无法处理短函数(只有一条指令的函数),也就是说,无法做到指令级别的操作。做到指令级别hook有什么用呢,当初考虑做这个工具,主要是处于以下两个用图,一个是分析某个函数时,对每个基本块打桩,看看执行流程;其二,可以做到指令级别的手术操作,比如在函数体中修改某个寄存器,查看某个寄存器。从前也写过一篇文章,但是那时候工具尚不成熟,写的也比较草,想仔细写写,介绍一下,希望能帮助需要的人,也不枉费了时间去写。

使用

frida提供了异常回调的机制,不必再自己写,直接用他的方法即可,代码主要是注册了一个自己的异常回调,调用set方法对指定地址下断点,执行到断点的时候系统就会把执行权交给我们注册的回调,进入我们的逻辑,同时会传一个寄存器上下文参数,这个上下文是可以修改生效的;

效果如下

这里只是打印了所有的寄存器上下文,实际上可以作进一步的操作,比如修改寄存器等等,在这个my_handler中可以执行自己编写的逻辑,要修改寄存器的话,直接把修改后的值赋给detail.context.x[i]即可,如下

原理

异常hook的原理

异常hook,也就是基于异常的hook方式,也叫做基于信号的hook。信号机制是linux的一种系统机制,信号是一种中断(也叫软中断),而中断是处理器的机制,现代处理器内部都有专门负责中断处理的中断模块(硬件实现),主要作用为,遇到中断事件时,比如键盘输入,代码出现除0错误等等,中断模块会暂停cpu工作,根据中断事件寻找相应的中断服务,把控制权交到中断处理服务(用户层)(此时cpu开始运行,执行中断处理服务),执行完中断处理服务之后,控制权回到中断芯片,中断芯片恢复cpu之前在执行的工作(被中断之前执行),比如本来在执行一个菲比那切数列计算,被键盘输入中断了,现在cpu就回到斐波那契继续执行,整个过程中,中断服务是用户层的代码,其余都是硬件实现,linux中可以通过信号机制来接管中断服务,通过

1
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

frida已经对这个东西进行封装,我们直接使用即可

1
Process.setExceptionHandler(ExceptionHook.ExceptionHandler)

这样,我们需要设置断点的地址处,将原始指令先保存,然后设置为断点指令,下图为各架构的断点指令,小端序

1
2
3
4
5
6
arm:
    bkpt,immm  (700020E1)      这个看了,发的信号是sigtrap
    01  de  (thumb下)  断点指令
arm64:
    BRK #imm    imm标识一个不大于16位的立即数
    600F20D4

当cpu碰到断点指令时,会触发中断,经过中断模块以及操作系统的处理,cpu会去执行我们注册的信号处理回调,也就是ExceptionHook.ExceptionHandler,同时会向这个回调传递一个包含了寄存器上下文的参数,可以在这个回调中执行我们的逻辑,比如查看或者修改寄存器等等,之后需要把断点指令恢复为原始指令,执行完我们的handler之后,cpu会返回到断点代码处继续执行。

复用问题

上面已经介绍了一个最基本的异常Hook的实现,然鹅存在那么一点点问题,就是,这样搞是一次性的,一次就好,它不能陪你到天荒地老...要想复用的话,一些文章,或者说网上大多数文章提的方法都是,在监控地址断点触发时,给下一条指令打上断点,这样的话,可以在第二个断点触发时,再给要监控地址重新下断点。天真的我去试了一下,意外发现一个惊喜,假如监控的地址是一个条件分支时,并不知道下一条地址在哪,当然结合原始指令的语义以及断点触发时传过来的寄存器上下文,是可以计算出来,但是增加了很多工作量,多线程时,还得处理资源竞争问题。所以采用了一个新的办法,在handler中,将原始指令保存下来,并且判断是否是pc相关指令,如果是pc无关指令,就把这条指令拷贝到一个新的内存去执行,新内存是一段shellcode,第一条指令为原始指令,之后的指令负责跳转回原始指令的后一条指令执行;如果原始指令是pc相关指令,那么就手动实现这条指令的语义,比如 bl xxx,就把xxx赋值给pc寄存器,如此即可解决复用问题,核心代码如下

frida方面问题

注意一下第一张图中的,load_my_so,这个是我用c写的一个辅助so,主要解决的问题是存储,因为frida每次ctrl s 刷新脚本之后,进程中就会重新执行新的脚本,之前的所有信息都会消失,所以用c写一个专门保存信息的类,保存一些必要的信息,否则的话,会产生一种错误,我们第一次打断点的时候,断点地址是原始指令,我们替换成了断点指令,在脚本中是保存了原始指令的,假如代码为var origin_insn=ptr(addr).readU32(),这条原始指令是要处理的,或者执行,或者模拟执行,不可能把这条丢掉,刷新脚本后,继续对这个地址下断点,由于此时这个地址已经是断点指令了,所以ar origin_insn=ptr(addr).readU32()这一块的逻辑就会有问题,由于frida似乎没有什么比较好的保存信息的办法,文件算一个,但是文件相比用一个类的存储,还是过于麻烦了,故而写了一个so专门用来保存信息,只需要在copy函数稍作修改即可,同时这个so中我们还可以做一些增强性的功能,以扩展frida的能力边际。

应用

当初的考虑是用于监控基本块是否执行的,但是这个问题基本是用stalker trace来处理了;其他主要应用场景是短函数的hook;一些游戏的修改,直接修改寄存器破解时可能会用到,再有就是对抗采用syscall去反调试反检测的时候,可以用这个工具对系统调用指令进行修改。

最后

前几天写的一个ctf解题文章,虽然感觉是有点点东西,但是写的比较急,比较乱,没想到得了优秀,感谢大佬!
惯例宣传一下党国的大业,有想副业或者创业尝试,抱团全暖以应对日益严峻的打工环境,可以看看
轻创业联盟频道
有想交流讨论技术,接单发单,摸鱼划水的,可以看看
技创接


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-5-7 23:36 被KerryS编辑 ,原因:
上传的附件:
收藏
免费 3
支持
分享
最新回复 (6)
雪    币: 5
活跃值: (2675)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
大佬,文件下载不了了
2024-5-8 09:47
1
雪    币: 2468
活跃值: (5088)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
wx_范迪塞尔 大佬,文件下载不了了
我试了一下可以下载呀
2024-5-8 14:48
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-5-8 16:18
1
雪    币: 5
活跃值: (2675)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不过还是会修改一行代码,要是检测的话,很容易就能检测出来了
2024-5-8 16:40
1
雪    币: 15
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
wx_范迪塞尔 不过还是会修改一行代码,要是检测的话,很容易就能检测出来了[em_13]
如果去读这一行指令的话应该是先触发异常进handler,handler结束后断点地址会被还原,最后读出来的是原来的字节吧。所以要检测的话一般是检测信号
2024-5-11 11:36
0
雪    币: 2468
活跃值: (5088)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
mb_fycbgpri 如果去读这一行指令的话应该是先触发异常进handler,handler结束后断点地址会被还原,最后读出来的是原来的字节吧。所以要检测的话一般是检测信号
读这行指令并不会触发异常,只有执行到才会,并且这一行永远是断点指令,不会修改,这个工具并不是为了过检测的,只是提供一个指令级的分析工具,方便分析,避免使用调试器,麻烦
2024-5-11 14:13
0
游客
登录 | 注册 方可回帖
返回
//