【安洵杯 2023】ez_cpp
收获
- 花指令的去除 
- 将一个变量指向一段代码的地址,然后使用这个变量来作为函数执行 
_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};思路
用 IDA 打开,定位到主函数,发现花指令:
首先 Patch 掉花指令
在 jmp 红色地址的语句处按 快捷键 D 将硬指令第一字节 E8 改为 90
然后使用 快捷键 C 转为代码
最后在 main 起始地址处按 快捷键 P 重新生成 main_0() 函数:
在 sub_411177() 中也存在花指令:
Patch 掉花指令:
提取 v14[] 的值,定位到几个 xmmword:
使用 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):
根据 return (**a1)(a1, a2),这里是将 a1 也就是 v19 和 v20 作为函数执行,a2 也就是 v18 作为参数
也就是 v19 和 v20 其实指向的是两个函数
跟进 sub_4113D4(v20),会执行 sub_4133A4(this):
*this = &calt::`vftable' 将 v20 指向一个函数的地址,跟进 calt::`vftable':
跟进 sub_411438(),会跳转到 sub_4138AF():
在 sub_411140(this[1], v6) 中发现花指令,Patch 掉:
这个函数的功能是将整数 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):
*this = &trans::`vftable' 将 v20 指向一个函数的地址,跟进 trans::`vftable'
sub_41132A() 会跳转到 sub_413972():
其中 this[1] = 'Z',this[2] = 'z',实现的是移位为 13 的凯撒密码,分别处理 a - z 和 A - 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!!!}
最后将 'a' 改为 '{' 即可,'a' + 13 = '{',这里没有像标准的凯斯密码那样取余数循环
































