问题
问题-1:找到开头“This program cannot be run …” 这个字符串的内存起始地址(即字符串的指针),并准确计算该字符串的长度;
问题-2:需要结合具体的加密操作逻辑,说明所发现的加密算法。
输入正确 flag 后的运行截图如下:

问题分析
问题1
首先找到——FOR FUN—–的位置,使用IDA Pro
Shift+F12进行字符串定位
追入
发现有一个%s夹在两行———-中间,可以判断这个输入的%s就是“This program cannot be run …”
查看main函数
发现在调用printf
函数时输入了一个参数,其地址为40004Eh
==因此我们就找到了字符串“This program cannot be run …”的起始地址40004Eh
==
接下来找字符串长度,此时使用x32dbg进行动态调试
因为我们已经知道了该字符串的起始地址,因此我们可以在程序运行到Entrypoint时直接定位到该字符串:
==数一数字符串长度 可知该字符串长度是43个字节==
问题2
首先发现exe文件在输入错误的flag之后直接结束或者退出了,因此我们只能直接看汇编代码对该程序进行研究
首先看提示的字符串” ———FOR FUN———-“以及”Please input your flag below:”,可以从这两个提示字符串入手研究该程序
先看main函数的整体结构:
首先调用了函数sub_402320
,随后输出提示信息————–FOR FUN——————,接着连续调用了三个函数,因此可以从一个一个函数入手研究这个程序
第1个函数
发现在调用printf
函数输出——-FOR FUN——–之前调用了一个函数sub_402320
追入该函数查看,利用IDA Pro的反汇编功能研究该函数:
解读这段反汇编代码:这段代码的功能是执行一个函数表中的一系列函数,并在程序退出时再次执行一个特定函数。它的基本思路是:如果全局变量dword_407080的值为0,则将其设为1,然后执行一个函数表中的一系列函数,最后调用sub_4014C0函数,该函数的参数是sub_402290函数的地址。如果dword_407080的值不是0,则直接返回它(result)。
具体来说,下面是每个步骤的解释:
result = dword_407080;
将dword_407080的值赋给result变量。if (!dword_407080)
如果dword_407080的值为0,执行以下代码块,否则跳过该代码块。dword_407080 = 1;
将dword_407080的值设为1。for (i = 0; dword_4033E0[i + 1]; ++i)
查找函数表dword_4033E0中最后一个函数的索引。该函数表是一个由地址组成的数组,最后一个地址为0,以便循环能够停止。for (; i; –i)
执行函数表dword_4033E0中的函数,从最后一个函数开始一直到第一个函数。((void (*)(void))dword_4033E0[i])();
将函数表dword_4033E0中第i个元素的地址强制转换为void (*)(void)类型的函数指针,然后执行该函数。return sub_4014C0((_onexit_t)sub_402290);
调用sub_4014C0函数,并将sub_402290函数的地址转换为_onexit_t类型的函数指针作为参数。_onexit_t是一个函数指针类型,它指向一个函数,该函数在程序退出时执行。return result;
如果dword_407080的值不为0,则直接返回该值。
这段函数的功能是调用函数表
第2个函数
追入sub_4015C0
这个函数
查看其汇编代码,这个函数的功能上,首先输出了提示信息:“Please input your flag below:”
接下来函数调用了scanf函数接收我们输入的flag:
可以看到这个scanf函数是用于接收一个长度不小于9个字符的字符串输入,**byte_407444
就是输入的字符串**
接下来,该函数计算了输入的字符串长度,并将其与存储在内存地址 byte_4040E4
处的值进行比较:
可以看到,如果输入的字符串长度不相等,则跳转到loc_40160F处,loc_40160F的功能是恢复函数栈寄存器并退出程序;如果相等则返回
因此我们需要知道byte_4040E4的值,追入查看byte_4040E4
可以看到4040E4
的值是9,也就是说,输入的字符串长度应当是9
因此,第二个函数的功能是输出提示信息,接收输入的字符串并将输入字符串的长度与预设的长度值9进行比较,如果长度不相等则退出程序
第3个函数
回到main函数,继续研究下一个函数sub_401770
查看其反汇编函数:
可以看到,本段代码的功能是对字符串进行合法性判断
以下是代码注释:
1 | for (i = 0; (unsigned __int8)byte_4040E4 != i; ++i) // 对每个字符,循环直到字符为0x00 |
因此,这个函数的目的是遍历一个字符串,并检查字符串中的每个字符是否是数字、大写字母或下划线,如果不是,则结束程序并返回错误码。
第3个函数调用的函数
如果字符串合法,则会跳转到sub_401700
我们追入这个函数查看
查看其反汇编代码,发现这个函数就是关键的进行字符串比较的函数
这个函数的功能是将输入的字符串byte_407444
与 a1234567890Abcd
字符串数组进行比较,如果相等,则将 dword_4040C0
数组中对应的元素设置为 v3
并返回
此时我们发现,关键的数组出现了,就是这个a1234567890Abcd
追入查看:
它的值是:1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ
第4个函数
回到main函数,继续查看第四个函数:
看到:
发现这里对字符串进行了仿射加密:对输入的明文字符串利用a1234567890Abcd
的字符集1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ
进行仿射加密
其中仿射加密的密钥a=v6=9, b=v1=3
第4个函数调用的函数
随后调用了一个函数sub_40210C
,追入查看,并定位到关键代码段:
这段代码中:
将字符串合并为了一个长度为32字符长度的定长字符串,因此我们可以判断这个函数是一个哈希算法,用以输出定长的256位散列值
随后定位到前面的关键函数sub_401A14
,推测这个函数就是用来进行哈希的函数,追入查看:
可以判断这是一个MD5加密算法!!
因此函数sub_40210C
的功能是将Block
变量分成多个32 字节的块,然后每个块都通过 sub_401A14
函数计算出其哈希值。最后,将所有块的哈希值合并成一个 128 位的哈希值,并将其转换为字符串格式写入 Buffer
变量中。
回到函数sub_40161C
,接下来函数进行了一个字符串比较:
Str1是sub_40120C
加密过后得到的散列值,因此判断Str2就是所比较的密文(散列值),追入查看:
Str2的值是:f8728f24e01c1aaf54e23f7f0d591384
如果比较成功,即输入的值加密后和预设的密文散列值相同,进入下一个比较,如果byte_407449
的值为32Z2
,则比较成功,跳出成功得到flag的消息框
我们发现byte_407449
和输入字符串的byte_407444
相差5,因此v4的strcmp**407449
所比较的字符串就是输入字符串的后4个字节,因此我们可以知道flag的后四个字节是32Z2
**
解密思路
在网上找了个MD5解密轮子(Link:https://www.somd5.com/),解密MD5得到明文:`K502G`
然后我们就可以奖字符串进行拼接进行仿射解密了:K502G32Z2
仿射解密脚本如下(Python):
其中注意,由于解密是逆变换,所以密钥a=3, b=9
1 | def main(): |
然后把这个字符串拼接起来得到flag:5M1LE_LOL
输入这个flag,得到成功的提示:
第一次尝试:
因此可以得到flag:3WC9H32Z2
–>它不对…. o(TヘTo)
难道是md5解错了?还是仿射回去的时候错了?
笑死 原来是仿射解密的密钥搞反了 解密是逆变换 密钥要反过来
而且32Z2也要一起丢进去仿射解密