小小vm
vm正如其名virtual machine也就是虚拟机的意思
如果他要模拟一个机器去执行一个文件的话,必然需要cpu中的寄存器、内存之间的堆栈,然后还要不断的读取指令去执行函数,来模拟执行流。
大概流程就是这样子的
他的伪堆栈和伪寄存器和一些字符串则会放在一个全局变量
中,来答到一个模拟机器的效果
我看别人的博客说一般有两种类型的题目
给可执行程序和opcode,逆向emulator,结合opcode文件,推出flag
只给可执行程序,逆向emulator,构造opcode,读取flag
先找了几个简单的练练手
类型一
[GKCTF2020]EzMachine
32位打开,开局有个花指令不难,可以看到main函数
这些和字符串一起放置的猜测是寄存器
0 11B0函数 eip+1
inc后面可能是寄存器加一,或者是内存地址加一,这里5BD8在字符集的地方也出现了,应该是eip,即eip加1
1 1000函数 mov
这里我把字符集名字修改了一下,借字符集实现现在的操作数放到寄存器里面
2 1070函数 push data
可以看出主要特点是读取数据、指针自增(栈顶向上移动)、写入数据、代码指针向前移可能是为读取下一个数据做准备
3 1030函数 push
这个和上面那个函数几乎没差
但是上面那个压入原始数据或字节码,这个则是压入经过查表后的操作码、字符、关键值,可以看到这个其实就是寄存器的地址
4 10A0函数 pop
和上面两个一对比可以明显发现是出栈
5 10E0函数 print
可以按r转化明显看到有打印出的字符串出现,是print函数
6 11D0函数 add
因为它加载了两个寄存器中的值,最后存储在了第一个寄存器中
7 1200函数 sub
看到代码就知道,同理上面的,是相减存在寄存器里面,下面几个基本都是这样的
8 1230函数 mul
9 1270函数 div
10 12B0函数 xor
11 12E0函数 jmp
它取出当前的字节码值 code[dword_F35BD8],乘以3后再减3,把这个结果赋值给程序计数器,相当于跳转到新的字节码地址,所以是跳转的指令
12 1300函数 sub
一个数减去另一个数存在了一个寄存器里面
13 1340函数 je
je的作用:检查ZF标志位
- 如果
ZF = 1
(即前一条比较或算术指令的结果为 0,表示相等),则跳转。 - 如果
ZF = 0
(不相等),则继续执行下一条指令。
14 1370函数 jne
15 13A0函数 jg
大于则跳转
16 13D0函数 jb
17 1400函数 gets
18 1430函数 clean
19 1470函数 mov
从栈中加载数据v0然后给了result所在的寄存器
20 14B0函数 mov
21 11C0函数 end
接下来处理,将字节码换成刚刚分析出的伪汇编
1 | code = [0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, |
打印出来是这样子的
1 | mov 3 3 |
接下来就是提取数据,写一个脚本,注意这里把数据压入栈中了,所以要先进后出
,字节是8位,把数组中两两组合成一个字节
1 | array = [0x7, 0xd, 0x0, 0x5, 0x1, 0xc, 0x1, 0x0, 0x0, 0xd, 0x5, 0xf, 0x0, 0x9, 0x5, 0xf, 0x3, 0x0, 0x2, 0x5, 0x3, 0x3, 0x1, 0x7, 0x7, 0xb, 0x2, 0x1, 0x2, 0x7, 0x2, 0xc, 0x2, 0x2, ] |
天外来题
这道我一开始没看出来是vm┭┮﹏┭┮
后来盯着这个循环才发觉,主程序是这样子的
首先解决vm函数,得到key的计算方法
1 | code = [ |
拿到一些表达式,直接用z3解决
1 | R[0]+132+R[1]=316 |
1 | from z3 import * |
9787254630123759
然后就能更新dll了,可以看到dll里面的内容是一个异或
1 | v5 = [13, 8, 26, 10, 29, 15,*b"2x*{*{|}qdz,{}d(}q,dxx}zd(z}p",127,*b"(z+~}yy4"] |
【网鼎杯2020】signal
v4是opcode,我们将它取出来
这里有一个switch-case结构构成vm
1 | int __cdecl vm_operad(int *opcode, int a2) |
这个是取出来的opcode
1 | [10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, 9, 8, 3, 32, 1, 2, 81, 8, 4, 36, 1, 12, 8, 11, 1, 5, 2, 8, 2, 37, 1, 2, 54, 8, 4, 65, 1, 2, 32, 8, 5, 1, 1, 5, 3, 8, 2, 37, 1, 4, 9, 8, 3, 32, 1, 2, 65, 8, 12, 1, 7, 34, 7, 63, 7, 52, 7, 50, 7, 114, 7, 51, 7, 24, 7, 4294967207, 7, 49, 7, 4294967281, 7, 40, 7, 4294967172, 7, 4294967233, 7, 30, 7, 122] |
但是7后面有几个数过大,想一下程序中有LOBYTE取低位的操作重新处理一下,可以得到有效的opcode
1 | [10, 4, 16, 8, 3, 5, 1, 4, 32, 8, 5, 3, 1, 3, 2, 8, 11, 1, 12, 8, 4, 4, 1, 5, 3, 8, 3, 33, 1, 11, 8, 11, 1, 4, |
好了然后我们可以根据vm函数来打印出整体程序的流程
1 | op = 0 |
打印出来发现对result[100]-[114]进行了操作,然后对比,只需要解出这14位就可以了
这里确实写脚本的话还不如手算来的方便,求出值之后换成ASCII码就行了,最后套个flag
1 | result[0..14] = [55, 53, 55, 53, 49, 53, 49, 50, 49, 102, 51, 100, 52, 55, 56] |
符号执行
上面是比较正统的方法,我在网上还看到一种我从没见过的插件
https://github.com/illera88/Ponce
但是如果数据大小那边填15的话,只能输出13个数据
然后我试了一下,大小填17,倒是能输出完整
不断尝试了很多遍任然没有驯服,但是拼拼凑凑也差不多。。。。
类型二
【DDCTF 2018】黑盒破解
这个就是一运行发现是要构造opcode,让程序输出binggo字符串,来读取flag
不正经的法一
看得我头晕
但是既然flag就在程序里面直接修改一下应该也能出来
结果o_o ….
正经的法二
好了,既然是练习还是要认真看一下的
首先先找循环来读取指令,可以发现在main函数中藏着这么个函数,在相等的地方看汇编可以发现要对比的值是固定的
动调提取出值,这里
取出的值,我们需要输入的是这些
1 | [0x2a,0x27,0x3e,0x5a,0x3f,0x4e,0x6a,0x2b,0x28] |
写个脚本获取输入
1 | data=[0x2,0x0,0x0,0xe,0x16,0x54,0x20,0x18,0x11,0x45,0x50,0x59,0x58,0x53,0x0,0x8,0x44,0x2d,0x46,0x39,0x0,0x54,0x42,0x1,0x3c,0xf,0x0,0x7,0x17,0x0,0x56,0x21,0x0,0x37,0x6d,0x2b,0x2a,0x6e,0x59,0x5d,0x47,0x3a,0x4a,0x34,0x44,0x48,0x43,0x6c,0x3f,0x59,0x25,0x33,0x55,0x2f,0x31,0x68,0x27,0x34,0x7c,0x28,0x67,0x59,0x0,0x52,0x0,0x26,0x0,0x3e,0x56,0x4e,0x33,0x21,0x45,0x6d,0x60,0x39,0x46,0x72,0x6d,0x4d,0x54,0x40,0x0,0x74,0x57,0x73,0x72,0x7a,0x47,0x45,0x0,0x71,0x0,0x4a,0x35,0x70,0x3b,0x36,0x2e,0x26,0x2c,0x6c,0x4a,0x0,0x7c,0x63,0x35,0x57,0x4d,0x41,0x43,0x62,0x0,0x68,0x37,0x0,0x5a,0x6a,0x6b,0x7c,0x29,0x69,0x4c,0x70,0x50,0x71,0x26,0x36,0x3c,0x6,0x1b,0x0,0x3c,0x30,0x0,0x0,0x0,0x4c,0xb,0x4b,0x48,0x8,0x54,0x47,0x12,0x9,0x24,0x0,0x0,0x24,0x40,0xd,0x39,0x6,0x5c,0x2c,0x1a,0x2d,0xa,0x38,0x35,0x37,0x16,0x3b,0x0,0x24,0x48,0x0,0x49,0x0,0x37,0x8,0x1f,0x24,0x45,0x1d,0x11,0x40,0x2f,0x4a,0x8,0x15,0x0,0x11,0x0,0x1a,0x22,0x41,0x52,0x5b,0xb,0x45,0x31,0x19,0x43,0x19,0x1e,0xa,0x21,0x5,0x4d,0x59,0x38,0x34,0x9,0x36,0x2f,0x43,0x2,0x53,0x12,0x2f,0x4c,0x21,0xd,0x3c,0x31,0x2e,0x37,0x8,0x30,0x29,0x32,0x2f,0x0,0x1a,0x14,0x41,0x53,0x15,0x21,0x0,0x8,0x13,0x38,0x5c,0x36,0x3b,0x50,0x0,0x2f,0x1e,0x57,0x0,0x30,0x2e,0xc,0x2e,0x37,0x52,0x1c,0x33,0x34,0x11,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0] |
找到开头的九个字符,正好九个,重开用动调在可以找到对应关系,而且也能到里层部分
1 | $8Ct0Eu#; |
1 | $->sub_400DC1 |
接下来动调分析函数里面的作用,一定要注意寄存器里面的值
[DDCTF 2018] 黑盒破解 - iPlayForSG - 博客园
得到函数的细节作用
直接构造就行,因为Binggo有6个字符后面要截断操作
总结
刚开始写下来真感觉自己的基础不是很好,寄存器汇编这边也是不断的查找很费时间U_U
练了几道题之后,任然是飞舞一个哈^_^