题目链接:下载
可以知道用户名是welcomebeijing,flag是密码的MD5加密。
无壳,拖入IDA进入主函数。
int wmain()
{
FILE *v0; // eax
FILE *v1; // eax
char v3; // [esp+3h] [ebp-405h]
char v4; // [esp+4h] [ebp-404h] BYREF
char v5[255]; // [esp+5h] [ebp-403h] BYREF
char Format; // [esp+104h] [ebp-304h] BYREF
char v7[255]; // [esp+105h] [ebp-303h] BYREF
char v8; // [esp+204h] [ebp-204h] BYREF
char v9[255]; // [esp+205h] [ebp-203h] BYREF
char v10; // [esp+304h] [ebp-104h] BYREF
char v11[255]; // [esp+305h] [ebp-103h] BYREF
printf("Come one! Crack Me~~~\n");
v10 = 0;
memset(v11, 0, sizeof(v11));
v8 = 0;
memset(v9, 0, sizeof(v9));
while ( 1 ) // 死循环
{
do
{
do
{
printf("user(6-16 letters or numbers):");// 输入用户名
scanf("%s", &v10);
v0 = (FILE *)sub_4024BE();
fflush(v0);
}
while ( !(unsigned __int8)sub_401000(&v10) );
printf("password(6-16 letters or numbers):");// 输入密码
scanf("%s", &v8);
v1 = (FILE *)sub_4024BE();
fflush(v1);
}
while ( !(unsigned __int8)sub_401000(&v8) );
sub_401090(&v10); // 对用户名操作
Format = 0;
memset(v7, 0, sizeof(v7));
v4 = 0;
memset(v5, 0, sizeof(v5));
v3 = ((int (__cdecl *)(char *, char *))loc_4011A0)(&Format, &v4);// v3恒等于1
if ( (unsigned __int8)sub_401830(&v10, &v8) )// 关键函数
{
if ( v3 )
break; // 唯一的跳出循环
}
printf(&v4); // please try again
}
printf(&Format); // Congratulations
return 0;
}
在主函数中可以看见,下图
发现是个未定义函数,跟进,发现是一个小的花指令。 红框中的全部nop掉。然后声明为函数即可。然后观察这个函数sub_4011A0,把数组里的元素变为字符:
所以主函数中 printf(&v4) 就是输出"Please try again", printf(&Format) 就是输出"Congratulations"。即
我们想要输出Congratulaions就要跳出这个while(1)这个大循环,所以执行break,所以v3必需不为0,所以进入和v3有关的函数:
返回1,所以v3 肯定不是0,所以肯定能执行到break,跳出循环,但要求sub_4011A0()函数的返回值不是0,才能执行到break,所以跟进函数sub_4011A0这个关键函数。
bool __cdecl sub_401830(int a1, const char *a2)
{
int v3; // [esp+18h] [ebp-22Ch]
int v4; // [esp+1Ch] [ebp-228h]
int v5; // [esp+28h] [ebp-21Ch]
unsigned int v6; // [esp+30h] [ebp-214h]
char v7; // [esp+36h] [ebp-20Eh]
char v8; // [esp+37h] [ebp-20Dh]
char v9; // [esp+38h] [ebp-20Ch]
unsigned __int8 v10; // [esp+39h] [ebp-20Bh]
unsigned __int8 v11; // [esp+3Ah] [ebp-20Ah]
char v12; // [esp+3Bh] [ebp-209h]
int v13; // [esp+3Ch] [ebp-208h] BYREF
char v14; // [esp+40h] [ebp-204h] BYREF
char v15[255]; // [esp+41h] [ebp-203h] BYREF
char v16; // [esp+140h] [ebp-104h] BYREF
char v17[255]; // [esp+141h] [ebp-103h] BYREF
v4 = 0;
v5 = 0;
v11 = 0;
v10 = 0;
v16 = 0;
memset(v17, 0, sizeof(v17));
v14 = 0;
memset(v15, 0, sizeof(v15));
v9 = 0;
v6 = 0;
v3 = 0;
while ( v6 < strlen(a2) )
{
if ( isdigit(a2[v6]) ) // 判断是否是数字字符
{
v8 = a2[v6] - 48; // 变为纯数字
}
else if ( isxdigit(a2[v6]) ) // 判断是否是a-f,A-F
{
if ( *((_DWORD *)NtCurrentPeb()->SubSystemData + 3) != 2 )// 反调试
a2[v6] = 34;
v8 = (a2[v6] | 0x20) - 87;
}
else
{
v8 = ((a2[v6] | 0x20) - 97) % 6 + 10; // 得到10-16
}
__rdtsc();
__rdtsc();
v9 = v8 + 16 * v9; // 得到对应一字节的十六进制所对应的十进制数
if ( !((int)(v6 + 1) % 2) ) // v6为奇数
{
*(&v14 + v3++) = v9; // 把这个十六进制数放入v14
v9 = 0;
}
++v6;
}
while ( v5 < 8 )
{
v10 += byte_416050[++v11];
v12 = byte_416050[v11];
v7 = byte_416050[v10];
byte_416050[v10] = v12;
byte_416050[v11] = v7;
if ( ((int)NtCurrentPeb()->UnicodeCaseTableData & 0x70) != 0 )// 反调试
v12 = v10 + v11;
*(&v16 + v5) = byte_416050[(unsigned __int8)(v7 + v12)] ^ *(&v14 + v4);// 异或
if ( (unsigned __int8)*(_DWORD *)&NtCurrentPeb()->BeingDebugged )// 反调试
{
v10 = -83;
v11 = 43;
}
sub_401710(&v16, a1, v5++);
v4 = v5;
if ( v5 >= (unsigned int)(&v14 + strlen(&v14) + 1 - v15) )
v4 = 0;
}
v13 = 0;
sub_401470(&v16, &v13);
return v13 == 43924;
}
倒着分析:前面提了为了让这个函数返回值为1,所以v13必须等于43924。和v13有关的只有上面的sub_401470()函数,跟进:
_DWORD *__usercall sub_401470@<eax>(int a1@<ebx>, _BYTE *a2, _DWORD *a3)
{
_DWORD *_EAX; // eax
char v5; // al
char _AL; // al
_DWORD *result; // eax
if ( *a2 != 'd' )
*a3 ^= 3u;
else
*a3 |= 4u;
if ( a2[1] != 'b' )
{
*a3 &= 0x61u;
_EAX = (_DWORD *)*a3;
}
else
{
_EAX = a3;
*a3 |= 0x14u;
}
__asm { aam }
if ( a2[2] != 'a' )
*a3 &= 0xAu;
else
*a3 |= 0x84u;
if ( a2[3] != 'p' )
*a3 >>= 7;
else
*a3 |= 0x114u;
if ( a2[4] != 'p' )
*a3 *= 2;
else
*a3 |= 0x380u;
if ( *((_DWORD *)NtCurrentPeb()->SubSystemData + 3) != 2 )
{
if ( a2[5] != 'f' )
*a3 |= 0x21u;
else
*a3 |= 0x2DCu;
}
if ( a2[5] != 's' )
{
v5 = (char)a3;
*a3 ^= 0x1ADu;
}
else
{
*a3 |= 0xA04u;
v5 = (char)a3;
}
_AL = v5 - (~(a1 >> 5) - 1);
__asm { daa }
if ( a2[6] != 'e' )
*a3 |= 0x4Au;
else
*a3 |= 0x2310u;
if ( a2[7] != 'c' )
{
*a3 &= 0x3A3u;
result = (_DWORD *)*a3;
}
else
{
result = a3;
*a3 |= 0x8A10u;
}
return result;
}
发现就是if判断,根据判断结果进行相应运算,所以尝试计算一下,哪些可以等于43924。发现当if条件不成立时,v13就会等于43924,所以也知道了a2即v17=‘dbappsec’。
好了继续分析这个关键函数:
是一个while循环,a2就是用户输入的密码,先通过isdigit函数判断是否输入的是数字字符,如果是就减去'0'的ascii码使变为纯数字,如果不是则通过isxdigit函数判断是否为十六进制数a-f/A-F。(v9 = (a2[v7] | 0x20) - 87;这个我实在没搞懂) ,v9 = ((a2[v7] | 0x20) - 97) % 6 + 10;则是把非数字字符和十六进制字符的字符转化为数字10-15。
然后看一下对v10赋值的这个语句,这个是不是很像求十六进制所对应的十进制数的方法。所以通过if语句判断当组成2个十六进制数后就存入v15数组中,相当于又把v10这个十进制数变为对应的十六进制。
然后看:
有一个异或运算,我们已经知道v17==‘dbappsec’。那知道数组byte_416050不就可以了嘛,所以用动态调试看一看参与异或运算的值是多少。找到对应异或运算的汇编代码下断点。然后运行,输入用户名和随便一个密码就可以发现
因为是个while循环,所以继续f9运行看其他参与运算的值。最终可知为[0x2a, 0xd7, 0x92, 0xe9, 0x53, 0xe2, 0xc4, 0xcd],之后就是代码:
import hashlib
str="dbappsec"
flag=""
a=[0x2a, 0xd7, 0x92, 0xe9, 0x53, 0xe2, 0xc4, 0xcd]
for i in range(8):
flag+=(hex(a[i]^ord(str[i]))[2:])
print(flag)
m=hashlib.md5()
flag=flag.encode('utf-8')
m.update(flag)
print(m.hexdigest())
#密码:4eb5f3992391a1ae
#md5后:d2be2981b84f2a905669995873d6a36c