本文所涉及的任何技术、信息或工具,仅供学习和参考之用。请勿利用本文提供的信息从事任何违法活动或不当行为。任何因使用本文所提供的信息或工具而导致的损失、后果或不良影响,均由使用者个人承担责任,与本文作者无关。作者不对任何因使用本文信息或工具而产生的损失或后果承担任何责任。使用本文所提供的信息或工具即视为同意本免责声明,并承诺遵守相关法律法规和道德规范。
https://mp.weixin.qq.com/s/z6kC60trpEpKQ_OdC0lksA
我们讨论了静态堆栈欺骗,那是关于 hook sleep ,在睡眠期间改变堆栈行为的欺骗,这篇文章我们来一起讨论一下主动欺骗,允许任意函数发起时的堆栈欺骗。
相关的基础知识在上篇文章已经介绍,并且给出了推荐阅读的链接,这里就不再多说,接下来让我们一起动起手来进行调试。
我们先在 x64dbg 中手动进行堆栈欺骗,这对我们理解接下来的项目有很大的帮助。
我随便找了一个程序,我们的想法是在栈底再伪造相同的两帧,都是RtlUserThreadStart +0x28,因为这是我系统上常见的偏移量,你在下面的截图中也可以发现相同的帧。
可以看到第一条指令是 sub rsp 78,这意味着它需要的帧栈大小为 0x78,注意这里是 16 进制,我们只需要在当前栈底向下移动 15 次(0x78=0x08*15 , 十六进制满 16 进 1),然后就可以在这个位置创建新栈了。
x64dbg 自动标注的范围也证实了我们的理论我们把这个位置改成想要的帧栈
此时在 Process Hacker 中查看(注意以管理员权限开启),可以看到两个帧栈已经成功伪造。
第一个要介绍的项目 https://github.com/susMdT/LoudSunRun,这是作者在学习另一个项目https://github.com/klezVirus/SilentMoonwalk 时的产物,由于原项目有点大,作者在这个项目较小的代码库、间接系统调用支持和多参数支持。
这个 Poc 实现了 pPrintf,NtAllocateVirtualMemory 的直接调用,以及pNtAllocateVirtualMemory 的间接系统调用,我们接下来看一下项目:
首先先获取 Printf 的地址,这是我们要去调用的函数,另外又获取了 BaseThreadInitThunk+0x14 和RtlUserThreadStart+0x21 的位置,这是我们要去伪造的栈帧,因为这是作者电脑上一个线程中常见的栈底,当然在不同 windows 版本下偏移量是不一样的,我的 windows 版本下偏移量是 RtlUserThreadStart+0x28。还有一个FindGadget 函数,这是为了帮助我们寻找一个 jmp [rbx] 小工具的,我们后面会讲到。
也许还有人注意到了 CalculateFunctionStackSizeWrapper 函数,这个函数是用来计算帧栈大小的,就像我们上面手动伪造 0x78,在当前栈底向下移动 15 次一样,这个函数是根据 UnwindOp 来进行计算的,想要深入理解的话可以阅读一下:https://codemachine.com/articles/x64_deep_dive.html
紧接着就来到了 Spoof 函数,这是最关键的函数,是我们的欺骗函数,这个函数的参数是可变的,但是 Spoof的前七个参数是相对固定的,前四个参数是我们想要去调用的函数的前四个参数,第五个参数是一个重要的结构体,里面存储着进程上下文,如果需要间接系统调用的 SSN 以及要伪造的栈帧,第六个参数是要调用的函数的地址,第七个参数用来指示是否还有别的参数,假如为 2 的话在 Spoof 里面就会想办法获取后面两个参数。
在这里我还想再多说一句 x64 下参数的传递,前四个参数是放在Rcx,Rdx,R8,R9四个寄存器中,后面的参数就要放在栈上了,如图(图源 Windows x64 调用约定 - 堆栈框架):
ok,现在让我们进入汇编看看到底发生了什么,
首先是一些准备操作,先将栈上的参数分别给 rdi 和 rsi,rdi 就是我们前面提到的结构体,为了便于恢复所以要先将当前寄存器的值给存储起来,rsi 就是要调用的函数的地址。
在下图的最后一行我们将 rax 给到了 r12,而之前 pop rax 则将原始的返回值给到了 rax,这样 r12 就存储了函数的返回值,这是因为 rax 是易失性寄存器,而 r12 是非易失性寄存器,也就是说即使被别的函数调用,r12 也会被 push 保护起来,最后再 pop 出来。
在x64的调用约定中规定易失性寄存器RAX, RCX, RDX, R8, R9, R10, R11, XMM0-XMM5 为易失性寄存器,RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, XMM6-XMM15为非易失性寄存器
这段代码是处理参数的准备工作,r11 和 r13 分别存储了需要额外处理的参数的个数和已经额外处理的参数的个数,通过比较这两个寄存器的值就可以处理完所有的额外的参数了。由于 printf 是不需要额外的参数的,所以我们之后再分析
下面是一个循环,和我们上面说的一样,比较两个寄存器的值来判断是否还需要处理,等下我们再说是如何处理的,先跟着代码调试
然后栈上分配一块空间,200h,然后 push 0,将之前的帧栈截断,剩下的就是我们自己要伪造的操作了。
接下来就是在伪造栈帧了,通过上面手动伪造应该很容易可以理解现在看一下我们的栈帧,成功伪造 ,接下来是为了跳转和 fixup 做准备的,syscall 的代码等下再讲。将返回地址,rbx 寄存器值,fixup 的值给到前面那个欺骗的结构体,然后将 fixup 的值给到 rbx,因为它也是个非易失性寄存器,最后 jmp11。
然后就是返回阶段了,当执行完 printf 后会进入到我们前面找到 jmp [rbx] 小工具,而我们的 rbx 存的是 fixup 函数地址,所以就会跳转到 fixup 函数
下面是我们的fixup 函数,主要就是恢复帧栈和前面保存的寄存器工作,最后 jmp 回到我们最初保持的返回点。
我们看一下多参数是怎么处理的
先将 rsp+30h 存储到 r10 里面,这样 r10+0x08 就可以找到下一个参数
这里 r14 是为了获取额外参数应该在的位置的,是我们需要压入栈中的数据的偏移量,首先加上 200h,这是我们在栈上分配的假栈的空间,然后是加 8,这对应着 push 0 指令,然后再加上要伪造的三个帧栈大小,这样就到了我们要调用的函数的帧栈了,然后以此为基础,第一个参数在 r14+0x28 位置处,然后每个参数依次加 0x08 即可
我们先找到参数需要移动到的位置,然后再将 r10+0x08 的值给到相应位置就可以了,相应位置是通过 rsp-r14 的值计算出来的,r14 是我们上面说的偏移量
这个实现起来就很简单了,我们 jmp 去的时候先将 ssn 号存到 rax,然后直接跳转到 syscall 指令就可以了
注意这里跳转的函数直接就是 syscall 指令,Poc 里面作者是手动找到 syscall 指令的
当然获取 syscall 指令可以自动化获取,这里不再展开
原文始发于微信公众号(影域实验室):重生之我在干免杀-动态堆栈欺骗
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
点赞
https://cn-sec.com/archives/2978485.html
复制链接
复制链接
-
左青龙
- 微信扫一扫
-
-
右白虎
- 微信扫一扫
-
评论