收获

  • 花指令的去除

  • 将一个变量指向一段代码的地址,然后使用这个变量来作为函数执行

_DWORD *__thiscall sub_41352E(_DWORD *this)  // sub_41352E(v20)
{
	*this = &calt::`vftable';  // this 指向 calt::`vftable'
	result = this;
	this[1] = 1388249934;
	return result;
}

---------------------------------------------------------------------

; const calt::`vftable'
.rdata:0041AC84 38 14 41 00                   ??_7calt@@6B@ dd offset sub_411438
.rdata:0041AC88 00                            db    0
.rdata:0041AC89 00                            db    0
.rdata:0041AC8A 00                            db    0
.rdata:0041AC8B 00                            db    0
.rdata:0041AC8C F0 B3 41 00                   dd offset ??_R4trans@@6B@

---------------------------------------------------------------------

int __cdecl sub_413AE0(int (__thiscall ***a1)(_DWORD, int), int a2)  // sub_413AE0(v20, &v18)
{
	return (**a1)(a1, a2);  // 将 v20 作为函数执行
}
  • IDA 中数据的表示
.rdata:0041ADC0 22 00 00 00 A2 FF FF FF 72 00+xmmword_41ADC0 xmmword 0FFFFFFE600000072FFFFFFA200000022h
.rdata:0041ADC0 00 00 E6 FF FF FF                                                     
.rdata:0041ADD0 00                            db    0
.rdata:0041ADD1 00                            db    0
.rdata:0041ADD2 00                            db    0
.rdata:0041ADD3 00                            db    0

---------------------------------------------------------------------

unsigned int xmmword_41ADC0[4] = {0x00000022, 0xFFFFFFA2, 0x00000072, 0xFFFFFFE6};

(2023年6月10日)【安洵杯 2023】ez_cpp


思路

用 IDA 打开,定位到主函数,发现花指令:

安洵杯2023-ez_cpp1.png

安洵杯2023-ez_cpp2.png

首先 Patch 掉花指令
在 jmp 红色地址的语句处按 快捷键 D 将硬指令第一字节 E8 改为 90
然后使用 快捷键 C 转为代码
最后在 main 起始地址处按 快捷键 P 重新生成 main_0() 函数:

安洵杯2023-ez_cpp4.png

安洵杯2023-ez_cpp3.png

sub_411177() 中也存在花指令:

安洵杯2023-ez_cpp5.png

安洵杯2023-ez_cpp6.png

Patch 掉花指令:

安洵杯2023-ez_cpp7.png

安洵杯2023-ez_cpp8.png

提取 v14[] 的值,定位到几个 xmmword

安洵杯2023-ez_cpp9.png

使用 IDA 提取数据可得:

unsigned int xmmword_41ADC0[4] = {0x00000022, 0xFFFFFFA2, 0x00000072, 0xFFFFFFE6};
   
unsigned int xmmword_41ADA0[4] = {0x00000052, 0xFFFFFF8C, 0xFFFFFFF2, 0xFFFFFFD4};
   
unsigned int xmmword_41AD00[4] = {0xFFFFFFA6, 0x0000000A, 0x0000003C, 0x00000024};
   
unsigned int xmmword_41AD20[4] = {0xFFFFFFA6, 0xFFFFFF9C, 0xFFFFFF86, 0x00000024};
   
unsigned int xmmword_41AD80[4] = {0x00000042, 0xFFFFFFD4, 0x00000022, 0xFFFFFFB6};
   
unsigned int xmmword_41AD60[4] = {0x00000014, 0x00000042, 0xFFFFFFCE, 0xFFFFFFAC};
   
unsigned int xmmword_41AD40[4] = {0x00000014, 0x0000006A, 0x0000002C, 0x0000007C};
   
unsigned int xmmword_41ACE0[4] = {0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE4, 0x0000001E};

得到 v14[32] 的值:

v14 = [0x00000022, 0xFFFFFFA2, 0x00000072, 0xFFFFFFE6,  
       0x00000052, 0xFFFFFF8C, 0xFFFFFFF2, 0xFFFFFFD4,  
       0xFFFFFFA6, 0x0000000A, 0x0000003C, 0x00000024,  
       0xFFFFFFA6, 0xFFFFFF9C, 0xFFFFFF86, 0x00000024,  
       0x00000042, 0xFFFFFFD4, 0x00000022, 0xFFFFFFB6,  
       0x00000014, 0x00000042, 0xFFFFFFCE, 0xFFFFFFAC,  
       0x00000014, 0x0000006A, 0x0000002C, 0x0000007C,  
       0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE4, 0x0000001E]

根据最后的校验逻辑,最后经过处理的 v21 的值就是 v14

已知:

for ( i = 0; i < 10; ++i )
    v15[i] = i + 1;
v5 = v15[0];
v6 = 0;

v7 = sub_411177(v15[5] + v15[4] * (v15[0] + v15[2] + v15[1] - v15[3]) + v17 * v16 * v15[6], 8 * v16);
v8 = 8 * v5;

do
{
	v21[v6] = v7 ^ sub_411177(v21[v6], v8);
    ++v6;
} while ( v6 < 32 );

可以先将上一步的 v21 求出来:
即,先通过 v7 ^ (v14[v6] & 0xFF) 得出 sub_411177(v21[v6], v8) 的结果
(注意 v7 为 char 类型,占一字节,所以 v14[v6] & 0xFF 取低位的一字节进行异或)
然后暴力破解 sub_411177(v21[v6], v8) 中的 v21[v6]

前面还将输入 v21 赋值给 v18,进行了 sub_411136(v19, &v18)sub_411136(v20, &v18) 处理

跟进 sub_411136(),会执行 sub_413AE0(a1, a2)

安洵杯2023-ez_cpp10.png

根据 return (**a1)(a1, a2),这里是将 a1 也就是 v19v20 作为函数执行,a2 也就是 v18 作为参数
也就是 v19v20 其实指向的是两个函数

跟进 sub_4113D4(v20),会执行 sub_4133A4(this)

安洵杯2023-ez_cpp11.png

*this = &calt::`vftable' 将 v20 指向一个函数的地址,跟进 calt::`vftable':

