BUUCTF_[网鼎杯 2020 青龙组]singal

BUUCTF_[网鼎杯 2020 青龙组]singal

今天尝试BUUCTF中的Reverse[网鼎杯 2020 青龙组]singal题目,下载附件后是一个32位的.exe文件,点击打开后是这样的
在这里插入图片描述
使用32位的IDA打开后找到字符串界面:
在这里插入图片描述
这里可能与想要找的flag有关,点击后跳转到对应的位置:
在这里插入图片描述
使用‘ctrl + x’对变量使用交叉引用找到对’aGoodTheAnswerF’进行操作的具体代码:
在这里插入图片描述
然后使用F5反汇编得到:
在这里插入图片描述
_main()函数应该是初始化一些变量,qmemcpy()函数是将指定地址unk_403040位置处复制长度0x1C8的数据到v4中,vm_operad()函数是对v4进行操作:

int __cdecl vm_operad(int *a1, int a2)
{
  int result; // eax
  char v3[100]; // [esp+13h] [ebp-E5h]
  char v4[100]; // [esp+77h] [ebp-81h]
  char v5; // [esp+DBh] [ebp-1Dh]
  int v6; // [esp+DCh] [ebp-1Ch]
  int v7; // [esp+E0h] [ebp-18h]
  int v8; // [esp+E4h] [ebp-14h]
  int v9; // [esp+E8h] [ebp-10h]
  int v10; // [esp+ECh] [ebp-Ch]

  v10 = 0;
  v9 = 0;
  v8 = 0;
  v7 = 0;
  v6 = 0;
  while ( 1 )
  {
    result = v10;
    if ( v10 >= a2 )                            // a2 = 114
      return result;
    switch ( a1[v10] )
    {
      case 1:
        v4[v7] = v5;
        ++v10;
        ++v7;
        ++v9;
        break;
      case 2:
        v5 = a1[v10 + 1] + v3[v9];
        v10 += 2;
        break;
      case 3:
        v5 = v3[v9] - LOBYTE(a1[v10 + 1]);
        v10 += 2;
        break;
      case 4:
        v5 = a1[v10 + 1] ^ v3[v9];
        v10 += 2;
        break;
      case 5:
        v5 = a1[v10 + 1] * v3[v9];
        v10 += 2;
        break;
      case 6:
        ++v10;
        break;
      case 7:
        if ( v4[v8] != a1[v10 + 1] )
        {
          printf("what a shame...");
          exit(0);
        }
        ++v8;
        v10 += 2;
        break;
      case 8:
        v3[v6] = v5;
        ++v10;
        ++v6;
        break;
      case 10:                                  // 只有case10有读取数据的函数,所以开始数据为'10'
        read(v3);                               // 读取数据
        ++v10;
        break;
      case 11:
        v5 = v3[v9] - 1;
        ++v10;
        break;
      case 12:
        v5 = v3[v9] + 1;
        ++v10;
        break;
      default:
        continue;
    }
  }
}

这个函数就是通过switch分支循环判断传入数组a1的值,在不同情况下对a1进行操作,值得注意的是case10分支下有一个read()函数,可以看出这里是通过键盘键入一个15长度的字符串保存到v3数组中,v3就是保存的输入。
在这里插入图片描述
经过分析可以看出vm_operad()函数读取v3数组的内容,对v3进行一些操作后将计算结果暂存到v5中,在case1分支下v5将数据传递给了v4数组,可以看出v5用于传递中间结果,v4数组可能是flag:
在这里插入图片描述
v4数组被赋值后在case7分支下和被传入的参数a1数组的值进行比较,判断是否相等:
在这里插入图片描述
可以看出v4并不是要找的flag,需要键盘输入的v3才是flag,对键入的值进行不同的操作后计算出v4,v4再和a1的值进行比较从而校验键盘输入的数据是否为flag,所以把计算的过程逆向就可求出v3的值。
首先需要提取出a1数组的值,即地址unk_403040处长为0x1C8的数据,在IDA中写入Python脚本对数据进行提取:
在这里插入图片描述
已知开始的地址为0x403040,取长度为0x1C8(即456)的数据,而v4为int型,占4个字节,所以使用Dword提取:(double word 4个字节)

addr = 0x403040
arr = []

for i in range(114): # 0x1c8/4 = 114
    arr.append(Dword(addr + 4*i))

print(arr)

提取后的结果为:

