2021强网杯LongTimeAgo复盘分析

robots

 

花指令

查看main函数能看出有异常跳转。

查看汇编,将此处c为代码。

之后再依次去除花指令,再反汇编得到main函数伪代码。

主函数不短,先根据参数大概还原函数名,可以看出是常规的对输入加密再check。

接下来结合动态调试逐步分析。

 

初步check与自定义结构体初始化

这一段出现的变量v3v4v5最终只在最后一个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_72buf_char_360中的每9个dword中的第2个dword中。

其中的while循环根据数据计算得到一个值,存储在每9个dword中的第1个dword中。
若输入为12345678,则该值为4。
若输入为00345678,则该值为3。
若输入全为0或非法(不在0123456789ABCDEF中),则该值为0。
实质上保存的是该值的有效字节数。

则这里的buf_char_72buf_char_360可能是结构体,应该是用来分别存储输入数据和最后的加密结果。

则可创建如下结构体。

再重新y设置buf_char_72buf_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函数则等待即可。

相关值如下。

key1 = 0x0FFFD  valid_flag = 2
key2 = 0x1FFFD  valid_flag = 3
key3 = 0x3FFFD  valid_flag = 3
key4 = 0x7FFFD  valid_flag = 3

回去分析加密函数sub_4029E0。代码非常长,先去除花指令。对部分变量重命名,再慢慢看。

根据变量引用能看出下面为一个整体部分。

数据有效字节长度范围是0-4,则if语句只可能走向最后两部种情况。若默认数据的有效长度为4,则能够看出其实就是将第一个数据存储到v53[0]处。

同理经过观察后面还有几部分类似的代码,根据偏移计算可知,实质上就是将两个输入数据和4个key值分别进行转储(按照自定义的数据结构体)。

据此并结合内存地址,设置如上结构体。重新设置数据类型与变量名。

输入与密钥的转储之后的一个循环应该是关键的加密。根据值sum每次循环都减去0x70C88617以及最后循环结束时值为0xE6EF3D20,得循环次数为32。显然为tea加密族。根据明显可以看到的 >> 11) & 3可知为xtea加密。

v = 0
i = 0
while v!=0xE6EF3D20:
    i+=1
    v -= 0x70C88617
    v &= 0xffff_ffff
print(i)  # 32

则根据xtea加密以及相关参数,可推出各个函数的作用。猜测这些函数是针对这种valid_flag+data的结构体专门实现的各种运算。

即这里对数据进行了被魔改的xtea加密。继续往后看。

之后根据前面生成key时所调用过的函数create_data_based_const又生成一个值。再计算其有效字节数形成自定义数据结构体,与加密后的data1进行异或。

之后又是类似最前面的代码,将异或后的结果又转储回原输入的地址。

同理,后面使用不同的参数调用函数create_data_based_const又生成一个值,与data2异或后转储回去。

该异或值可以通过动调,在前面生成密钥的地方修改参数后运行来获得。
create_data_based_const(3,5) = 0xFD
create_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) = 0x3FDcreate_data_based_const(3,8) = 0x7FD异或。即对输入生成的后4个dword数据分别进行魔改的tea加密后再与固定数据异或。

主函数加密后就是check部分。

根据已知的check数据,可以知道加密后的valid_flag值都为4。则这里首先判断加密结果和check数据的有效字节数是否相同。

根据内存地址计算可知v30[-0xD8]v30[-0xD8]实质上就是input_datares_data的地址。

print(hex(0x48+0xd8*4))  # 0x3A8
print(hex(0x48+0x48*4))  # 0x168

则之后就是逐字节比较加密结果和check数据是否相同。

 

总结与解密

则加密与check流程大致如下。

len(input) == 64
every_char in '0123456789ABCDEF'
data = binascii.a2b_hex(input)
xtea_encrypt_xor(data[:4])
tea_encrypt_xor(data[4:8])

结合一些混淆,以及实现自定义结构体的相关代码(计算有效字节数,结构体对应的相关运算),使得代码量比较大,增大难度。

最后写出逆向解密脚本。

def xtea_xor_decipher(value, key):
    v0, v1 = value[0], value[1]
    v0 ^= 0xFD
    v1 ^= 0x1FD
    delta = 0x70C88617
    su = 0xE6EF3D20
    for i in range(32):
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (su + key[(su >> 11) & 3])
        v1 &= 0xffff_ffff
        su = (su + delta) & 0xffff_ffff
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (su + key[su & 3])
        v0 &= 0xffff_ffff
    value[0] = v0
    value[1] = v1

def tea_xor_decipher(value, key):
    v0, v1 = value[0], value[1]
    k0, k1, k2, k3 = key[0], key[1], key[2], key[3]
    v0 ^= 0x3FD
    v1 ^= 0x7FD
    delta = 0x1E3F4AEF^0x230A6353
    su = (32*delta)&0xffff_ffff
    for i in range(32):
        v1 -= ((v0<<4) + k2) ^ ((v0>>5) + k3) ^ (v0 + su)
        v1 &= 0xffff_ffff
        v0 -= ((v1<<4) + k0) ^ ((v1>>5) + k1) ^ (v1 + su)
        v0 &= 0xffff_ffff
        su = su - delta
    value[0] = v0
    value[1] = v1

res = [0x1F306772, 0xB75B0C29, 0x4A7CDBE3, 0x2877BDDF, 0x1354C485 ,0x357C3C3A, 0x738AF06C, 0x89B7F537]
key = [0x0FFFD,0x1FFFD,0x3FFFD,0x7FFFD]
flag = ''

for i in range(0, len(res)//2, 2):
    tmp = [res[i], res[i + 1]]
    xtea_xor_decipher(tmp, key)
    res[i], res[i + 1] = tmp[0], tmp[1]
    flag += hex(res[i])[2:] + hex(res[i+1])[2:]

for i in range(len(res)//2, len(res), 2):
    tmp = [res[i], res[i + 1]]
    tea_xor_decipher(tmp, key)
    res[i], res[i + 1] = tmp[0], tmp[1]
    flag += hex(res[i])[2:] + hex(res[i+1])[2:]

print(flag.upper())
# CD402B6A139283822F0DEA49E65794356F44EA9B3F56652F2DA39881EC491878

运行验证flag正确。

 

参考资料

wp_by_SYJ-Re

wp_by_Qfrost

(完)