Rating:

很直来直去的算法逆向题目,总共两部分。

第一部分,首先判断输入长度是否为32字节,接着根据输入的第七位、第十六位、第三十位计算得到一个种子 seed,然后生成33个随机数放在数组 a 中,使用这个数组 a 的值和输入进行计算得到大小为64的数组 b,数据类型是 unsigned int。

第一部分题目代码如下。

```
char *input = (char*)malloc(33);

scanf("%s", input);

int length = strlen(input);

if (length != 32) {

exit(0);

}

int value = (input[6] + input[15] + input[29]) * 53;

unsigned int *tmp = myrandint((~value)&0xfff, input);

unsigned long long int * tmp2 = (unsigned long long int *)tmp;

unsigned long long int * data = (unsigned long long int *)malloc(sizeof(unsigned
long long int) * 32);

```

```

unsigned int * myrandint(unsigned int seed, char * input)

{

unsigned long long int a = 32310901;

unsigned long long int b = 1729;

unsigned int c = seed;

int m = 254;

unsigned int * ret1 = (unsigned int*)malloc(33 * sizeof(int));

memset(ret1, 0, 33 * sizeof(int));

for (int i = 0; i < 33; i++) {

ret1[i] = (a * c + b) % m;

c = ret1[i];

}

unsigned int * ret2 = (unsigned int*)malloc(64 * sizeof(int));

memset(ret2, 0, 64 * sizeof(unsigned int));

for (int i = 0; i < 32; i++)

{

for (int j = 0; j < 33; j++)

{

unsigned int tmp = (unsigned int)input[i] ^ ret1[j];

ret2[i + j] += tmp;

}

}

return ret2;

}

```

第二部分,使用 unsigned long long int 类型指针指向数组b,可以理解为把数组 b 转化为大小为32、数据类型为 unsigned long long int 的数组 c,接着就是进一步处理和加密数组 c。每次处理数组 c 两个值,第一个当作 key,第二个当作 data。

然后首先是已经很常见的算法套路,就是64重循环对 key 不断乘二,并且对乘二后的值进行判断,如果溢出了 key 就异或一个奇数,逆向的时候从 key 异或奇数这里入手,因为乘二后一定是偶数,偶数异或奇数后一定是奇数。

我在这里的基础上加了 DES 加密,用了两种模式。如果没有溢出就使用 key 对 data 进行 DES-ECB 加密,如果溢出了就使用 key 对 data 进行 DES-CBC 加密,最后再对得到的32位数据进行比较。DES-CBC 加密的 iv 是“syclover”。

第二部分题目代码如下。

```
unsigned long long int * tmp2 = (unsigned long long int *)tmp;

unsigned long long int * data = (unsigned long long int *)malloc(sizeof(unsigned
long long int) * 32);

for (int i = 0; i < 16; i++) {

unsigned long long int* ret = encrypt(tmp2[2 * i], tmp2[2 * i + 1]);

data[2 * i] = ret[0];

data[2 * i + 1] = ret[1];

}

unsigned long long int cmp_data[32] = {
2153387829194836539,4968037865209379450,8168265158727502467,7752938936513403525,14501680424383085918,17239894214146562937,8631814439533536846,14038875394924393076,4195845133744611697,5882449358190368069,16593579054240177091,6042071195929524833,4901359238874180132,5391991813165233830,1262912001997768975,10592048914693378762,16027373129319566784,8683865403612614472,1074685249143409626,14830847864020240442,839851004411889868,6756767667889788695,10980352984506363454,15143378206568444148,9137722182184199592,16483482195781840874,213411729123350449,8809840326310832316,6556887299588007217,4475244256249997594,4953583337191211260,6316604661095411857 };

for (int i = 0; i < 32; i++) {

if (cmp_data[i] != data[i]) {

exit(0);

}

}

printf("Success!\n");

```

```

unsigned char * llu2key(unsigned long long int key) {

unsigned char * ckey = (unsigned char *)malloc(9);

memset(ckey, 0, 9);

memcpy(ckey, &key, 8);

return ckey;

}

unsigned char * llu2data(unsigned long long int data) {

unsigned char * cdata = (unsigned char *)malloc(17);

memset(cdata, 0, 17);

memcpy(cdata, &data, 16);

return cdata;

}

unsigned long long int* encrypt(unsigned long long int key, unsigned long long int data)
{//,unsigned char * data 64 * 4 8 * 32 16 * 16

unsigned char * ckey = 0;

unsigned char * cdata;

cdata = llu2data(data);

for (int i = 0; i < 64; i++) {

unsigned long long int temp = key;

key = key * 2;

if (key < temp) {

key = key ^ 0x3FD99AEBAD576BA5;

ckey = llu2key(key);

des_cbc_encrypt(cdata, cdata, 8, ckey);

}

else {

ckey = llu2key(key);

des_ecb_encrypt(cdata, cdata, 8, ckey);

}

}

unsigned long long int* retarr = (unsigned long long int*)malloc(sizeof(unsigned
long long int) * 2);

retarr[0] = *(unsigned long long int*)ckey;

retarr[1] = *(unsigned long long int*)cdata;

return retarr;

}

```

