Introduction
Last week, me and my team Kena Paksa (disclaimer: no one was forced) participated in the Rentas CTF qualifiers hosted by RawSec, EliteGhost and Gemas Lestari. All of our team members were having exams on both of the day and we take turns playing, and luckily we made it into the finals! This will be a very short writeup for each challenge we solved.
Web
bringyourownscript
Simple. Bruteforce every directory and page, find which one is different.
import aiohttp
import asyncio
from bs4 import BeautifulSoup
import urllib.parse
base_url = "https://byos.ctf.rawsec.com/root/index.php"
async def visit(session, url):
async with session.get(url) as response:
print(url)
if response.status != 200:
print(f"Unexpected status {response.status}: {response.reason}")
return []
else:
page_content = BeautifulSoup(await response.text(), "html.parser")
directories = [urllib.parse.urljoin(url, directory.get("href")) for directory in page_content.find_all(class_="directory-link")]
if "No directories found." not in str(page_content) and not page_content.find_all(class_="directory-link"):
print(f"Different page found at {url}\nPage content:\n{page_content}\n")
raise Exception("Different page found. Ending script.")
return directories
async def main(base_url):
to_visit = [base_url]
visited = set()
async with aiohttp.ClientSession() as session:
while to_visit:
current_url = to_visit.pop()
if current_url not in visited:
visited.add(current_url)
tasks = []
try:
tasks.append(asyncio.ensure_future(visit(session, current_url)))
except Exception as e:
print(f"Exception at {current_url}: {str(e)}")
continue
pages_content = await asyncio.gather(*tasks)
for page_content in pages_content:
to_visit.extend(url for url in page_content if url not in visited)
if base_url in to_visit:
print("\033[91m\033[1mVisited base URL again\033[0m")
asyncio.run(main(base_url))
https://byos.ctf.rawsec.com/root/%F0%9F%A4%A4%F0%9F%A4%95%F0%9F%98%83/%F0%9F%98%94%F0%9F%98%81%F0%9F%98%95%F0%9F%98%B5/%F0%9F%98%BA%F0%9F%98%AA%F0%9F%A5%B4%F0%9F%98%87/%F0%9F%A5%B0%F0%9F%A5%B6%F0%9F%A4%A3%F0%9F%98%82/%F0%9F%A4%A7%F0%9F%98%85/index.php
Flag : RWSC{J4CKP0T}
simplelazy
Local file inclusion RCE, used this tool https://github.com/synacktiv/php_filter_chain_generator
to get RCE.
After enumeration, found this long suspicious looking PHP file esdasxasdcessxsadx.php
but you use cat
on the file because the filename is too long for the GET
param if we were to convert it into filter chain payload.
So we used cat esdas*.php
to read the php file and the flag should be in the page source.
Flag : RWSC{S1MPL3_4ND_L4ZY}
Reverse
resign letter
Used olevba
tool to inspect the dotm/word document, then you will find a link to a github repo which have a malicious malware.
Used strings
on the malware binary then you’ll find a command of user creation with a base64 encoded text.
Decode that base64 and you’ll get the password
Flag : RWSC{p@ss123}
OSINT
Cali Cartel
Cali Cartel intext:"RWSC{"
Used the google dork to find the flag, and you’ll find the flag in a reddit post.
Flag: RWSC{C4L1_C4RT3L_PWN3D}
Medellin Cartel
After numerous hints that were given, we found a suspicious lead on UNITEN IG Official and we found a suspicious account related to the challenge.
Looking at the page source code, you will get the flag
Flag: RWSC{Bl4cky_S1c4r1o}
DFIR
Mobile
This challenge is also solved based on a hint. We were given a video of Joe Grand in Twitter of him breaking through the Android passcode to get someone’s Bitcoin account back. Knowing this, we started searching through the pdf for /data/system
and then we will see a very suspicious hash.
Used https://github.com/Webblitchy/AndroidGestureCrack and you will get the password for the hash.
8e7e00c0bd5ce227f7be204c8b7c159669c776d4
Flag: RWSC{875463120}
Cryptography
Round and round
def decrypt(message):
decipher = ''
for i in map(int, message.split('_')):
original_number = i - 3
original_letter = chr(original_number + 96) if original_number != 0 else "{"
decipher += original_letter
return decipher
encrypted_message = "21_26_22_06_19_12_29_29_12_17_12_6_12_19_19_11_18_21_26_4_22_8_4_29_28"
decrypted_message = decrypt(encrypted_message)
print(f'Decrypted message: {decrypted_message}')
#Output : rwscpizzinicipphorwaseazy
For this question, we just passed it to chatGPT and we got this script. Actually for this question we almost used up our attempts, because it turns out the chatGPT output isn’t correct. Knowing that we now have part of the flag that tells us about the encryption used, we used an online tool to decrypt Pizzini cipher, and we got the flag!
Flag : RWSC{PIZZINI_CIPHER_WAS_EAZY}
Network
Last hope
Just run aircrack-ng -w pwds-file.txt -b <BSSID> file.pcap
and the key is [anonymous]
Flag : RWSC{anonymous}
Misc
Hidden Discord
Up until the fourth part, we used BetterDiscord plugin to get the flag. On the fifth part, we just used the API endpoint and set the image size to 2048
.
Flag: RWSC{r34d_d15c0rd_d3v3l0p3r_API_r3f3r3nc3}
Buttercup
This is the most frustating challenge of all but I would love to explain it in detail, because it’s a thing I’ve never worked with before (steganography challenge), and we solved it in time, also thanks to the hints!
After we checked the video, we figured it may be related to steganography. Initially, we tried to watch the video until the end, but we found nothing. So we tried to play around with the colour aspects of the video frame-by-frame using Capcut. Yes, Capcut. And we found these:
^
We figured that these may be parts of the actual flag, so we tried to find the rest of the flag using the same way, but we didn’t find anything new after that. We figured that we could try using Audacity to see if the rest of the flag is hidden in the sound instead. So we converted the video into .wav
form, then using Audacity, we played around with spectrogram settings and found this and we got the full flag!
Flag: RWSC{3XP3RT_1N_4UD10}