在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
来源:oschina
链接:https://my.oschina.net/u/4357854/blog/3566277