2020年DASCTF五月线上赛

五月赛,从学习信安以来第二次参加的比赛,慢慢来。

Reverse

ViQinere

64位elf文件,载入ida后发现与以往的不同。很多函数都没显示出来。

linux中运行一下,显示对flag加密,然后是一些乱码。后来群里通知, 这个有靶机。

nc连接一下,那题目的flag存放在服务器,文件操作取出flag,经过加密。

image-20200523203121150

接着再在ida中看一下,有几个字符很懵,后面才发现就是改变输出字符串样式,这也是为什么linux中运行的时候会有涂鸦与闪动。

image-20200523204020172

然后是被涂鸦的那一段坑了,显示 Flag was encrypted by http://cc.TaQini.space ,一直以为加密和这个有关, 嗯…后来发现是题目作者给自己打的广告。

虽然很多函数不能显示出来,但也是能猜出来作用。看看汇编会更清楚。

最后找到加密函数 sub_1249((__int64)byte_4080); 这下就好办了。进去函数可以知道,大写字母加密为大写字母,小写字母加密为小写字母,其他字符不变。

image-20200523204833774

对于逆这个算法,没有过多的想,算是直接穷尽的吧。因为大写字母变大写字母,小写字母变小写字母。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <string.h>

int sub_1209(int a)
{
if(a >= 97 && a <= 122)
return a-97;
else if(a >= 65 && a <= 90)
return (a-65)^0xFFFFFF80;
else
return a;
}

int main(void)
{
int i = 0, v4 = 0, j = 0, v2 = 0, v5 = 0, v1 = 0;
char a[26] = {0};
char b[] = "TaQini";
char c[] = "FQD{GfjuJ5UbLrWjZjpvErXkiAZzlvO0xTa!cwnLLAsy3B0iEvEy}";

for(i = 0; i < 26; i++)
{
a[i] = 122-i;
}

for(i = 0; i < strlen(c); i++)
{
if((c[i] >= 97 && c[i] <= 122))
{
for(j = 97; j <= 122; j++)
{
v4 = sub_1209(j);
v2 = sub_1209(b[v5 & 5]);
if(c[i] == a[((v2&0x7F) + v4)%26])
{
c[i] = j;
break;
}
}
v5++;
}
else if(c[i] >= 65 && c[i] <= 90)
{
for(j = 65; j <= 90; j++)
{
v4 = sub_1209(j);
v1 = sub_1209(b[v5 & 5]);
if(c[i] == a[(v4+(v1 & 0x7F) + 128)%26] - 32)
{
c[i] = j;
break;
}
}
v5++;
}

}

puts(c);

return 0;

}

最后补充一下对改变输出字符串的知识。

