强强联手:2021 强网杯 LongTimeAgo 复盘分析
![](https://static001.geekbang.org/infoq/0a/0a51cd7db1983520fdc5056c64b5743c.png)
花指令
![](https://static001.geekbang.org/infoq/8e/8e6e5f3e9805d9a5aa75c07fcf7b02f2.png)
查看汇编,将此处 c 为代码。
![](https://static001.geekbang.org/infoq/63/63d8715e31bf93651759ce06bdfdcbb4.png)
之后再依次去除花指令,再反汇编得到 main 函数伪代码。
主函数不短,先根据参数大概还原函数名,可以看出是常规的对输入加密再 check。
接下来结合动态调试逐步分析。
初步 check 与自定义结构体初始化
![](https://static001.geekbang.org/infoq/26/2615264f58dcde77bec3bc5dc240c560.png)
这一段出现的变量 v3,v4,v5 最终只在最后一个 if 语句中使用,可以直接动调查看比较的左侧的值即可得知这段代码的作用。
![](https://static001.geekbang.org/infoq/c2/c21dffc0a22db77663e4e2c53ee28259.png)
在比较处下断点,可知值为我们输入的长度,因此这段代码实质上是用来计算输入长度。即输入长度为 64。
之后进行数据初始化后进入一个循环。
![](https://static001.geekbang.org/infoq/98/98e7e621400fd7d6b6b9749553edd622.png)
可以看出循环每次将输入字符串的地址加 8,直到到达字符串结束地址。因此该循环应该是对输入的每 8 个字符进行一次处理。循环 8 次。
每次循环都首先调用sub_401DB0
函数,参数为本次循环处理的 8 个字节的起始地址和常数 8。进行分析。
![](https://static001.geekbang.org/infoq/fa/fa153d461e3e2d5fa48a9592de6edd3b.png)
显然前面一段还是用来得到字符串的长度,这里字符串为大写的 16 进制的 16 个字符,则长度为 16,分析可知这里就是将 8 个字符按十六进制转换为 32 位数据并返回,且输入必须在 0123456789ABCDEF 中,否则返回 0。
类似
binascii.a2b_hex(v)再看之前的循环,其中有两段类似的操作,一段处理的是根据输入转换而来的 8 个 dword 数据,一段处理的是 data 段固定的 8 个 dword 数据。
将其分别存储在 buf_char_72 与 buf_char_360 中的每 9 个 dword 中的第 2 个 dword 中。
其中的 while 循环根据数据计算得到一个值,存储在每 9 个 dword 中的第 1 个 dword 中。若输入为 12345678,则该值为 4。若输入为 00345678,则该值为 3。若输入全为 0 或非法(不在 0123456789ABCDEF 中),则该值为 0。实质上保存的是该值的有效字节数。
![](https://static001.geekbang.org/infoq/f2/f285069ca0e629d895f072d7749a1dff.png)
则这里的 buf_char_72 与 buf_char_360 可能是结构体,应该是用来分别存储输入数据和最后的加密结果。
则可创建如下结构体。
![](https://static001.geekbang.org/infoq/f6/f6972ea6b7114f3b4bf6201e0ffab315.png)
再重新 y 设置 buf_char_72 与 buf_char_360 的数据类型,显然为 struc_data 数组,长度都应该为 8,再重命名变量。
![](https://static001.geekbang.org/infoq/76/767e538133a40152e7ae1915269d3789.png)
之后该循环如下。
![](https://static001.geekbang.org/infoq/c3/c3b44bf08e2941d77b20f009677c2218.png)
xtea 与 tea 的魔改与异或加密该循环之后,分别调用 4 次 sub_403460 函数、2 次 sub_4029E0 函数和 2 次 sub_402030 函数。
![](https://static001.geekbang.org/infoq/7e/7ee6e4108ce8ed9f139b74a117d624c7.png)
根据参数,显然后面四次函数调用,使用了输入转换来的数据,每两个为一组进行加密处理,密钥即为前面 sub_403460 函数的参数 v25-v28,由于 v25-v28 前面均未使用,则这里 sub_403460 函数应该是对其赋值,进行密钥的初始化。(由于是每两个一组加密,且密钥数据为 4 个,可以猜测是 tea 加密,没有用 Findcrypt 找到说明可能常数被改了)
查看 sub_403460 函数。
![](https://static001.geekbang.org/infoq/f9/f90d598894f989d1ff386244e4c08ff0.png)
显然根据传入参数的不同,每次调用该函数会使得 sub_401EF0 函数生成一个值,类似前面对输入和加密结果的处理,又根据该值生成有效字节数,猜测这里是一个类似前面的结构体。
同样设置结构体。
![](https://static001.geekbang.org/infoq/19/194c967d45fa66648c052389fc852dd2.png)
则 4 次调用可以生成 4 个 key 值,且数据固定。因此可以动调得到 4 个 key 值。第二个参数的数值越大,运行时间越长,不想去分析sub_401EF0
函数则等待即可。
相关值如下。
回去分析加密函数sub_4029E0
。代码非常长,先去除花指令。对部分变量重命名,再慢慢看。
根据变量引用能看出下面为一个整体部分。
![](https://static001.geekbang.org/infoq/c7/c7383910438a933bffbe3563ac154641.png)
数据有效字节长度范围是 0-4,则 if 语句只可能走向最后两部种情况。若默认数据的有效长度为 4,则能够看出其实就是将第一个数据存储到 v53[0]处。
同理经过观察后面还有几部分类似的代码,根据偏移计算可知,实质上就是将两个输入数据和 4 个 key 值分别进行转储(按照自定义的数据结构体)。
![](https://static001.geekbang.org/infoq/56/56294a79826fac2f662f2c89c8617964.png)
据此并结合内存地址,设置如上结构体。重新设置数据类型与变量名。
![](https://static001.geekbang.org/infoq/06/06702845f346fc4c7631275081ff1741.png)
输入与密钥的转储之后的一个循环应该是关键的加密。根据值 sum 每次循环都减去 0x70C88617 以及最后循环结束时值为 0xE6EF3D20,得循环次数为 32。显然为 tea 加密族。根据明显可以看到的 >> 11) & 3 可知为 xtea 加密。
则根据 xtea 加密以及相关参数,可推出各个函数的作用。猜测这些函数是针对这种 valid_flag+data 的结构体专门实现的各种运算。
![](https://static001.geekbang.org/infoq/e9/e96fb261a519d21106a0a93ef86e2718.png)
![](https://static001.geekbang.org/infoq/88/88435d2644613b89973f40bc91ca86c5.png)
即这里对数据进行了被魔改的 xtea 加密。继续往后看。
![](https://static001.geekbang.org/infoq/2e/2e46c960d8c7c0f9bbc32d4b89ad214e.png)
之后根据前面生成 key 时所调用过的函数create_data_based_const
又生成一个值。再计算其有效字节数形成自定义数据结构体,与加密后的 data1 进行异或。
![](https://static001.geekbang.org/infoq/67/67a885de483582712bfcbe8624cd66f7.png)
之后又是类似最前面的代码,将异或后的结果又转储回原输入的地址。
同理,后面使用不同的参数调用函数 create_data_based_const 又生成一个值,与 data2 异或后转储回去。
该异或值可以通过动调,在前面生成密钥的地方修改参数后运行来获得。create_data_based_const(3,5) = 0xFDcreate_data_based_const(3,6) = 0x1FD
至此 sub_4029E0 加密函数分析完毕,即对输入生成的前 4 个 dword 数据分别进行魔改的 xtea 加密后再与固定数据异或。
接下来分析 sub_402030 函数。类似 sub_4029E0 函数,是 tea 加密,其中 delta 由 0x1E3F4AEF^0x230A6353 得到。最后再分别与 create_data_based_const(3,7) = 0x3FD 和 create_data_based_const(3,8) = 0x7FD 异或。即对输入生成的后 4 个 dword 数据分别进行魔改的 tea 加密后再与固定数据异或。
主函数加密后就是 check 部分。
![](https://static001.geekbang.org/infoq/d9/d92844d594d212ee222b5c2dcc1e087f.png)
根据已知的 check 数据,可以知道加密后的 valid_flag 值都为 4。则这里首先判断加密结果和 check 数据的有效字节数是否相同。
![](https://static001.geekbang.org/infoq/28/28c8be048b8a16e2573ded6623602b87.png)
根据内存地址计算可知 v30[-0xD8]与 v30[-0xD8]实质上就是 input_data 与 res_data 的地址。
print(hex(0x48+0xd84)) # 0x3A8print(hex(0x48+0x484)) # 0x168 则之后就是逐字节比较加密结果和 check 数据是否相同。
总结与解密则加密与 check 流程大致如下。
len(input) == 64every_char in '0123456789ABCDEF'data = binascii.a2b_hex(input)xtea_encrypt_xor(data[:4])tea_encrypt_xor(data[4:8])结合一些混淆,以及实现自定义结构体的相关代码(计算有效字节数,结构体对应的相关运算),使得代码量比较大,增大难度。
最后写出逆向解密脚本。
运行验证 flag 正确。
![](https://static001.geekbang.org/infoq/7e/7ef1439e852191b22f6baccf02bdbcd5.png)
需要网络安全学习视频/工具包/渗透测试/应急响应/漏洞等架构资料的点我【领取资料】
评论