主函数并不复杂,关键内容在vm_opcode中,先提取出main函数中的opcode
unsigned int OpCode[114] = {
0x0000000A, 0x00000004, 0x00000010, 0x00000008, 0x00000003, 0x00000005, 0x00000001, 0x00000004,
0x00000020, 0x00000008, 0x00000005, 0x00000003, 0x00000001, 0x00000003, 0x00000002, 0x00000008,
0x0000000B, 0x00000001, 0x0000000C, 0x00000008, 0x00000004, 0x00000004, 0x00000001, 0x00000005,
0x00000003, 0x00000008, 0x00000003, 0x00000021, 0x00000001, 0x0000000B, 0x00000008, 0x0000000B,
0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002,
0x00000051, 0x00000008, 0x00000004, 0x00000024, 0x00000001, 0x0000000C, 0x00000008, 0x0000000B,
0x00000001, 0x00000005, 0x00000002, 0x00000008, 0x00000002, 0x00000025, 0x00000001, 0x00000002,
0x00000036, 0x00000008, 0x00000004, 0x00000041, 0x00000001, 0x00000002, 0x00000020, 0x00000008,
0x00000005, 0x00000001, 0x00000001, 0x00000005, 0x00000003, 0x00000008, 0x00000002, 0x00000025,
0x00000001, 0x00000004, 0x00000009, 0x00000008, 0x00000003, 0x00000020, 0x00000001, 0x00000002,
0x00000041, 0x00000008, 0x0000000C, 0x00000001, 0x00000007, 0x00000022, 0x00000007, 0x0000003F,
0x00000007, 0x00000034, 0x00000007, 0x00000032, 0x00000007, 0x00000072, 0x00000007, 0x00000033,
0x00000007, 0x00000018, 0x00000007, 0xFFFFFFA7, 0x00000007, 0x00000031, 0x00000007, 0xFFFFFFF1,
0x00000007, 0x00000028, 0x00000007, 0xFFFFFF84, 0x00000007, 0xFFFFFFC1, 0x00000007, 0x0000001E,
0x00000007, 0x0000007A
};
可以在每条指令处加上打印语句获取指令执行顺序,这个操作仅能大概看一下执行流,自动解密不能靠这个
在switch执行前打印i的值以获取opcode的执行流
注意case10的read要改成scanf,case7的比较可以改成赋值
int __cdecl vm_operad1(int* opcode, int len)
{
int result; // eax
unsigned char Str[100]; // [esp+13h] [ebp-E5h] BYREF
unsigned char buffer[100]; // [esp+77h] [ebp-81h] BYREF
unsigned char chr; // [esp+DBh] [ebp-1Dh]
int z; // [esp+DCh] [ebp-1Ch]
int x; // [esp+E0h] [ebp-18h]
int j; // [esp+E4h] [ebp-14h]
int y; // [esp+E8h] [ebp-10h]
int i; // [esp+ECh] [ebp-Ch]
i = 0;
y = 0;
j = 0;
x = 0;
z = 0;
int count = 0;
while (1)
{
result = i;
if (i >= len)
return result;
printf("%d,", i);//打印程序执行流
switch (opcode[i])
{
case 1:
//printf("x=%d,y=%d\n", x, y);
buffer[x] = chr;
++i;
++x;
++y; // 只有这条语句修改了v8
break;
case 2:
chr = opcode[i + 1] + Str[y];
//printf("data[%d]=%d;\n",count++, opcode[i + 1]);
i += 2;
break;
case 3:
chr = Str[y] - LOBYTE(opcode[i + 1]);
//printf("data[%d]=%d;\n", count++, opcode[i + 1]);
i += 2;
break;
case 4:
chr = opcode[i + 1] ^ Str[y];
//printf("data[%d]=%d;\n", count++, opcode[i + 1]);
i += 2;
break;
case 5:
chr = opcode[i + 1] * Str[y];
//printf("data[%d]=%d;\n", count++, opcode[i + 1]);
i += 2;
break;
case 6:
++i;
break;
case 7: // v7只在case7中使用到
buffer[j] = opcode[i + 1];
//printf("cmpdata[%d]=%d;\n", j, buffer[j]);
//if (buffer[j] != opcode[i + 1]) // 出现了15次7,所以flag应该是15字符
//{
// printf("what a shame...");
// exit(0);
//}
++j;
i += 2;
break;
case 8:
//printf("z=%d\n", z);
Str[z] = chr;
++i;
++z;
break;
case 10: // 由于arr[0]==10,所以第一次循环执行的必定是read,所以第一次就要输入Str
scanf("%15s", Str); // strlen=15
++i; // 只执行一次
break;
case 11:
chr = Str[y] - 1;
++i;
break;
case 12:
chr = Str[y] + 1;
++i;
break;
default:
continue;
}
}
}
最终可以得到一个op数组存储执行流
unsigned int op[76] = {0,1,3,4,6,7,9,10,12,13,15,16,17,18,19,20,22,23,25,26,28,29,30,31,32,33,35,36,38,39,41,42,44,45,46,47,48,49,51,52,54,55,57,58,60,61,63,64,66,67,69,70,72,73,75,76,78,79,81,82,83,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112 };
也可以打印case7的比较操作来获取最终用于比较的数据
unsigned char data[15] = { 0X22, 0X3F, 0X34, 0X32, 0X72, 0X33, 0X18, 0XFFFFFFA7, 0X31, 0XFFFFFFF1, 0X28, 0XFFFFFF84, 0XFFFFFFC1, 0X1E, 0X7A };
不获取这个data数组也没问题,因为数据保存在opcode数组中,只要执行流正确就可以
将每条case的指令修改为逆指令,倒序执行指令流即可得到flag
注意这里的几个数组最好用unsigned char,用char会导致一些错误
int __cdecl vm_operad(int* opcode, int len)
{
unsigned char data[15] = { 0X22, 0X3F, 0X34, 0X32, 0X72, 0X33, 0X18, 0XFFFFFFA7, 0X31, 0XFFFFFFF1, 0X28, 0XFFFFFF84, 0XFFFFFFC1, 0X1E, 0X7A };
unsigned char flag[100] = { 0 }; // [esp+13h] [ebp-E5h] BYREF
unsigned char buffer[15] = {0 }; // [esp+77h] [ebp-81h] BYREF
unsigned char chr=0; // [esp+DBh] [ebp-1Dh]
int z; // [esp+DCh] [ebp-1Ch]
int x; // [esp+E0h] [ebp-18h]
int j; // [esp+E4h] [ebp-14h]
int y; // [esp+E8h] [ebp-10h]
int i; // [esp+ECh] [ebp-Ch]
i = 0;//反向操作
y = 15;
j = 15;
x = 15;
z = 15;
int k = 75;
int count = 0;
while (k>=0)
{
i = op[k];
switch (opcode[i])
{
case 1:
//这里调换一下顺序就可以得到结果
--x;
--y;
printf("x=%d,y=%d\n", x, y);
chr= buffer[x];//先给chr中间变量赋值,后续处理后再赋给flag[y],所以y=15,--y的形式是正确位置
//如果y=14,--y那么后续第一个赋值的位置是flag[13]而不是flag[14]
// 只有这条语句修改了v8
break;
case 2:
printf("using y=%d\n", y);
flag[y]=chr- opcode[i + 1];
break;
case 3:
printf("using y=%d\n", y);
flag[y]= chr + opcode[i + 1];
break;
case 4:
printf("using y=%d\n", y);
flag[y]=(chr^ opcode[i+ 1]);
break;
case 5:
printf("using y=%d\n", y);
flag[y] = chr / opcode[i + 1];
break;
case 6:
break;
case 7: // v7只在case7中使用到
buffer[--j] = opcode[i + 1];
break;
case 8:
z--;
printf("z=%d\n", z);
chr= flag[z] ;//这里也要调换顺序
break;
case 10: // 由于arr[0]==10,所以第一次循环执行的必定是read,所以第一次就要输入Str
printf("read\n");
// printf("%s", flag);
break;
case 11:
printf("using y=%d\n", y);
flag[y]= chr + 1;
break;
case 12:
printf("using y=%d\n", y);
flag[y]=chr-1;
break;
default:
continue;
}
k--;
}
printf("flag{%s}", flag);
}
值得一提的是这里xyzj最好是赋15,每次使用前自减
如果是赋值14,使用后自减,中间变量chr先赋值后,xyzj等的值变成13
导致后续flag[y]赋值时从flag[13]开始而非flag[14]开始
总而言之需要防止使用错误的下标
首先创建python虚拟环境安装angr,防止依赖冲突
注意尽量在linux环境使用,windows会出一些bug
pip install angr 即可安装
脚本
要将signal.exe放到和脚本同一文件夹内
import angr
project = angr.Project('signal.exe') #创建项目,加载二进制文件
state = project.factory.entry_state() #创建state
sim = project.factory.simgr(state) #创建sim
sim.explore(find=0x40175e,avoid=0x4016e6) # 希望到达的和避免的分支
if sim.found:
res = sim.found[0]
res = res.posix.dumps(0)
print("[+] Success! Solution is: {}".format(res.decode("utf-8")))
结果
github项目地址Ponce
下载对应系统最新版压缩包,解压后根据ida版本找到对应文件夹
将文件夹中的Ponce.dll和Ponce64.dll复制到ida的plugins文件夹中即可
使用Ponce
设置如下即可
在read中下断点(便于后续符号化str,防止执行其他操作改变了str)
在case7处下断点(后续逐步获取flag就是根据这里)
动态调试,先随便输入一个长度为15的字符串
在read处断下后找到字符串保存地址(这里是61FBC3),右键符号化字符串
按F9,运行到第二个断点,查看流程图,对着jz指令右键 SMT Solver>Negate and Inject to reach
之后会输出答案(第一个)
按f9再次到jz指令处,使用相同的操作最终可以得到flag
2020网鼎杯青龙组部分逆向题
IDA插件Ponce初体验
IDA插件Ponce的使用
[网鼎杯 2020 青龙组]singal
2020网鼎杯青龙组部分逆向题
IDA插件Ponce初体验
IDA插件Ponce的使用
[网鼎杯 2020 青龙组]singal
[re]符号执行一把梭:2020网鼎杯青龙组re_signal_wp