This writeup primarily uses a tool called Binary Ninja, it is a free/paid tool but since this challenge uses the MIPS architecture, decompilation requires the paid version.
Foreword
12:00 AM ET

Just when I was about to go to sleep, a rev challenge dropped. I couldn't go to sleep without trying it!
12:19 AM ET

This writeup walks through how I approached nips, one of the least solved rev challenges at BCACTF 6.0, and I managed to first blood the challenge (somehow) through only static analysis. Let’s get into it.
Challenge Overview
nips
Hardbcactf{S3lf_M0d1fy1ng_MIPS}
nips yips nips yips this was going to be more complex but i got lazy yips yips yips mips mips mips
Initial Triage
A typical first move is running file
to see what we're working with.
❯ file nips
nips: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, BuildID[sha1]=98990fa2683a1eae86ad8639507965ac7eee11a9, for GNU/Linux 3.2.0, stripped
Interesting, we are going to be working with the MIPS architecture for this challenge and the binary is stripped.
MIPS is another CPU architecture, like x86 and ARM. MIPS is specifically a RISC (Reduced Instruction Set Computing) architecture meaning it uses a smaller, simpler set of instructions to achieve a higher processing speeds and more efficient execution
The next tool I would typically open is Binary Ninja, using its decompiler and strings view, we can get some more information about this binary.

This ends our triage and gives us a trail to hike on. The binary is UPX-packed, so our next step is to unpack it and then analyze the binary.
Binary packing is a technique used to compress or obfuscate an executable binary. UPX is a very popular tool for binary packing.
Unpacking
To unpack UPX binaries, we will use the upx
command along with -d
to decompress the binary.
❯ upx -d nips
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
573171 <- 262812 45.85% linux/mipsel nips
And if we run it (again), it will still execute perfectly fine after unpacking.
❯ ./nips
Usage: ./nips <flag>
To execute MIPS binaries, look into installing QEMU user-mode emulation.
And now that we can confirm everything runs and was unpacked smoothly, thus begins our analysis.
Analysis
The goal for our analysis here is to analyze the logic of the binary, then reverse its logic to get the flag.
Binary Ninja Decompilation & Analysis
Finding main()

The binary is stripped leaving us with no named functions to analyze, not even a main
function.
We do know that executing the binary outputs binary usage. With this knowledge, we can search for the usage text in the binary to find what may potentially be the main
function.

A neat thing you can do with Binary Ninja is search for references after clicking on data you want it to map to.

and that leads us to this...

Rename your functions in Binary Ninja by selecting the function and pressing N. Afterwards, you can enter the name you want to rename the symbol to.
Understanding main
's logic
As you can see, I have renamed a few symbols for post-solve analysis, but this will be irrelevent as I want to focus on how I solved it, which wasn't spending 20 minutes analyzing the binary.
Let's divert our attention towards this section.

The C snippet loops through 32 bytes (0x20
) in 8-byte chunks and runs each chunk through sub_40082c
and stores the result of it in var_54
.
I put my focus on this section specifically because there is some data being passed to a function which might suggest this is an important element in the challenge. Let's look in the function.

Looking at the function, it looks like a some sort of encryption they made until I noticed the constant 0x61c88647
for var_18
while you can assume this may be an author chosen constant, I decided to google it.

Aha! This is a constant used for the TEA encryption and the rest of the function logic seems to be for TEA encryption.
TEA or Tiny Encryption Algorithm is a symmetric-key block cipher made to be simple and efficient in environments where there may not be as many resources.
This is a good lead for us and might be the direction we need to focus on for this challenge. But what is the data that's being passed through the function?

Nice! These are the keys the TEA encryption function uses. Let's make it more prettier with Binary Ninja.
Change data types by selecting the data and pressing Y, afterwards you can enter the type you want to change the data to.

We can change it into an unsigned 32-bit integer with an array of 4 elements and here is what it looks like after.

Boom! That looks so much prettier and we are able to see the keys better, thanks to Binary Ninja.
Recap from analysis
Now from what I understand:
-
Input is provided through args (e.g.
./nips bcactf{test}
) -
arg2 and strlen of arg2 (named in Binary Ninja) is passed to a function which then stores arg2 in
var_34
(see below)

var_34
is then encrypted (essentially) with the keys then compares this to the ciphertext (see below) which is also 0x20 (32) bytes

- If the ciphertext matches
var_54
then "yips yips yips!" otherwise "nips..."
Solving the challenge
We have the ciphertext, keys, and know that it is TEA. From a simple Google search for a Python implementation and I found this.

Final Solution
# decrypted: bcactf{S3lf_M0d1fy1ng_MIPS}
import tea # the python tea implementation gist
import base64
ciphertext = "45a8d67f79b2e728e8c51b244fc5d7f7549ba908909f81ec0f841fc39715f955"
# base64'ed because the python implementation expects it to be in base64
ct_b64ed = base64.b64encode(bytes.fromhex(ciphertext)).decode()
keys = (0xdeadbeef, 0xcafebabe, 0xfeedface, 0xc0defeed)
# joining the key into one 16 byte key like the tea implementation expects
key_bytes = b''.join(k.to_bytes(4, 'little') for k in keys)
key_str = key_bytes.decode('latin-1') # decode from latin-1
plaintext = tea.decrypt(ct_b64ed, key_str)
print(f"decrypted: {plaintext}")
Above is what my final solve looks looks like, though I had to change the line below in tea.py specifically the decrypt()
function because UTF-8 would blow up bytes ≥ 0x80 unlike latin-1.
Afterword
Thanks to the BCA CTF 6.0 admin team for hosting such a fun CTF. I had a great time solving some of the challenges and learned a few things along the way. Hope this writeup helped in some way to anyone reading it. Peace ✌️