I was not planning on participating any CTF this week, but I figured I’d check the CTFTime schedule few days ago. That was when I saw a CTF named “rgbCTF” was coming up within a few days. I was like “screw it, let’s go, why not?” My interest peaked when I saw that the first team gets free Chroma keyboards - even though I knew full well that I wasn’t going to get top 5, and of course we didn’t.

This time around I figured I’d hit up Emzi, a friend of mine on Discord - the same person that challenged me to Capture the Nitro. This is his first CTF, and he solved quite a few challenges, which was amazing. He will be writing the Part 2 of the writeup. If some challenges seem to be missing in this post, please refer to Part 2 instead!

Interestingly, the rgbSec team seems to be using something called mellivora instead of the usual CTFd, which appears to be much more stable and looks far more stylish. This might be an alternative solution to consider in the future when hosting a jeopardy-styled CTF.

picture 11

There were 7 major categories for this CTF, with the ZTC one being basically the Misc category made by the sponsors of this competition.

picture 7

Out of all of the challenges, I loved the PI series under the Forensics category. A lot of work was definitely put into making the challenge. Kudos to the author(s)! When I was solving PI1, a challenge involving reading the btsnoop log, it took me few hours to figure out the HID keycodes, and when I thought I had it with the number 0736727859, I found out that that still wasn’t the flag! Emzi solved it for me in Part 2 of the series - it turns out that I was so focused on the numbers, I didn’t read any of the other text sent by the keyboard. I still learned a lot from this challenge though!

Towards the end we were able to secure 53rd place out of the 1345 teams - that’s the top 4% of the scoreboard! While I wish that we could have climbed our way to top 50, we were just close enough to the target. Oh well, we’ll get em’ next time!

picture 6

picture 10


Joke Check! (50 pts)

Challenge description

What do you call a chicken staring at lettuce?

The provided string was crmNEQ{l_nstnvpy_nlpdlc_dlwlo}, which was obviously an ROT13 string. Just plop it into Cyberchef and rotate it a few times and bang.

I hate this joke thanks

Flag: rgbCTF{a_chicken_caesar_salad}

Shoob (50 pts)

Challenge description

s h o o b

Shoob doggo

At first I couldn’t tell what was going on with this image. It’s just a doggo with no helpful EXIF data according to exiftool and no embedded data according to binwalk. It wasn’t until another while that I figured out that the image has a very faint string on the doggo.

picture 14

Flag: rgbCTF{3zier_4haN_s4n1ty}

A Fine Day (50 pts)

Challenge description

It's a fine day to break some ciphers!
Sujd jd bgxopksbm ljsu tg tqqjgb xjkubo. Tqqjgb xjkubod tob t qvor vq dhidsjshsjvg xjkubo. Jsd nbp xvgdjdsd vq slv ghribod, t tgm i. Sv bgxopks t cbssbo, rhcsjkcp jsd kctxb jg sub tckutibs (dv t=0, i=1, bsx.) ip t, tgm subg tmm i. Qjgtccp stnb suts rvm 26 tgm xvgwbos js itxn jgsv t xutotxsbo.
Sub tqqjgb xjkubo jdg's obtccp suts dsovgf. Djgxb js'd rvm 26, subob tob vgcp t qbl uhgmobm mjqqbobgs nbpd, lujxu xtg ib btdjcp iohsb qvoxbm. Tgpltp, ubob'd pvho qctf: ofiXSQ{t_qjgb_tqqjgb_xjkubo}

Seems like a substitution cipher! Let’s plop this into Substitution Cipher Solver Tool and let the tool do the job.

The top result was the following:

this is encrypted with an affine cipher affine ciphers are a form of substitution cipher its key consists of two numbers a and b to encrypt a letter multiply its place in the alphabet so a b etc by a and then add b finally take that mod and convert it back into a character the affine cipher isn t really that strong since it s mod there are only a few hundred different keys which can be easily brute forced anyway here s your flag rgbctf a fine affine cipher

