虎符第三题 -vm 虚拟机逆向
2020-04-24 22:24:49 Author: bbs.pediy.com(查看原文) 阅读量:635 收藏

虎符ctf -- vm

目录

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

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

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

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

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

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

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

首先题目的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_reg;   // *(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: 这个储存另一部分数据,主要是用于储存循环时的计数器,是一个python解释器中block_stack的地位,

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卡死崩溃, 又一个历史遗留问题? 还没找到啥办法,(好丢人),

[培训]《安卓高级研修班(网课)》6月班开始招生!一年后遇见不一样的自己!(现在进入看雪课程可以试看两节)

最后于 2小时前 被kanxue编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-259116.htm
如有侵权请联系:admin#unsafe.sh