\033[0m关闭所有属性
\033[01m设置高亮度
\033[04m下划线
\033[05m闪烁
\033[07m反显
\033[08m消隐
\033[30m – \033[37m设置前景色
\033[40m – \033[47m设置背景色
\033[nA光标上移n行
\033[nB光标下移n行
\033[nC光标右移n行
\033[nD光标左移n
\033[y;xH设置光标位置
\033[2J清屏
\033[K清除从光标到行尾的内容
\033[s保存光标位置
\033[u恢复光标位置
\033[?25l隐藏光标
\033[?25h显示光标

总结: 题本来很简单,主要几次被带入坑。也是没经验。

MiscVm

下载下来, 64位的无壳elf文件和一个操作码文件。操作码其实题中也有, 这里单独给了方便很多。

载入ida. C++逆向类的,但还好都是很基本的语法。

image-20200524075414831

分别进去三个加密函数,第一个: sub_19E2((int64)&v13 (__int64)&v14); 很简单,就是 2个数的值交换。

image-20200524075631032

第二个: sub_1551((int64)&v14, (int64)&v13); 就是按照给定顺序进行简单的值交换。

image-20200524075954652

第三个: v8 = (unsigned int)sub_EEC(&v14, &v15, 0LL) != 0,这个相对于要麻烦一些, 但分析一下, 思路理清,逆回去就好了。

image-20200524083055817

exp: C语言写的,比较冗长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdio.h>

int decode_4(int a)
{
int i = 0, j = 0, temp = 0;

for(i = 0; i < 2000; i++)
{
j = i;

j ^= 10;
temp = (16*j >> 31)>>28;
if(a == j + (((temp + 16*(*(char *)&j))&0xF)-temp))
break;
}

return i;
}

int main(void)
{
union
{
unsigned char ida_chars[152];
int a[37];
}A = {
66, 0, 0, 0, 74, 0, 0, 0, 68, 0,
0, 0, 123, 0, 0, 0, 51, 0, 0, 0,
112, 3, 0, 0, 70, 0, 0, 0, 212, 0,
0, 0, 60, 0, 0, 0, 16, 6, 0, 0,
79, 0, 0, 0, 200, 0, 0, 0, 108, 0,
0, 0, 32, 3, 0, 0, 30, 0, 0, 0,
144, 1, 0, 0, 111, 0, 0, 0, 48, 6,
0, 0, 70, 0, 0, 0, 144, 1, 0, 0,
59, 0, 0, 0, 16, 6, 0, 0, 29, 0,
0, 0, 196, 0, 0, 0, 62, 0, 0, 0,
96, 6, 0, 0, 75, 0, 0, 0, 208, 0,
0, 0, 108, 0, 0, 0, 16, 3, 0, 0,
70, 0, 0, 0, 136, 1, 0, 0, 51, 0,
0, 0, 112, 3, 0, 0, 76, 0, 0, 0,
204, 0, 0, 0, 125, 0, 0, 0, 0, 0,
0, 0
};
int i = 0, j = 36, temp = 0;
int d[100] = {0}, e[100] = {0};
int a1[100] = {3,6,5,4,5,11,1,7,9,5,3,6,10,9,5,4,11,1,6,3,7,9,10,9,5,4,14,1,7,9,10,9,4,5,3,6,1,5,3,6,7,9,10,9,36,3,6,36,4,1,5,7,9,3,6,5,10,9,4,1,7,9,10,9,5,4,5,1,5,7,9,5,10,9,3,6,5,4,5,11,1,7,9,5,3,6,10,9};
int b[100] = {1,2,3,4,19,31,25,14,23,33,13,9,24,6,26,34,17,10,8,29,12,15,22,11,18,16,32,28,21,36,20,7,5,27,30,35,37};
int c[100] = {1,2,3,4,31,29,7,35,14,21,9,16,27,18,25,10,20,15,17,22,28,26,36,33,32,5,8,12,23,34,13,30,24,11,19,6,37};

while(i >= -87)
switch(a1[87+i])
{

case 1:
--j;
--i;
A.a[j] = (A.a[j] / 16)*2;
A.a[j] = A.a[j];
A.a[j] /= 2;
break;
case 2:
A.a[j] -= 128;
A.a[j] = A.a[j]*j;
--i;
break;
case 3:
A.a[++j] /= 10;
--i;

break;
case 4:
--j;
--i;
A.a[j] = decode_4(A.a[j]);
break;
case 5:
A.a[j] = A.a[j];
--i;
break;
case 6:
A.a[j--] *= 10;
--i;
break;
case 7:
A.a[j] -= 128;
A.a[j] = ~A.a[j];
--i;
break;
case 9:
--i;
--j;
break;
case 10:
A.a[j] /= 4;
--i;
break;
default:
--i;
break;

}


for(i = 0; i < 37; i++)
{
d[c[i]-1] = A.a[i];
}

for(i = 0; i < 37; i++)
{
e[b[i]-1] = d[i];
}

for(i = 4; i <= 19; i++)
{
temp = e[i];
e[i] = e[i+16];
e[i+16] = temp;
}

for(i = 0; i < 37; i++)
printf("%c", e[i]);

return 0;

}

image-20200524083340342


BScript

感觉有点意思。主要就是考对文件内容的提取,还有就是找规律!!!

下载下来803个exe文件。随便打开一个看见简单的输入对比, 再打开一个还是同样的.但没有发现与解题相关的东西。

也是后面想着再去看看这个题才发现 每个exe文件让我们输入的都是一个PE文件的一部分, 那 熟悉的 MZ(4D5A)。image-20200524100310951

接下来就是去文件内容了。但是每个程序都是加了upx壳的。

然后用C语言写了脱壳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int i = 0;
char a[100] = {0};

for(i = 83; i < 804; i++)
{
sprintf(a, "upx -d %d.exe", i);
system(a);
}


return 0;
}

image-20200524102949962

下面提取每个程序的 PE文件那部分的内容. 弄完, 保存为exe文件后, 打不开…ida都不行, 看看了PE文件内容发现区段都没有。。

那就提取的内容有问题, 多看了几个原来的exe文件, 果然,有的提取内容的位置不一样。

但有803个exe。

开始找规律。列举了很多还是没发现。这也是本题花费时间最多的地方……..根据exe文件的字节大小来区分…改了一下提取内容的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int i = 0, n = 779, j = 0, lenth = 0;
unsigned char a[4000] = {0};
FILE *fp = NULL;
char filename[100] = {0};

while(n--)
{
sprintf(filename, "%d.exe", j++);
fp = fopen(filename, "rb");
fseek(fp, 0, SEEK_END);
lenth = ftell(fp);
if(lenth == 0xBDF1)
{
fseek(fp, 0x1c20, 0);
fread(a, 1, 0x20, fp);
fclose(fp);

for(i = 0; i < 32; i++)
printf("%02X", a[i]);

}
else
{
fseek(fp, 0x1C40, 0);
fread(a, 1, 0x40, fp);
fclose(fp);

for(i = 63; i >= 0; i--)
{
printf("%02X", a[i]);
}

}

}

return 0;
}

仍然打不开,但是这次区段都是齐的, ida可以打开就好了。

载入ida,发现有很多干扰项,但可以在比较函数上面一个看出是 base64加密。最后与密文比较:

image-20200524101529508

找密文, 可以发现是程序运行才赋值的。

image-20200524101634787

提取出来base解密一下,再md5得到flag. e801bcbcc42d3120d910ccc46ae640dd

Crypto

bbcrypto

由于python接触的很少,做了一个密码学的感觉像是在学python。不断查语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding:utf-8 -*-

import A,SALT

from itertools import *

def encrypt(m, a, si):

c=""

for i in range(len(m)):

c+=hex(((ord(m[i])) * a + ord(next(si))) % 128)[2:].zfill(2) #不足2位以0填充。

return c

if __name__ == "__main__":

m = 'flag{********************************}'

a = A

salt = SALT

assert(len(salt)==3) #assert相当于一个判断表达式,若括号中表达式不成立则退出。

assert(salt.isalpha()) #检查是否是字母

si = cycle(salt.lower()) #cycle把字符串salt变成一个循环,相当于 si[i++%3];

print("明文内容为:")

print(m)

print("加密后的密文为:")

c=encrypt(m, a, si)

print(c)

#加密后的密文为:

#177401504b0125272c122743171e2c250a602e3a7c206e014a012703273a3c0160173a73753d

从题中可以看到,A与salt未知,但是salt给出了限制,必须字母且3位。

又知道flag的格式 flag{ },那结合密文与加密表达式,直接列方程加爆破解出未知量即可。

记得当时解出 A = 57. si = “ahh” 开始解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <string.h>

int main(void)
{
char a[] = "177401504b0125272c122743171e2c250a602e3a7c206e014a012703273a3c0160173a73753d";
int i = 0, lenth = strlen(a), sum = 0;
char b[100] = {0}, j = 0;
char c[3] = {97, 104, 104};

for(i = 0; i < lenth; i += 2)
{
if(a[i] >= 97 && a[i] <= 122)
a[i] -= 87;
else if(a[i] >= 65 && a[i] <= 90)
a[i] -= 55;
else
a[i] -= 48;

sum = sum*16 + a[i];

if(a[i+1])
{
if(a[i+1] >= 97 && a[i+1] <= 122)
a[i+1] -= 87;
else if(a[i+1] >= 65 && a[i+1] <= 90)
a[i+1] -= 55;
else
a[i+1] -= 48;

sum = sum*16 + a[i+1];
}

b[j++] = sum, sum = 0;
}

for(j = 0; j < 38; j++)
{
for(i = 33; i < 127; i++)
{
if((i*57 + c[j%3])%128 == b[j])
printf("%c", i);
}
}


return 0;
}

flag: flag{ad7d973ffdd285b476a1a727b3a8fbc4}

Misc

Questionaire

开始网站始终打不开,因为没接触过web,始终认为是考web的知识。

后面想着挂vpn试试。打开了,很多刁难性问题,直接查看网页源代码,找到对应答案即可。

/bin/cat 2

得到一张 gif 图片, 还是老办法放在 stegesolve 工具中看看。

开始看了好几圈也没看出什么名堂,也是很久了,在red plane的页面发现很像一个二维码,带着试一试用工具截取了下来。

使用 PsQREdit 2.4.3 真的得到了 flag. 具体题没有保留下来。

-------------本文结束感谢您的阅读-------------