6.1.21 pwn HITCONCTF2016 Secret_Holder

下载文件

题目复现

$ file SecretHolder
SecretHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=1d9395599b8df48778b25667e94e367debccf293, stripped
$ checksec -f SecretHolder
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               2       SecretHolder
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.3.1 20160413.

64 位程序,开启了 Canary 和 NX,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

$ ./SecretHolder
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret
1
Which level of secret do you want to keep?
1. Small secret
2. Big secret
3. Huge secret
1
Tell me your secret:
AAAA
1. Keep secret
2. Wipe secret
3. Renew secret
3
Which Secret do you want to renew?
1. Small secret
2. Big secret
3. Huge secret
1
Tell me your secret:
BBBB
1. Keep secret
2. Wipe secret
3. Renew secret
2
Which Secret do you want to wipe?
1. Small secret
2. Big secret
3. Huge secret
1

该程序运行我们输入 small、big、huge 三种 secret,且每种 secret 只能输入一个。通过 Renew 可以修改 secret 的内容。Wipe 用于删除 secret。

猜测三种 secret 应该是有不同的 chunk 大小,但程序没有我们常见的打印信息这种选项来做信息泄漏。

题目解析

下面我们逐个来逆向这些功能。

Keep secret

果然该函数使用 calloc() 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个,chunk 的指针放在 .bss 段上。另外其实读入 secret 的逻辑还是有问题的,它没有处理换行符,也没有在字符串末尾加 \x00

Wipe secret

该函数在释放 secret 时,首先将对应的 chunk 释放掉,然后设置 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。

Renew secret

该函数首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。

漏洞利用

总结一下我们知道的东西:

  • small secret: small chunk, 40 bytes

    • small_ptr: 0x006020b0

    • small_flag: 0x006020c0

  • big secret: large chunk, 4000 bytes

    • big_ptr: 0x006020a0

    • big_flag: 0x006020b8

  • huge secret: large chunk, 400000 bytes

    • huge_ptr: 0x006020a8

    • huge_flag: 0x006020bc

漏洞:

  • double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk

  • use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用

有个问题是,400000 bytes 的 huge secret 连 top chunk 都不能满足,此时会调用 sysmalloc(),通过 brk() 或者 mmap() 为其分配空间,该函数首先判断是否满足 mmap() 的分配条件,即需求 chunk 的大小大于阀值 mp_.mmap_threshold,且此进程通过 mmap() 分配的总内存数量 mp_.n_mmaps 小于最大值 mp_.n_mmaps_max

此时将使用 mmap() 来分配内存。然而这样得到的内存将与初始堆(由brk()分配,位于.bss段附近)的位置相距很远,难以利用。所以我们要想办法使用 brk() 来分配,好消息是由于性能的关系,在释放由 mmap() 分配的 chunk 时,会动态调整阀值 mp_.mmap_threshold 来避免碎片化,使得下一次的分配时使用 brk()

因为在分配 large chunk 的时候,glibc 首先会调用函数 malloc_consolidate() 来清除 fastbin 中的块。所以 big secret 被放到了原 small secret 的位置,当再次分配 small secret 的时候就造成了堆块重叠。

首先制造 double free:

然后在 big secret 里布置一个 fake chunk:

释放 huge secret,即可触发 unsafe unlink:

于是我们就获得了修改 .bss 段的能力。

leak libc

修改 big_ptr 指向 free@got.plt,small_ptr 指向 big_ptr:

修改 free@got.pltputs@plt,big_ptr 指向 puts@got.plt

此时释放 big secret,其实就是 puts(puts_addr),通过偏移计算即可得到 libc 基址和 one-gadget 地址。

pwn

最后可以通过两次修改,将 puts@got.plt 修改为 one-gadget,获得 shell。

开启 ASLR,Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?