Flag: rgbCTF{a_fine_affine_cipher}

Simple RSA (50 pts)

Challenge description

Can you find a way to attack this RSA implementation?

📎 Attachments: simple_rsa.txt,

 cat .\simple_rsa.txt
n = 5620911691885906751399467870749963159674169260381
e = 65537
c = 1415060907955076984980255543080831671725408472748

Why yes! This is simple RSA! In fact, it was so simple that I all had to do was to fire up the infamous RsaCtfTool to solve it.

 ./ -n 5620911691885906751399467870749963159674169260381 -e 65537 --uncipher 1415060907955076984980255543080831671725408472748
private argument is not set, the private key will not be displayed, even if recovered.

[*] Testing key /tmp/tmpqjqs9llh.
[*] Performing londahl attack on /tmp/tmpqjqs9llh.
[*] Performing pollard_p_1 attack on /tmp/tmpqjqs9llh.
[*] Performing fermat attack on /tmp/tmpqjqs9llh.
[*] Performing factordb attack on /tmp/tmpqjqs9llh.

Results for /tmp/tmpqjqs9llh:

Unciphered data :
HEX : 0x00007d33637230665f33747572627b465443626772
INT (big endian) : 2792069716136470955248443562608151407320262514
INT (little endian) : 167201404043347807005542357307720988742653755326464
STR : b'\x00\x00}3cr0f_3turb{FTCbgr'

You might notice that the string seems a little weird; that’s because as stated by the source, the string was encrypted with little-endian in mind.

Flag: rgbCTF{brut3_f0rc3}

Quirky Resolution (50 pts)

Challenge Description


Once again, I couldn’t tell what was wrong at first until I started messing with the image in an image editor.

picture 15

Hey, look, it’s a QR code!

picture 43


Flag: rgbCTF{th3_qu1rk!er_th3_b3tt3r}


I Love Rainbows (50 pts)

Challenge description

Can you figure out why?

Yes, I can. The challenge provided us with a list of hash intended for us to “crack,” and judging by the title, it probably wanted us to use a rainbow table.


For those uninitiated, a hash is a one-way process. Hashing takes a stream of data, performs a series of calculations to obtain a unique stream of data that is most of the time unique to this object. It is important to distinguish the difference between hashing and encryption; hashing is not a form of encryption, and therefore cannot be “decrypted.” However, one could in theory keep track of a string that may produce the same hash and keep a database of this key value pair - and that is the gist of a rainbow table. CrackStation is one of such site that keeps track of frequently seen strings and their respective hashes. Let’s submit our hashes and see if there are matching results!

picture 16

Annnd this looks promising! Let’s submit all the hashes and get the flag.

Flag: rgbCTF{4lw4ys_us3_s4lt_wh3n_h4shing}


Alien Transmission 1 (219 pts)

Challenge description

I was listening to my radio the other day and received this strange message... I think it came from an alien?

I thought this was some sort of weird signal that I had to decipher, but this seemed… awfully familiar. I had heard something similar before… Where?

…It’s an SSTV signal isn’t it? I thought this seemed familiar because I was around when Valve added their Transmission Received ARG back when Portal 2 was imminent. As described by Wikipedia,

Slow Scan television (SSTV) is a picture transmission method used mainly by amateur radio operators, to transmit and receive static pictures via radio in monochrome or color.

The fact that this signal was used in space by NASA matches the challenge description as well,

SSTV was used to transmit images of the far side of the Moon from Luna 3.

So let’s try feeding this to an SSTV decoder.

picture 17

And that was indeed it!

Flag: rgbctf{s10w_2c4n_1s_7h3_W4V3}

Robin’s Reddit Password (490 pts)

Challenge description

I'm Batman!
Lately Robin's been acting suspicious... I need to see what he's been up to. Can you get me his reddit password? Just don't try to break into reddit's server...

Tip : Wrap the Password in flag format

This one took me soooooo long, and it wasn’t even rewarding to get the answer. Therefore, this was my least favorite challenge out of the entire CTF. I spent hours trying to figure out what the heck this means.

