[网鼎杯 2020 青龙组]singal详细题解--VMP 直接逆向,angr模拟执行,ponce符号化

直接逆向

提取opcode

主函数并不复杂,关键内容在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数组中,只要执行流正确就可以

getflag

将每条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]开始

总而言之需要防止使用错误的下标

请添加图片描述

使用Angr

首先创建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")))

结果

在这里插入图片描述

使用Ponce插件

安装并配置Ponce

github项目地址Ponce

下载对应系统最新版压缩包,解压后根据ida版本找到对应文件夹

将文件夹中的Ponce.dll和Ponce64.dll复制到ida的plugins文件夹中即可
在这里插入图片描述

使用Ponce

先设置config
在这里插入图片描述

设置如下即可

在这里插入图片描述

在read中下断点(便于后续符号化str,防止执行其他操作改变了str)

请添加图片描述

在case7处下断点(后续逐步获取flag就是根据这里)

请添加图片描述

具体操作

动态调试,先随便输入一个长度为15的字符串

在read处断下后找到字符串保存地址(这里是61FBC3),右键符号化字符串

请添加图片描述
请添加图片描述

按F9,运行到第二个断点,查看流程图,对着jz指令右键 SMT Solver>Negate and Inject to reach

请添加图片描述

之后会输出答案(第一个)

请添加图片描述

按f9再次到jz指令处,使用相同的操作最终可以得到flag

请添加图片描述

参考资料

  1. 2020网鼎杯青龙组部分逆向题

  2. IDA插件Ponce初体验

  3. IDA插件Ponce的使用

  4. [网鼎杯 2020 青龙组]singal

  5. 2020网鼎杯青龙组部分逆向题

  6. IDA插件Ponce初体验

  7. IDA插件Ponce的使用

  8. [网鼎杯 2020 青龙组]singal

  9. [re]符号执行一把梭:2020网鼎杯青龙组re_signal_wp

猜你喜欢

转载自blog.csdn.net/OrientalGlass/article/details/132822924
今日推荐