- PROJEKT: OVERFLOW là một trò chơi học tập biến assembly RISC-V và buffer overflow thành luật chơi cờ bàn, để người chơi trực tiếp lần theo việc thao tác bộ nhớ, stack và địa chỉ trả về
- Người chơi chia sẻ cùng một bộ nhớ và cùng một chương trình, rồi cạnh tranh theo kiểu lập lịch ưu tiên ngắt quãng, không có bộ nhớ ảo và chỉ thực thi 10 lệnh mỗi lượt
- Cục diện thắng thua được quyết định ở quá trình sao chép các lệnh có sẵn để tạo shellcode, rồi ghi đè return address của đối thủ để đưa họ tới
game_over() - Truy cập bộ nhớ sai, đọc/ghi không căn chỉnh và lệnh bất hợp pháp sẽ dẫn tới crash và chạy exception handler; việc đổi địa chỉ trap và monkeypatch
noplà các biến số chiến lược then chốt - Có bản chơi trên web, bàn cờ để in, cùng game helper cho ESP32 và di động, nhưng một số luật vẫn đang được điều chỉnh nên hiện khá giống một câu đố hack mang tính thử nghiệm
Mục tiêu trò chơi và mô hình thực thi
- PROJEKT: OVERFLOW là một dự án đưa assembly RISC-V và buffer overflow lên bàn cờ tabletop
- Mục tiêu cốt lõi là sao chép các lệnh sẵn có để tạo một shellcode nhỏ trong bộ nhớ, nhảy tới đoạn mã đó bằng buffer overflow, rồi ghi đè return address của đối thủ để buộc họ gọi hàm
game_over() - Chiến lược không chỉ dừng ở thực thi mã đơn thuần mà còn bao gồm thiết lập exception handler và cả monkeypatch
- Tất cả người chơi chia sẻ cùng một bộ nhớ, cùng một chương trình, và dùng chung một bộ xử lý theo kiểu chia lát thời gian
- Mỗi lượt thực thi 10 lệnh
stack pointercủa mỗi người chơi bắt đầu ở vị trí khác nhau- Không có bộ nhớ ảo
Quy trình build và tạo bàn cờ
- Mã được biên dịch bằng
riscv64-unknown-elf-gcccho đích RV32- Các tùy chọn chính gồm
-march=rv32g,-mabi=ilp32,-ffreestanding,-nostdlib,-nostartfiles,-O0 - Nhờ
-O0, mã máy trở nên dài dòng hơn nhưng dễ lần theo hơn
- Các tùy chọn chính gồm
- Tài liệu cho bàn cờ được tạo bằng cách parse đầu ra
riscv64-unknown-elf-objdump -S -l -fd game- Chỉnh sửa các lệnh
▲và✎ - Đổi offset nhảy từ hệ 16 sang hệ 10
- Dọn lại assembly và ghép với mã nguồn
- Tạo SVG rồi chuyển sang PDF bằng Inkscape
- Chỉnh sửa các lệnh
In ấn và vật dụng cần chuẩn bị
- Bàn cờ được dùng bằng cách in PDF phần trái và phải
- Khổ A3 là lựa chọn được khuyến nghị, A4 cũng dùng được nhưng khá nhỏ
- Cần chuẩn bị 1 quân cho lệnh
nop, 1 quân cho địa chỉ trap, mỗi người chơi 2 quân choprogram countervàstack pointer, cùng bút chì và tẩy - Bản web hỗ trợ chơi một mình hoặc cùng bạn bè, đồng thời cũng có game helper cho ESP32 và di động
Luật cơ bản và diễn tiến lượt chơi
- Trạng thái ban đầu như sau
- Tất cả thanh ghi bắt đầu từ 0, nhưng thanh ghi return address
rabắt đầu ở 1000 spcủa Player 1 khởi tạo là 2244,spcủa Player 2 là 3844pccủa cả hai người chơi bắt đầu ở 1000, là địa chỉ bắt đầu của hàmmain- Quân trap được đặt ở địa chỉ 1000
- Mọi địa chỉ bộ nhớ ngoài chương trình được nạp sẵn đều là 0
- Quân lệnh
nopban đầu không được đặt lên bàn cờ
- Tất cả thanh ghi bắt đầu từ 0, nhưng thanh ghi return address
- Mỗi lượt phải thực thi 10 lệnh, và các nhảy như
jal,beqcũng phải được đi theo đúng như vậy - Người chơi có thể dừng lượt sau khi đã thực thi ít nhất 1 lệnh và chuyển số lệnh còn lại sang lượt sau
- Số lệnh có thể tích lũy tối đa là 20
Monkeypatch và điều kiện chiến thắng
- Ở đầu mỗi lượt, sau khi thực thi đúng 1 lệnh, người chơi có thể di chuyển quân lệnh
noptới một địa chỉ bất kỳ trong một hàm mà không có người chơi nào đang thực thi - Khi
pctới địa chỉ đó, lệnh tại đó sẽ hoạt động như no-operation - Nếu di chuyển quân
nop, bạn sẽ mất lượt hiện tại và lượt kế tiếp, còn đối thủ có thể thực thi tối đa 20 lệnh ở lượt tiếp theo - Luật monkeypatch hiện vẫn chưa cân bằng và đang được chỉnh nhẹ vài ngày một lần
- Ở chế độ khó, trò chơi kết thúc khi bạn hack đối thủ để buộc họ gọi hàm
game_over() - Nếu không bên nào còn có thể đưa đối thủ vào
game_over(), kết quả là hòa - Ở chế độ dễ, người chơi đầu tiên thực thi
rettrongmainđể thoát vòng lặp chính sẽ thắng
Ký hiệu đặc biệt và xử lý ngoại lệ
✎cho phép chọn một số 12-bit bất kỳ từ 0 đến 4095 làm giá trị immediate của lệnhli▲cho phép chọn một giá trị trong phạm vi ±128 byte tính từstack pointercủa chính mình ở lệnh load- Ví dụ, nếu
splà 2180 thì có thể chọn từ 2052 đến 2308
- Ví dụ, nếu
- Các hành vi bị cấm sẽ dẫn tới crash chương trình
- Ghi đè địa chỉ bộ nhớ thấp hơn 1192
- Đọc hoặc ghi không căn chỉnh ở địa chỉ không chia hết cho 4
- Thực thi lệnh bất hợp pháp
- Khi crash xảy ra, exception handler sẽ chạy và nhảy tới địa chỉ trap
- Địa chỉ trap ban đầu là 1000 nhưng có thể bị ghi đè trong hàm
set_trap() - Khi ngoại lệ xảy ra,
program counterđược đặt thành một giá trị nhất định rồi tiếp tục thực thi
- Địa chỉ trap ban đầu là 1000 nhưng có thể bị ghi đè trong hàm
- Nếu bị phát hiện gian lận hoặc mắc lỗi, trạng thái chương trình, bộ nhớ và thanh ghi của người chơi đó sẽ bị reset
Luật mở rộng cho 3~4 người chơi
spcủa Player 3 được đặt là 2116spcủa Player 4 được đặt là 3716- Khi có từ 3 người trở lên, ký hiệu
▲chỉ có thể dùng trong vùng cáchstack pointer128 byte về phía âm - Khi chơi với hơn 2 người, trò chơi trở nên khá bất ổn và xuống cấp rất nhanh
- Việc đạt điều kiện chiến thắng sẽ khó hơn, nhưng trải nghiệm chơi lại vui và hỗn loạn hơn
Ví dụ chiến lược hack
- Crash có thể được dùng như một chiến lược tấn công để chặn tiến trình của đối thủ
- Nếu đổi trap handler thành hàm
game_over, người chơi crash đầu tiên sẽ thua- Trong trạng thái này, quân
noptrở nên cực kỳ mạnh - Nếu đối thủ đặt
noplên lệnhretcủa hàm mà bạn đang thực thi, bạn có thể thua
- Trong trạng thái này, quân
- Trong hàm
bug(), nếu làm overflow chỉ số thành 400 hoặc -400 thì có thể truy cập stack của đối thủ và ghi đè return address của họ- Ví dụ, để đi từ địa chỉ 3784 tới 2184 thì
(3784 - 2184) / 4 = 400, nên cần chỉ số-400
- Ví dụ, để đi từ địa chỉ 3784 tới 2184 thì
- Có thể dùng hàm
copy()để sao chép các lệnh cụ thể và dựng một shellcode ngắn trong bộ nhớ- Một shellcode ví dụ dùng tổ hợp
li a4, ✎,li a5, ✎,sw a4, 0(a5),retđể thực hiện ghi tùy ý - Nếu sao chép lệnh
ret, return address sẽ được đặt thành điểm bắt đầu shellcode và gây ra vòng lặp vô hạn
- Một shellcode ví dụ dùng tổ hợp
- Trong hàm
bug(), nếu đặt chỉ số là 6 thì có thể ghi biếnvalueđè lên địa chỉ return đã lưu trên stack là28(sp)- Khi
bug()trả về, giá trị ở28(sp)sẽ được chép vào thanh ghi return address - Nếu ghi địa chỉ shellcode đã tạo vào đó, bạn có thể nhảy vào bộ nhớ
- Khi
Diễn giải lệnh và các thay đổi
- Mọi lệnh nhảy đều là nhảy tương đối theo
program counterhiện tại, dù trong disassembler chúng có thể trông như địa chỉ tuyệt đối- Ví dụ, mã máy 1903 của
jal a4, 0khi thực thi sẽ thành vòng lặp vô hạn
- Ví dụ, mã máy 1903 của
- Danh sách các lệnh hợp lệ trong game được tổng hợp từ các lệnh RV32 JRI có mã máy từ 0 đến 4095, ở các dạng dùng
a0,a4,a5,sp,rav.v. - Nhật ký thay đổi 0.0.6 có thay đổi từ
while(run)sangwhile(*prun)- Đối thủ giờ có thể ép crash bằng cách dẫn tới dereference không căn chỉnh
- Luật NOP cũng đã đổi để chỉ cho phép đặt trong các hàm hiện không được thực thi
Thiết kế và tài liệu học tập
- Các hình chữ nhật ở hai bên bàn cờ là một thông điệp nhị phân được mã hóa bằng ASCII
- Hình chữ nhật trắng là 1, hình chữ nhật đen là 0
- Màu sắc chỉ dùng đỏ, xanh dương, đen và trắng để phù hợp với in ấn giá rẻ và khả năng đọc trên máy in đen trắng
- Không dùng syntax highlighting
- Đây là lựa chọn để tránh việc một số phần mã trông có vẻ quan trọng hơn chỉ vì theme, và để người chơi tự đánh giá, tự tập trung
- Tài liệu học assembly RISC-V gồm riscv-programming.org, cs3410 risc-v interpreter, rvcodecjs của luplab
- Tài liệu học C sử dụng phần đầu của Beej's Guide to C Programming
- Ngoài ra còn có PDF bài tập assembly để in về biến, gọi hàm, con trỏ, chuỗi, struct, mảng, đệ quy, cùng một phiên bản “assembly hangman” kiểu điền vào chỗ trống
1 bình luận
Các ý kiến trên Hacker News
Thật sự ấn tượng. Đặc biệt, điều tuyệt nhất có vẻ là đã khiến cô con gái 12 tuổi cùng chơi trò này
Khi nào có thể kỳ vọng phiên bản CHERI? :-D
Core War là trò chơi diễn ra trong đấu trường bộ nhớ của một máy ảo hỗ trợ một ngôn ngữ assembly mô phỏng đơn giản. Tôi thấy nó lần đầu trên Scientific American năm 1984, và vì khi đó đã lập trình khoảng 15 năm nên tôi nhận ra nó được lấy cảm hứng từ trò chơi cũ hơn của Bell Labs là Darwin
Darwin được tạo ra năm 1961 và chạy trên IBM 7090. Các chương trình cạnh tranh tài nguyên với nhau; chương trình nào sao chép để chiếm toàn bộ không gian được cấp phát sẽ thắng. Sau khi Robert Morris Sr. tạo ra một chương trình không thể bị đánh bại, nó không tồn tại được lâu. Xem [2]
Vào giữa thập niên 1970, Software Practice and Experience là một trong những tạp chí khoa học máy tính tôi thích nhất, và thường có chuyên mục Computer Recreations viết dưới bút danh Aleph-Null. Hồi học cao học, tôi đã rất vui khi tự triển khai vài trò chơi trong chuyên mục đó. Tạp chí thì đắt, nhưng nếu là sinh viên đại học, nhiều khả năng bạn có thể tìm thấy nó trong thư viện trường như tôi ngày trước. Các số thập niên 1970 có những chủ đề như trình biên dịch Pascal, Algol 68, lập trình đồng thời; đọc dễ hiểu và thú vị, và qua các bài viết của N. Wirth tôi đã biết đến Module[3,4] rồi sau đó là Oberon[5]
[1] https://en.wikipedia.org/wiki/Core_War
[2] https://en.wikipedia.org/wiki/Darwin_(programming_game)
[3] https://onlinelibrary.wiley.com/doi/abs/10.1002/spe.43800701...
[4] https://onlinelibrary.wiley.com/doi/abs/10.1002/spe.43800701...
[5] https://onlinelibrary.wiley.com/doi/abs/10.1002/spe.43801909...
Tôi từng có một người bạn nói rằng thích game nhưng không có đầu óc lập trình; thế mà qua Human Resource Machine, về cơ bản anh ấy đã lập trình, và một số lời giải còn tốt hơn của tôi dù tôi có nhiều năm kinh nghiệm
Đứa con 12 tuổi của tôi ghét toán, nhưng lại chơi Human Resource Machine và SpaceChem giỏi đáng kinh ngạc. Điều đó khiến tôi tự hỏi liệu toán ở phổ thông và toán trong lập trình có khác nhau về căn bản không
Rất thú vị. Nhìn vào dung lượng bộ nhớ máy tính ngày nay, tôi luôn cảm thấy mnemonic ngắn là một lựa chọn kỹ thuật không hay
Ở đây cũng vậy, việc đầu tiên phải làm là học và nhớ từng lệnh làm gì. Nếu đổi tên sang dạng diễn giải đầy đủ hơn, việc học, ghi nhớ và đọc mã sẽ dễ hơn nhiều. Việc mọi người không thường làm như vậy khiến tôi thấy đáng nghi
Việc loại lỗ hổng này có thể tồn tại cũng cho thấy sự thất bại trong thiết kế toàn hệ thống. Điều đó không có nghĩa đây không phải là trò chơi vui hay cách học tốt, nhưng trong kỹ thuật, các vấn đề mang tính cấu trúc đang được chấp nhận quá dễ dàng. Phần lớn thậm chí còn không nhìn thấy khiếm khuyết cấu trúc đó
Tôi nghĩ trẻ em phản hồi rất tốt khi ta không coi thường chúng. Ít nhất con tôi là như vậy
Bạn có nghĩ có người không xem đọc và ghi tùy ý là khiếm khuyết cấu trúc không? Hàng nghìn người đang xử lý vấn đề đó và cũng đã đạt được khá nhiều tiến bộ. Đồng thời, tôi vẫn thấy peek và poke rất vui
Cái này thật sự tuyệt. Muốn thử ở công ty
Trông khá thú vị. Bạn nghĩ phù hợp với độ tuổi nào?
bug(), tôi nghĩ trẻ 10–15 tuổi cũng làm đượcCon gái tôi 12 tuổi và chúng tôi đang chơi rất vui cùng nhau. Điều kiện thắng khó, tức khiến đối thủ nhảy đến hàm
game_over(), thì khó hơn, nhưng tôi nghĩ có thể đạt tới trong 5–6 thángNgười lớn thì tôi không chắc. Một số người sợ assembly như thể nó do quỷ tạo ra, nên có khi còn khó khiến họ chơi hơn trẻ con
Điều thú vị là chúng ta có xu hướng nhìn thế giới như tấm gương phản chiếu chính mình
Việc tôi quan tâm đến buffer overflow và lập trình rồi cho rằng con gái mình đương nhiên cũng sẽ rất quan tâm thì có xác suất đến mức nào? Nếu lại còn là con đầu lòng và là con gái thứ hai thì xác suất có vẻ thấp hơn nữa, nhưng tôi vẫn thấy nhiều ông bố cứ thúc đẩy
Khi làm những dự án kiểu này, tôi tò mò liệu ít nhất ở mức nào đó người ta có ý thức rằng đây là một dự án hư vinh hay không. Dù sao thì tôi cũng quan tâm đến những thứ như vậy, nên rất vui vì nó được công bố
Bạn đang ám chỉ rằng người tạo dự án ép con gái làm việc này vì tính hư vinh của mình, nhưng căn cứ ở đâu? Tôi đã xem vài trang trên site mà chẳng thấy gì gợi ý như vậy; ngược lại còn có nhiều cách diễn đạt nhẹ nhàng cho thấy cô bé vui vẻ và rất hứng thú
Vì sao lại loại trừ khả năng cô bé là người bắt đầu, vì cứ tò mò bố mình làm gì trên máy tính? Có thể nó bắt đầu nhỏ thôi, rồi phát triển thành một quá trình hai chiều giữa người chia sẻ mối quan tâm và một bạn đồng hành khám phá nhỏ tuổi
Thực tế thế nào thì tôi cũng không biết, nhưng bạn cũng không biết. Từ góc nhìn của người từng tham gia giáo dục vài năm, trẻ em nói chung là những người học giỏi hơn nhiều so với niềm tin phổ biến. Cấu trúc trường học có lẽ cũng là một lý do, nhưng cốt lõi có thể là kiểu niềm tin giới hạn như thế này. Tôi muốn vỗ tay cho người cha đã cố chia sẻ mối quan tâm và niềm đam mê của mình với con gái và với thế giới
Một số trong đó sẽ có giá trị, một số thì không. Xác suất luôn bất lợi. Cuộc sống vốn là vậy
Khi đường mã RISC-V 64-bit ổn định, hoạt động đủ tốt và ngay cả “buffer overflow” cũng biến mất, trong bối cảnh C/C++ không phải lúc nào cũng chịu thay đổi cú pháp, các bạn định xử lý sự lỗi thời có kế hoạch thế nào? Những linh hồn tội nghiệp…
Khoan đã.
Một board game để bàn có viết code assembly ư? Sao trước giờ mình không nghĩ ra nhỉ? :D
PL/I đã làm đúng các phần như kiểm tra biên chuỗi/mảng, stack tăng lên trên thay vì xuống dưới
https://www.acsac.org/2002/papers/classic-multics.pdf