[OGeek2019 Final]OVM

main函数,由用户指定pc和栈指针,解析用户输入的十六进制数作为指令和操作数,除了直接数外还可以选用4字节的寄存器作为参数。

最后会释放一个大小为0x8c的堆块,这个堆块的内容可以由我们写。想到了打free_hook
具体的取指和运行:

由指定pc开始取指,读取memory中对应的指令一个一个执行。

看到ssize_t就觉得不对劲了,这东西是有符号的呀……
如果寄存器变量内的有符号数为负数,那就能利用memory越界读写。看一下case 0x40对应的汇编代码验证:
1 | .text:0000000000000F24 ; 98: memory[reg[hex1]] = reg[hex5]; |
movzx和movsxd分别代表零扩展和符号扩展,此事在csapp中亦有提及:

不仅是case 0x40的写,case 0x30的读也同样存在这个问题:

这么一来我们就可以先访问got表,将libc地址泄露之后越界写comment的地址为free_hook-8,在最后往comment上写/bin/sh\x00+p64(system)。
首先,根据伪代码写一些可能要用到的函数,方便我们构造数据或者越界读写:
1 | def code_read(reg, memory): |
我们可以分别计算出需要覆盖的comment和需要泄露的stdin的got表相对memory的下标偏移,再算出相应的有符号数表示(偏移懒得截图,我直接上数据了
1 | offset_comment = 0x30/4 #8 -8=0xfffffff8 |
然后就可以开始构造了:
1 | #构造0xffffffc8 |
构造出-0x38后,我们将stdin的got表读进来,但是寄存器变量只有双字大小,got表的虚拟地址至少有7字节,我们需要分别读到两个寄存器中:
1 | #读stdingot表 |
然后打开gdb看一眼free_hook和stdin的偏移,0x1098,我们取0x1090,前八个字节写一个/bin/sh\x00,释放的时候好给参数
1 | #stdin 0x00007fc8dca78710 free_hook 0x7fc8dca797a8 0x1098 |
最后,往comment上写__free_hook地址:
1 | mov(9, 0x2f) #reg[9] = 0x2f |
万事俱备。在执行完代码后程序会把每个寄存器的内容打印出来,我们可以依次得到free_hook的真实地址,计算一下偏移很容易就能得到system地址,往free_hook上一写大功告成。
完整exp:
1 | from pwn import * |

只在本地打通了,2.23的环境。buuoj没给libc附件,远程懒得去调了。