安恒杯月赛 2019-01 Old-drive write up
June 4, 2019
0 Comments
环境配置
系统 : win10 64bit
程序 : Old-drive.zip or Old-drive.zip
要求 : 输入口令
使用工具 :ida pro
开始分析
静态分析
使用ida载入程序,发现主流程如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // ecx
signed int v4; // eax
char v6; // [esp+0h] [ebp-40h]
char Dst; // [esp+1h] [ebp-3Fh]
char v8; // [esp+27h] [ebp-19h]
int v9; // [esp+34h] [ebp-Ch]
__int16 v10; // [esp+38h] [ebp-8h]
char v11; // [esp+3Ah] [ebp-6h]
v6 = 0;
memset(&Dst, 0, 0x31u);
printf("input flag:\n");
scanf("%50s", &v6);
if ( strlen(&v6) == 40 )
{
for ( i = 0; i < (char *)nullsub_1 - (char *)sub_401000; ++i )
*((_BYTE *)sub_401000 + i) ^= 0xBBu;
v10 = 32123;
v9 = 'galf';
v11 = 0;
v4 = 0;
do
{
if ( *(&v6 + v4) != *((_BYTE *)&v9 + v4) )
goto LABEL_8;
++v4;
}
while ( v4 < 5 );
LOBYTE(i) = v8;
if ( v8 != *((_BYTE *)&v9 + v4) )
LABEL_8:
exit(0);
sub_4010B0(i, &v6);
}
return 0;
}
动态调试
这样看着不是很直观,直接ida调试程序,发现主要流程在检查格式:
v9 = 'galf';
v11 = 0;
v4 = 0;
do
{
if ( *(&v6 + v4) != *((_BYTE *)&v9 + v4) )
goto LABEL_8;
++v4;
}
while ( v4 < 5 );
LOBYTE(i) = v8;
if ( v8 != *((_BYTE *)&v9 + v4) )
所以flag格式为flag{.+}
进入第一个关键函数
.text:004010B0 loc_4010B0: ; CODE XREF: _main+B4↓p
.text:004010B0 push ebp
.text:004010B1 mov ebp, esp
可以看到有很多乱码,这里鼠标选择.text:004010B0
–.text:0040125E
,按下c
然后一直ok就可以。之后对函数头右击Create Function就可以了:
int __fastcall sub_4010B0(int a1, _BYTE *a2)
{
signed int v2; // eax
unsigned __int8 *v3; // ecx
int v4; // edi
char v5; // cl
char v6; // al
char v7; // cl
char v8; // al
char v9; // dl
unsigned int v10; // kr00_4
signed int v11; // esi
char *v12; // ebx
int v13; // edi
unsigned int v14; // eax
char *v15; // ecx
unsigned int v16; // edx
signed int v17; // ecx
int v18; // esi
unsigned int v19; // eax
signed int v20; // ecx
signed int v21; // eax
_BYTE *v23; // [esp+8h] [ebp-2Ch]
char *v24; // [esp+Ch] [ebp-28h]
unsigned int v25; // [esp+10h] [ebp-24h]
char Dest[4]; // [esp+14h] [ebp-20h]
char *Source; // [esp+18h] [ebp-1Ch]
int v28; // [esp+1Ch] [ebp-18h]
char v29; // [esp+28h] [ebp-Ch]
char v30; // [esp+29h] [ebp-Bh]
char v31; // [esp+2Ah] [ebp-Ah]
char v32; // [esp+2Bh] [ebp-9h]
char v33; // [esp+2Ch] [ebp-8h]
char v34; // [esp+2Dh] [ebp-7h]
v23 = a2;
v2 = 5;
v3 = (unsigned __int8 *)&unk_4021B8;
do
{
v4 = *v3++;
if ( ((char)a2[v2] ^ 0x86) != v4 )
LABEL_12:
exit(0);
++v2;
}
while ( v2 < 11 );
v5 = a2[11];
strcpy(&v28, "c19zbWNf");
v29 = v5;
v6 = a2[12];
v31 = a2[13];
v7 = a2[15];
v30 = v6;
v8 = a2[14];
v9 = a2[16];
v33 = v7;
v34 = v9;
v32 = v8;
v10 = strlen(&v29);
v11 = v10 / 3 + (v10 % 3 != 0);
v12 = (char *)malloc(4 * v11 + 1);
v24 = v12;
memset(v12, 0, 4 * v11 + 1);
if ( v11 > 0 )
{
Source = &v29;
v25 = v10 / 3 + (v10 % 3 != 0);
do
{
v13 = 0;
*(_DWORD *)Dest = 0;
strncpy(Dest, Source, 3u);
v14 = 0;
v15 = &Dest[strlen(Dest) + 1];
v16 = v15 - &Dest[1];
if ( v15 != &Dest[1] )
{
v17 = 16;
do
{
v18 = Dest[v14++] << v17;
v17 -= 8;
v13 |= v18;
}
while ( v14 < v16 );
}
v19 = 0;
v20 = 18;
do
{
if ( v16 + 1 <= v19 )
v12[v19] = 61;
else
v12[v19] = aAbcdefghijklmn[(v13 >> v20) & 0x3F];
v20 -= 6;
++v19;
}
while ( v20 > -6 );
Source += 3;
v12 += 4;
--v25;
}
while ( v25 );
v12 = v24;
}
v21 = 0;
do
{
if ( *((_BYTE *)&v28 + v21) != *((_BYTE *)&v28 + v21 + v12 - (char *)&v28) )
goto LABEL_12;
++v21;
}
while ( v21 < 8 );
return sub_401000(v23);
}
solved part1
直接用python把flag的第一部分找出来:
➜ playground python ./add_prefix.py
F2 EE EF F5 D9 EF
[ 0xF2, 0xEE, 0xEF, 0xF5, 0xD9, 0xEF ]
➜ playground python
Python 2.7.14+ (default, Mar 13 2018, 15:23:44)
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> data = [ 0xF2, 0xEE, 0xEF, 0xF5, 0xD9, 0xEF ]
>>> for ch in data:
... print chr(ch^0x86),
...
t h i s _ i
solved part2
根据算法结构以及算法常数aAbcdefghijklmn数组可知,这是一个base64算法,密文在代码段中已经给出c19zbWNf
,解码出来是:s_smc_
。
进入关键函数2
调试技巧
这里多次从头调试可以发现,给定部分正确的flag可以进入关键函数sub_401000
。而第二次调试的时候却会引发异常,代码似乎也变了。这里可以采取以下两种方法:
1. 在虚拟机中调试,走入一个关键函数保存一个镜像,发现有奇怪的错误时直接回滚就可以。
2. 这个样本有两个关键函数,所以理论上只需要调试两次,那么结束调试时删除idb文件,直接从零开始就可以。
发现迷宫
这里,程序主流程是这样的:
char __cdecl sub_401000(int a1)
{
int v1; // edx
int v2; // ecx
int v3; // edx
int v4; // ecx
signed int v5; // edx
char *v6; // ecx
char v7; // al
char result; // al
char v9; // [esp+8h] [ebp-64h]
char v10; // [esp+10h] [ebp-5Ch]
int v11; // [esp+4Ch] [ebp-20h]
int v12; // [esp+50h] [ebp-1Ch]
int v13; // [esp+54h] [ebp-18h]
int v14; // [esp+58h] [ebp-14h]
int v15; // [esp+5Ch] [ebp-10h]
__int16 v16; // [esp+60h] [ebp-Ch]
v1 = *(_DWORD *)(a1 + 21);
strcpy(&v9, "--------g + ++ + ++ ++ + #+ ++ ++++ ++ ++++ ++ +--------");
v11 = *(_DWORD *)(a1 + 17);
v2 = *(_DWORD *)(a1 + 25);
v12 = v1;
v3 = *(_DWORD *)(a1 + 29);
v13 = v2;
v4 = *(_DWORD *)(a1 + 33);
v14 = v3;
LOWORD(v3) = *(_WORD *)(a1 + 37);
v15 = v4;
v16 = v3;
v5 = 0;
v6 = &v10;
do
{
v7 = *((_BYTE *)&v11 + v5);
switch ( v7 )
{
case 97:
v6 += 8;
break;
case 113:
--v6;
break;
case 119:
++v6;
break;
case 50:
v6 -= 8;
break;
}
result = *v6;
if ( *v6 == 35 )
{
printf("Congratulation!!!!!!\n");
LABEL_15:
exit(0);
}
if ( result != 32 )
goto LABEL_15;
++v5;
}
while ( v5 < 22 );
return result;
}
在内存中查看v9,以每行八字节的方式显示:
0019FE58 2D 2D 2D 2D 2D 2D 2D 2D --------
0019FE60 67 20 2B 20 20 20 20 2B g + +
0019FE68 2B 20 2B 20 2B 2B 20 2B + + ++ +
0019FE70 2B 20 2B 20 23 2B 20 2B + + #+ +
0019FE78 2B 20 2B 2B 2B 2B 20 2B + ++++ +
0019FE80 2B 20 2B 2B 2B 2B 20 2B + ++++ +
0019FE88 2B 20 20 20 20 20 20 2B + +
0019FE90 2D 2D 2D 2D 2D 2D 2D 2D --------
调试发现,我们的开始位置是0x67,最终到达0x23就算成功。
移动按键杯设定为:
↑ 2
↓ a
← q
→ w
solved part3
最终我们可以确定走出迷宫的路线为:waaaaawwwww22222qqqaaw
夺旗成功
输入拼接而成的flag,提示成功:
C:\Users\Vincent_GU\Downloads\mission\anheng\old-drive\Old-drive>Old-drive3.exe
input flag:
flag{this_is_smc_waaaaawwwww22222qqqaaw}
Congratulation!!!!!!