Number mashing
My second writeup for Down Under CTF 2024. Feedback is much appreciated.
Prologue¶
Difficulty: beginner
Category: reverse engineering
Solved: 299
Description
Mash your keyboard numpad in a specific order and a flag might just pop out!
Input files:
NB:
-
Following indices bases system is used to avoid ambiguity. Whenever element of a collection is referenced by number, 0-based index implied.
Ie, element
0
of list[1, 2, 4, 8, 16]
is1
, Element3
is8
.When element is reference in explanation with word (first, third...), 1-based system is implied.
Ie, first character of string
Hello World!
isH
, fifth iso
.
- Solution code was redacted for readability purposes. Due to time pressure during the competition I was using a lot of one-letter variables and questionable code structure.
- I am using gdb with pwndbg plugin
My struggle¶
Check what type of file we got:
1 2 3 4 5 6 7 8 9 10 |
|
I didn't have arm environment ready at that moment, so won't be able to run the binary locally. Instead, lets fire up ghidra and try to understand the code, as mentioned above it should make a lot of sense given binary is not stripped.
original ghydra output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
We can see print, scanf, then some calculations. Cleaned version with extra comments:
cleaned source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Now the task is clear: we are looking for two numbers such that when one is divided by the other the result is equal to dividend.
Usually we would achieve it with by having second number as one 4 / 1 = 4
, but extra condition in the code that we should use 1.
Quick check with my times table confirmed that calculus won't help us here. Instead, we want to take advantage
of overflow. Goal is to find such numbers that result won't fit into the register and when truncated will be equal to dividend.
Its quite easy to do with multiplication, for example for 1 byte numbers: 0x10 * 0x11 = 0x110
, which is truncated to 0x10
.
To experiment locally I've compiled the code above gcc -o number-mashing number-mashing.c
.
After a bit trial and error within constraints that second cannot be large number, in fact only -1 makes sense (and maybe 2 if we treat divide by two as shift right where flag bit is carried). And first number should have top bits sets, so they will be truncated by flag bit. Testing with following inputs -2147483648 -1 gives us something interesting:
1 2 3 |
|
- You divide by zero
- The division result is not in the range that can be represented by the
eax
register
Indeed, range for 4 byte number is from -2147483648 to 2147483647, so result of -2147483648 / -1 = 2147483648
doesn't fit in
the range above, hence we got error.
On arm architecture sdiv instruction is used, it doesn't raise exception, instead carry flag is set in cpsr register.
Connecting to the challenge server and submitting two numbers got us the flag.
Epilogue¶
- Official website: https://downunderctf.com/
- Official writeups: https://github.com/DownUnderCTF/Challenges_2024_Public