题目链接
达芬奇密码
描述
一道windows的32位逆向题,涉及动态解密,大数运算,佩尔方程通解问题。
解题过程
首先运行程序,发现字符串Wrong!,使用IDA搜索字符串搜不到,说明应该加密了,在程序中动态解密。
在程序运行时搜索内存,得如下内容
确定程序的关键函数401EA0,在IDA中反编译得如下代码
int __thiscall sub_401EA0(CWnd *this)
{
CWnd *v1; // esi
int v2; // eax
WCHAR String; // [esp+Ch] [ebp-310h]
char v5; // [esp+Eh] [ebp-30Eh]
char v6; // [esp+20Ch] [ebp-110h]
char v7; // [esp+20Dh] [ebp-10Fh]
DWORD v8; // [esp+30Ch] [ebp-10h]
CWnd *v9; // [esp+310h] [ebp-Ch]
int v10; // [esp+314h] [ebp-8h]
DWORD flOldProtect; // [esp+318h] [ebp-4h]
v1 = this;
v9 = this;
String = 0;
memset(&v5, 0, 0x1FEu);
v6 = 0;
memset(&v7, 0, 0xFFu);
CWnd::GetDlgItemTextW(v1, 1000, &String, 20);
if ( wcslen(&String) == 16 ) //flag长度时16字节
{
v2 = 0;
while ( !(*(&String + v2) & 0xFF00) ) //输入是ASCII字符
{
*(&v6 + v2) = *((_BYTE *)&String + 2 * v2);//输入在内存中是宽字符,赋值给V6,V6是字节数组
if ( ++v2 >= 16 )
{
v8 = 64;
flOldProtect = 0;
VirtualProtect(sub_4010E0, 0xD17u, 0x40u, &flOldProtect);
if ( GetLastError() )
return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
qmemcpy(sub_4010E0, &byte_5647B8, 0x330u);//前面修改内存权限,这里把数据写入函数sub_4010E0,是动态解密的
VirtualProtect(sub_4010E0, 0xD17u, flOldProtect, &v8);
if ( !GetLastError() )
{
v10 = 0;
v10 = sub_4010E0(&v6);
if ( v10 == 1 )
return CWnd::MessageBoxW(v9, L"Congratulations! You are right!", 0, 0);
}
v1 = v9;
return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
}
}
}
return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
}
在判断代码中发现FLAG是长度位16字节的ASCII码字符数组,并且函数sub_4010E0是动态解密的,且此函数返回值为1则成功。
此时需要转储内存函数sub4010E0
使用lordPE把此进程完整转存为dump.exe
随后拖入IDA中反编译函数4010E0进行分析,如下
在下面的函数中涉及到大数运算,大数表示字节数组,此时把大数看成256进制的数就很容易理解其中的代码。
signed int __cdecl sub_4010E0(int a1)
{
signed int v1; // eax
char v2; // cl
signed int v3; // ecx
signed int v4; // eax
signed int v5; // eax
signed int v6; // esi
signed int v7; // ecx
__int16 v8; // dx
char *v9; // edi
__int16 v10; // ax
signed int v11; // eax
signed int v12; // ecx
unsigned __int16 v13; // bx
signed int v14; // esi
signed int v15; // ecx
__int16 v16; // dx
char *v17; // edi
__int16 v18; // ax
signed int v19; // eax
signed int v20; // ecx
unsigned __int16 v21; // bx
unsigned int v22; // eax
signed int v23; // ecx
unsigned __int16 v24; // dx
char v25; // dl
signed int v26; // eax
__int16 v27; // si
int v28; // eax
int v30; // [esp+8h] [ebp-90h]
int v31; // [esp+Ch] [ebp-8Ch]
int v32; // [esp+10h] [ebp-88h]
int v33; // [esp+14h] [ebp-84h]
int v34; // [esp+18h] [ebp-80h]
int v35; // [esp+1Ch] [ebp-7Ch]
int v36; // [esp+20h] [ebp-78h]
int v37; // [esp+24h] [ebp-74h]
int v38; // [esp+28h] [ebp-70h]
int v39; // [esp+2Ch] [ebp-6Ch]
int v40; // [esp+30h] [ebp-68h]
int v41; // [esp+34h] [ebp-64h]
int v42; // [esp+38h] [ebp-60h]
int v43; // [esp+3Ch] [ebp-5Ch]
int v44; // [esp+40h] [ebp-58h]
int v45; // [esp+44h] [ebp-54h]
int v46; // [esp+48h] [ebp-50h]
int v47; // [esp+4Ch] [ebp-4Ch]
int v48; // [esp+50h] [ebp-48h]
int v49; // [esp+54h] [ebp-44h]
char v50; // [esp+58h] [ebp-40h]
int v51; // [esp+5Ch] [ebp-3Ch]
int v52; // [esp+60h] [ebp-38h]
int v53; // [esp+64h] [ebp-34h]
int v54; // [esp+68h] [ebp-30h]
char v55; // [esp+6Ch] [ebp-2Ch]
int v56; // [esp+70h] [ebp-28h]
int v57; // [esp+74h] [ebp-24h]
int v58; // [esp+78h] [ebp-20h]
int v59; // [esp+7Ch] [ebp-1Ch]
char v60; // [esp+80h] [ebp-18h]
int v61; // [esp+84h] [ebp-14h]
int v62; // [esp+88h] [ebp-10h]
int v63; // [esp+8Ch] [ebp-Ch]
int v64; // [esp+90h] [ebp-8h]
char v65; // [esp+94h] [ebp-4h]
v31 = 1684969601;
v1 = 0;
v30 = -477325802;
v32 = -2116286332;
v33 = 1330138558;
v34 = 0;
v35 = 0;
v36 = 0;
v37 = 0;
v38 = 0;
v39 = 0;
v44 = 0;
v45 = 0;
do
{
v2 = *((_BYTE *)&v32 + v1) ^ *(_BYTE *)(a1 + v1 + 8);
*((_BYTE *)&v38 + v1) = *((_BYTE *)&v30 + v1) ^ *((_BYTE *)&v30 + v1 + a1 - (_DWORD)&v30);
*((_BYTE *)&v44 + v1++) = v2;
}
/*上面通过把输入值的前8字节和后8字节分别与固定值异或后得V44和V38*/
while ( v1 < 8 );
v30 = 0;
v56 = 0;
v57 = 0;
v58 = 0;
v59 = 0;
v60 = 0;
v61 = 0;
v62 = 0;
v63 = 0;
v64 = 0;
v65 = 0;
v46 = 0;
v47 = 0;
v48 = 0;
v49 = 0;
v50 = 0;
v51 = 0;
v52 = 0;
v53 = 0;
v54 = 0;
v55 = 0;
v31 = 0;
v32 = 0;
v33 = 0;
LOBYTE(v34) = 0;
v3 = 8;
LOBYTE(v30) = 8;
v4 = 7;
do
{
if ( *((_BYTE *)&v38 + v4) )
break;
--v3;
--v4;
}
while ( v4 >= 0 );
if ( v3 == 8 )
{
v5 = 7;
do
{
if ( *((_BYTE *)&v44 + v5) )
break;
--v3;
--v5;
}
while ( v5 >= 0 );
if ( v3 == 8 && !(v39 & 0xF0000000) )
{
v6 = 0;
do
{
v40 = 0;
v41 = 0;
v42 = 0;
v43 = 0;
v7 = 0;
v8 = *((unsigned __int8 *)&v38 + v6);
v9 = (char *)&v40 + v6;
do
{
v10 = *((unsigned __int8 *)&v42 + v6) + v8 * *((unsigned __int8 *)&v38 + v7);
v9[v7] = *((_BYTE *)&v42 + v6) + v8 * *((_BYTE *)&v38 + v7);
++v7;
*((_BYTE *)&v42 + v6) = HIBYTE(v10);//取进位
}
while ( v7 < 8 );
LOBYTE(v11) = 0;
v12 = 0;
do
{
v13 = (char)v11 + *((unsigned __int8 *)&v56 + v12 + v6) + (unsigned __int8)v9[v12];
*((_BYTE *)&v56 + v12++ + v6) = v13;
v11 = (signed int)v13 >> 8;
}
while ( v12 < 9 );
++v6;
}
while ( v6 < 8 );//V56 = V38 * V38
v14 = 0;
do
{
v40 = 0;
v41 = 0;
v42 = 0;
v43 = 0;
v15 = 0;
v16 = *((unsigned __int8 *)&v44 + v14);
v17 = (char *)&v40 + v14;
do
{
v18 = *((unsigned __int8 *)&v42 + v14) + v16 * *((unsigned __int8 *)&v44 + v15);
v17[v15] = *((_BYTE *)&v42 + v14) + v16 * *((_BYTE *)&v44 + v15);
++v15;
*((_BYTE *)&v42 + v14) = HIBYTE(v18);
}
while ( v15 < 8 );
LOBYTE(v19) = 0;
v20 = 0;
do
{
v21 = (char)v19 + *((unsigned __int8 *)&v61 + v20 + v14) + (unsigned __int8)v17[v20];
*((_BYTE *)&v61 + v20++ + v14) = v21;
v19 = (signed int)v21 >> 8;
}
while ( v20 < 9 );
++v14;
}
while ( v14 < 8 );//V61=V44 * V44
LOBYTE(v22) = v50;
v23 = 0;
do
{
v24 = (unsigned __int8)v22 + 7 * *((unsigned __int8 *)&v61 + v23);
*((_BYTE *)&v46 + v23++) = v24;
v22 = (unsigned int)v24 >> 8;
}
while ( v23 < 17 );V46 = 7 * V61
v50 = HIBYTE(v24);
v25 = 0;
v26 = 0;
do
{
v27 = *((unsigned __int8 *)&v56 + v26) - *((unsigned __int8 *)&v46 + v26) - v25;
*((_BYTE *)&v51 + v26) = v27;
if ( v27 < 0 )
v25 = 1;
++v26;
}
while ( v26 < 17 );//V56 = V38*V38 - 7 * V44 * V44
if ( !v25 )
{
v28 = 0;
while ( *((_BYTE *)&v51 + v28) == *((_BYTE *)&v30 + v28) )
{
if ( ++v28 >= 17 )
return 1; //V56 == 8 返回1
}
}
}
}
return 0;
}
在上面代码中发现V38和V44是方程 V38^2 - 7 V44^2 = 8的解,且满足
2^56<=V38<2^60
2^56<=V44<2^60
调研后得知此方程是佩尔方程,且此时有无数组解,于是只需找到满足上面条件的一组解即可。
此方程通解的递推关系为
Xn = 8 Xn-1 + 21 Yn-1
yn = 3 Xn-1 + 8 * Yn-1(n>=2)
X1 = 6
Y1 = 2
求解代码如下
#include <stdio.h>
int main()
{
unsigned long long a = 6;
unsigned long long b = 2;
while(a < 0x0100000000000000 || b < 0x0100000000000000){
unsigned long long temp = a;
a = 8 * a + 21 * b;
b = 3 * temp + 8 * b;
}
printf("%llu %llu\n",a,b);
return 0;
}
解得
V38=385044246406735194
V44=145533045678356702
继续回推可得FLAG,相关代码如下
#include <stdio.h>
int main()
{
int a = -477325802;
int b = 1684969601;
int c = -2116286332;
int d = 1330138558;
unsigned long long p = 385044246406735194;
unsigned long long q = 145533045678356702;
char FLAG[17];
FLAG[16] = 0;
for(int i = 0;i < 8; i++) {
FLAG[i] = (*((char *)&a + i)) ^ (*((char *)&p + i));
FLAG[i + 8] = (*((char *)&c + i)) ^ (*((char *)&q + i));
}
printf("%s\n",FLAG);
return 0;
}
最后得到FLAG为L3mZ2k9aZ0a36DMM
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。