虎符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, 和一部分循环的结构。
一个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就储存着i
和j
, 还有后面循环的i
也都是在block中储存的,
记录下用到了的 一些指令, 格式, 简单的标记, 后面有参数的标注已经写了参数的意义(如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]]
,
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]
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]
opcode(0x2) ==> 0x2, ==> print vm_stack[vm_sp]
打印栈顶的数值对应字符。
opcode(0x1) ==> 0x1, ==> push input
, 接收一个用户输入字节并压入栈中,
其中的语句构成一些结构,我们需要注意,
压栈,然后将栈顶弹出到arr中,其实就相当于是向arr里面赋值,一开始大片的0x4 -0x8
是赋值arr1, 接着就是0x1-0x8
是设置好arr2,
0x4-0x6是设置block内的值,0x5-0x4-0x16是判断block里的值和0x4参数的大小, 在code中出现过三次,都是循环的开头部分,
0x1d 后面的参数有负数的情况,其实就是相当于减法,回跳,一般配合前面这个0x5-0x4-0x16,形成一个循环结构,
最后可以得到的还原出来大致是:
该文件在附件中,在附件中有,
可以点下右侧那个箭头,展开代码块, 里面是比较详细的标注和对应的opcode,
注:
那个 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卡死崩溃, 又一个历史遗留问题? 还没找到啥办法,(好丢人),
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-5-2 02:43
被令则编辑
,原因: 修改关于block的位置
上传的附件: