首页
论坛
课程
招聘

[原创]虎符第三题 -vm 虚拟机逆向

2020-4-24 16:16 2747

[原创]虎符第三题 -vm 虚拟机逆向

2020-4-24 16:16
2747

虎符ctf -- vm

 

目录

一个 调了好久好久的虚拟机题目

原本比赛的时候是上午做了个pyc, 下午开始调vm, 调到结束也没弄出来个道道,tcl, 就开始等待大佬们的wp, 好几天过去, 似乎没有???

走投无路的菜鸡只好自己慢慢逆。。然后写下这个题目的wp。 欢迎围观

虎符ctf, vm题目, 个人觉得还是一个比较不错的虚拟机,指令好多,不太好调。

几个简单的虚拟机题目, 我的博客有一个整理, 对应的文件应该是在里面有,没有的话,博客主页转csdn里对应文章肯定有(这,是个历史遗留问题...)

 

似乎也没怎么找到比较好的方法,对于虚拟机的题目,就看虚拟机指令然后去还原, 再逆吧,不过发现还是要注意下栈堆机器和寄存器机器是不一样,

 

这个题目给出的就是一个栈堆机器,所以里面有一部分处理栈的指令,用了python字节码去标注,(pyc字节码:"哈哈,没想到吧!还是我!"),

vm

首先题目的main函数比较简单:

 


注意下打开文件是用的参数。即运行时要指定参数./vm code , ida调试要在debugger -> process options -> parameters写上参数code,

 

重点在于函数vm(code):

 

 

比较典型的一个while(1)switch(opcode)的结构做的虚拟机,

 

然后运行的大体情况, 如下示:

对于详细的可以看附件中的code.py文件, 本文简单写下data架构体和opcode, 和一部分循环的结构。

data

.bss:00000000006020A0   : bss_data 
bss_data{
    Dword vm_eip;
    Dword vm_sp;
    Qword code; // *(bss_data+1)
    Qword vm_stack;  // *(bss_data+2)
    Qword vm_var_arr;  //*(bss_data+3)
    Qword vm_block;   // *(bss_data+4)
}

一个bss段的一个结构体, 主要我们运行的时候储存信息:

 

vm_eip一个数字, 代表偏移量,使用这个配合code地址检索到opcode,opcode = *(&code + vm_eip)

 

vm_sp: 代表栈内数据数目,也配合栈地址形成指向栈顶的指针,

 

code: 这个就是储存code, 没啥要说的,

 

vm_stack: 这个是这个栈堆机器操作的栈,

 

vm_arr: 一片内存空间,主要用于储存三个数组, 一个预定义好了的arr1(在50-91), 一个用户输入的arr2(在100-141), 一个由arr2处理成的arr3(在0-41),对arr的处理是重点

 

vm_block: 主要是用于储存循环时的计数器。

比如题目中在for i in range(7): for j in range(6)的里面, bolck就储存着ij, 还有后面循环的i也都是在block中储存的,

opcode

记录下用到了的 一些指令, 格式, 简单的标记,
后面有参数的标注已经写了参数的意义(如index,var啥的), 在代码中的*(&code + vm_eip + 1), 就是获取到指令后面的参数了

  • 基础的对栈的操作

opcode(0x1) ==> 0x1, ==> push input, 接收一个用户输入字节并压入栈中,

 

opcode(0x4) ==> 0x4, var, ==> push var;

 

opcode(0x7) ==> 0x7, index, ==> push vm_arr[index],

 

opcode(0x5) ==> 0x5, index, ==> push vm_block[index]

 

opcode(0x12) ==> 0x12, ==> vm_stack[vm_sp] = ~vm_stack[vm_sp] 对栈顶数值取反。

 

opcode(0x19) ==> 0x19, ==> vm_stack[vm_sp-1] = vm_arr[vm_stack[vm_sp-1]]

  • 运算, 这里标记使用了python字节码的标记,基本是弹出栈顶两个,运算后压栈,

opcode(9) ==> 9 ==> binary_add 加法 +

 

opcode(0xa) ==> 0xa ==> binary_subtract 减法-

 

opcode(0xb) ==> 0xb ==> binary_multiply乘法 ×

 

opcode(0xd) ==> 0xd ==> binary_modulo 取余 %

 

opcode(0xf) ==> 0xf ==> binary_and 按位与 &

 

opcode(0x10) ==> 0x10 ==> binary_or 按位或 |

  • 判断和跳转:

opcode(0x1d) ==> 0x1d, tar, ==>jump $+tar, 直接跳转到参数指定的位置,但是要注意,有时候这个参数其实是负数, 回跳,形成一个循环结构。

 

剩下几个都是,判断栈顶两个值,决定是否跳转到参数指定的位置,

 

opcode(0x18) ==> 0x18, tar, ==> if vm_stack[vm_sp] < vm_stack[vm_sp-1]: jump $+tar

 

opcode(0x16) ==> 0x16, tar, ==> if vm_stack[vm_sp] > vm_stack[vm_sp-1]: jump $+tar

 

opcode(0x14) ==> 0x14, tar, ==> if vm_stack[vm_sp] == vm_stack[vm_sp-1]: jump $+tar,

  • 对于数组的操作:( 这个可是重点)

opcode(8) ==> 8, index, ==> vm_arr[index] = vm_stack[vm_sp];

 

opcode(0x1a) ==> 0x1a, ==> vm_arr[vm_stack[vm_sp]] = vm_stack[vm_sp-1]

  • 对于block的操作, 和和循环相关:

