I’ve only reached chapter 5, but so far this book is awesome!
After an introduction to the whole compilation process, a detailed walk-through of the ELF format, a primer on the PE Windows format, how to write your binary tools with libbfd and a step-by-step introduction to the first level of a CTF, up to the reader to get his hands dirty by tackling the next levels of the CTF.
This post is about my findings for the levels 2, 3 and 4 during the covid19 lock-down -_-
More to come!
The CTF challenge
The Capture The Flag challenge offered in the book consists of finding a hidden flag (a string) in a binary, without access to its source code, by using reverse engineering techniques.
Once discovered, the flag unlocks the next levels and so on and so forth.
Only basics tools like a hexeditor, gdb, objdump, nm, readelf, strings will be used, and not more complex tools like IDA, Ghidra or Binary Ninja to be sure to understand the basics first.
Everything has been completed on a Kali Linux VM or on the Linux VM provided by the book author.
The flag?
At the end of the chapter 5 (walk-through of the initial level of the CTF) we are given the flag to be fed to the provided oracle binary, that will in turn generate the next level binaries, namely lvlXX.
The goal is to find for every level the flag to unlock the next level, using oracle <flag> that will:
validate the flag is the expected one for the level
generate the next level binary
Example with the level 1: the flag 84b34c124b2ba5ca224af8e33b077e9e has been found after the lvl1 binary analysis, we feed it to oracle that confirms the flag is correct
1
2
3
4
5
$ ./oracle 84b34c124b2ba5ca224af8e33b077e9e
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| Level 1 completed, unlocked lvl2 |+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
Run oracle with -h to show a hint
A new binary lvl2 has been generated waiting for our investigation :)
Level 2
When executed (remember, in a VM) a different hexadecimal number is generated on every execution, with sometimes some repetitions (time based?):
If combined all together the resulting string would be 32 characters long which matches the expected flag length, so as a first try I’ll concatenate them in order of appearance in the binary:
1
2
3
4
5
6
$ ./oracle 034fc4f6a536f2bf74f8d6d3816cdf88
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| Level 2 completed, unlocked lvl3 |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
Run oracle with -h to show a hint
That’s it!
Level 3
The binary can’t be executed on my VM because of an architecture mismatch; my VM system architecture being x86-64:
Manage to run this binary on a Motorola Coldfire system is very likely a waste of time. I think it worth trying first to patch the ELF header to make it behave like a x86-64 binary that can be executed in the VM.
There is more: readlelf shows that at least the binary ELF header is somehow corrupted:
$ readelf -h lvl3
ELF Header:
Magic: 7f 45 4c 46 02 01 01 0b 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: Novell - Modesto
ABI Version: 0
Type: EXEC (Executable file)
Machine: Motorola Coldfire
Version: 0x1
Entry point address: 0x4005d0
Start of program headers: 4022250974 (bytes into file)
Start of section headers: 4480 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
readelf: Error: Reading 0x1f8 bytes extends past end of file for program headers
I suppose the ELF header has been corrupted on purpose: given the lvl3 binary is only 6.2kb long, the Start of program headers: 4022250974 (bytes into file) entry is definitively not possible.
Using an ELF header reference to get the offsets and fields lengths, with bvi(binary editor) I am going to patch the appropriate fields of the ELF header:
e_ident[EI_OSABI] from 0x0b (Novell Modesto) to 0x00 (System V)
e_machine from 0x34 (Motoroal Coldfire) to 0x3e (amd64)
e_phoff from 0xdeadbeef(note the reference) to 0x40(64 bytes) because here the program headers table follows directly the file header
Notes:
when reading the ELF header reference - the offset/size to pick are the 64-bits ones
in bvi:
move to the field offset (leftmost number on bottom right)
type ‘r’ to replace a single byte (keep in mind the little endian format)
So there is something else that needs to be modified on the binary itself, in order the get the expected md5sum, i.e. the flag to unlock the next level.
What strikes me a bit is the absence of the .text section when disassembling the file:
1
2
3
4
5
6
$ objdump -d -Mintel lvl3|grep section
Disassembly of section .init:
Disassembly of section .plt:
Disassembly of section .plt.got:
Disassembly of section .fini:
Can readelf tell us more?
1
2
$ readelf -S lvl3|grep text
[14] .text NOBITS 0000000000400550 00000550
The .text section is here!
But with an incorrect type! It should be PROGBITS instead of NOBITS since it contains executable code.
This is why objdump skipped it during the disassembly earlier.
In an ELF section header this is the sh_type field, which should be set to 0x1SHT_PROGBITS. This will be a bit more challenging to patch with bvi, but I’ll try anyway before resorting to develop a program with libbfd.
First we need to get the offset from the start of the file to the .text section header:
(output trimmed for readability)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ readelf -S --wide lvl3
There are 29 section headers, starting at offset 0x1180:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
(...)
[14] .text NOBITS 0000000000400550 000550 0001f2 00 AX 0 0 16
(...)
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
What is relevant to us:
There are 29 section headers, starting at offset 0x1180:
The **.text** section is the 14th entry within all section headers- [14]hex value: 0xe
Given that a section header is 64 bytes long (hex 0x40), the .text offset from the start of the file will be:
Trying to execute the binary and checking if its output still match its md5 checksum (shouldn’t have changed), maybe that was the only other thing the author wanted us to check:
$ ./oracle 3a5c381e40d2fffd95ba4452a0fb4a40
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| Level 3 completed, unlocked lvl4 |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
Run oracle with -h to show a hint
Here we go to level4!
Level 4
Looks like a standard stripped binary, no output when executed:
$ ./oracle 656cf8aecb76113a4dece1688c61d0e7
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
| Level 4 completed, unlocked lvl5 |
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
Run oracle with -h to show a hint
Next levels writeup in an upcoming post.
Feedback
Positive criticism always welcome! (comments here, contact in About ) as I would be more than happy to learn more!