Note: All the related files that are mentioned in this post can be found here.
Task
If you’re not careful enough you can get framed into something you didn’t do!
We were given a sample binary to analyze & a vulnerable server to exploit in order to get the flag.
Analysis
Overview
Running checksec on the challenge file shows:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
The file itself is constructed from two functions called from main
:
hello
- Asks for your name & store the string on the stack. Annoyingly, it almost vulnerable for buffer overflow, but not actually overflows.feeling_lucky
- has a buffer overflow in it, but requires you to win a ‘game of chance’ first(which, is not in your favor) in order to trigger the overflow.
feeling_lucky
is below:
You enter the ‘amount of shuffles’ you want to perform as a player and the program runs in a loop (notice the loc_A6A
branch) to generate numbers with _rand
.
If you’re lucky enough, you’ll get a message saying Seems you're lucky!
following a prompt that lets you write 0x1337
bytes into memory. If not, it will jump straight to a Nope :(
message.
So our goal is to be lucky enough, but we can’t rely on _rand
to actually help us with this.
Exploitation part 1 - getting luck(y)
- When
hello
, asks for your name, we fill the buffer and make sure to put0xdeadbeefcafebabe
in it. - When
feeling_lucky
asks us ‘How many shuffles?’, we answer 0. This will jump straight to the comparasion of “un-initilized” variables. Those variables values’ will be the values from the previous stack frame(fromhello
) 😈 - This will allow us to get a “Seems you’re lucky!” message and unlock the vulnerable part for buffer overflow(bottom left in the screenshot above)
After unlocking the “Seems you’re lucky!” message, we need to continue exploitation & overcome other protections(NX/ASLR).
Exploitation part 2 - getting the flag
It’s good that the binary already has a flag
function that prints the flag. However, ASLR is enabled and we don’t know where this function is located in memory on the destination server.
When digging into the binary more, I realized we don’t even have to get a leak / break ASLR and know the full address of flag
. This is because flag
and main+71
(=the return address of feeling_lucky
) are both allocated in the same page.
Yes, it’s a random page(due to ASLR), but they are both allocated in the same page in memory(notice that the only difference is just one byte. 0x1b
<~> 0x14
)
Hence: instead of overwriting the entire return address, we can just overwrite the least significant byte of it(replacing 0x14
with 0x1b
). This will return us back to flag
instead of main+71
without having to know the full address of flag
in memory.
backtrace before:
───────────────────[ BACKTRACE ]───────────────────
► f 0 56279e198aa3 feeling_lucky+162
f 1 56279e198b14 main+71
f 2 7f5d813f90b3 __libc_start_main+243
───────────────────────────────────────────────────
backtrace after:
───────────────────[ BACKTRACE ]───────────────────
► f 0 56279e198aa8 feeling_lucky+167
f 1 56279e198b1b flag
f 2 0
───────────────────────────────────────────────────
Now all we do is wait for a ret
instruction :D
Solution
I added inline comments
output:
root@56bec2e1a941:~/ctfs/omh2021/framed# python3 solve.py REMOTE
[*] '/root/ctfs/omh2021/framed/chall_framed'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to framed.zajebistyc.tf on port 17005: Done
[*] Switching to interactive mode
!
Read 57 payload bytes
flat{uninitialized_variables_are_not_really_uninitialized}