[10L, 4L, 16L, 8L, 3L, 5L, 1L, 4L, 32L, 8L, 5L, 3L, 1L, 3L, 2L, 8L, 11L, 1L, 12L, 8L, 4L, 4L, 1L, 5L, 3L, 8L, 3L, 33L, 1L, 11L, 8L, 11L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 81L, 8L, 4L, 36L, 1L, 12L, 8L, 11L, 1L, 5L, 2L, 8L, 2L, 37L, 1L, 2L, 54L, 8L, 4L, 65L, 1L, 2L, 32L, 8L, 5L, 1L, 1L, 5L, 3L, 8L, 2L, 37L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 65L, 8L, 12L, 1L, 7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L]

可以看出提取的a1数组开始的部分为10,所以首先会进入case10进行键盘输入,这与上面分析一致。
为了方便分析,我自己写了一个demo,将case10中if条件注释:

#include <iostream>
#include<windows.h>
#include<string>
#include <stdlib.h>
#include <iomanip> 
using namespace std;

size_t read(char *a1)
{
    size_t result; // eax

    printf("string:");
    scanf("%s", a1); 
    // cout<<"v4"<<"\t\t"<<"a1"<<endl;
    result = strlen(a1);
    if ( result != 15 )
    {
        puts("WRONG!\n");
        exit(0);
    }
    return result;
}

int vm_operad(unsigned int *a1, int a2)
{
    int result; // eax
    char v3[100]; // [esp+13h] [ebp-E5h]
    char v4[100]; // [esp+77h] [ebp-81h]
    char v5; // [esp+DBh] [ebp-1Dh] temp值
    int v6; // [esp+DCh] [ebp-1Ch]
    int v7; // [esp+E0h] [ebp-18h]
    int v8; // [esp+E4h] [ebp-14h]
    int v9; // [esp+E8h] [ebp-10h]
    int v10; // [esp+ECh] [ebp-Ch]
    unsigned int temp;
    

    v10 = 0;    // a1下标
    v9 = 0;     // v3赋值下标
    v8 = 0;     // case 7判断时v4值下标
    v7 = 0;     //v4下标
    v6 = 0;     //v3被赋值下标
    temp = 0;
    while ( 1 )
    {
        result = v10;
        if ( v10 >= a2 )
            return result;
        switch ( a1[v10] )
        {
            case 1:
                v4[v7] = v5;
                ++v10;
                ++v7;
                ++v9;
                break;
            case 2:
                v5 = a1[v10 + 1] + v3[v9];
                v10 += 2;
                break;
            case 3:
                v5 = v3[v9] - LOBYTE(a1[v10 + 1]); // LOBYTE()   取32bit数的低16bit  
                v10 += 2;
                break;
            case 4:
                v5 = a1[v10 + 1] ^ v3[v9];
                v10 += 2;
                break;
            case 5:
                v5 = a1[v10 + 1] * v3[v9];
                v10 += 2;
                break;
            case 6:
                ++v10;
                break;
            case 7:
                temp = v4[v8];
                // cout<<setw(16)<<setiosflags(ios::left); 
                // cout<<temp<<a1[v10 + 1]<<endl;
                if ( temp != a1[v10 + 1] ) // v4 = {34L, 63L, 52L, 50L,  114L,  51L, 24L, 4294967207L, 49L, 4294967281L, 40L, 4294967172L, 4294967233L, 30L, 122L}
                {
                    // printf("what a shame...");
                    // exit(0);
                }
                ++v8;
                v10 += 2;
                break;
            case 8:
                v3[v6] = v5;
                ++v10;
                ++v6;
                break;
            case 10:
                read(v3);
                ++v10;
                break;
            case 11:
                v5 = v3[v9] - 1;
                ++v10;
                break;
            case 12:
                v5 = v3[v9] + 1;
                ++v10;
                break;
            default:
                continue;
        }
    }
}

int main()
{
    unsigned int a[] =  {10L, 4L, 16L, 8L, 3L, 5L, 1L, 4L, 32L, 8L, 5L, 3L, 1L, 3L, 2L, 8L, 11L, 1L, 12L, 8L, 4L, 4L, 1L, 5L, 3L, 8L, 3L, 33L, 1L, 11L, 8L, 11L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 81L, 8L, 4L, 36L, 1L, 12L, 8L, 11L, 1L, 5L, 2L, 8L, 2L, 37L, 1L, 2L, 54L, 8L, 4L, 65L, 1L, 2L, 32L, 8L, 5L, 1L, 1L, 5L, 3L, 8L, 2L, 37L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 65L, 8L, 12L, 1L,  7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L};
    // cout<<sizeof(a) / sizeof(a[0])<<endl;
    vm_operad(a,114);
    puts("good,The answer format is:flag {}");
    return 0;
}