编译的话是用 Visual Studio 2017 进行的 release x86 方式进行的编译,优化得还是比较厉害的,比如CBC模式加密的 iv 我本来藏得还是比较深的,然后程序生成后进行反编译是能直接看到的,还有就是 unsigned long long int 型的整数会使用两个寄存器来表示等等。

然后说下我自己的解题思路。

第一部分我自己的逆向解题思路是爆破中使用z3,因为在计算种子 seed 时候最后有 &0xfff,范围就这么大,随机数数组 a 是根据种子 m 生成的,可以爆破所有可能的种子 m ,然后剩下的交给 z3 来求解。听别的师傅说这个题被花样爆破了,甚至是用汇编爆破的,真是太强了,期待看到师傅分享出来不同的解题思路...

然后第二部分就是直接逆了,前面有说到64重循环 key 乘二从异或奇数入手,然后进行 DES 的两种模式解密。

放出我自己的解题脚本,python3 装下 z3-solver 和 pycryptodome 库就可以运行了。

```
from Crypto.Cipher import DES
import struct
from z3 import *

def des_ecb_decrypt(cipher,key):
des = DES.new(key, mode=DES.MODE_ECB)
cipher = bytes(cipher)
if (len(cipher) != 8):
cipher = b'\0' * (8 - len(cipher)) + cipher
plain = des.decrypt(cipher)
return plain

def des_cbc_decrpt(cipher,key):
iv = b"syclover"
des = DES.new(key, mode=DES.MODE_CBC, iv=iv)
if (len(cipher) != 8):
cipher = b'\0' * (8 - len(cipher)) + cipher
cipher = bytes(cipher)
plain = des.decrypt(cipher)
return plain

def myfun(key,data):
#print("%x %x" % (key, data))
data = struct.pack(">Q", data)[::-1].strip()
#print(data)
#print(list(data))
for i in range(64):
if(key%2==0):
ckey = struct.pack(">Q", key)[::-1]
data = des_ecb_decrypt(data,ckey)
key = key // 2
else:
ckey = struct.pack(">Q", key)[::-1]
data = des_cbc_decrpt(data,ckey)
key ^= 0x3FD99AEBAD576BA5
key = (key // 2) + (0xffffffffffffffff - 1) // 2 + 1

key_str = "%x"%key
if(len(key_str)%2 !=0):
key_str = "0"+key_str

key_arr = list(bytes.fromhex(key_str))[::-1]
for i in range(8-len(key_arr)):
key_arr.append(0)

tmp = []
tmp+=key_arr
tmp+=list(data)
return tmp

def myrandint( start,end,seed):
a=32310901
b=1729
rOld=seed
m=end-start
while True:
rNew=int((a*rOld+b)%m)
yield rNew
rOld = rNew

def Z3(xor_data,cmp_data):
s = Solver()
flag = [BitVec(('x%d' % i),8) for i in range(32) ]

xor_result = [0 for i in range(64)]
for i in range(32):
for j in range(33):
a = flag[i] ^ xor_data[j]
xor_result[i + j] += a

for i in range(0,64):
s.add(xor_result[i] == cmp_data[i])

if s.check() == sat:
model = s.model()
str = [chr(model[flag[i]].as_long().real) for i in range(32)]
print( "".join(str))
time.sleep(5)
exit()
else:
print ("unsat")

if __name__ == "__main__":
key = 2153387829194836539
data = 4968037865209379450
cmp_data = [2153387829194836539,4968037865209379450,8168265158727502467,7752938936513403525,14501680424383085918,17239894214146562937,8631814439533536846,14038875394924393076,4195845133744611697,5882449358190368069,16593579054240177091,6042071195929524833,4901359238874180132,5391991813165233830,1262912001997768975,10592048914693378762,16027373129319566784,8683865403612614472,1074685249143409626,14830847864020240442,839851004411889868,6756767667889788695,10980352984506363454,15143378206568444148,9137722182184199592,16483482195781840874,213411729123350449,8809840326310832316,6556887299588007217,4475244256249997594,4953583337191211260,6316604661095411857]
sum = []
for i in range(16):
sum+=(myfun(cmp_data[2*i],cmp_data[2*i+1]))
value = []
for i in range(len(sum)//4):
value.append(sum[4*i]+0x100*sum[4*i+1]+0x1000*sum[4*i+2]+0x10000*sum[4*i+3])

for seed in range(0xfff):
print(seed)
xor_data = []
#r = myrandint(1, 255, 99)
r = myrandint(1, 255, seed)
for i in range(33):
xor_data.append(next(r))
Z3(xor_data,value)

```

爆破到 99 时候就出来 flag了,flag 为 SCTF{b5c0b187fe309af0f4d35982fd}。

![](https://i.loli.net/2020/07/08/lKpejGkhon6HiAT.png)