After searching for various combinations of reddit, password, robin for what felt like an eternity, I found this.

picture 18

Apparently, there’s an obscure Easter egg on reddit’s site for the infamous /etc/passwd file. If you go to, you’d see a fake /etc/passwd containing a list of users and their hashes. Thankfully, someone did the work for us and decoded the passwords for us as shown in the screenshot.

The main reason why I hated this challenge so much was a) this doesn’t necessarily encourage or teach the audience what an forensics or OSINT user might do b) without any hints, this challenge became a huge guessing game, which makes the challenge very unenjoyable to solve. c) the fact that we were told to “find someone’s password,” most of us would instinctively think that we’re supposed to look through places like password dumps. If you are a CTF challenge planner, please never do something like this.

Flag: rgbCTF{bird}


Hallo? (50 pts)

Challenge description

The flag is exactly as decoded. No curly brackets.
NOTE: keep the pound symbols as pound symbols!

For those of you who have recently called someone or texted someone (you have in the last few months, right?), this tone should be familiar.

Yup, it’s a series of DTMF (Dual-Tone Multi-Frequency signaling) tones. A DTMF tone is most frequently heard over a landline telephone or dumb phone in the early 2000s. Whenever you dial someone, a combination of tones are generated to let the phone and the operator know what numbers were being punched.

Dual-tone multi-frequency signaling (DTMF) is a telecommunication signaling system using the voice-frequency band over telephone lines between telephone equipment and other communications devices and switching centers. DTMF was first developed in the Bell System in the United States, and became known under the trademark Touch-Tone for use in push-button telephones supplied to telephone customers, starting in 1963. DTMF is standardized as ITU-T Recommendation Q.23. It is also known in the UK as MF4.

- Wikipedia

After knowing that it’s a DTMF tone, we can plop the results into a DTMF decoder and see what we get.

The decoded result was 7774222228333#99933338#386333#866666337777#, annnnd sent!

…wait, what do you mean this wasn’t the flag? It turns out there was a reason that silences were included in the mp3 file.

picture 29

To those of us cool kids (not really) in the 2000s, T9 texting was the only reliable and most common texting input we had. What was T9 texting? Since there are more than a dozen characters in the English alphabet, the only way people could cram all those characters onto a T9 keypad was to assign multiple characters to a single key. Essentially, to enter a single character on a limited T9-style keypad, you had to press a key for multiple times to select the character you wanted to input. It was an awful time. I’m glad those days were over.

picture 30

So after analyzing the text, we were able to get rgbctf#yeet#dtmf#tones#, and that was indeed the flag!

Flag: rgbctf#yeet#dtmf#tones#

Penguins (295 pts)

Challenge description

waddle waddle

The attached file was called Upon extracting the archive, it becomes obvious that the archive is just a zipped git repo.


    Directory: C:\Users\Still\AppData\Local\Temp\git

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           7/15/2020  8:18 PM                .git
-a---           7/15/2020  8:18 PM             93 1yeet
-a---           7/15/2020  8:18 PM             34 2yeet
-a---           7/15/2020  8:18 PM             88 3penguin
-----           6/30/2020  5:13 AM             60 flag

Obviously, flag does not contain anything useful. Let’s dive deeper into the directory.

 cat flag
