GREYCTF 2022 Write-ups

mcdulltii
9 min readJun 10, 2022

Introduction

I participated in this CTF competition along with beanbeah and ariana. (I totally didn’t forget that this was quals. Now that my team placed 6th, I’ll have to cancel my Saturday work plans)

Only in the competition did we realize that none of us were “good” at pwn and web, so… that explains a lot about that huge gap with the top 3 teams.

In this blog, I will only detail the challenges that have been interesting to solve, or the ones that I have stressed on.

flappy-o2 | Memory Game (Part 2)

Runtime Environment 1 | Runtime Environment 2

Angry Robot

flappy-o2

This challenge uses the same binary as flappy-o.Every 10000 points you reach, you unlock a character of the bonus flag. This is the real test of your skills.

We’re given an ELF64 binary, which uses the ncurse library to visualize a flappy bird game.

With the first flag obtained within the first 10000 points, we need multiples of 10000 to unlock the second flag.

As the binary has not been stripped, it’s easy to patch out the error functions.

Left: Original binary, Right: Patched binary

To describe which areas have been patched, we first see the gameLoop function below.

In the processInput function above, references the isOver variable. It has been patched to stay 0 as below. The isOver variable is checked every loop of the game, which will not exit when is 0 .

In the checkAndHandleCollision function, it has been patched to jump directly to the addition of score . Notice that the collision checks have been jumped, so the game will not exit since there’s no collisions.

And finally, the call to usleep references a variable wait_duration , which has been patched to 1 .

The above gif is not fast-forwarded, it has a refresh rate of 1ms due to usleep(1) . As such, it is possible to just run the game indefinitely to increase the score until the entire second flag is fully decrypted.

I initially wrote the script below to solve the second flag immediately, but had some trouble with typecasting. Perhaps someone who solved this challenge too will notice my mistake.

My team got fed up and just ran the patched binary for 50 minutes. :pepeCopium:

Flag: grey{y0u_4r3_v3ry_g00d_4t_7h1s_g4m3_c4n_y0u_t34ch_m3_h0w_t0_b3_g00d_ef4bd282d7a2ab1ebdcc3616dbe7afb}

Memory Game (Part 2)

Can you finish MASTER difficulty in 20 seconds? If you can, the flag will be given to you through logcat with the tag FLAG.

We’re given an APK, which supposedly runs a memory card game, with difficulty levels, time limits and 3-star point systems based on how long the player completes a level.

The Jadx decompiler gives decompilation errors at com.snatik.matches.engine . It’s time to use JEB decompiler.

As shown above, is the decompiled event function when the card is flipped. Regarding the flag decryption, the APK uses the MTRandom library to generate random seeded values for its AES IV with 1.01.001007 as key. It then base64 decodes “diDrBf4+uZMtDV+0k/3BCGM4xyTpEyGEuUFYegIaSjQyQcgfIfZRbvGQ9hHMqnuflNCKv4HW/NXq93j4QqLc/Q==” and AES CBC decrypt the result with PKCS#5 padding.

By copying the above function and resolving the necessary imports and libraries, here is the full solve script.

Flag: grey{hum4n_m3m0ry_i5_4lw4y5_b3tt3r_th4n_r4nd0m_4cc3ss_m3m0ry}

Runtime Environment 1

GO and try to solve this basic challenge.FAQ: If you found the input leading to the challenge.txt you are on the right track

We’re given a plaintext challenge.txt file and an ELF64 binary.

Initially, by comparing the inputs and outputs of the binary, there is a one to one mapping (with respect to the index of the character you’re comparing to).

For example, an input of A gives an output of VV-- , and an input of AA will give an output of VbJ- . So the first character A will always map to an output of V at the first output index.

A 1-3 character input will give a 4 character output, and a 4 character input will give an 8 character output, a multiple of the previous.

As such I wrote the above script to bruteforce this one-to-one mapping. (Luckily it didn’t work entirely)

Because the script didn’t run fully to give the entire output, I took a look at the binary again.

When testing out different length inputs, the padding character is always - . This immediately reminds me of base64, as the easiest encoding method.

And there it is… the base64 custom charset.

NaRvJT1B/m6AOXL9VDFIbUGkC+sSnzh5jxQ273d4lHPg0wcEpYqruWyfZoM8itKe

How to know whether it is a charset? Every character is used and there are no duplicates.

After using the custom base64 to decode the challenge text file once, we can get the correct input to match it.

The input has been reversed, but what? It’s not the flag…

beanbeah then ran the base64 decode a couple more times to get the flag. Very guessy indeed.

Flag: grey{B4s3d_G0Ph3r_r333333}

Runtime Environment 2

This time it HAS to be harder.

We’re given a raw challenge.bin file and an ELF64 binary.