安洵杯2023-ez_cpp12.png

跟进 sub_411438(),会跳转到 sub_4138AF()

安洵杯2023-ez_cpp13.png

安洵杯2023-ez_cpp14.png

sub_411140(this[1], v6) 中发现花指令,Patch 掉:

安洵杯2023-ez_cpp15.png

安洵杯2023-ez_cpp16.png

安洵杯2023-ez_cpp17.png

这个函数的功能是将整数 a1 的二进制表示从高位到低位的每一位值(0 或 1)存储到整型数组 a2 中,并按顺序逐个存储(共 32 位)
sub_411140(this[1], v6)this[1] = 1388249934 转换为 32 位二进制数据存储在数组 v6[]

得到 v6[32] = {0,1,0,1,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,1,0,1,0,0,1,1,1,0}
处理过程把逻辑反一下就行

同理,跟进 sub_4112DA(v19),会执行 sub_41352E(this)

安洵杯2023-ez_cpp18.png

*this = &trans::`vftable' 将 v20 指向一个函数的地址,跟进 trans::`vftable'

sub_41132A() 会跳转到 sub_413972()

安洵杯2023-ez_cpp19.png

安洵杯2023-ez_cpp21.png

安洵杯2023-ez_cpp20.png

其中 this[1] = 'Z'this[2] = 'z',实现的是移位为 13 的凯撒密码,分别处理 a - zA - Z
按照逻辑移位 13 还原即可


脚本

#include <stdio.h>
#include <iostream>

using namespace std;

int sub_411177(int a1, int a2)
{
    int v2; // edx
    int v3; // edi
    int v4; // ebx

    v2 = 0;
    v3 = 0;
    if ( a2 > 0 )
    {
        v4 = a2 - 1;
        do
            v2 |= ((a1 >> v3++) & 1) << v4--;
        while ( v3 < a2 );
    }
    return v2 + 1;
}


