vm 对我也说一直是个很有意思的东西,自想还原vmp失败以来,一有机会了解vm都会去看一下,js的vm有很多教程了,对我启发很大,这篇文章的实现方案是我的一种对vm还原的尝试,按照我最开始的想法是要还原到binja的 il 然后去构建一个函数的,但是我懒癌犯了,就不打算继续了(这个绝对是可以这么干的)
由于我懒得搭建 unidbg 去模拟执行(菜是原罪),所以本文全程使用静态分析去完成
此文更多的是解释我的vm还原的代码,以及对还原vm的一种尝试,而不是具体的介绍我代码是怎么写的(如果有需要的话,我可能会出一个视频来讲解怎么实现的代码,打字太累了),我的还原思路应该是可以通用还原别的vm的,我设计的时候也是尽可能的这么做的 阅读此文的前置条件:
1: 对参考文章中内容有一定了解
2: 对angr(符号执行)有一定了解
某短视频虚拟机分析和还原 https://bbs.kanxue.com/thread-282300.htm
我是这么划分 vm 解释器的(应改看不清 该函数偏移为 0x2c18)
本文解释我还原的代码主要围绕调用外部函数
在我上一篇还原 ollvm 的文章中,我还原的主要思路 是用 angr在分发器设置 负责跳转的寄存器的值,然后angr就可以把这个值对应的执行的过程全部记录下来,一直到下一次重新跳转到分发器,这次分析vm我最开始也是这个思路的,但是后面发现了问题,在ollvm中,我只是为了还原控制流,所以跟代码实际执行逻辑是不相关的,但 vm就不一样了,vm执行的过程是跟前面变量有关的,就导致无法像上次一样去还原 稍微解释下
这里 opcode 0xdfbdfc13 对应的就是判断虚拟寄存器0的值大于1走一个流程,如果我 使用之前的方法,从分发器设置 opcode的值,然后一直符号执行直到下一次到分发器,就会出现分支(可能会很多),也有的时候,后面的opcode 会使用前面设置的虚拟寄存器,在符号执行后面的opcode的时候,不知道这个值.也会产生分支
为了尽可能的减少分支,让符号执行的执行流尽可能的去接近 opcodes 对应的流程,而不包含解释器执行过程产生的分支,就需要用一个state(里面包含内存信息),从解释器开始执行一直到解释完所有opcode
还原出来的伪代码:
0x2c0c 是跳板函数,x4 存储的是要调用的函数的地址, x5 存储的是被调用函数的参数指针,每次调用外部函数的时候都会给 x4,x5赋值,在binja中把llil整个函数复制下来,搜索当前函数可以得到x4,x5在哪使用的
所以只需要给 x19 - 0x110 和 x19 - 0x108 赋值,就可以控制好调用外部函数了,通过阅读 金罡 大佬的文章获得了 vm解释器的内存布局
可以看到 x4 就是 vreg4,x5就是 vreg5,所以给 vreg4 vreg5 设置值就可以完成跳转
这里拿出我还原的代码,第一次调用外部函数开始解释
tips: 部分虚拟寄存器的做了改变(让分析起来更加方便), x4 就是 vreg4, x5就是 vreg5, sp就是 vreg29,上面伪代码中的 branch 是产生分支的位置,因为我没得打算完整还原所有分支,所以忽略了,未知的位置一般都是设置标志位,也可以忽略
调用的 0x2b40 是被前面的 vm代码计算出来的,我用angr符号执行的时候直接得到了地址,然后记录了下来,先来验证下我符号执行的得到的 伪代码的正确性
x4就是存储的跳转的地址,拿到上面给x4设置值的部分进行分析
总的来说,x4是 x0 | vreg_30
x0 = 0, vreg_30 = x1 + vreg_2
vreg_6 根据上面内存布局知道 是 ext_func_list_21d10,这里面全部都是被加密的外部函数地址
vreg_6 + 0x0 = 0xddd2d0
x1是个常数 0xffffffffff225870
vreg 最大是 8字节 ,所以溢出的不管, 这个值就是 2b40,跟 angr 符号执行得到的结果是对上的
继续分析参数
vreg_5 最终就等于 0x20
这里看一下 金罡 大佬 还原的汇编
汇编中显示的就是 x20,所以基本确定我还原的是没问题的
再来看一下 0x2bd0 对应的 汇编
可以看到函数的返回值是放在了 [x19 + 8].q 的,x19就是上面 的x5
再稍微多拿一点 还原的伪代码
调用 0x2b5c 准备的参数 x5 是 sp + 0x1a0 = vreg_21 = sp + 0x1b8 调用 0x2b40 准备的参数 x5 是 sp + 0x1b0
0x1b8 = 0x1b0 + 8
所以这里可以看到 调用 0x2b40的参数是 0x2b5c的返回值,逻辑上基本与金罡大佬文章中还原的代码一致(下面的就是,randNumberSize我没写出来,但读者可以试着分析下我还原的伪代码,不难得出也是 0x20)
更多的对我还原 伪代码的验证就不再进行, 借助我还原的伪代码,去分析 原始opcode的执行过程到底做了什么事,是可以很容易分析出来的
https://github.com/zhuzhu-Top/de_vm
本来这部分是准备变写代码的时候边记录的,但是问题太多了,后面就没记录了
按照我的理解,所有 opcode 都要从分发器分发,直到 ip++ 位置的 实际的时候发现,确实 分发器的位置,捕获到的所有 opcode 都是对的, 但是部分 opcode 没有在 ip++ 位置出现
解决:
忘记在 extra_stop_points 里面加 ip++位置的偏移,以为会停在那,实际没有,所以 以后想在哪 里做处理,都需要添加上去
opcode 执行逻辑
0xdfbdfc16 vreg0=3
0xdfbdfc15
0xdfbdfc14
0xdfbdfc13
if
(vreg0 > 1) ip+=8
0xdfbdfc12 vreg0+=2
0xdfbdfc11 vreg0+=1
opcode 执行逻辑
0xdfbdfc16 vreg0=3
0xdfbdfc15
0xdfbdfc14
0xdfbdfc13
if
(vreg0 > 1) ip+=8
0xdfbdfc12 vreg0+=2
0xdfbdfc11 vreg0+=1
sp
=
sp
+
-
0x210
sp
+
0x208
=
vreg_31
sp
+
0x200
=
vreg_30
sp
+
0x1f8
=
vreg_23
sp
+
0x1f0
=
vreg_22
sp
+
0x1e8
=
vreg_21
sp
+
0x1e0
=
vreg_20
sp
+
0x1d8
=
vreg_19
sp
+
0x1d0
=
vreg_18
sp
+
0x1c8
=
vreg_17
sp
+
0x1c0
=
vreg_16
vreg_16
=
x0 | vreg_7
vreg_11
=
x4
+
0x10
vreg_12
=
x4
+
0x0
vreg_2
=
vreg_6
+
0x0
x3
=
vreg_6
+
0x8
vreg_5
=
vreg_6
+
0x10
vreg_7
=
vreg_6
+
0x18
vreg_8
=
vreg_6
+
0x20
vreg_9
=
vreg_6
+
0x28
vreg_18
=
x4
+
0x18
vreg_10
=
vreg_6
+
0x30
vreg_6
=
vreg_6
+
0x38
vreg_19
=
x4
+
0x8
x1
=
vreg_19
+
0x76
sp
+
0x88
=
x1
x4
=
vreg_18
+
0x0
branch0(先不处理)
未知
x1
=
0xffffffffff220000
x1
=
x1 | (
0x5870
&
0xffff
)
x4
=
x1
+
vreg_6
sp
+
0x40
=
x4
x4
=
x1
+
vreg_10
sp
+
0x38
=
x4
x4
=
x1
+
vreg_9
sp
+
0x30
=
x4
x4
=
x1
+
vreg_8
sp
+
0x20
=
x4
x4
=
x1
+
vreg_7
sp
+
0x18
=
x4
x4
=
x1
+
vreg_5
sp
+
0x0
=
x4
vreg_23
=
x1
+
x3
vreg_30
=
x1
+
vreg_2
vreg_20
=
x0
+
0x20
sp
+
0x1b0
=
vreg_20
vreg_5
=
sp
+
0x1b0
x4
=
x0 | vreg_30
vreg_25
=
x0 | vreg_16
sp
+
0x28
=
vreg_11
vreg_31
=
ip
+
8
sp
+
0x8
=
vreg_12
call
0x2b40
sp
+
0x1a8
=
vreg_20
vreg_21
=
sp
+
0x1b8
sp
+
0x1a0
=
vreg_21
vreg_5
=
sp
+
0x1a0
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_23
call
0x2b5c
vreg_5
=
sp
+
0x190
vreg_22
=
x0
+
0x10
sp
+
0x190
=
vreg_22
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
vreg_5
=
sp
+
0x180
sp
+
0x180
=
vreg_22
vreg_17
=
sp
+
0x198
sp
+
0x10
=
vreg_17
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
sp
+
0x150
=
vreg_21
sp
+
0x158
=
vreg_20
sp
+
0x160
=
vreg_17
sp
+
0x168
=
vreg_22
sp
+
0x178
=
vreg_22
vreg_20
=
sp
+
0x188
sp
+
0x170
=
vreg_20
x4
=
sp
+
0x0
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0x150
call
0x2b94
vreg_17
=
vreg_19
+
0x40
vreg_5
=
sp
+
0x140
sp
+
0x140
=
vreg_17
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
vreg_30
=
sp
+
0x48
vreg_22
=
sp
+
0x148
sp
+
0x130
=
vreg_19
vreg_23
=
sp
+
0x8
sp
+
0x128
=
vreg_23
sp
+
0x138
=
vreg_30
x4
=
sp
+
0x18
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0x128
call
0x2ba8
vreg_5
=
sp
+
0x110
x1
=
x0
+
0x40
sp
+
0x118
=
vreg_30
sp
+
0x110
=
vreg_22
sp
+
0x120
=
x1
vreg_30
=
sp
+
0x20
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2bb8
vreg_5
=
sp
+
0xf8
x1
=
vreg_22
+
0x40
sp
+
0x100
=
vreg_23
sp
+
0xf8
=
x1
sp
+
0x108
=
vreg_19
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2bb8
x1
=
x0
+
32
sp
+
0xe8
=
vreg_21
vreg_19
=
sp
+
0x28
sp
+
0xe0
=
vreg_19
sp
+
0xf0
=
x1
x4
=
sp
+
0x30
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0xe0
call
0x2bc8
x1
=
vreg_19
+
0x26
vreg_19
=
sp
+
0x10
vreg_2
=
x0
+
16
x3
=
sp
+
0x88
sp
+
0xa8
=
vreg_19
sp
+
0xb0
=
vreg_2
sp
+
0xb8
=
vreg_20
sp
+
0xc0
=
vreg_22
sp
+
0xc8
=
vreg_17
sp
+
0xd0
=
x1
sp
+
0xd8
=
x3
x1
=
sp
+
0x88
x1
=
x1
+
-
0x26
sp
+
0x88
=
x1
x4
=
sp
+
0x38
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0xa8
call
0x2be8
vreg_2
=
sp
+
0x88
未知
x1
=
vreg_2
+
0x26
vreg_18
+
0x0
=
x1
sp
+
0xa0
=
vreg_21
vreg_5
=
sp
+
0xa0
vreg_17
=
sp
+
0x40
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_17
call
0x2c04
sp
+
0x98
=
vreg_19
vreg_5
=
sp
+
0x98
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_17
call
0x2c04
vreg_5
=
sp
+
0x90
sp
+
0x90
=
vreg_22
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_17
call
0x2c04
vreg_16
=
sp
+
0x1c0
vreg_17
=
sp
+
0x1c8
vreg_18
=
sp
+
0x1d0
vreg_19
=
sp
+
0x1d8
vreg_20
=
sp
+
0x1e0
vreg_21
=
sp
+
0x1e8
vreg_22
=
sp
+
0x1f0
vreg_23
=
sp
+
0x1f8
vreg_30
=
sp
+
0x200
vreg_31
=
sp
+
0x208
sp
=
sp
+
0x210
sp
=
sp
+
-
0x210
sp
+
0x208
=
vreg_31
sp
+
0x200
=
vreg_30
sp
+
0x1f8
=
vreg_23
sp
+
0x1f0
=
vreg_22
sp
+
0x1e8
=
vreg_21
sp
+
0x1e0
=
vreg_20
sp
+
0x1d8
=
vreg_19
sp
+
0x1d0
=
vreg_18
sp
+
0x1c8
=
vreg_17
sp
+
0x1c0
=
vreg_16
vreg_16
=
x0 | vreg_7
vreg_11
=
x4
+
0x10
vreg_12
=
x4
+
0x0
vreg_2
=
vreg_6
+
0x0
x3
=
vreg_6
+
0x8
vreg_5
=
vreg_6
+
0x10
vreg_7
=
vreg_6
+
0x18
vreg_8
=
vreg_6
+
0x20
vreg_9
=
vreg_6
+
0x28
vreg_18
=
x4
+
0x18
vreg_10
=
vreg_6
+
0x30
vreg_6
=
vreg_6
+
0x38
vreg_19
=
x4
+
0x8
x1
=
vreg_19
+
0x76
sp
+
0x88
=
x1
x4
=
vreg_18
+
0x0
branch0(先不处理)
未知
x1
=
0xffffffffff220000
x1
=
x1 | (
0x5870
&
0xffff
)
x4
=
x1
+
vreg_6
sp
+
0x40
=
x4
x4
=
x1
+
vreg_10
sp
+
0x38
=
x4
x4
=
x1
+
vreg_9
sp
+
0x30
=
x4
x4
=
x1
+
vreg_8
sp
+
0x20
=
x4
x4
=
x1
+
vreg_7
sp
+
0x18
=
x4
x4
=
x1
+
vreg_5
sp
+
0x0
=
x4
vreg_23
=
x1
+
x3
vreg_30
=
x1
+
vreg_2
vreg_20
=
x0
+
0x20
sp
+
0x1b0
=
vreg_20
vreg_5
=
sp
+
0x1b0
x4
=
x0 | vreg_30
vreg_25
=
x0 | vreg_16
sp
+
0x28
=
vreg_11
vreg_31
=
ip
+
8
sp
+
0x8
=
vreg_12
call
0x2b40
sp
+
0x1a8
=
vreg_20
vreg_21
=
sp
+
0x1b8
sp
+
0x1a0
=
vreg_21
vreg_5
=
sp
+
0x1a0
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_23
call
0x2b5c
vreg_5
=
sp
+
0x190
vreg_22
=
x0
+
0x10
sp
+
0x190
=
vreg_22
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
vreg_5
=
sp
+
0x180
sp
+
0x180
=
vreg_22
vreg_17
=
sp
+
0x198
sp
+
0x10
=
vreg_17
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
sp
+
0x150
=
vreg_21
sp
+
0x158
=
vreg_20
sp
+
0x160
=
vreg_17
sp
+
0x168
=
vreg_22
sp
+
0x178
=
vreg_22
vreg_20
=
sp
+
0x188
sp
+
0x170
=
vreg_20
x4
=
sp
+
0x0
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0x150
call
0x2b94
vreg_17
=
vreg_19
+
0x40
vreg_5
=
sp
+
0x140
sp
+
0x140
=
vreg_17
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2b40
vreg_30
=
sp
+
0x48
vreg_22
=
sp
+
0x148
sp
+
0x130
=
vreg_19
vreg_23
=
sp
+
0x8
sp
+
0x128
=
vreg_23
sp
+
0x138
=
vreg_30
x4
=
sp
+
0x18
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0x128
call
0x2ba8
vreg_5
=
sp
+
0x110
x1
=
x0
+
0x40
sp
+
0x118
=
vreg_30
sp
+
0x110
=
vreg_22
sp
+
0x120
=
x1
vreg_30
=
sp
+
0x20
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2bb8
vreg_5
=
sp
+
0xf8
x1
=
vreg_22
+
0x40
sp
+
0x100
=
vreg_23
sp
+
0xf8
=
x1
sp
+
0x108
=
vreg_19
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
x4
=
x0 | vreg_30
call
0x2bb8
x1
=
x0
+
32
sp
+
0xe8
=
vreg_21
vreg_19
=
sp
+
0x28
sp
+
0xe0
=
vreg_19
sp
+
0xf0
=
x1
x4
=
sp
+
0x30
vreg_25
=
x0 | vreg_16
vreg_31
=
ip
+
8
vreg_5
=
sp
+
0xe0
call
0x2bc8
x1
=
vreg_19
+
0x26
vreg_19
=
sp
+
0x10
vreg_2
=
x0
+
16
x3
=
sp
+
0x88
sp
+
0xa8
=
vreg_19
sp
+
0xb0
=
vreg_2
sp
+
0xb8
=
vreg_20
sp
+
0xc0
=
vreg_22
sp
+
0xc8
=
vreg_17
sp
+
0xd0
=
x1
sp
+
0xd8
=
x3
x1
=
sp
+
0x88
x1
=
x1
+
-
0x26
sp
+
0x88
=
x1
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: