强强联手:2021 强网杯 LongTimeAgo 复盘分析
花指令
查看汇编,将此处 c 为代码。
之后再依次去除花指令,再反汇编得到 main 函数伪代码。
主函数不短,先根据参数大概还原函数名,可以看出是常规的对输入加密再 check。
接下来结合动态调试逐步分析。
初步 check 与自定义结构体初始化
这一段出现的变量 v3,v4,v5 最终只在最后一个 if 语句中使用,可以直接动调查看比较的左侧的值即可得知这段代码的作用。
在比较处下断点,可知值为我们输入的长度,因此这段代码实质上是用来计算输入长度。即输入长度为 64。
之后进行数据初始化后进入一个循环。
可以看出循环每次将输入字符串的地址加 8,直到到达字符串结束地址。因此该循环应该是对输入的每 8 个字符进行一次处理。循环 8 次。
每次循环都首先调用sub_401DB0
函数,参数为本次循环处理的 8 个字节的起始地址和常数 8。进行分析。
显然前面一段还是用来得到字符串的长度,这里字符串为大写的 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。实质上保存的是该值的有效字节数。
则这里的 buf_char_72 与 buf_char_360 可能是结构体,应该是用来分别存储输入数据和最后的加密结果。
则可创建如下结构体。
再重新 y 设置 buf_char_72 与 buf_char_360 的数据类型,显然为 struc_data 数组,长度都应该为 8,再重命名变量。
之后该循环如下。
xtea 与 tea 的魔改与异或加密该循环之后,分别调用 4 次 sub_403460 函数、2 次 sub_4029E0 函数和 2 次 sub_402030 函数。
根据参数,显然后面四次函数调用,使用了输入转换来的数据,每两个为一组进行加密处理,密钥即为前面 sub_403460 函数的参数 v25-v28,由于 v25-v28 前面均未使用,则这里 sub_403460 函数应该是对其赋值,进行密钥的初始化。(由于是每两个一组加密,且密钥数据为 4 个,可以猜测是 tea 加密,没有用 Findcrypt 找到说明可能常数被改了)
查看 sub_403460 函数。
显然根据传入参数的不同,每次调用该函数会使得 sub_401EF0 函数生成一个值,类似前面对输入和加密结果的处理,又根据该值生成有效字节数,猜测这里是一个类似前面的结构体。
同样设置结构体。
则 4 次调用可以生成 4 个 key 值,且数据固定。因此可以动调得到 4 个 key 值。第二个参数的数值越大,运行时间越长,不想去分析sub_401EF0
函数则等待即可。
相关值如下。
回去分析加密函数sub_4029E0
。代码非常长,先去除花指令。对部分变量重命名,再慢慢看。
根据变量引用能看出下面为一个整体部分。
数据有效字节长度范围是 0-4,则 if 语句只可能走向最后两部种情况。若默认数据的有效长度为 4,则能够看出其实就是将第一个数据存储到 v53[0]处。
同理经过观察后面还有几部分类似的代码,根据偏移计算可知,实质上就是将两个输入数据和 4 个 key 值分别进行转储(按照自定义的数据结构体)。
据此并结合内存地址,设置如上结构体。重新设置数据类型与变量名。
输入与密钥的转储之后的一个循环应该是关键的加密。根据值 sum 每次循环都减去 0x70C88617 以及最后循环结束时值为 0xE6EF3D20,得循环次数为 32。显然为 tea 加密族。根据明显可以看到的 >> 11) & 3 可知为 xtea 加密。
则根据 xtea 加密以及相关参数,可推出各个函数的作用。猜测这些函数是针对这种 valid_flag+data 的结构体专门实现的各种运算。
即这里对数据进行了被魔改的 xtea 加密。继续往后看。
之后根据前面生成 key 时所调用过的函数create_data_based_const
又生成一个值。再计算其有效字节数形成自定义数据结构体,与加密后的 data1 进行异或。
之后又是类似最前面的代码,将异或后的结果又转储回原输入的地址。
同理,后面使用不同的参数调用函数 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 部分。
根据已知的 check 数据,可以知道加密后的 valid_flag 值都为 4。则这里首先判断加密结果和 check 数据的有效字节数是否相同。
根据内存地址计算可知 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 正确。
需要网络安全学习视频/工具包/渗透测试/应急响应/漏洞等架构资料的点我【领取资料】
评论