因为case7分支下对v4数组进行判断,所以a1中紧跟着7之后的数据可能为正确v4的值,即:

v4[] = {34L, 63L, 52L, 50L,  114L,  51L, 24L, 4294967207L, 49L, 4294967281L, 40L, 4294967172L, 4294967233L, 30L, 122L}

随机键入15个字符,运行demo验证一下:
在这里插入图片描述
现在已知了正确的v4值,在case1分支下v5将计算得到的值赋给v4,所以可以知道case1分支相当于将存储之前进行的计算的结果,即a1中数字‘1‘之前的部分操作就是根据v3的值计算求解出v4,在读取a1的值为1时存储结果,然后在计算v4下一位的结果,所以a1数组的操作可以分解为:

10L, //输入
4L, 16L, 8L, 3L, 5L, 1L, // 1次运算
4L, 32L, 8L, 5L, 3L, 1L, 
3L, 2L, 8L, 11L, 1L, 
12L, 8L, 4L, 4L, 1L, 
5L, 3L, 8L, 3L, 33L, 1L, 
11L, 8L, 11L, 1L,
4L, 9L, 8L, 3L, 32L, 1L, 
2L, 81L, 8L, 4L, 36L, 1L, 
12L, 8L, 11L, 1L, 
5L, 2L, 8L, 2L, 37L, 1L, 
2L, 54L, 8L, 4L, 65L, 1L, 
2L, 32L, 8L, 5L, 1L, 1L,
5L, 3L, 8L, 2L, 37L, 1L, 
4L, 9L, 8L, 3L, 32L, 1L, 
2L, 65L, 8L, 12L, 1L, // 刚好15次计算

7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L// 这些是在验证v4

接下来就是根据每一次计算的结果逆向求出v3的值:

//v4[0] = (a1[2] ^ v3[0]) - LOBYTE(a1[5]); -> v3[0] = (v4[0] + LOBYTE(a1[5])) ^ a1[2];
v5 = a1[2] ^ v3[0];
v3[0] = v5;
v5 = v3[0] - LOBYTE(a1[5]);
v4[0] = v5; // v4[0] = 34L



//v4[1] = a1[11] * (a1[8] ^ v3[1]); -> v3[1] = (v4[1] / a1[11]) ^ a1[8];
v5 = a1[8] ^ v3[1];
v3[1] = v5;
v5 = a1[11] * v3[1];
v4[1] = v5;


//v4[2] = v3[2] - LOBYTE(a1[14]) -1; -> v3[2] = v4[2] + LOBYTE(a1[14]) +1;
v5 = v3[2] - LOBYTE(a1[14])
v3[2] = v5;
v5 = v3[2] - 1;
v4[2] = v5;


// v4[3] = a1[21] ^ (v3[3] + 1); -> v3[3] = (a1[21] ^ v4[3]) -1;
v5 = v3[3] + 1;
v3[3] = v5;
v5 = a1[21] ^ v3[3];
v4[3] = v5;


//v4[4] = (a1[24] * v3[4]) - LOBYTE(a1[27]); -> v3[4] = (v4[4] + LOBYTE(a1[27])) / a1[24];
v5 = a1[24] * v3[4];
v3[4] = v5;
v5 = v3[4] - LOBYTE(a1[27]);
v4[4] = v5;


//v4[5] = (v3[5] - 1) - 1; -> v3[5] = (v4[5] + 1) + 1;
v5 = v3[5] - 1;
v3[5] = v5;
v5 = v3[5] - 1;
v4[5] = v5;


//v4[6] = (a1[34] ^ v3[6]) - LOBYTE(a1[37]); -> v3[6] = (v4[6] + LOBYTE(a1[37])) ^ a1[34];
v5 = a1[34] ^ v3[6];
v3[6] = v5;
v5 = v3[6] - LOBYTE(a1[37]);
v4[6] = v5;


//v4[7] = a1[43] ^ (a1[40] + v3[7]); -> v3[7] = (v4[7] ^ a1[43]) - a1[40];
v5 = a1[40] + v3[7];
v3[7] = v5;
v5 = a1[43] ^ v3[7];
v4[7] = v5;


