HKCERT CTF 2023 Write-ups
HKCERT CTF 2023 was my first foray into the broad world of Catch-The-Flags (CTFs). I wanted to write up about a few problems I solved (totally not to flex). I’m blessed to have my teammates carry me…
Sign me a Flag (I)
There is a partial guide here, so I’m not gonna repeat the details…
The challenge provided a chall.py
and solve.py
. It told us to run nc chal.hkcert23.pwnable.hk 28029
, which seems to be running chall.py
…
chall.py
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
import signal
import os
import hmac
import hashlib
import sys
def tle_handler(*args):
print('⏰')
sys.exit(0)
def xor(a, b):
return bytes(u^v for u, v in zip(a, b))
def sign_message(key_client: bytes, key_server: bytes, message: str) -> bytes:
key_combined = xor(key_client, key_server)
signature = hmac.new(key_combined, message.encode(), hashlib.sha256).digest()
return signature
def main():
signal.signal(signal.SIGALRM, tle_handler)
signal.alarm(120)
flag = os.environ.get('FLAG', 'hkcert23{***REDACTED***}')
key_server = os.urandom(16)
for id in range(10):
action = input('🎬 ').strip()
if action == 'sign':
key_client = bytes.fromhex(input('🔑 '))
message = input('💬 ')
if 'flag' in message:
return print('😡')
signature = sign_message(key_client, key_server, message)
print(f'📝 {signature.hex()}')
elif action == 'verify':
key_client = b'\0'*16 # I get to decide the key :)
message = input('💬 ')
signature = bytes.fromhex(input('📝 '))
if message != 'gib flag pls':
return print('😡')
if signature != sign_message(key_client, key_server, message):
return print('😡')
print(f'🏁 {flag}')
if __name__ == '__main__':
try:
main()
except Exception:
print('😒')
If we analyze the algorithm, we can see the following:
- The server generates a random 16-byte key
- To obtain the flag, we have to guess the key, and then sign the string “gib flag pls” for the server.
- We can input a
key_client
, which is XORed with thekey_server
to obtain akey_combined
. - If we input
sign
to the server, we can input akey_client
and sign anymessage
.
The most important part is the solve.py
.
solve.py
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
import hmac
import hashlib
from pwn import *
import itertools
def sign_message(key_client: bytes, key_server: bytes, message: str) -> bytes:
key_combined = xor(key_client, key_server)
signature = hmac.new(key_combined, message.encode(), hashlib.sha256).digest()
return signature
def sign(r, key_client: bytes, message: str):
r.sendlineafter('🎬 '.encode(), b'sign')
r.sendlineafter('🔑 '.encode(), key_client.hex().encode())
r.sendlineafter('💬 '.encode(), message.encode())
r.recvuntil('📝 '.encode())
return bytes.fromhex(r.recvline().decode().strip())
def get_flag(r, key_server: bytes):
signature = sign_message(b'\0'*16, key_server, 'gib flag pls')
r.sendlineafter('🎬 '.encode(), b'verify')
r.sendlineafter('💬 '.encode(), b'gib flag pls')
r.sendlineafter('📝 '.encode(), signature.hex().encode())
if __name__ == '__main__':
r = remote('chal.hkcert23.pwnable.hk', 28029)
key_server = b''
for i in range(16):
s = sign(r, b'\0'*(i+1), 'testing')
for guess in range(256):
key_server_guess = key_server + int.to_bytes(guess, 1, 'big')
if sign_message(b'\0'*(i+1), key_server_guess, 'testing') != s: continue
key_server = key_server_guess
break
print(f'{key_server = }')
get_flag(r, key_server)
r.interactive()
We can see that it runs the sign()
function 16 times, thereby calling the server 16 times, guessing 1 byte at a time.
However, we only have 10 calls, so let’s add an inner loop and guess 2 bytes at a time.
Modified solve.py
This calls the server 8 times only.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if __name__ == '__main__':
r = remote('chal.hkcert23.pwnable.hk', 28029)
key_server = b''
for i in range(0, 16, 2):
s = sign(r, b'\0'*(i+2), 'testing')
for guess in range(256):
for guess_2 in range(256):
key_server_guess = key_server + int.to_bytes(guess, 1, 'big') + int.to_bytes(guess_2, 1, 'big')
if sign_message(b'\0'*(i+2), key_server_guess, 'testing') != s: continue
key_server = key_server_guess
break
print(f'{key_server = }')
get_flag(r, key_server)
r.interactive()
And there! The server should give you the flag
Flag: hkcert23{l34k1n9_th3_k3y_b1t_6y_bi7_1s_fun}