Article directory
direct reverse
Extract opcode
The main function is not complicated. The key content is in vm_opcode. First, extract the opcode in the main function.
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
};
Get instruction execution flow
You can add a print statement to each instruction to obtain the instruction execution sequence. This operation can only give a rough look at the execution flow. Automatic decryption cannot rely on this.
Print the value of i before switch execution to obtain the execution flow of opcode
Note that the read in case10 can be changed to scanf, and the comparison in case7 can be changed to assignment.
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;
}
}
}
Finally, you can get an op array to store the execution flow
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 };
You can also print the comparison operation of case7 to obtain the final data used for comparison.
unsigned char data[15] = {
0X22, 0X3F, 0X34, 0X32, 0X72, 0X33, 0X18, 0XFFFFFFA7, 0X31, 0XFFFFFFF1, 0X28, 0XFFFFFF84, 0XFFFFFFC1, 0X1E, 0X7A };
There is no problem if you don’t get this data array, because the data is stored in the opcode array, as long as the execution flow is correct.
getflag
Modify the instructions of each case into reverse instructions, and execute the instruction flow in reverse order to get the flag.
Note that it is best to use unsigned char for the arrays here. Using char will cause some errors.
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);
}
Notice
It is worth mentioning that it is best to assign 15 to xyzj here, and decrement it before each use.
If it is assigned a value of 14, it will be decremented after use. After the intermediate variable chr is assigned a value first, the value of xyzj, etc. becomes 13.
As a result, subsequent flag[y] assignments start from flag[13] instead of flag[14].
In summary, it is necessary to prevent the use of wrong subscripts
Using Angr
First create a python virtual environment to install angr to prevent dependency conflicts
Be careful to use it in a Linux environment as much as possible. Windows will have some bugs.
pip install angr to install
Script
To put signal.exe in the same folder as the script
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")))
result
Using the Ponce plugin
Install and configure Ponce
github project addressPonce
Download the latest version of the compressed package corresponding to the system, and after unzipping, find the corresponding folder according to the ida version.
Copy Ponce.dll and Ponce64.dll in the folder to the plugins folder of ida
Use Ponce
Set up config first
Just set it up as follows
Set a breakpoint in read (to facilitate subsequent symbolization of str and prevent other operations from changing str)
Set a breakpoint at case7 (the subsequent step-by-step acquisition of the flag is based on this)
Specific operations
Dynamic debugging, first enter a string of length 15
After breaking off at read, find the string storage address (here is 61FBC3), right-click to symbolize the string
Press F9, run to the second breakpoint, view the flow chart, right-click on the jz command SMT Solver>Negate and Inject to reach
The answer will be output later (the first one)
Press f9 again to go to the jz command, and use the same operation to finally get the flag