cmon now
u didnt think ACTUALLY think it would be that easy
 ls -File -Recurse | %{$_; cat $_}
    Directory: C:\Users\Still\AppData\Local\Temp\git\.git\logs

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-----           6/30/2020  5:39 AM           2999 HEAD
0000000000000000000000000000000000000000 9dcf170a0fb6ae21b5299669b4336a6324c0c316 John Doe <> 1593464930 +0000      commit (initial): first commit
9dcf170a0fb6ae21b5299669b4336a6324c0c316 1117a337faf1ac693cf26bb3bcccb3caa0381d6d John Doe <> 1593464967 +0000      commit: birds are cool
1117a337faf1ac693cf26bb3bcccb3caa0381d6d 1117a337faf1ac693cf26bb3bcccb3caa0381d6d John Doe <> 1593464995 +0000      checkout: moving from master to feature1
1117a337faf1ac693cf26bb3bcccb3caa0381d6d 955eeb70bcb49d8de331b61f38219bccb7e8f933 John Doe <> 1593465063 +0000      commit: add content
955eeb70bcb49d8de331b61f38219bccb7e8f933 7d6997a74cfa2b5e266355d33fd73c76c9fe9b75 John Doe <> 1593465146 +0000      commit: cooler bird
7d6997a74cfa2b5e266355d33fd73c76c9fe9b75 1117a337faf1ac693cf26bb3bcccb3caa0381d6d John Doe <> 1593465153 +0000      checkout: moving from feature1 to master
1117a337faf1ac693cf26bb3bcccb3caa0381d6d 8ee62379b45217202e75011966b813512cafcbb0 John Doe <> 1593465185 +0000      commit: added an interesting file
8ee62379b45217202e75011966b813512cafcbb0 b474ae165218fec38ac9fb8d64f452c1270e68ea John Doe <> 1593465223 +0000      commit: some new info
b474ae165218fec38ac9fb8d64f452c1270e68ea 102b03d19f932fc5e76d460604804dd522d6850d John Doe <> 1593465324 +0000      commit: some more changes
102b03d19f932fc5e76d460604804dd522d6850d 27440c52e8a7a3d2e50f8fcdee0a88b0f937598d John Doe <> 1593465369 +0000      commit (merge): Merge branch 'feature1'
27440c52e8a7a3d2e50f8fcdee0a88b0f937598d b474ae165218fec38ac9fb8d64f452c1270e68ea John Doe <> 1593465694 +0000      checkout: moving from master to b474ae1
b474ae165218fec38ac9fb8d64f452c1270e68ea b474ae165218fec38ac9fb8d64f452c1270e68ea John Doe <> 1593465718 +0000      checkout: moving from b474ae165218fec38ac9fb8d64f452c1270e68ea to fascinating
b474ae165218fec38ac9fb8d64f452c1270e68ea d14fcbfd3c916a512ad1b956cd19fb7be16c20c6 John Doe <> 1593465745 +0000      commit: an irrelevant file
d14fcbfd3c916a512ad1b956cd19fb7be16c20c6 cfd97cd36fe6c5e450d5057bf25aa1d7ddeca9ef John Doe <> 1593465781 +0000      commit: add content to irrelevant file
cfd97cd36fe6c5e450d5057bf25aa1d7ddeca9ef 5dcac0eddbcb4bffdec552a1172f84762a0b4174 John Doe <> 1593465822 +0000      commit: another perhaps relevant file
5dcac0eddbcb4bffdec552a1172f84762a0b4174 fb70ca39a7437eaba2850703018e1cf9073789e6 John Doe <> 1593465988 +0000      commit: probably not relevant
fb70ca39a7437eaba2850703018e1cf9073789e6 57adae71c223a465b6db3a710aab825883286214 John Doe <> 1593466025 +0000      commit: relevant file
57adae71c223a465b6db3a710aab825883286214 800bcb90123137a6ee981c93c140bd04c75f507f John Doe <> 1593466646 +0000      commit: some things are not needed
800bcb90123137a6ee981c93c140bd04c75f507f 27440c52e8a7a3d2e50f8fcdee0a88b0f937598d John Doe <> 1593466672 +0000      checkout: moving from fascinating to master

By reading all the files in the directory, we quickly found out HEAD contains some useful git log. Let’s try checking out to these commits.

 git checkout fb70ca39a7437eaba2850703018e1cf9073789e6
