花指令
查看main
函数能看出有异常跳转。
查看汇编,将此处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
函数则等待即可。
相关值如下。
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) = 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+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正确。