MOCTF RE 暗恋的烦恼
在moctf平台上,一道逆向题目很有意思,记录一下:
文件链接:http://119.23.73.3:6001/re4/jiamiqi.exe
jiamiqi.exe文件执行效果:
执行到这里可以发现了,加密器输出的密文长度和明文长度是一致的,并且不加密空格。后面我们再去详细验证。
拖入IDA pro静态分析:
找到main函数,双击进入
再次双击,跳转
main函数反汇编代码:
.text:00401340 var_50 = byte ptr -50h .text:00401340 var_10 = dword ptr -10h .text:00401340 var_C = dword ptr -0Ch .text:00401340 var_8 = dword ptr -8 .text:00401340 Str = dword ptr -4 .text:00401340 .text:00401340 push ebp .text:00401341 mov ebp, esp .text:00401343 sub esp, 50h .text:00401346 push ebx .text:00401347 push esi .text:00401348 push edi .text:00401349 lea edi, [ebp+var_50] .text:0040134C mov ecx, 14h .text:00401351 mov eax, 0CCCCCCCCh .text:00401356 rep stosd .text:00401358 push 0FFh ; unsigned int .text:0040135D call ??2@YAPAXI@Z ; operator new(uint) .text:00401362 add esp, 4 .text:00401365 mov [ebp+var_C], eax .text:00401368 mov eax, [ebp+var_C] .text:0040136B mov [ebp+Str], eax .text:0040136E push 1Ah ; unsigned int .text:00401370 call ??2@YAPAXI@Z ; operator new(uint) .text:00401375 add esp, 4 .text:00401378 mov [ebp+var_10], eax .text:0040137B mov ecx, [ebp+var_10] .text:0040137E mov [ebp+var_8], ecx .text:00401381 push offset sub_40102D .text:00401386 push offset aIFIG ; "请输入您的明文:" .text:0040138B push offset unk_439920 .text:00401390 call sub_4010AA ;打印 .text:00401395 add esp, 8 .text:00401398 mov ecx, eax .text:0040139A call sub_40107D .text:0040139F mov edx, [ebp+Str] .text:004013A2 push edx ; Buffer .text:004013A3 call _gets .text:004013A8 add esp, 4 .text:004013AB push offset sub_40102D .text:004013B0 push offset aIFIG_0 ; "请输入您的密匙:" .text:004013B5 push offset unk_439920 .text:004013BA call sub_4010AA .text:004013BF add esp, 8 .text:004013C2 mov ecx, eax .text:004013C4 call sub_40107D .text:004013C9 mov eax, [ebp+var_8] .text:004013CC push eax ; Buffer .text:004013CD call _gets .text:004013D2 add esp, 4 .text:004013D5 mov ecx, [ebp+var_8] .text:004013D8 push ecx ; int .text:004013D9 mov edx, [ebp+Str] .text:004013DC push edx ; Str .text:004013DD call j_encrypt_func ; 调用函数获得加密信息,传入buffer .text:004013E2 add esp, 8 .text:004013E5 mov [ebp+Str], eax ; eax返回加密信息 .text:004013E8 mov eax, [ebp+Str] .text:004013EB push eax .text:004013EC push offset Format ; "%s\n" .text:004013F1 call _printf .text:004013F6 add esp, 8 .text:004013F9 call sub_419590 .text:004013FE xor eax, eax .text:00401400 pop edi .text:00401401 pop esi .text:00401402 pop ebx .text:00401403 add esp, 50h .text:00401406 cmp ebp, esp .text:00401408 call __chkesp .text:0040140D mov esp, ebp .text:0040140F pop ebp .text:00401410 retn .text:00401410 _main_0 endp
一键F5,查看伪代码
进入函数encrypt_func也就是加密函数,再次F5产看代码(已优化命名):
加密函数内,先分配了加密数组enc_buffer的内存地址,也分别计算了明文、密钥的长度;然后进入for循环,i是递增的循环变量,也是明文字符串索引,循环次数取决于明文长度;v6 是密钥索引,由if 判断来重置密钥的值,这就避免了密钥长度短于明文长度带来的不便,简单的循环密钥思想。
重点是这一行加密代码:
*(_BYTE *)(i + enc_buffer) = sub_401005(plain[i], K[v6++]);
可见,他是明文字符 plain[i] 和密钥字符 K[v6] 加密填入enc_buffer[i],因此密文长度等于明文长度,验证了前面的判断。
好,现在重点分析sub_401005地址,又是一个跳转,我命名之为encrypt_char函数
双击进入函数(优化命名后)、F5:
逻辑十分清楚了,名文字符、密钥字符取得大写,判断不是空格后开始加密(再次验证开头关于空格不加密的判断),由明文的ascii-‘A’的ascii(65)得到这一次函数循环的次数,循环更简单,就是每次把密钥的ascii值加1。我们通过函数流图看看后续操作(伪代码后面的结束步骤返回不详细)。
(局部)
(loc_401200 程序结束)
分析完毕。
然后根据题目要求来写出解密脚本,只需要写出最后的encrypt_char函数解密脚本:
enc_text = "QWDRILDWNTW" K = "ILOVEMOCTFI" #直接取大写,并且循环连接密钥使K长度和明文一致,简化后面操作 x = 0 #索引 decoded_text = "" #目标解密字符串 while x < len(K): if enc_text[x] == " ": #空格直接回填 decoded_text += " " continue temp_num = 0 #保存ascii码值 #正常情况下,密文是密钥K若干次加一得到的,要大于K;否则说明密文经过-0x19操作 if enc_text[x] < K[x]: temp_num = ord(enc_text[x]) + 0x19 else: temp_num = ord(enc_text[x]) cycle_time = temp_num - ord(K[x]) #还原循环次数 decoded_text += chr(cycle_time+65) #通过ascii值+64 还原明文 x += 1 print(repr(decoded_text))
结果:
moctf{ILOVEYOUTOO}
参考:https://bbs.pediy.com/thread-250320.htm