Note: switching to 'fb70ca39a7437eaba2850703018e1cf9073789e6'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at fb70ca3 probably not relevant

    Directory: C:\Users\Still\AppData\Local\Temp\git

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           7/15/2020  8:15 PM                .git
-a---           7/15/2020  8:15 PM              0 1yeet
-a---           7/15/2020  8:15 PM              0 2yeet
-a---           7/15/2020  8:15 PM              0 3parakeet
-----           6/30/2020  5:13 AM             60 flag
-a---           7/15/2020  8:15 PM             37 irrelevant_file
-a---           7/15/2020  8:15 PM             40 perhaps_relevant
-a---           7/15/2020  8:15 PM            150 perhaps_relevant_v2

 cat .\perhaps_relevant_v2

 ConvertFrom-Base64 YXMgeW9kYSBvbmNlIHRvbGQgbWUgInJld2FyZCB5b3UgaSBtdXN0IgphbmQgdGhlbiBoZSBnYXZlIG1lIHRoaXMgLS0tLQpyZ2JjdGZ7ZDRuZ2wxbmdfYzBtbTE3c180cjNfdU5mMHI3dW40NzN9
as yoda once told me "reward you i must"
and then he gave me this ----

And there’s the file we’re looking for!

Flag: rgbctf{d4ngl1ng_c0mm17s_4r3_uNf0r7un473}


Too Slow (50 pts)

Challenge description

I've made this flag decryptor! It's super secure, but it runs a little slow.

📎 Attachment: a.out

❯ file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, BuildID[sha1]=462dfe207acdfe1da2133cac6b69b45de5169ee2, for GNU/Linux 3.2.0, not stripped

So it looks like a standard Linux ELF executable. What happens if I run it?

Flag Decryptor v1.0
Generating key...

…and it hangs indefinitely. Yup, talk about slow. Looks like it’s time to investigate using Ghidra! Upon examining the main method’s pseudo-code output, we see the following,

undefined8 main(void)
  uint uVar1;
  puts("Flag Decryptor v1.0");
  puts("Generating key...");
  uVar1 = getKey();
  return 0;