//v4[8] = (v3[8] + 1) - 1; -> v3[8] = (v4[8] + 1) - 1;
v5 = v3[8] + 1;
v3[8] = v5;
v5 = v3[8] - 1;
v4[8] = v5;


//v4[9] = a1[53] + (a1[50] * v3[9]); -> v3[9] = (v4[9] - a1[53])/a1[50];
v5 = a1[50] * v3[9];
v3[9] = v5;
v5 = a1[53] + v3[9];
v4[9] = v5;


//v4[10] = a1[59] ^ (a1[56] + v3[10]); -> v3[10] = (v4[10] ^ a1[59]) - a1[56];
v5 = a1[56] + v3[10];
v3[10] = v5;
v5 = a1[59] ^ v3[10];
v4[10] = v5;


//v4[11] = a1[65] * (a1[62] + v3[11]); -> v3[11] = (v4[11] / a1[65]) - a1[62];
v5 = a1[62] + v3[11];
v3[11] = v5;
v5 = a1[65] * v3[11];
v4[11] = v5;


//v4[12] = a1[71] + (a1[68] * v3[12]); -> v3[12] = (v4[12] - a1[71]) /a1[68]; 
v5 = a1[68] * v3[12];
v3[12] = v5;
v5 = a1[71] + v3[12];
v4[12] = v5;


//v4[13] = (a1[74] ^ v3[13]) - LOBYTE(a1[77]); -> v3[13] = (v4[13] + LOBYTE(a1[77])) ^ a1[74];
v5 = a1[74] ^ v3[13];
v3[13] = v5;
v5 = v3[13] - LOBYTE(a1[77]);
v4[13] = v5;


//v4[14] = (a1[80] + v3[14]) + 1; -> v3[14] = (v4[14] - 1) - a1[80];
v5 = a1[80] + v3[14];
v3[14] = v5;
v5 = v3[14] + 1;
v4[14] = v5;

最后写出一个脚本即可逆向求出v3,即flag:

#include <iostream>
#include<windows.h>
using namespace std;

int main()
{
    unsigned int a1[] =  {10L, 4L, 16L, 8L, 3L, 5L, 1L, 4L, 32L, 8L, 5L, 3L, 1L, 3L, 2L, 8L, 11L, 1L, 12L, 8L, 4L, 4L, 1L, 5L, 3L, 8L, 3L, 33L, 1L, 11L, 8L, 11L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 81L, 8L, 4L, 36L, 1L, 12L, 8L, 11L, 1L, 5L, 2L, 8L, 2L, 37L, 1L, 2L, 54L, 8L, 4L, 65L, 1L, 2L, 32L, 8L, 5L, 1L, 1L, 5L, 3L, 8L, 2L, 37L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 65L, 8L, 12L, 1L,  7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L};
    unsigned char v4[] = {34L, 63L, 52L, 50L,  114L,  51L, 24L, 167L, 49L, 241L, 40L, 132L, 193L, 30L, 122L};
    unsigned char v3[15];

    v3[0] = (v4[0] + LOBYTE(a1[5])) ^ a1[2];
    v3[1] = (v4[1] / a1[11]) ^ a1[8];
    v3[2] = v4[2] + LOBYTE(a1[14]) +1;
    v3[3] = (a1[21] ^ v4[3]) -1;
    v3[4] = (v4[4] + LOBYTE(a1[27])) / a1[24];
    v3[5] = (v4[5] + 1) + 1;
    v3[6] = (v4[6] + LOBYTE(a1[37])) ^ a1[34];
    v3[7] = (v4[7] ^ a1[43]) - a1[40];
    v3[8] = (v4[8] + 1) - 1;
    v3[9] = (v4[9] - a1[53])/a1[50];
    v3[10] = (v4[10] ^ a1[59]) - a1[56];
    v3[11] = (v4[11] / a1[65]) - a1[62];
    v3[12] = (v4[12] - a1[71]) /a1[68]; 
    v3[13] = (v4[13] + LOBYTE(a1[77])) ^ a1[74];
    v3[14] = (v4[14] - 1) - a1[80];

    cout<<"flag{";
    for(int i = 0; i < 15; i++)
    {
        // cout<<i<<"  "<<v3[i]<<" "<<char(v3[i])<<endl;
        cout<<char(v3[i]);
    }
    cout<<"}"<<endl;

    return 0;
}

运行结果:在这里插入图片描述
demo验证一下:
在这里插入图片描述
参考了这个大佬的博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值