狗儿

热爱的话就坚持吧~

0%

CUMTCTF2020纪实(出题人视角)

原本期待这个比赛好久了,想和各位师傅们同台竞技一波。结果老张同我讲话,说”组委会都决定了,你来当逆向出题人。“我说另请高明吧,我实在也不是谦虚,我一个搞逆向的菜逼怎么到出题组里来了呢?但是, 老张讲‘大家已经研究决定了。’ 所以后来我念了两首诗,叫‘+++++++,+++++++。’ 所以我就到了出题组。

一周时间,各个方向都要出8道题目,还要避免原题,真的炒鸡辛苦哒,几个出题人都肝了好久。新荣和萄萄在负责平台运维的同时还要出题,鼎哥出pwn题的同时,还出了好多misc和密码学。好像就我最水,只出了8道逆向和2道misc(主要是咱也不会密码学啊,逃

其实8道也不算很多,但是这可不是往实训平台上放题,这个题目不能出的太简单,所以耗费的精力会尤为多一些。

彩蛋

首先先放一个小彩蛋。

打过比赛的都知道,鼎哥这次的pwn题,引入了一套全新的交互模式

男孩子拿到假的flag:

image-20200923132605091

女孩子拿到qq号和验证问题的答案:

image-20200923132640000

想法是好的,但是加鼎哥好友的清一色的老男人,一个萌妹子也没有。

直到我伪装成一个可爱的萌妹子去找鼎哥要flag。

鼎哥在出题人群里:

image-20200925190349953

image-20200925190433353

image-20200925190640376

哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

那时候我真的想把下面这张截图发在出题人群里,但是我忍住了:

QQ图片20200925191022

QQ图片20200925190756

很可惜,这波社工不是完美的。

首先这是小号,看等级就可以看出来。其次,很可惜我手头没有女生常用的表情包,对话前后文有点不像女生。(第一次女装,大家理解一下

我不行啦,再让我笑一会,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈

然后说一下我负责的题目的出题思路吧。

re1~re4

re1~re3纯送分。

re1懂得都懂,不懂的也没办法。

image-20200925202249738

image-20200925191346741

从解题人数来看,估计是都懂了。拖进IDA查看字符串即可。

re2-兄弟们快来帮帮萄萄,这题给的提示足够详细,下载upx,然后去壳,重复re1的步骤,即可拿到flag

image-20200925202301450

re3是最简单的pyc字节码,最初版本没这么简单的,但是第一天放出的逆向题目太难了,被人喷了,所以改成了人人都能做的签到题。看不懂逻辑的也能做。就一个异或0x13而已。

image-20200925202310266

re4-C艹,一个简单的换位密码。c++看起来很难顶,但是不需要过多关注c++的stl,看关键的逻辑就行。

image-20200925202320586

常规做法大家应该都会,这里放个我审核wp时发现的一个很有意思的解法,第一的队伍主名做的:

他先输入了ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=,动调得到了VCKWTLRID1Z7S0YQJ8-XEOMU4A3HBG5PF2N96=,然后利用已知明文和已知密文是否对应,以确定flag某一字符的偏移。太巧妙了,出题人表示学到了。

1
2
3
4
5
6
7
8
9
before = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-='
after = 'VCKWTLRID1Z7S0YQJ8-XEOMU4A3HBG5PF2N96='
flag_encrpyt = 'eMl1_l1hT9_ldcoR3OC1CW0HhC_{UF30Tp__l}'
flag = ''
for i in range(len(before)):
for j in range(len(before)):
if before[i] == after[j]:
flag += flag_encrpyt[j]
print(flag)

再放一个青老年混合队的绝佳做法:

image-20200925234259930

image-20200925234317277

image-20200925234328509

绝了,excel解逆向,太强啦

re5-给ACM的师傅们送分

image-20200925202239788

这题是我平生得意之作。ACM的逻辑是纯c#,CTF的逻辑是调了一个vc写的dll。为了降低难度,没加混淆,甚至连函数名等符号信息都保留着。

这题的码量极大,我肝了整整两个凌晨。

image-20200925191823080

image-20200925191830150

image-20200925191846400

把两道题合成一道题,估计也就我一人这么搞了吧哈哈哈哈

第一波放hint的时候,我写道:

image-20200925192011304

有句ACM阵营:太过简单,不予提示,但是真的不是在嘲讽大家。

是因为我们最近都在学《信息安全数学基础》这门课,都学过同余了。我真的觉得大家看到没有经过任何混淆、没有删除符号信息的c#源码,都能够第一时间想到线性同余方程组这个概念,然后去网上找轮子。这么基础的数学问题,网上一定会有轮子的。

第二次放hint,放出的是:

image-20200925192628030

大概一个小时后,我正吃饭水群的时候,承淳大佬和我说他做出来了,而且是在我放hint之前就已经找到相应的轮子了(早知道我再等一个小时啊,哈哈哈哈)ACM的大佬果然名不虚传,太太太太太强啦~

image-20200925194957589

关于承淳大佬说的提示的关键词不够优秀这件事,我当时放这个提示的时候,主要是想透露网上有轮子这个信息而已,要是直接把轮子给师傅们,那这题岂不是人人都能做?何况根据提示的关键字也是能找到轮子的相关信息的,只是没那么直接而已。

然后晚上,主名也做出这题来了:

image-20200925194913425

image-20200925195056373

最终这题只有两队做出来。而且选的都是ACM阵营。

CTF阵营确实很难。这个阵营对应的验证逻辑,我是打算升级一下,参加KCTF的防守一方的,所以具体细节在KCTF结束之前不会公开,望师傅们理解,谢谢~

主名的acm阵营解题代码:

https://blog.csdn.net/pig_dog_baby/article/details/82079073

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include<cstdio>
#define maxn 110
#define r register
using namespace std;
typedef long long ll;
int n,p,maxi;
ll tmp,ans[maxn],a[maxn][maxn];
int key[41] ={
233,
136,
189,
132,
157,
100,
196,
185,
138,
222,
90,
101,
115,
229,
161,
97,
135,
122,
127,
230,
143,
203,
137,
119,
80,
141,
227,
156,
178,
105,
133,
194,
184,
179,
159,
220,
111,
177,
145,
200,
181};
int sum[41] = {
46384,
31562,
39797,
36757,
62393,
15780,
41763,
29976,
5998,
4308,
40650,
45891,
6897,
54534,
14623,
49558,
23530,
37973,
3560,
18854,
47021,
52794,
16283,
28942,
33213,
25540,
62337,
7253,
14550,
60109,
25945,
26838,
55988,
46800,
47119,
44280,
58951,
62100,
59760,
25395,
16590
};
int read()
{
r char ch=getchar();r int in=0;
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9') in=(in<<3)+(in<<1)+ch-'0',ch=getchar();
return in;
}
ll ksm(r ll x,r int y) //快速幂算法
{
if(!y) return 1;
r ll ret=ksm(x,y>>1);
if(y&1) return ret*ret%p*x%p;
return ret*ret%p;
}
int main()
{
//sum[0] = flag[i]*(k^(j-i)) ...... flag[41]*(k^(1))
//从1开始。
/*n=read(),p=read();
for(r int i=1;i<=n;i++)
for(r int j=1;j<=n+1;j++)
a[i][j]=read();*/

n = 41 ; //41个未知数
p = 65537; //p是取模
for(r int i=1;i<=n;i++) //赋值a数组,其内容是k的幂,n从1开始,到41
for(r int j=1;j<=n;j++) //i是行,j是列,也是从第一列开始到第41列
a[i][j]=ksm(key[i-1],40-j+1);
for(int i =1;i<=41;i++){ //对矩阵最后一列(第42列)进行sum赋值
a[i][42]=sum[i-1];
}

for(r int i=1;i<=n;i++)
{
if(!a[i][i])//主元不能为0
{
maxi=0;
for(r int j=i+1;j<=n&&!maxi;j++)
if(a[j][i]) maxi=j;
if(!maxi) continue;//如果一整列都为0,不需要消元
for(r int j=i;j<=n+1;j++)
tmp=a[maxi][j],a[maxi][j]=a[i][j],a[i][j]=tmp;
}
for(r int j=i+1;j<=n;j++)
{
tmp=a[j][i];
if(!tmp) continue;//已经为0,不需要消元
for(r int k=i;k<=n+1;k++)
a[j][k]=((a[j][k]*a[i][i]-a[i][k]*tmp)%p+p)%p;
}
}
for(r int i=n;i;i--)
{
for(r int j=i+1;j<=n;j++)
a[i][n+1]=((a[i][n+1]-ans[j]*a[i][j])%p+p)%p;
ans[i]=a[i][n+1]*ksm(a[i][i],p-2)%p;
}
for(r int i=1;i<=n;i++) printf("%lld ",ans[i]);
return 0;
}

承淳大佬找到的轮子应该和猪娃的是一样的,这里就只放下他的思路吧:

image-20200926001129360

re6-兄弟们快来帮帮新荣

image-20200925202223092

嘿嘿嘿,用到的是我今年七夕写过的一个小算法,文章发表到了看雪上,我博客也有副本。

由于担心难度过大,这题我直接给出了相关的博文,其中有算法的原理解析和加解密算法的DEMO以及生成加解密函数的迭代脚本。

由于嵌套了15次,所以汇编指令及其庞大,ida无法整成类c代码。

这题是主名第一个做出来的,感谢易桑和猪娃顶着烦人的自动回复与我进行侧信道通话。

image-20200925201307732

image-20200925201437907

image-20200925201420492

但是周四的算法课上,楠姐和炜昊都和我说ghidra可以反编译:

image-20200925201031050

不过看起来还不如汇编的格式整齐划一。但是可以代码量比汇编短了不少,应该可以快速提取关键数据。

然后炜昊周四下午也做出来了,他发现了一个我没考虑到的地方,本题难度瞬间下降:

image-20200925201541251

image-20200925201615172

注意看下图:

QQ图片20200925201708

由于是迭代变量替换,所以最后一次的加密一定是c = y ^ (y << n),y中一定包含此前14次嵌套所用的14个常数。。。

没别的可说的,炜昊太强啦。

幸好炜昊帮我发现了这个bug,不然这个逻辑拿到KCTF上去会被大师傅们笑话的。

让我再好好想想怎么改进。

主名的解题:

只能一步步跟了,大概分析出前几个逻辑之后。分析出规律,并且每次得到的新变量都会放进[ebp+var10]处,直接x跟踪这个变量,就能在上下文中快速得到剩下的加密逻辑。

啊这,看来这样也可以快速找到关键数据。

re7-兄弟们快来帮帮鼎哥

image-20200925202623051

这题就是绕过网络验证而已,改hosts,将访问的域名改成127.0.0.1,或者你的服务器ip,然后使用任一后端语言,简单写几行代码,搭一个最简单的私服,响应劫持dns解析定向来的post请求,返回success,即可绕过网络验证。

有人说这题不是逆向,是web,其实真的是逆向,为了证明这一点,我给大家看个实战,虽然这个网络验证做的很简单(其实我出题的那个网络验证比这还简单):

Snipaste_2020-09-25_17-59-43

然而还是被炜昊发现了非预期。其实出题人眼中的非预期没有贬低参赛的师傅们的意思,只是懊悔自己为什么没有考虑周全,从而让希望考察的知识点没有考察到(把逆向出题人菜打在公屏上)

当然炜昊找到那个非预期的过程未必比绕过网络验证要简单。毕竟绕过网络验证仅仅只需要几行后端代码响应下post包即可。

他的正常解题思路就是nop掉关键验证函数(和正常逆向一样,毕竟pyd就是个dll)。关于这个,详细的去看他题解吧,我就不越俎代庖了。

他发现的非预期是:

QQ图片20200925203517

这是由于我的失误,把getflag单独列了一个函数出来。如果我把getflag内部的代码,和向服务器请求的代码写在同一函数内,就可以避免这个非预期。

再次说句炜昊tttttttql!

re8-英勇赛尔,智慧童年

哈哈哈哈。这题不写题解。哈哈哈哈。

image-20200925203858272

misc-别做题了听歌吧

image-20200925203956073

mp3stego(应该是这么怕拼写的),密码是cumt,解出txt

里面tab表示长,空格表示短,摩尔电码拿到字符串,大写字母裹上CUMTCTF{}提交即可

misc-时光机

image-20200925204130243

git版本回滚

提交标题为3的记录里有个password开头的文件,是压缩包密码

提交标题为4的记录里有个flag.zip开头的文件,压缩包

解压拿flag

一些碎碎念

比赛过程中一堆神仙,真的感受到了压力,还是太菜啊。

借re6的图片,送自己一句话吧:

secret

不小心写了几千字,也没检查有没有错字,就先这样吧,要是还想到什么的话再补充,本文定时10点(wp提交截至时间)发,我先跑步去啦