vmPwn
一直都是我比较反感的题目,因为逆向工作量太大了,而自己的逆向水平还是太烂了。但是这类题越来越多,还是得坚持学呀。
vm 解释器的基本认识
寄存器
PC
程序计数器,存放的是一个内存地址,该地址中存放着下一条要执行的计算机指令;SP
指针寄存器,永远指向当前栈顶BP
基址寄存器,用于指向栈的某些地址,在调用函数的时候会用到AX
通用寄存器,用于存放一条指令执行后的结果
在程序中 PC
的初始值指向目标代码的 main
函数。
指令集
虚拟机定义的时候,会定义一个全局变量的枚举类型,里面有我们需要的指令 如:MOV
, ADD
之类的。
ciscn_2019 virtual
程序分析
程序总体就是模拟了一个栈的运行,我们主要分析两个函数:load
和 save
1 | __int64 __fastcall load(__int64 res_list) |
load
函数会取出模拟的 *res_List
栈顶的数作为偏移 num
,然后将 *res_list+*res_list_num+num
处的值移到*res_list
栈顶
1 | __int64 __fastcall save(__int64 res_list) |
save
函数会取出模拟的 *res_list
栈顶作为 偏移 num
,再取出 *res_list
栈顶的值 value
。然后会将 值 value
存储到 *res_list+*res_num+num
的地址处。
我们可以看到这两个函数,都没有判断偏移 num
。这也就使得我们可以数组越界读取数据。
而 *res_list
存储的就是 我们模拟的栈的地址。而模拟栈的地址 与 res_list
的地址,仅仅相差 0x18
。
利用分析
这里由于程序没有开启 PIE
且可以修改 Got
表,所以这里我们采取的方法是修改 puts_got
为system
。
考虑了很久怎么泄露 system
地址,后面才想到其实我们不一定需要知道 system
地址,只需要将 puts
的地址减去其与 system
地址的偏移即可,而且这里还有 add
和 sub
函数。
- 首先修改
*res_list
的值为 0x404088
先将 0x404088
和 偏移 -3
依次push 进入 *res_list
中,然后执行 save
函数。此时就会修改 *res_list+res_list_num-3
的位置为 0x404088
,而 res_list_num
初始为0,也就是最后修改了 res_list
地址处存储的值为 0x404088
。
- 将puts地址移到 0x404088
由于我们上面修改了 *res_list
的值为 0x404088
,也就相当于将 虚拟栈 移到了 0x404088
。这个地址离 puts_got
仅相差 0x68
,也就是 -12
的偏移。所以我们只需要将 -13
Push进入虚拟栈,在执行 load
,就可以将 puts
的地址 赋值到 0x404088
- pust_addr加上偏移得到system_addr
然后我们将 system_addr-puts_addr 的偏移 push进入虚拟栈中,然后执行 add
函数,那么 0x404088
处就会赋值为 system
的地址
- 修改 puts_got
最后,我们只需要再将 -12
的偏移压入虚拟栈,执行 save
,就能将 pust_got
改为 system
地址。
EXP
1 | from pwn import * |
D^3 CTF babyrop
程序分析
这道题就更加符合 VM_PWN
的形式了。程序首先有两个数据结构需要知道:
第一个是我们输入的 data_list
,里面既可以包含指令序列,也可以包含 数据。
另一个是 程序模拟的 虚拟栈结构:stack_list
,这个结构体有3个成员变量,如下:
1 | stack_list{ |
然后,程序中最重要的是 VM
函数,我们简要分析:
1 | esp1 = (_QWORD **)stack_list; |
首先是对 data_list
做了初始化,同时 esp1
指向了 stack_begin
。
然后就是一系列操作函数,我们重点说以下函数:
1 | __int64 __fastcall mov_rsp1_rsp2(_DWORD *esp1) |
首先是 mov_rsp1_rsp2
,其中 esp1
表示的意思是虚拟栈的栈顶指针,该函数的汇编我们可以理解为如下形式:
1 | mov [rsp-8], [rsp]; |
然后是 Add_next
,是将 add [rsp-8], [rsp]
1 | __int64 __fastcall Add_next(_QWORD **esp1) |
然后,在 Add_esp80
函数中,可以将 esp1
向高地址迁移 0x50
1 | __int64 __fastcall Add_esp80(__int64 a1, _QWORD *edit_flag) |
最后是 Mov
函数,汇编为 mov [rsp], value
1 | __int64 __fastcall Mov(_QWORD **esp1, int value) |
利用分析
首先,我们可以知道这个 虚拟栈是在程序真正的栈上模拟的。而且 stack_begin
与 函数的 ret
地址仅相差 0x68
。那么我们的思路应该是利用 vm
的各类操作,将 ret
的返回地址最终改为 system
地址。
- 利用
Add_esp80
迁移栈顶
由于现在虚拟栈顶位于 ret
指令的低地址,所以我们先使用一次 Add_esp80
将栈顶向高位迁移 0x50
。然后,我们不能直接再次执行 Add_esp80
,因为有检查,这里我们随便 执行一个 push
函数即可。然后再次执行 Add_esp80
,此时 虚拟栈顶位于 ret
地址之后。
- 写入
system
地址
构造 system
地址的方式和上一题类似,不过这里是利用栈上已有的地址。我们查看栈上,可以在 ret_addr+0x28
处发现一个 libc_start_main
的libc
地址。那么我们让这个地址加上与 system
的偏移,即可使其为 system
地址。这里就需要利用 Mov
函数将 偏移写入栈上,再利用 Add_next
函数,将两个地址相加,得到system
地址
- 覆写 ret 地址
最后我们就利用多次 mov_rsp1_rsp2
函数,将 system
地址不断向低位写,写4次即可覆盖 ret
地址。
EXP
1 | from pwn import * |
OGeek Final OVM
程序分析
利用分析
EXP
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/11/20/VMPwn入门学习/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!