解题过程
先脱壳,然后拖进64位IDA,定位到主函数F5查看伪C代码,代码如下:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 result; // rax
unsigned __int64 v4; // kr08_8
char *v5; // ST18_8
char *v6; // ST20_8
char *v7; // ST28_8
char *v8; // ST30_8
bool v9; // cf
bool v10; // zf
signed __int64 v11; // rcx
char *v12; // rsi
const char *v13; // rdi
signed int i; // [rsp+38h] [rbp-88h]
char v15[48]; // [rsp+40h] [rbp-80h]
char v16; // [rsp+70h] [rbp-50h]
__int16 v17; // [rsp+76h] [rbp-4Ah]
char dest[40]; // [rsp+90h] [rbp-30h]
unsigned __int64 v19; // [rsp+B8h] [rbp-8h]
v19 = __readfsqword(0x28u);
puts("Please input flag:");
__isoc99_scanf("%s", &v16);
if ( (unsigned int)sub_401773(&v16) )
{
puts("Format Error !!");
result = 0LL;
}
else
{
v4 = strlen(&v16) + 1;
strncpy(dest, (const char *)&v17, (signed int)v4 - 8);
dest[(signed int)v4 - 8] = 0;
v5 = strtok(dest, "_");
v6 = strtok(0LL, "_");
v7 = strtok(0LL, "_");
v8 = strtok(0LL, "_");
sub_4016DC(v5, 0);
sub_4016DC(v6, 4);
sub_4016DC(v7, 8);
sub_4016DC(v8, 12);
for ( i = 0; ; ++i )
{
v9 = (unsigned int)i < 0xF;
v10 = i == 15;
if ( i > 15 )
break;
sprintf(&v15[2 * i], "%02x", (unsigned __int8)digest[i]);
}
v11 = 33LL;
v12 = v15;
v13 = "f58e6a50d0dbe91515913c1002c77002";
do
{
if ( !v11 )
break;
v9 = (unsigned __int8)*v12 < *v13;
v10 = *v12++ == *v13++;
--v11;
}
while ( v10 );
if ( (!v9 && !v10) == v9 ) // v9=0;v10=1
puts("Success!! You are right !!");
else
puts("You are wrong !!");
result = 0LL;
}
return result;
}
主函数先通过sub_401773函数判断输入的flag是否符合格式,这里的格式是指开头为“afctf{”,结尾为“}”,afctf{……}中被三个“_”分成四段,每一段长度为4且所有字符都是小写英文字母。
sub_4016DC函数对四段字符串进行某操作,然后将digest[i]中的数据与串”f58e6a50d0dbe91515913c1002c77002”进行对比,如果相等则输出”Success!! You are right !!”。而主函数中调用的四个sub_4016DC函数的参数不同,下面我们跟进这个函数,发现这个函数内容与MD5相似,按照网上MD5代码修改函数名称。
unsigned __int64 __fastcall sub_4016DC(const char *a1, int a2)
{
char v3; // [rsp+20h] [rbp-60h]
unsigned __int64 v4; // [rsp+78h] [rbp-8h]
v4 = __readfsqword(0x28u);
MD5Init(&v3); // 初始化缓冲区
MD5Update(&v3, (char *)a1, strlen(a1));
Md5Final(&v3, (__int64)&digest[a2]);
return __readfsqword(0x28u) ^ v4;
}
MD5Init函数和MD5Update函数与MD5代码相同,传进来的四个不同的参数a2在Md5Final函数中用到,继续跟进:
unsigned __int64 __fastcall sub_4008E4(_DWORD *a1, __int64 a2)
{
int v2; // eax
unsigned int v3; // ST1C_4
unsigned int v5; // [rsp+18h] [rbp-18h]
char v6; // [rsp+20h] [rbp-10h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
v5 = (*a1 >> 3) & 0x3F;
if ( v5 > 0x37 )
v2 = 120 - v5;
else
v2 = 56 - v5;
v3 = v2;
Md5Encode((__int64)&v6, (__int64)a1, 8u);
MD5Update(a1, (char *)&unk_603060, v3);
MD5Update(a1, &v6, 8u);
Md5Encode(a2, (__int64)(a1 + 2), 4u);
return __readfsqword(0x28u) ^ v7;
}
发现这个函数在Md5Encode(a2, (_int64)(a1 + 2), 4u);与MD5不同,相当于是通过MD5Encode计算的值只有前四个传回给了digest[i]。总的来说就是进行四次MD5计算,将每次得到的消息值得前四个字节依次传给digest数组去进行后面的匹配,而每一次传进去计算的字符串就是我们之前通过“”分割出来的四个字符。
由于在进行flag格式判断的时候就限定了这四段字符均为小写英文字母,所以可以尝试爆破,代码如下:
import hashlib
import sys
for a in range(0x61, 0x7b):
for b in range(0x61, 0x7b):
for c in range(0x61, 0x7b):
for d in range(0x61, 0x7b):
m = hashlib.md5()
m.update((chr(a) + chr(b) + chr(c) + chr(d)).encode("utf-8"))
if m.hexdigest()[0:8] == "f58e6a50":
print(chr(a) + chr(b) + chr(c) + chr(d))
if m.hexdigest()[0:8] == "d0dbe915":
print(chr(a) + chr(b) + chr(c) + chr(d))
if m.hexdigest()[0:8] == "15913c10":
print(chr(a) + chr(b) + chr(c) + chr(d))
if m.hexdigest()[0:8] == "02c77002":
print(chr(a) + chr(b) + chr(c) + chr(d))
运行结果为:
dead
live
oops
zoom
得出本题flag为:afct{dead_live_oops_zoom}
总结
从这个题目可以看出对一些基本密码算法还不太熟悉,在阅读伪C代码的过程中花费了很多功夫。
循环左移运算:x循环左移n位可以表示为
MD5算法及其实现
SHA1算法实现及详解
SHA-256算法实现
DES和3DES加密算法C语言实现
AES算法实现分析
RC4加密算法的原理及实现
IDEA加密算法实现