vm_init: 初始化虚拟机环境
1.初始化寄存器
2.初始化eip,使eip指向opcode的地址e
3.将opcode与对应的handle函数关联在一起
vm_start:进入虚拟机
使eip指向要被解释的opcode地址
将eip的值与0xf4(ret)比对,若≠则调用vm_dispatcher()
vm_dispatcher:调度器,解释opcode,并选择对应的handle函数执行,当handle执行完后会跳回这里,形成一个循环。
虚拟机逆向中最重要的两个部分:
-
opcode
-
对opcode的解释(handle函数)
[DDCTF 2018] 黑盒破解
主要判断
查看引用byte_603F00,类似check(),满足调节则置为1
看看sub_40133D的引用,是一些函数的偏移
注意到main中关键判断之前的函数sub_401A48()
感觉像是调用函数指针,看汇编代码
看到call rax,估计就是通过这个来调用相应的函数处理
在这两处设置断点,查看输入的passcode以及dump出byte_603900中的data
在外层调用Dispatcher函数的地方断点并运行,得到
[0x2a,0x27,0x3e,0x5a,0x3f,0x4e,0x6a,0x2b,0x28]
byte_603900=[2, 0, 0, 14, 22, 84, 32, 24, 17, 69, 80, 89, 88, 83, 0, 8, 68, 45, 70, 57, 0, 84, 66, 1, 60, 15, 0, 7, 23, 0, 86, 33, 0, 55, 109, 43, 42, 110, 89, 93, 71, 58, 74, 52, 68, 72, 67, 108, 63, 89, 37, 51, 85, 47, 49, 104, 39, 52, 124, 40, 103, 89, 0, 82, 0, 38, 0, 62, 86, 78, 51, 33, 69, 109, 96, 57, 70, 114, 109, 77, 84, 64, 0, 116, 87, 115, 114, 122, 71, 69, 0, 113, 0, 74, 53, 112, 59, 54, 46, 38, 44, 108, 74, 0, 124, 99, 53, 87, 77, 65, 67, 98, 0, 104, 55, 0, 90, 106, 107, 124, 41, 105, 76, 112, 80, 113, 38, 54, 60, 6, 27, 0, 60, 48, 0, 0, 0, 76, 11, 75, 72, 8, 84, 71, 18, 9, 36, 0, 0, 36, 64, 13, 57, 6, 92, 44, 26, 45, 10, 56, 53, 55, 22, 59, 0, 36, 72, 0, 73, 0, 55, 8, 31, 36, 69, 29, 17, 64, 47, 74, 8, 21, 0, 17, 0, 26, 34, 65, 82, 91, 11, 69, 49, 25, 67, 25, 30, 10, 33, 5, 77, 89, 56, 52, 9, 54, 47, 67, 2, 83, 18, 47, 76, 33, 13, 60, 49, 46, 55, 8, 48, 41, 50, 47, 0, 26, 20, 65, 83, 21, 33, 0, 8, 19, 56, 92, 54, 59, 80, 0, 47, 30, 87, 0, 48, 46, 12, 46, 55, 82, 28, 51, 52, 17, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
查看合法的opcode,九个,也证实了有九个操作
在call rax下断点,输入这九个字符分别分析它们的作用
$:其中rax指向
而rax=*((a1+8)+(a1+288)),(从rax指向的值可知(a1+8)指向“PaF.....”这个字符串
8:
与"$"操作相反
C:
这边的(a1+664)是passcode[i+1],在vm_dipatcher定义时就定义了
看相应的汇编也可以得到反汇编后的代码
t:
emmmm,其实和上面的‘C’操作相反
0:
E:*(check)
*(_BYTE *)(a1 + 664) == 's' ,判断passcode[i+1]=='s'
这个判断我们就过不去的
所以修改一下eip到0x4013D7,也就是s数组的赋值,看看这个赋值的流程
可以看到ptr[rax]的值是
将(a1+8里的值赋给s数组)
u:
#:
a+16:
可以看到a+16是我们输入值的地址,将【rax+16】的地址赋值给rsi
而rdx的地址为strarray
;:
和上一个操作类似,只不过在赋值前有一个循环
for(i=0;i<*(a1+644);i++)
++*(a1+288)
至此,这些opcode对应的函数就看完了
经过分析后
+665: 临时变量tmp
+8: str = "PaF0!&Prv}H{ojDQ#7v="//
str[]=PaF0!& Prv}H{ojDQ#7v=
Binggo
+288: index
+16: 我们输入的passcode(input)
+664: 下一位输入(next, 即input[i + 1])
$ sub_400DC1(func1): tmp=str[index]
8 sub_400E7A(func2): str[index]=tmp
C sub_400F3A(func3): tmp=tmp+next-33
t sub_401064(func4): tmp=tmp-next+33
0 sub_4011C9(func5): ++index
E sub_40133D(func6):check
if (a1 + 664) == 's'
s = str[i],len=20
if (right(s))
success
u sub_4012F3(func7): --index
# sub_4014B9(func8); str[index] = input[index + next - 48] - 49
str[6] = input[6+ next - 48] - 49=input[next-42]-49=input[74-42]-49=input[32]-49->input[32]=49='1'
; sub_400CF1(func9): for (i = 0; i < next; ++i) ++index;
str[index] = input[index + next - 48] - 49
而check中就是验证str经过这些操作后是否与Binggo相等,
将P->B
以此类推,当str对应转换为Binggo后还有多出的字符,此时需要截断
这是就需要用到
可以得到我们输入的opcode为
$t/80$C)80$CI80$CX80$Cg80$Cj80#J1uuuuuuEs