void v20(int *key)
{
    // v6[32] 为 1388249934 对应的二进制数:1010010101111110000001101001110
    int v6[32] = {0,1,0,1,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,1,0,1,0,0,1,1,1,0};
    int v4 = 0;
    for (int i = 0; i < 32; i++) {
        if (v4 <= 16) {
            if (v4 >= 16)
                key[v4] ^= 4u;
            else {
                int result = v6[v4];
                if (result) {
                    if (!--result)
                        key[v4] ^= 9u;
                }
                else
                    key[v4] += 2;
            }
        }
        else {
            int result = v6[v4];
            if (result) {
                if (!--result)
                    key[v4] ^= 6u;
            }
            else
                key[v4] += 5;
        }
        ++v4;
    }

    for (int i = 0; i < 32; i++) {
        printf("%c", key[i]);
    }cout<<endl;
}


void v19(int *key)
{
    for (int i = 0; i < 32; i++) {
        if (key[i] >= 'a' && key[i] <= 'z') {
            key[i] = key[i] + 13;
            if (key[i] > 'z')
                key[i] = key[i]  % 'z' + 'a' - 1;
        }

        if (key[i] >= 'A' && key[i] <= 'Z') {
            key[i] = key[i] + 13;
            if (key[i] > 'Z')
                key[i] = key[i]  % 'Z' + 'A' - 1;
        }
    }

    for (int i = 0; i < 32; i++) {
        printf("%c", key[i]);
    }cout<<endl;
}


int main() {
    int v5; // esi
    int v6; // edi
    char v7; // bj
    int v8; // esi
    int v15[10]; // [esp+10Ch] [ebp-68h]
    int v16; // [esp+128h] [ebp-4Ch]
    int v17; // [esp+12Ch] [ebp-48h]
    unsigned int v14[] = {
            0x00000022, 0xFFFFFFA2, 0x00000072, 0xFFFFFFE6,
            0x00000052, 0xFFFFFF8C, 0xFFFFFFF2, 0xFFFFFFD4,
            0xFFFFFFA6, 0x0000000A, 0x0000003C, 0x00000024,
            0xFFFFFFA6, 0xFFFFFF9C, 0xFFFFFF86, 0x00000024,
            0x00000042, 0xFFFFFFD4, 0x00000022, 0xFFFFFFB6,
            0x00000014, 0x00000042, 0xFFFFFFCE, 0xFFFFFFAC,
            0x00000014, 0x0000006A, 0x0000002C, 0x0000007C,
            0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE4, 0x0000001E
    };

    for (int i = 0; i < 10; ++i ) {
        v15[i] = i + 1;
    }
    v5 = v15[0];

    v16 = v17 = 0;  // IDA 未给初值,这里是 0
    v7 = sub_411177(v15[5] + v15[4] * (v15[0] + v15[2] + v15[1] - v15[3]) + v17 * v16 * v15[6], 8 * v16);
    printf("v7: %d\n", v7);


    v6 = 0;
    v8 = 8 * v5;
    int key[32];
    do {
        int tmp = v7 ^ (v14[v6] & 0xFF);  // 注意 char v7 为一字节,这里 & 0xFF 取一字节
        for (int j = 32; j < 127; ++j) {  // 爆破 sub_411177(v21[v6], v8)
            if (sub_411177(j, v8) == tmp) {
                key[v6] = j;
                printf("%c", key[v6]);
                break;
            }
        }
        ++v6;
    }
    while (v6 < 32);
    cout<<endl;

    v20(key);
    v19(key);

    return 0;
}

结果

SYC{Y3S-yE5-y0u-S0Ve-Th3-C9P!!!}

安洵杯2023-ez_cpp22.png

最后将 'a' 改为 '{' 即可,'a' + 13 = '{',这里没有像标准的凯斯密码那样取余数循环