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}。