ulong getKey(void)
  uint local_10;
  uint local_c;
  local_10 = 0;
  while (local_10 < 0x265d1d23) {
    local_c = local_10;
    while (local_c != 1) {
      if ((local_c & 1) == 0) {
        local_c = (int)local_c / 2;
      else {
        local_c = local_c * 3 + 1;
    local_10 = local_10 + 1;
  return (ulong)local_10;

If you take a closer look at getKey, you’ll realize that it’s doing some meaningless calculation inside a for loop until it loops 0x265d1d22 times, which is 643,636,515 times in decimal!


Since all we care about is for local_10 (or i) to reach the expected value, which is 0x265d1d22, we can just patch the binary to make it start with that value. To do this in Ghidra, we’ll select the instruction we want to patch and hit CTRL+SHIFT+G and modify the instruction accordingly.

picture 37

picture 38

Unfortunately, Ghidra’s binary patching is a little bit broken for the moment. While a PR (#1505) is on its way to fix the behavior of Export Program (File > Export Program), you will have to wait another while for that PR to get merged. In the meantime though, an alternative method is to import the binary as a raw binary when adding the program in the current Ghidra Project, patch the instruction, and export the program that way.

picture 39

picture 40

And with that, we were able to successfully get the flag in less than a second!

Flag Decryptor v1.0
Generating key...
Your flag: rgbCTF{pr3d1ct4bl3_k3y_n33d5_no_w41t_cab79d}

Flag: rgbCTF{pr3d1ct4bl3_k3y_n33d5_no_w41t_cab79d}

Object Oriented Programming (413 pts)

Challenge description

There's this small up and coming language called java I want to tell you about

This solve was made possible by our Java guru, Kieran, who pointed me to the right direction. Go give them a follow!

The moment I saw the file I burst out in tears. Just… take a look.

    import java.lang.reflect.*;//think of this as #include <stdio.h>, basically every program needs it for IO
    //it's always good practice to declare constants for unclear magic values
    public static final int ZERO = BigInteger.valueOf(0).intValue(); //BigInteger for precision
    public static void main(String[] args) throws Exception { //running Java is dangerous, that's why everything throws an Exception
      InputStream standardInputStream = new StandardInputStreamInstantiator().getStandardInputStreamFactory().getStandardInputStream();
      //i'm going to take a shortcut here, behold the power of java libraries - so many lines saved!
      Scanner scanner = Scanner.class.getConstructor(standardInputStream.getClass().getSuperclass().getSuperclass()).newInstance(standardInputStream);
    protected static char secureEncryptionKey; //it's protected, how you gonna crack it now?

Even if you’ve never ever touched Java before, as long as you had dabbled with OOP, you should notice the absurdity the author is trying to convey here.

Anyways… It looks like the majority of the code is security by obscurity - the code tries to hide what it’s doing by giving the reverse engineer a harder time decoding the meaning of the code. With a debugger though, it’s slightly easier to understand what it’s trying to do. Let’s break this down by following the execution of each line in main.

  String userInput = getUserInputMethodFromScannerAndInvokeAndReturnOutput(scanner);
  if (userInput.length() != SIXTEEN)

It should be safe to assume that these lines want to ensure that the length to be 16.

picture 41

When you step through the executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut method, you will notice that the string is getting “encrypted,” as the variable states. The string is also getting split into chunks, per 4 characters. It is also at this time that I should note that there were other classes included with the file as illustrated below.

picture 42

After stepping through multiple times, it becomes apparent that every 4 characters are broken down into 2 groups of characters. For example, asdf -> cqfd -> ['cq', 'fd']. Notice what happens if I let the execution continue:

Exception in thread "main" java.lang.ClassNotFoundException: cq
 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(
 at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(
 at java.base/java.lang.ClassLoader.loadClass(
 at java.base/java.lang.Class.forName0(Native Method)
 at java.base/java.lang.Class.forName(
 at Main.executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut(
 at Main.main(

A stack trace is thrown telling me it can’t find the class cq. What is happening here is that it is taking the first 2 characters and then using reflection to find the class and using the latter 2 characters as the method name to call. If we take a look at the other classes included,

public class qa {

public String go() {
 return "kz";

public String bl() {
 return "pu";

…you’ll see that if I were to find a set of characters that can translate to qago, then it would call the qa class and execute the method go, and then returning the characters kz. Additionally, the if (executeCodeThatDoesSomethingThatYouProbablyNeedToFigureOut(userInput).equals(scanner.getClass().getPackageName().replace(".", ""))) check essentially wants the translated to string to match the word javautil. So, all that’s left is to figure out what character sets would give us the output we want!

After a bit of trial and error, I was able to figure out that the input should be enterprisecodeee, and that was indeed the flag!

Nice. Flag: rgbCTF{enterprisecodeee}

Flag: rgbCTF{enterprisecodeee}

Five Fives (476 pts)

Challenge description

java SecureRandom is supposed to be, well, secure, right? nc 7425

The included source code was as follows,

import java.util.*;
import java.nio.ByteBuffer;
import java.util.concurrent.ThreadLocalRandom;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner in = new Scanner(;

        System.out.println("Welcome to the Five Fives Lotto!");
        System.out.println("Generating seed...");

        //You'll never find my seed now!
        int sleep = ThreadLocalRandom.current().nextInt(10000);
        long seed = System.currentTimeMillis();
        ByteBuffer bb = ByteBuffer.allocate(Long.BYTES);
        SecureRandom r = new SecureRandom(bb.array());
        Thread.sleep(10000 - sleep);

        System.out.println("Yesterday's numbers were: ");
        for (int i = 0; i != 5; i++) {
            System.out.print((r.nextInt(5) + 1) + " ");

        System.out.println("You have $20, and each ticket is $1. How many tickets would you like to buy? ");
        int numTries = Integer.parseInt(in.nextLine());
        if (numTries > 20) {
            System.out.println("Sorry, you don't have enough money to buy all of those. :(");

        int[] nums = new int[5];
        for (int a = 0; a != 5; a++) {
            nums[a] = r.nextInt(5) + 1;

        for (int i = 0; i != numTries; i++) {
            System.out.println("Ticket number " + (i + 1) + "! Enter five numbers, separated by spaces:");
            String[] ticket = in.nextLine().split(" ");

            boolean winner = true;
            for (int b = 0; b != 5; b++) {
                if (nums[b] != Integer.parseInt(ticket[b])) {
                    winner = false;

            if (!winner) {
                System.out.println("Your ticket did not win. Try again.");
            } else {
                System.out.println("Congratulations, you win the flag lottery!");

    public static void outputFlag() {
        try {
            BufferedReader in = new BufferedReader(new FileReader("flag.txt"));
        } catch (IOException e) {
            System.out.println("Error reading flag. Please contact admins.");

Upon first glace, it really doesn’t seem like there’s much we can do here. It’s PRNG, but the use of SecureRandom takes the P (pseudo) out of the PRNG. As stated by the Oracle Java documentation,

This class provides a cryptographically strong random number generator (RNG).
A cryptographically strong random number minimally complies with the statistical random number generator tests specified in FIPS 140-2, Security Requirements for Cryptographic Modules, section 4.9.1. Additionally, SecureRandom must produce non-deterministic output. Therefore any seed material passed to a SecureRandom object must be unpredictable, and all SecureRandom output sequences must be cryptographically strong, as described in RFC 1750: Randomness Recommendations for Security.

A caller obtains a SecureRandom instance via the no-argument constructor or one of the getInstance methods:

      SecureRandom random = new SecureRandom();

Many SecureRandom implementations are in the form of a pseudo-random number generator (PRNG), which means they use a deterministic algorithm to produce a pseudo-random sequence from a true random seed. Other implementations may produce true random numbers, and yet others may use a combination of both techniques.

Typical callers of SecureRandom invoke the following methods to retrieve random bytes:

      SecureRandom random = new SecureRandom();
      byte bytes[] = new byte[20];

Callers may also invoke the generateSeed method to generate a given number of seed bytes (to seed other random number generators, for example):

      byte seed[] = random.generateSeed(20);

Note: Depending on the implementation, the generateSeed and nextBytes methods may block as entropy is being gathered, for example, if they need to read from /dev/random on various Unix-like operating systems.

So that means we should potentially seek for other attack vectors instead of focusing on the “cryptographically-secure RNG.” Let’s try connecting to the remote server first then plot our attack later.

❯ nc 7425
Welcome to the Five Fives Lotto!
Generating seed...
Yesterday's numbers were:
2 3 1 3 1
You have $20, and each ticket is $1. How many tickets would you like to buy?
Ticket number 1! Enter five numbers, separated by spaces:
1 1 1 1 1
Your ticket did not win. Try again.

After connecting to the server once and trying out the challenge hands-on, I was able to plot out the following flowchart.

Lotto flowchart

Let’s take a step back to see if we can find any other potential attack vectors in the code. If we take a look at how the program determines the number of tickets can be bought, we quickly find a loophole that we can exploit.

int numTries = Integer.parseInt(in.nextLine());
// no lower bound!
if (numTries > 20) {
    System.out.println("Sorry, you don't have enough money to buy all of those. :(");
// ...
// loop until i matches numTries
for (int i = 0; i != numTries; i++) {

Yup, because the program did not account for the lower-bound of the parseInt or explicitly use parseUnsignedInt, the user can enter a negative integer and it would happily take the input and loop infinitely for us until something breaks out of the loop. With that in mind, we can write a script that brute-forces the challenge for us.

import pwn
pwn.context.timeout = 1
r = pwn.remote("", 7425)
# while loop due to the quirkiness of pwntool's recv when dealing with tubes that delay for an abnormal amount of time
while True:
    l = r.recv(timeout=0.5)
    if 'buy?' in l.decode():
# attempts is an array of all combinations of 1 ~ 5
for attempt in attempts:
        print(r.recvline_contains('Congratulations', timeout=0.5))
        print(r.recvuntil('rgbCTF', timeout=pwn.Timeout.maximum))

In a nutshell, the Python script will continue to try for all the possible combinations until the flag was sent by the server. After a short while, I was able to get the following:

b'Your ticket did not win. Try again.\n'
1 4 1 4 3
b'Congratulations, you win the flag lottery!'

Awesome, and it looks like the author did intend us to brute-force according to the flag.

Flag: rgbCTF{s0m3t1m3s_4ll_y0u_n33d_1s_f0rc3}


Ralphie! (50 pts)

Challenge description

Ralphie, on the double! Little Orphan Annie sent you this decoder ring, decode the secret message!

picture 19

Well at least this QR code is painfully obvious. Let’s just fiddle around with the brightness and crop the QR code.

picture 20

Wait, what?

picture 21

Uhhh, I bet it must be the color that’s screwing with the code. Let’s slap a B&W filter over the image.

picture 23

picture 22

It was! But why? Color screws with QR code output? Apparently, yes! One can store more than one layer of data in QR code based on the use of color and can lead to different output, as pointed out in the ART-UP: A Novel Method for Generating Scanning-robust Aesthetic QR codes paper.

The use of colors in QR codes enhances its functionality now. The data rate which is high among the 2D barcodes can be further enhanced with the use of colors.

Huh, TIL!


Vaporwave 1 (190 pts)

Challenge description

Do you believe in synesthesia? 📎 Attachment: vaporwave1.mp3

As someone who had previously worked in the audio SFX and mastering industry, I happened to have learned a lot during that journey. One of the most common thing that a audio mastering engineer or producer would have to do is check for frequency collisions and other anomalies that may appear in the raw audio. This typically involves two things: a spectrum analyzer and an EQ visualizer.

In laymen’s term, a spectrum analyzer plots out a graph that helps a mixing engineer see the fidelity of the audio as well as identify what portion of the track use up what section of the frequency and how loud it is (keep in mind I’m drastically simplifying things here).

picture 32

Sometimes an artist may also plant an easter egg within the spectrogram of the track. For example, the following screenshot features John Romero’s sprite from DOOM 2 in Mick Gordon’s DOOM 2016 soundtrack, SkullHacker.

picture 33

My knowledge happens to acquainted me with one of the most common steganography techniques used among beginner audio steganography challenges: hiding textual or visual information within these spectrums. That’s why my first instinct was to open up the file in a spectrogram viewer - in this case, spek.

I happened to have solved the track without trying! Awesome!

picture 34

Funnily enough, last year I made a similar challenge for our CTF club, Pingtung Hacker, though the flag was far more subtle. The base track was SeamlessR’s Bass Antics; I just slapped an image containing the flag into Harmor and played it along with the base track.

picture 36

Flag: rgbCTF{s331ng_s0undz}

peepdis (263 pts)

Challenge description

Do you peep what I peep?

The included file was called peepdis.dae. Opening the file reveals that it’s an XML-like file.

❯ cat .\peepdis.dae | head
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<COLLADA xmlns="" version="1.4.1">
      <authoring_tool>Assimp Exporter</authoring_tool>
    <unit name="meter" meter="1" />

The XML version header claims that it’s using the COLLADA Schema. Huh. I can’t say I’ve ever heard of this schema before. Let’s look up what this is.

COLLADA is an interchange file format for interactive 3D applications. It is managed by the nonprofit technology consortium, the Khronos Group, and has been adopted by ISO as a publicly available specification, ISO/PAS 17506.

Huh, does that mean it’s a 3D model of some sorts? Let’s see if I can open it with a 3D viewer online.

picture 25

Okay…? It’s a square?

picture 26

Wait… No, that’s a QR code? Let’s play around with the lighting a bit and see if we can get the QR code part to show up a bit more.

picture 27

Annnd let’s plop this into an image editor.

picture 28

And with that we were able to obtain the flag! I have to say though, while this was creative, this really doesn’t have much to do with steganography techniques but a novelty. I wouldn’t say this was the best challenge.

Flag: rgbCTF{3d-1337!}

Leave a comment