[NetDing Cup 2020 Qinglong Group] Singal detailed problem solution--VMP direct reverse, angr simulation execution, ponce symbolization

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

Please add image description

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.

Insert image description here

pip install angr to install

Insert image description here

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

Insert image description here

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
Insert image description here

Use Ponce

Set up config first
Insert image description here

Just set it up as follows

Insert image description here

Set a breakpoint in read (to facilitate subsequent symbolization of str and prevent other operations from changing str)

Please add image description

Set a breakpoint at case7 (the subsequent step-by-step acquisition of the flag is based on this)

Please add image description

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

Please add image description
Please add image description

Press F9, run to the second breakpoint, view the flow chart, right-click on the jz command SMT Solver>Negate and Inject to reach

Please add image description

The answer will be output later (the first one)

Please add image description

Press f9 again to go to the jz command, and use the same operation to finally get the flag

Please add image description

References

  1. 2020 Netding Cup Qinglong Group Partial Reverse Questions

  2. First experience with IDA plug-in Ponce

  3. Use of IDA plug-in Ponce

  4. [Netding Cup 2020 Qinglong Group] singal

  5. 2020 Netding Cup Qinglong Group Partial Reverse Questions

  6. First experience with IDA plug-in Ponce

  7. Use of IDA plug-in Ponce

  8. [Netding Cup 2020 Qinglong Group] singal

  9. [re]Symbol execution: 2020 Netding Cup Qinglong Group re_signal_wp

Guess you like

Origin blog.csdn.net/OrientalGlass/article/details/132822924