opcode(0x5) ==> 0x5, index, ==> push vm_block[index]

 

opcode(0x6) ==> 0x6, ==> vm_block[var] = vm_stack[vm_sp]

 

opcode(0x1c) ==> 0x1c, ==> vm_block[vm_stack[vm_sp]] = vm_stack[vm_sp-1]

  • io

opcode(0x2) ==> 0x2, ==> print vm_stack[vm_sp]打印栈顶的数值对应字符。

 

opcode(0x1) ==> 0x1, ==> push input, 接收一个用户输入字节并压入栈中,

一些结构

其中的语句构成一些结构,我们需要注意,

  • 0x1/0x4 - 0x8

压栈,然后将栈顶弹出到arr中,其实就相当于是向arr里面赋值,一开始大片的0x4 -0x8 是赋值arr1, 接着就是0x1-0x8是设置好arr2,

  • 0x4-0x6, 0x5-0x4-0x16:

0x4-0x6是设置block内的值,0x5-0x4-0x16是判断block里的值和0x4参数的大小, 在code中出现过三次,都是循环的开头部分,

  • 0x1D

0x1d 后面的参数有负数的情况,其实就是相当于减法,回跳,一般配合前面这个0x5-0x4-0x16,形成一个循环结构,

还原

最后可以得到的还原出来大致是:

 

 

该文件在附件中,在附件中有,

可以点下右侧那个箭头,展开代码块, 里面是比较详细的标注和对应的opcode,

逆向

arr1 =  [102, 78, 169, 253, 60, 85, 144, 36, 87, 246, 93, 177, 1, 32, 129, 253, 54, 169, 31, 161, 14, 13, 128, 143, 206, 119, 232, 35, 158, 39, 96, 47, 165, 207, 27, 189, 50, 219, 255, 40, 164, 93]
arr = [0] * 42
arr[0] = arr1[0]
flag = ''

for i in range(1, 42):
    if i % 2 ==0:
        arr[i] = (arr1[i] - arr1[i-1]) & 0xff
    if i % 2 ==1:    
        for j in range(0xff):
            if (j * 0x6b) & 0xff == arr1[i]:
                arr[i] = j

for j in range(7):
    for i in range(6):
        flag += chr(arr[i * 7+j] ^ ((i + 2) * j))

print(flag)

注:

 

那个 var * 0x6b 的位置可以print下, 爆破每个都是只爆破出来一个数,直接爆破即可。

 

设置了另一个arr的原因是,arr1[i] - arr1[i-1], 减的时候arr[i-1]已经是arr[i]*0x6b处理了, 要逆得倒着写, 写了下没成功,弄起来感觉有点麻烦(其实是菜),直接设置另一个就直接用就好了 (看起来就比较优雅)。

 

然后后面的那个异或是对于前面那个花里胡稍的操作, 其实就是个异或关系,即a ^ b = (~a & b) | (a & ~b)

 

然后得到flag:

 

最后

附件中是整个的压缩包,题目的bin和code文件,一个solver.py文件,code.py是代码还原的文件,里面注释还是比较多,

剩下还有一堆vm.*是...ida分析的文件,emmmm manjaro-kde的wine跑ida, 因为qt的原因会在打包的时候ida卡死崩溃, 又一个历史遗留问题? 还没找到啥办法,(好丢人),



[推荐]看雪工具下载站,全新登场!(Android、Web、漏洞分析还未更新)

最后于 2020-5-2 02:43 被令则编辑 ,原因: 修改关于block的位置
上传的附件:
最新回复 (11)
上海刘一刀 2 2020-4-24 16:40
2
0
感谢分享
Highk 2020-4-24 23:03
3
0
感谢分享 :)
冰雄 2020-4-25 11:38
4
0
楼主vm这方面有相关书籍学习吗
Oday小斯 2020-4-25 12:14
5
0
感谢分享
令则 2020-4-25 12:27
6
0

我记得加密解密里面有个虚拟机构造的一章, 讲的是寄存器机器,我之前有个关于python解释器的帖子,也算是讲的一个栈堆机器。有关于两种机器的底层运行原理啥的,应该会是对虚拟机的理解有些帮助。最近发现, 看雪大佬整理的一个专题帖子

最后于 2020-4-30 22:42 被令则编辑 ,原因: tcl
菜鸟m号 2020-4-25 21:21
7
0
挺好
ZwCopyAll 2020-4-26 09:52
8
0
vm_block: 这个储存另一部分数据,主要是用于储存循环时的计数器,是一个python解释器中block_stack的地位,vm_block到底是啥 没看懂
令则 2020-4-26 12:30
9
0
ZwCopyAll vm_block: 这个储存另一部分数据,主要是用于储存循环时的计数器,是一个python解释器中block_stack的地位,vm_block到底是啥 没看懂

在循环结构中保存循环计数器。比如题目中在for i in range(7): for j in range(6)的里面, bolck就储存着i和j, 还有后面循环的i也都是在block中储存的, (在python中循环都会使用一个block_stack,用来处理循环中的计数器,也就因此命名了),就是这个意思啦,文章已修改

肥宅小盆友 2020-4-26 17:52
10
0
感谢分享
iyzyi 2020-4-28 19:08
11
0
感谢令则师傅,第一次接触基于栈的vm,看了两天,终于做出来了
cook1es 2020-4-28 22:42
12
0
感谢分享
游客
登录 | 注册 方可回帖
返回