Hidden
For this challenge we are given two files:
firmware
: an arm32v7 executable.hidden.sal
: a Saleae logic trace.
Taking a look at the executable first, we see that it's a fairly simple program
that appears to be XORing a pre-existing flag and then writing it to a file.
Since it appears the flag is hard coded in the binary, with a dummy flag being
present in the binary given to us, the first step is to extract the XORed output
since we can use the following property of XOR to calculate the flag: (key XOR flag) XOR (key XOR dummyflag) XOR dummyflag = flag
However it doesn't just write to any type of file, in the program the
tcgetattr
and tcsetattr
functions are used to set some terminal attributes
that only exist for files that are backed by serial interfaces. Therefore the
program will only ever write to such a file.
Luckily we can just emulate a serial interface using the command socat -d -d pty,raw,echo=0 - > output
, which will create a virtual serial interface
(probably at /dev/pts/X
) and save anything written to output
.
Additionally to run the program on an x86 machine, we'll need to use an emulator
such as qemu. I Used
this
guide to set up my machine so I could run an ARM docker container, allowing me
to run the firmware
program.
By using docker run --rm -it -v (pwd):/root/ -v /dev/pts/5:/f arm32v7/ubuntu
to start up an arm container with the virtual terminal made by socat mounted to
/f
inside the container, we get the following output and the encoded flag
written to output
.
root@0ea2fbc64380:~# ./firmware /f
[!] sending data to /f
λ xxd output
00000000: 0b00 0000 0500 0000 0100 0000 0700 0000 ................
00000010: 0d00 0000 0100 0000 0b00 0000 0400 0000 ................
00000020: 0d00 0000 0400 0000 1000 0000 0f00 0000 ................
00000030: 0500 0000 0a00 0000 0400 0000 0f00 0000 ................
00000040: 0900 0000 0600 0000 0700 0000 1000 0000 ................
00000050: 0700 0000 0c00 0000 0700 0000 0900 0000 ................
00000060: 0400 0000 0a00 0000 0b00 0000 0300 0000 ................
00000070: 0900 0000 0700 0000 0e00 0000 0a00 0000 ................
00000080: 0800 0000 0300 0000 0700 0000 0b00 0000 ................
00000090: 0e00 0000 0d00 0000 0600 0000 0500 0000 ................
000000a0: 0e00 0000 0b00 0000 1000 0000 0b00 0000 ................
000000b0: 0500 0000 0c00 0000 0400 0000 0100 0000 ................
000000c0: 0500 0000 0200 0000 0f00 0000 1000 0000 ................
000000d0: 0a00 0000 0600 0000 1000 0000 0700 0000 ................
000000e0: 0100 0000 0f00 0000 0400 0000 0300 0000 ................
000000f0: 1000 0000 0900 0000 0800 0000 1000 0000 ................
00000100: 0f00 0000 0d00 0000 0f00 0000 0d00 0000 ................
00000110: 0500 0000 1000 0000 0100 0000 0300 0000 ................
00000120: 0800 0000 0400 0000 0200 0000 0400 0000 ................
00000130: 0800 0000 0600 0000 1000 0000 0600 0000 ................
00000140: 0a00 0000 0400 0000 0500 0000 0c00 0000 ................
00000150: 0100 0000 0200 0000 0200 0000 0800 0000 ................
00000160: 0500 0000 0600 0000 0700 0000 0300 0000 ................
00000170: 0400 0000 0b00 0000 0b00 0000 0100 0000 ................
00000180: 0600 0000 0e00 0000 0700 0000 0e00 0000 ................
Looking at the reconstruction of the decompiled binary, we can see that the
program does some funny stuff with the XORed flag before writing it to the
terminal. Instead of just writing the flag straight to the serial port, instead
the higher and lower four bits of each character is placed next to each other in
an array of int32_t
, which is then written.
This matches up with the output, as it should have the format [upper, 0, 0, 0, lower, 0, 0, 0, ...]
Now that we know what to expect in the serial trace, we can open it up in Saleae logic.
Opening up the trace we see something odd, normally each character sent down a serial port has eight bits, first the signal goes low for a cycle, then the bits are sent, then finally the signal is set high for two cycles to signal the end of the character. But for this signal it appears that only five bits are being sent per transmission. Makes sense why each character is split into the upper and lower nibbles before hand.
Looking at the decoded data, we can see it looks very similar to the output of
the firmware
program.
To export this data the csv export feature of Saleae logic was used, xsv select data out.csv | grep "[^\"]" > out
, Cyberchef was then used to get this as a
string of hex numbers because I'm lazy.
With both transmissions now formatted nicely, I wrote a small python script to decrypt the flag:
output_a = bytes.fromhex("2b 00 00 00 05 00 00 00 22 00 00 00 2b 00 00 00 2e 00 00 00 2d 00 00 00 2b 00 00 00 24 00 00 00 2d 00 00 00 24 00 00 00 09 00 00 00 0f 00 00 00 24 00 00 00 09 00 00 00 21 00 00 00 09 00 00 00 0a 00 00 00 0c 00 00 00 22 00 00 00 30 00 00 00 22 00 00 00 0a 00 00 00 06 00 00 00 30 00 00 00 05 00 00 00 0c 00 00 00 2e 00 00 00 03 00 00 00 0a 00 00 00 09 00 00 00 30 00 00 00 0f 00 00 00 21 00 00 00 03 00 00 00 22 00 00 00 0a 00 00 00 30 00 00 00 21 00 00 00 27 00 00 00 03 00 00 00 2b 00 00 00 30 00 00 00 09 00 00 00 0a 00 00 00 24 00 00 00 0a 00 00 00 05 00 00 00 05 00 00 00 06 00 00 00 30 00 00 00 0a 00 00 00 0a 00 00 00 0f 00 00 00 21 00 00 00 09 00 00 00 27 00 00 00 03 00 00 00 05 00 00 00 21 00 00 00 21 00 00 00 0f 00 00 00 27 00 00 00 21 00 00 00 0a 00 00 00 0a 00 00 00 2e 00 00 00 30 00 00 00 03 00 00 00 27 00 00 00 27 00 00 00 28 00 00 00 28 00 00 00 21 00 00 00 21 00 00 00 24 00 00 00 27 00 00 00 05 00 00 00 24 00 00 00 09 00 00 00 21 00 00 00 0f 00 00 00 21 00 00 00 24 00 00 00 0a 00 00 00 22 00 00 00 30 00 00 00 28 00 00 00 28 00 00 00 05 00 00 00 05 00 00 00 22 00 00 00 21 00 00 00 05 00 00 00 30 00 00 00 2e 00 00 00 21 00 00 00 03 00 00 00 05 00 00 00 27 00 00 00 2e 00 00 00")
output_b = bytes.fromhex("0b 00 00 00 05 00 00 00 01 00 00 00 07 00 00 00 0d 00 00 00 01 00 00 00 0b 00 00 00 04 00 00 00 0d 00 00 00 04 00 00 00 10 00 00 00 0f 00 00 00 05 00 00 00 0a 00 00 00 04 00 00 00 0f 00 00 00 09 00 00 00 06 00 00 00 07 00 00 00 10 00 00 00 07 00 00 00 0c 00 00 00 07 00 00 00 09 00 00 00 04 00 00 00 0a 00 00 00 0b 00 00 00 03 00 00 00 09 00 00 00 07 00 00 00 0e 00 00 00 0a 00 00 00 08 00 00 00 03 00 00 00 07 00 00 00 0b 00 00 00 0e 00 00 00 0d 00 00 00 06 00 00 00 05 00 00 00 0e 00 00 00 0b 00 00 00 10 00 00 00 0b 00 00 00 05 00 00 00 0c 00 00 00 04 00 00 00 01 00 00 00 05 00 00 00 02 00 00 00 0f 00 00 00 10 00 00 00 0a 00 00 00 06 00 00 00 10 00 00 00 07 00 00 00 01 00 00 00 0f 00 00 00 04 00 00 00 03 00 00 00 10 00 00 00 09 00 00 00 08 00 00 00 10 00 00 00 0f 00 00 00 0d 00 00 00 0f 00 00 00 0d 00 00 00 05 00 00 00 10 00 00 00 01 00 00 00 03 00 00 00 08 00 00 00 04 00 00 00 02 00 00 00 04 00 00 00 08 00 00 00 06 00 00 00 10 00 00 00 06 00 00 00 0a 00 00 00 04 00 00 00 05 00 00 00 0c 00 00 00 01 00 00 00 02 00 00 00 02 00 00 00 08 00 00 00 05 00 00 00 06 00 00 00 07 00 00 00 03 00 00 00 04 00 00 00 0b 00 00 00 0b 00 00 00 01 00 00 00 06 00 00 00 0e 00 00 00 07 00 00 00 0e 00 00 00")
key = "CTHB{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}"
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
import itertools
def recombine(b):
for c in chunks(list(b), 8):
upper = int.from_bytes(c[0:4], byteorder='little', signed=False)
lower = int.from_bytes(c[4:8], byteorder='little', signed=False)
upper = (upper - 1) << 4
lower -= 1
lower &= 0xf
yield (upper | lower) & 0xff
r = [a ^ b ^ ord(c) for a, b, c in zip(recombine(output_a),
recombine(output_b),
list(key))]
print(r)
print("".join(map(chr, r)))
Running this gives us the flag: CHTB{10w_13v31_f12mw4235_741ks_70_h42dw423_!@3418}