最后用angr跑一下结果就出来了。比赛半天没想到,一直在哪僵着看算法。
# coding=utf8
import angr
import claripy
a = angr.Project('cyvm')
b= [claripy.BVS('argv %d' % i, 8) for i in range(32)]
c = claripy.Concat(*b)
st = a.factory.blank_state(addr=0x400CB1)
sm = a.factory.simulation_manager(st)
sm.explore(find=0x400CD2)
found = sm.found[0]
print sm.found[0].posix.dumps(0)
图片.png
正好一直在弄vm ,比赛为了快速出结果angr最好。结束之后还是按照流程分析一下程序吧。
图片.png进入主函数,看到40068有两个形参。看下byte_602100数组的内容是多少。
图片.png
之后进入函数,找到内核部分。
图片.png
可以清楚的看出是vm的类型。
- 先mark一下vm的概念逻辑
虚拟机通常是围绕一个解码 和执行VM指令的主循环构建的,通过循环带有一个转发器,它调用一个函数要在函数列表里进行挑选;对应程序里面这块跳转列表。
图片.png跟进每个偏移,观察每部分的开头和结尾的跳转部分。
图片.png 图片.png
除40089d,400c6e其余的结尾都跳转到400c72位置。
图片.png
暂时只知道比较,具体是什么数值后面再判断。
将这些按照下面找到的对照参考。引用的是看雪论坛:(https://bbs.pediy.com/thread-247037.htm)
- 建立虚拟机Context
2 进入虚拟机循环
3 读取VM.EIP地址处的字节码,检查指令的类型,以下是支持的指令类型:
4 二进制指令
5 一元指令
6 流程控制指令
7 特殊指令
8 调试指令
9 NOP和HLT("退出VM")指令。-后者结束虚拟机循环。
10 跳转到VM循环的起始处。
VM的初始化块/初始化函数
执行VM程序中的指令的循环块/循环函数
通用块/函数,用于解码VM指令的参数、寄存器、寻址模式以及VM创造者任何想要解码的东西
一个用来执行每个VM指令任务的列表块。这些指令大致相当于现代CPU中用于分解和执行常见ASM指令的微代码。
一组宏指令,VM相关,不容易映射到ASM操作码,这些指令可能更难理解。
分析流程
程序首先有许多宏定义,这些宏定义可以是程序中的寄存器,也可以是指令。之后进入循环,通过类似switch-case:操作(handler),对符合条件的数据依次从定义好的数组中读出,组合成我们熟悉的汇编。
while ( 2 )
{
if ( v4 < a2 )
{
switch ( *(v4 + a1) ) //byte_602100 处的数据
{
case 15:
__isoc99_scanf(&unk_400D78, &s);
v5 += strlen(&s);
++v4;
continue;
case 3:
*(&v6 + *(v4 + 1LL + a1) - 20) = *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 16:
*(&v6 + *(v4 + 1LL + a1) - 20) = *(v4 + 2LL + a1);
v4 += 3;
continue;
case 1:
*(&s + *(&v6 + *(v4 + 2LL + a1) - 20)) = *(&v6 + *(v4 + 1LL + a1) - 20);
v4 += 3;
continue;
case 2:
*(&v6 + *(v4 + 1LL + a1) - 20) = *(&s + *(&v6 + *(v4 + 2LL + a1) - 20));
v4 += 3;
continue;
case 4:
*(&v6 + *(v4 + 1LL + a1) - 20) += *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 17:
*(&v6 + *(v4 + 1LL + a1) - 20) += *(v4 + 2LL + a1);
v4 += 2;
continue;
case 6:
*(&v6 + *(v4 + 1LL + a1) - 20) ^= *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 5:
*(&v6 + *(v4 + 1LL + a1) - 20) -= *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 18:
++*(&v6 + *(v4 + 1LL + a1) - 20);
v4 += 2;
continue;
case 19:
--*(&v6 + *(v4 + 1LL + a1) - 20);
v4 += 2;
continue;
case 11:
*(&v6 + *(v4 + 1LL + a1) - 20) |= *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 7:
*(&v6 + *(v4 + 1LL + a1) - 20) &= *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 10:
v8 = *(&v6 + *(v4 + 1LL + a1) - 20) != *(&v6 + *(v4 + 2LL + a1) - 20);
v4 += 3;
continue;
case 12:
if ( v8 )
v4 = *(v4 + 1LL + a1);
else
v4 += 2;
continue;
case 13:
printf("%d,", *(&v6 + *(v4 + 1LL + a1) - 20));
v4 += 2;
continue;
case 9:
v4 = *(v4 + 1LL + a1);
continue;
default:
++v4;
continue;
case 14:
goto LABEL_24;
}
}
break;
}
reg[0] = 0x20
reg[2] = 0
jmp 36
reg[1] = s[reg[2]]
reg[2]++
reg[3] = s[reg[2]]
reg[2]--
reg[1] ^= reg[3]
reg[1] ^= reg[2]
s[reg[2]] = reg[1]
reg[2]++
(会看着很乱,按照流程就能写出来)
作用就是判断字符串的长度,将数组中的字符和他后一位进行异或,之后再和它当前位置异或,在和储存的结果进行比较。
按照此处结果写逆运算:
poc
#include<stdio.h>
int main()
{
int a[]={0x0A,0x0C,0x04,0x1F,0x48,0x5A,0x5F,0x03,0x62,0x67,0x0E,0x61,0x1E,0x19,0x08,0x36,0x47,0x52,0x13,0x57,0x7C,0x39,0x54,0x4B,0x05,0x05,0x45,0x77,0x15,0x26,0x0E,0x62};
for (int i=0;i<32;i++){
a[i]=a[i]^i;
}
for(int i=31;i>=0;i--)
{
a[i-1]=a[i-1]^a[i];
}
for (int i=0;i<32;i++)
{
printf("%c",a[i]);
}
return 0;
}
网友评论