【安洵杯 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 = '{'
,这里没有像标准的凯斯密码那样取余数循环