Ret2esp / Ret2reg
Ret2esp
由于ESP(堆栈指针)始终指向堆栈的顶部,这种技术涉及将EIP(指令指针)替换为**jmp esp或call esp**指令的地址。通过这样做,shellcode被放置在被覆盖的EIP之后。当ret指令执行时,ESP指向下一个地址,恰好是shellcode存储的位置。
如果在Windows或Linux中未启用地址空间布局随机化(ASLR),则可以使用在共享库中找到的jmp esp或call esp指令。然而,如果ASLR激活,可能需要在受影响的程序内部寻找这些指令(您可能需要击败PIE)。
此外,能够将shellcode放置在EIP损坏之后,而不是在堆栈中间,可以确保在函数运行过程中执行的任何push或pop指令不会干扰shellcode。如果shellcode放置在函数堆栈的中间,可能会发生这种干扰。
空间不足
如果在覆盖RIP后写入的空间不足(也许只有几个字节),可以编写一个初始的**jmp** shellcode,例如:
sub rsp, 0x30
jmp rsp并将shellcode提前放在堆栈中。
示例
您可以在https://ir0nstone.gitbook.io/notes/types/stack/reliable-shellcode/using-rsp中找到此技术的示例,最终利用如下:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
jmp_rsp = next(elf.search(asm('jmp rsp')))
payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
sub rsp, 10;
jmp rsp;
''')
pause()
p.sendlineafter('RSP!\n', payload)
p.interactive()你可以在https://guyinatuxedo.github.io/17-stack_pivot/xctf16_b0verflow/index.html中看到这种技术的另一个示例。这里存在一个未启用NX的缓冲区溢出,使用了一个小工具来减少$esp的地址,然后使用jmp esp;跳转到shellcode:
Ret2reg
类似地,如果我们知道一个函数返回存储shellcode的地址,我们可以利用**call eax或jmp eax指令(称为ret2eax技术),提供另一种执行我们的shellcode的方法。就像eax一样,包含有趣地址的任何其他寄存器**都可以被使用(ret2reg)。
示例
您可以在这里找到一些示例:
**
strcpy将存储在eax中shellcode所在的缓冲区的地址,而eax**没有被覆盖,因此可以使用ret2eax。
ARM64
Ret2sp
在ARM64中,没有允许跳转到SP寄存器的指令。可能会找到一个将sp移动到寄存器然后跳转到该寄存器的gadget,但在我的kali libc中找不到类似的gadget:
我发现的唯一方法是更改在跳转到它之前复制sp的寄存器的值(因此它将变得无用):

Ret2reg
如果一个寄存器有一个有趣的地址,只需找到适当的指令就可以跳转到它。您可以使用类似以下的内容:
在ARM64中,是**x0**存储函数的返回值,因此可能是x0存储用户控制的缓冲区地址,其中包含用于执行的shellcode。
示例代码:
检查函数的反汇编代码,可以看到缓冲区的地址(容易受到缓冲区溢出攻击,由用户控制)在从缓冲区溢出返回之前被存储在 x0 中:

在**do_stuff函数中也可以找到br x0**这个特殊指令:

我们将使用该特殊指令进行跳转,因为二进制文件是没有启用 PIE 编译的。通过模式匹配,可以看到缓冲区溢出的偏移量为 80,因此利用将是:
如果使用的是read而不是fgets,也可以通过仅覆盖返回地址的最后2个字节来绕过PIE,返回到br x0;指令,而无需知道完整地址。
使用fgets不起作用,因为它在末尾添加了一个空字节(0x00)。
保护措施
NX: 如果堆栈不可执行,这不会有帮助,因为我们需要将 shellcode 放在堆栈中并跳转执行它。
参考资料
最后更新于