OGeek2019-Final OVM WP
题目名称: OVM
题目来源: OGeek2019
题目靶场: CTF2
思路
1 | Arch: amd64-64-little |
一个还算简单的 VM PWN
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
main 函数非常直接,让我我们输入 PC、SP、CODE SIZE 还有 CODE 序列。需要注意的是:
1 | for ( i = 0; v4 > i; ++i ) |
这些代码会将操作码 0xFF 强行转化为 0xE0 也就是该虚拟机中的退出码
然后就是虚拟机的主循环
1 | while ( running ) |
fetch() 会不断从全局数组 memory 中取出下一条指令并交给 execute() 执行。
最后有一个 “用户反馈”,读取我们的输入放到一个分配好的 chunk 中,这个 chunk 的指针存放在全局变量 comment 中,其与 memory 相邻。
该虚拟机的指令列表如下:
| 操作码 | 助记符 | 行为 |
|---|---|---|
| 0x10 | LOADI | reg[v4] = imm(立即数载入) |
| 0x20 | SETZ | reg[v4] = (imm == 0) |
| 0x30 | LOAD | reg[v4] = memory[reg[v2]] ★ |
| 0x40 | STORE | memory[reg[v2]] = reg[v4] ★ |
| 0x50 | PUSH | stack[SP++] = reg[v4] |
| 0x60 | POP | reg[v4] = stack[–SP] |
| 0x70 | ADD | reg[v4] = reg[v2] + reg[v3] |
| 0x80 | SUB | reg[v4] = reg[v3] - reg[v2] |
| 0x90 | AND | reg[v4] = reg[v2] & reg[v3] |
| 0xA0 | OR | reg[v4] = reg[v2] | reg[v3] |
| 0xB0 | XOR | reg[v4] = reg[v2] ^ reg[v3] |
| 0xC0 | SHL | reg[v4] = reg[v3] << reg[v2] |
| 0xD0 | SHR | reg[v4] = reg[v3] >> reg[v2] |
| 0xE0 | EXIT | running = 0(输出 “EXIT”) |
| 0xFF | HALT | 用 printf(“R%d: %X”) 打印全部寄存器后停机 ★ |
漏洞主要出在 LOAD 和 STORE 这两个指令上,其存在数组越界,让我们可以访问 memory 以外的内存。所以说我们可以控制 comment 中存放的 chunk 指针,将其指向我们需要的地方。由于题目环境为 glibc 2.23 且在 sendcomment() 函数中存在释放操作:
1 | void __fastcall sendcomment(void *a1) |
所以这里还是使用常规的劫持 __free_hook 的方法
攻击思路
将 opcode 封装为如下函数
1 | def loadi(v4, v2): |
首先泄露 libc 地址。memory 在 bss 段内,在网上一点就是 got 表。们通过越界读将got表中的libc地址读取到寄存器中,这里需要注意的是,由于寄存器是双字,也就是四字节的,而地址是八字节的,所以我们需要两个寄存器才能存储一个地址。
got表中最后一个是stderr,不过不选它来泄露,因为stderr地址的最后两位是00,在这里我们选择stdin来泄露,因为后续我们需要通过stdin的地址来计算得到__free_hook-8,因此尽量选择与free_hook地址相差较小的来泄露,能够减小计算量。
memory 距离 stdin 的距离是 -56。转化为十六进制是 0xffffffc8 我们可以通过 shl 和 add 来构造
1 | loadi(0, 8), |
这样我们就将 stdin 的地址读取到了虚拟机的寄存器中。然后我们计算出 __free_hook - 8 和 stdin 的偏移然后将其 add 到之前的寄存器上
1 | loadi(1, 0x10), |
然后我们需要将 __free_hook - 8 的地址放到 comment[0] 中
1 | loadi(1, 47), |
最后退出。
但是有一点需要注意,我们最后输入的退出操作码是 0xFF 其会被强制转换为 0xE0。其详细的逻辑如下:
1 | if ( HIBYTE(opcode) == 0xE0 ) |
这里的 unk_242094 存放的是 main 函数读取的 SP 的数值,我们需要让这个值在退出时等于 1。才能让虚拟机打印出所有寄存器的值。否则虚拟机会直接退出。
EXP
1 | #!/bin/python |