Challenge

file silver_bullet

checksec

run binary


ghidra
main()


create_bullet()

power_up()

beat()

struct BULLET & WOREWOLF

Analysis
Chương trình bắt đầu bằng việc khởi tạo một con sói WOREWOLF *werewolf có tên là werewolf.name = "GIN" và sức mạnh là werewolf.power = 0x7fffffff. Mục đích của trò chơi là ta sẽ tạo 1 viên đạn BULLET *bullet và đem đi bắn con sói. Bắn đến khi nào sói hết máu thì chiến thắng.
Chương trình có 3 chức năng chính tương ứng với 3 hàm:
create_bullet(): tạo một viên đạn BULLET *bullet có tên là bullet.name dài tối đa 0x30 kí tự do người dùng nhập vào, set power của bullet làbullet.power = len(bullet.name).power_up(): ta có thể tăng sức mạnh cho bullet bằng cách nối dài thêm name, bullet.power sẽ được cập nhật lại là chiều dài của tên mới. Chiều dài tối đa của bullet.name vẫn là 0x30beat(): tiến hành bắn sói, sức mạnh của sói sẽ giảm đúng bằng bullet.power. Nếu sói hết máu thì thông báo chiến thắng.
Để giải quyết 1 thử thách pwnable, lúc nào cũng vậy. Ta sẽ bắt đầu bằng việc phân tích để hiểu được flow của chương trình. Sau đó sẽ soi kỹ code, chú ý vào những hàm dễ bị lỗi để tìm xem lỗi nằm ở đâu. Ở bài này rõ ràng có 1 lỗi nằm ở hàm strncat() bên trong chức năng power_up().
strncat(bullet.name, name, 0x30-bullet.power)
Mục đích của hàm này là nhằm nối chuỗi name mà người dùng mới nhập vào chuỗi bullet.name, số ký tự được copy tối đa là 0x30-bullet.power chính là chiều dài chuỗi name. Nghe qua thì chẳng có gì sai, nhưng thật ra có 1 lỗi off-by-one. Hàm strncat() sẽ copy chuỗi name vào chuỗi bullet.name, sau đó sẽ thêm 1 ký tự NULL vào cuối chuỗi. Giả sử lúc đầu bullet.name là một chuỗi gồm 0x2f kí tự ‘A’, name là chuỗi ‘B’ dài 0x1 kí tự. Sau khi thực hiện strncat() thì bullet.name sẽ gồm 0x30 kí tự, cộng thêm 1 kí tự NULL được strncat() tự động thêm vào. Chính kí tự NULL này sẽ overwrite vào biến nằm ngay sau bullet.name trên stack, đó chính là vị trí của bullet.power. Và bullet.power sẽ được cập nhật lại thành bullet.power = bullet.power + len = 0 + 1 = 1.

Bây giờ bullet.name có chiều dài là 0x30 nhưng chiều dài được lưu trong bullet.power chỉ là 0x1, do đó ta có thể power_up() thêm 1 lần nữa, và chuỗi name mà ta nhập vào sẽ overwrite từ vị trí của bullet.power, do đó ta có thể overwrite return address.
Bài này được set NX enabled nên không thể thực hiện ret2shellcode, đề bài cho file libc_32.so.6, đó là 1 gợi ý để ta dùng kĩ thuật ret2libc.
Exploit
Mục tiêu của ta là dùng kĩ thuật ret2libc để gọi hàm system() với tham số là “/bin/sh”. Chương trình được compile với ASLR được bật, sử dụng thư viện liên kết động libc_32.so.6. Do đó trước hết ta cần leak được địa chỉ của thư viện libc sau khi load. Ta sẽ overwrite return address thành hàm puts() với tham số là 1 hàm nào đó của libc (chẳng hạn như chính hàm puts), mục đích là để chương trình in ra địa chỉ của hàm puts sau khi được load vào vùng .GOT.

Sau khi đã leak, tính địa chỉ của libc bằng cách:
(libc_base_address) = (puts_address_leaked) - (offset_of_puts)
Từ đó tính được địa chỉ của system() và chuỗi “/bin/sh”.
Cuối cùng ta thực hiện payload để return to system(“/bin/sh”):

Sourcecode