Haskell… After this challenge, I hate functional programming with a passion.

Luckily, this Haskell binary has been compiled with Glasgow Haskell Compiler, as there is this Haskell decompiler.

By running the decompiler on this binary, we’re given the below output. (Full output)

In the decompiled output are several errors, but are enough to infer upon. The functions getLine , getCurrentTime , nominalDiffTimeToSeconds and utcTimeToPOSIXSeconds are referenced. More importantly, are the functions from line 63 to line 67 .

They can be converted into a more readable pseudocode like below.

fn loc_4229392(IntegralInt arg0, Int32 arg1) -> Int32
arg0 ^= (arg0 << 13);
arg0 ^= (arg0 >> 17);
arg0 ^= (arg0 << 5);
return (arg0 ^ arg1);

As mentioned, getCurrentTime , nominalDiffTimeToSeconds and utcTimeToPOSIXSeconds are referenced, and when used together, will give the current time in POSIX format. We can obtain this information the same way in Python, by getting the file creation time of our raw file, since this file is created at the same time the binary runs with getCurrentTime .

As such, we have everything we need. Our input is retrieved via getLine , and for each character in our input, is parsed into either arg0 or arg1 of loc_4229392 . Then, the result is zipped and putStr into STDOUT .

We can translate the above pseudocode and export our challenge bin as an int array in a C script below. (Regarding whether arg0 or arg1 is used by time or our input character, is entirely trial and error)

As the C script uses the same typecasting as that in the Haskell binary, the bitshifting in C will be the same as shiftL and shiftR . As Python handles its values and bitshifts differently, the solve script was written in C instead.

Flag: grey{Funct1on41_P4rad1s3_iZ_Fun}

Angry Robot

You have entered a top secret robot  production facilities and your clumsy friend tripped the alarm. You and  your friends are about to be "decontaminated".Luckily, you have unpacked the authentication firmware during  previous reconaissance. Can you use them to override the decontamination  process?

We’re given 100 ELF64 files.

A. HUNDRED. DIFFERENT. FILES.

Me and beanbeah worked on it, left and came back to this challenge countless of times. We were truly as the challenge name said, angry.

Luckily, as you look through the binaries, they all have the same program flow.

The main function above calls fgets to get 30 characters from stdin . Then returns a bool of whether the result of sub_401209 is 0.

This function sub_401209 copies two strings into a int64 array, and runs a comparison function sub_401176 with our input string.

Initially, we thought that the challenge name was a hint, being “ANGRy robot”. So Angr it is.

Perhaps the script was wrong, we were not the most familiar with Angr after all. We did not get any results that fit the check function at sub_401209 .

Over a few days, we noticed this challenge having many more solves. Surely not Angr then? Could it be z3?

Again, there is no result. Wait a minute, if you look closely at the check function, the arguments are checked one by one, so bruteforcing is possible! :facepalm:

As mentioned, the 100 binary files have the same program flow, just that the integer values and the cross referenced strings used within the check functions are different.

In order to script bruteforcing all 100 binary files for their correct inputs, we have used IDA batch mode to decompile the functions that contained the different values and strings used in the check. (Some of the binary files have these check functions at different offsets, which is why there is try except)

The decompiled functions extracted all follow the same format as below.

As such, using a bit of regex and string slicing, we can retrieve the different values and strings used. Specifically, in the check function are different in the modulo and addition values.

As the strings decompiled in the C exported files have escaped backslashes, they need to be stripped when retrieved in the bruteforcing script.

This script will bruteforce the inputs for all 100 files, and print a dictionary with a key-value pair for the binary files with their correct inputs.

Finally, with all the correct inputs, we are able to tackle the remote host at challs.nusgreyhats.org:10523 .

from pwn import *
p = remote("challs.nusgreyhats.org", 10523)
context.log_level='debug'
p.recvuntil("y/n")
p.sendline("y")
for i in range(100):
p.recvuntil("challenge code:")
hash = p.recvline().strip().decode()
p.sendline(final[hash + ".c"])

Running the above script will connect to the remote host and send the correct input when prompted a binary file’s hash, for all the 100 files. Right?

Narrator: And, he was wrong.

The remote host challenged for 5 random binary file inputs, only timing out after 10 minutes.

So it was actually possible to have just remained in the same remote session and bruteforce the required 5 binary file inputs in 10 minutes. :sadge:

Flag: grey{A11_H4il_SkyN3t}

Afterthoughts

This week has really been tough for me mentally, as I have been sick since the second day of the CTF competition.

Juggling the competition, my day job, and being sick most of all, has really taken a toll on my health. See you in finals in a week?

P.S. The top 3 teams are really in a league of their own. The gap in the scoreboard is even more obvious.

--

--

mcdulltii
0 Followers

A programming enthusiast that does image synthesis on the side.