Shellcode as a Service - SeeTF2023
Bài này hard quá nên mình đã đọc lại wu và viết lại bài này
Last updated
Bài này hard quá nên mình đã đọc lại wu và viết lại bài này
Last updated
Một chút trick để làm những bài như vậy. Đó là hãy quan sát các thanh ghi trước khi nó được call shellcode. Biết đâu đó ta có thể tận dụng các thanh ghi để call ra các system call.
Source
Như đề bài nói thì ở bài này chỉ cho phép nhập vào 0x6
bytes (Dòng 29) , có 2 quyền đó là read
và open
(Không có write). Tức là mở và đọc nó từ trên server và không có quyền viết ra màn hình nên các xử lí byte ở bài này mình thấy nó khó với mình ở thời điểm hiện tại :<. Oke bắt đầu phân tích sâu hơn.
Analysis
Oki, không có gì đáng chú ý ở đây vì bài này đã gọi ra 1 vùng nhớ khác như ảnh bên dưới.
Đặt break point tại call
và xem có gì xảy ra:
Nhưng cái chính ở đây là ta chỉ có thể nhập được 6 bytes (đủ để chứa 2-3 instruction). Vậy làm sao để có thể nhận nhiều hơn ???
Bạn có thể thấy instruction ở trên call rdx
là mov eax, 0x0
. Thì mình nghĩ đến syscall read của chương trình.
RAX: 0x0
RDI: 0x0 (stdin)
RSI: 0x1337000 (Nơi đọc dữ liệu vào)
RDX: 0x200 (Byte muốn đọc (Bạn có thể chỉnh size tùy ý))
Mà từ ảnh trên bạn cần 2 instruction + 1 syscall để có thể thực hiện lệnh SYS.read
.
Như bạn có thể thấy thì 3 câu lệnh đã tồn tại trong địa chỉ 0x1337000
Và nếu chạy tiếp thì nó sẽ bị lỗi nên bây giờ tôi sẽ gửi lại 1 lần nữa với các syscall.open
và syscall.read
flag từ trên server.
Tôi gửi shellcode 2 lần là do sau khi gọi SYS.read
nó sẽ đọc bắt đầu từ địa chỉ 0x1337006
và tôi kiểm tra shellcode vừa nhập vào tôi thấy bị lệch 1 byte như này, nên có thể dẫn tới thay đổi chương trình. ( Bạn có thể so sánh với ảnh bên trên (đúng) và ảnh bên dưới (sai)
Bạn có thể thấy flag của mình nằm ngay trên stack(rsp)
. Vậy câu hỏi đặt ra bây giờ là làm sao có thể nhìn thấy nó khi mà chương trình từ chối sys.write
như ảnh dưới đây:
Vậy nên tôi sẽ dùng 1 kỹ thuật khác học được từ write-up. Và kĩ thuật này sẽ leak ra từng bit của flag và kết hợp nó lại.
Bước đầu tiên thì bạn nên move 1 byte ra đâu đó rồi xử lí từng bit của nó:
Để khắc phục vấn đề trên tôi sẽ gọi chương trình 8 lần.
Và Kĩ thuật mà tôi nói trong bài này khi mà tôi đọc được wu đó là xử dụng luồng chương trình và thời gian xử lí chương trình để xử lí chương trình. Thông qua 1 loạt các câu lệnh như sau:
Giải thích 1 chút: Nếu chương trình nhận được bit 0 nó sẽ lập tức thoát và thời gian giữa lúc gửi đến lúc nhận là như nhau ---> Ta có thể có 1 điều kiện:
( nhận - gửi ) < 1
--->bit 0
Và ngược lại, khi nhận được bit 1 chương trình sẽ quay trở lại chương trình và khiến cho thời gian giữa lúc gửi đến lúc nhận là lâu hơn ---> Ta có điều kiện:
(nhận - gửi) >= 1
--->bit 1.
Vậy nên ta có các đoạn code bổ trợ cho logic trên như sau:
Khi đó màn hình sẽ in ra các kí tự 0
và 1
. Tôi sẽ ghép chúng lại, đảo ngược và chuyển nó thành dạng chữ để xem liệu output có đúng như mong muốn chưa.
Oke vậy là đã xong byte đầu tiên. Làm tiếp tục với các bytes còn lại. Tôi đoán flag < 100 bytes nên sẽ lặp lại ~ 100 lần.
Nó mất 1 thời gian dài để leak từng byte một nên hãy đi làm 1 thứ khác xong quay lại :)) Tôi chỉ biết là nó vẫn đang hoạt động khá tốt.
Và nó giống với flag giả mà đề bài đã cho. Vậy tôi sẽ test lên server luôn.
Sau khi test trên server, tôi nhận ra có chút sai lệch ở đây:
Tôi sửa lại thời gian nhận và gửi lại vì tôi thấy server khá nhanh, nhanh hơn máy của tôi nên tôi đoán đã sai ở đấy.
À thực ra là do ta dẫn sai path nên nó in ra tên file là flag\x00\x00
:>>
sửa lại path flag ban đầu thành /flag
là được.
Full payload (server)
Theo như tôi được biết thì byte kia là chữ i or 1
tức là nice
ấy :>>. Đen xíu thôi.