Shellcode thần chưởng: nhập môn

The most powerful of buffer overflows are never going to be in worms. Custom exploits are what gets you in, and what keeps you in. If you see a truly top notch hacker going after someone, he'll have a complete mirror of the target hosts' environments, all for one purpose: To create a buffer overflow he only plans on using once. ---Dave Aitel
Lỗi tràn bộ đệm. Blog bảo mật thông tin mà không có một bài nào về lỗi tràn bộ đệm giống như một anh tiều phu mà chẳng sắm nổi một chiếc rìu. Nghĩ vậy nên từ khi mới bắt đầu viết blog, tôi đã dự tính sẽ làm một loạt bài về lỗi tràn bộ đệm. Vấn đề còn lại là viết như thế nào để những người mới bắt đầu có thể hiểu được một đề tài khá phức tạp như lỗi tràn bộ đệm? Có rất nhiều tài liệu tiếng Anh và tiếng Việt về đề tài này nhưng chúng luôn yêu cầu người đọc phải có một trình độ nhất định về C, Assembly hay debug với gdb, những lĩnh vực mà những người mới bắt đầu khó lòng nắm vững. Thật ra muốn hiểu rõ lỗi tràn bộ đệm, bạn không cần phải cực kì thông thạo C hay Assembly mà chỉ cần học một phần rất nhỏ trong hai ngôn ngữ khó nuốt này mà thôi. Thực tế bạn chỉ cần học C và Assembly đủ để viết shellcode. Nhưng...shellcode là cái gì mới được chứ?

Có khá nhiều định nghĩa về shellcode:
  • Shellcode, là đoạn chương trình giúp bạn có được...một cái shell. Thế shell là cái gì? Theo Wikipedia, shell là một phần mềm cung cấp một giao diện dòng lệnh (command line interface) giúp bạn có thể tương tác với hệ điều hành. Shell sẽ nhận lệnh của bạn, gửi xuống phần lõi (kernel) của hệ điều hành để thực thi, rồi nhận kết quả trả về và gửi lại cho bạn. Có thể hiểu nôm na rằng, có shell trên một máy tính nào đó có nghĩa là bạn có quyền thực thi lệnh trên máy tính đó.
  • Shellcode là payload của các đoạn mã khai thác lỗ hổng bảo mật (exploit). Khi viết exploit, bạn phải giải quyết hai vấn đề chính: a) injection vector: xác định cách chèn shellcode vào trong hệ thống muốn tấn công; b) payload: xác định shellcode mà bạn muốn chạy trên hệ thống đó. Shellcode có thể làm được mọi thứ, từ việc điều chỉnh giở hệ thống hay download và thực thi một file từ Internet cho đến gửi một email ra ngoài. Mục tiêu chính của các exploit là làm sao có thể chạy được shellcode nằm trong phần payload của nó.
  • Shellcode còn được gọi là bytecode, tạm dịch là mã máy. Chúng ta đều biết mã máy là thứ ngôn ngữ duy nhất mà bộ vi xử lí có thể hiểu được. Tất cả các chương trình viết bằng bất kì ngôn ngữ nào đều phải được biên dịch sang mã máy trước khi máy tính có thể chạy được chương trình đó. Khác với các chương trình này, shellcode được thể hiện như một nhóm các mã máy, do đó máy tính có thể hiểu và thực thi trực tiếp shellcode mà không cần phải trải qua bất kì công đoạn biên dịch nào cả.
Như vậy muốn viết được shellcode, tôi phải học cách viết mã máy? Câu trả lời là không. Không ai trực tiếp viết ra mã máy khi muốn tạo shellcode. Thay vào đó, bạn có thể: a) sử dụng các shellcode có sẵn trên Internet; b) viết bằng C, dịch sang Assembly rồi tiếp tục dịch sang mã máy; c) viết bằng Assembly rồi dịch luôn ra mã máy.

Đối với cách a), tin vui là có rất nhiều thư viện shellcode trên Internet, thậm chí có một số chương trình cho phép bạn tạo shellcode và viết exploit bằng một ngôn ngữ cao cấp như Python hay Ruby. Tin buồn là nếu bạn nhào vào sử dụng liền các shellcode có sẵn mà không tìm hiểu chúng, bạn có thể bị gài bom. Nên nhớ rằng, shellcode có thể làm được tất cả mọi chuyện, hành động lấy shellcode từ Internet rồi chạy mà không thật sự hiểu shellcode đó làm gì giống như việc chạy các file .exe nhận được từ một người lạ mặt! Đến một lúc nào đó, bạn sẽ sử dụng các chương trình tạo shellcode tự động kể trên để đơn giản hóa việc viết exploit nhưng trước mắt, bạn cần phải tự viết được shellcode đã.

Trong tài liệu này chúng ta sẽ sử dụng luân phiên hai cách b)c) để viết shellcode. Bạn nên sử dụng cách b) khi mới bắt đầu hoặc khi cần phải triển khai một loại shellcode phức tạp. Nhìn vào hai cách này, bạn có thể thấy rằng viết shellcode đòi hỏi phải có sự thông hiểu về ngôn ngữ Assembly của kiến trúc máy tính mà bạn dự định sẽ chạy shellcode trên đó. Đây là điều hiểu nhiên bởi lẽ các loại máy khác nhau (x86, x86-64, sparc, ppc, amd hay mips...) chỉ hiểu được một nhóm mã máy khác nhau. Ngoài ra, bạn còn phải thông hiểu cách giao tiếp với hệ điều hành (linux, windows, solaris hay freebsd...) để có thể thực thi được lệnh trong shellcode. Thông thường, bạn cần phải có một phiên bản shellcode khác nhau cho mỗi loại hệ điều hành chạy trên mỗi loại kiến trúc phần cứng khác nhau. Nói cách khác, shellcode phụ thuộc vào hệ điều hành và kiến trúc phần cứng.

Bạn vẫn còn đang đọc bài này đó chứ :p? Nếu có bất kì chỗ nào còn mù mờ, bạn nên đọc lại từ đầu và đừng ngại hỏi nếu bạn muốn. Những gì tôi trình bày về shellcode từ đầu đến giờ là những ý quan trọng nhất về shellcode, nếu bạn chỉ muốn biết shellcode để nói chuyện cho vui thì bạn có thể dừng lại ở đây. Còn nếu bạn muốn tự viết cho mình những đoạn shellcode tối ưu thì hãy đi tiếp cùng tôi nhé.

Trong bài tiếp theo, tôi sẽ trình bày những kiến thức căn bản về Assembly đủ để bạn có thể hiểu và viết được shellcode đầu tiên của mình. Sau đó tôi sẽ đề cập đến hai trở ngại quan trọng nhất mà bạn cần phải vượt qua khi viết shellcode: vấn đề địa chỉ ô nhớ (addressing problem) và vấn đề của các byte có giá trị null (null byte problem). Chúng ta cũng sẽ thảo luận sơ lược một số ví dụ về shellcode trên kiến trúc máy Intel 32-bit (còn gọi là x86).

Tôi cần chuẩn bị những gì để học viết shellcode?

Như đã nói từ đầu, bài viết này không yêu cầu bạn phải có kiến thức trước về Assembly hay C. Yêu cầu duy nhất là bạn phải có sự khao khát học hỏi cái mới. Ngoài ra, tôi sử dụng Ubuntu Linux trên máy x86 để viết các shellcode mẫu trong bài này, do đó bạn cũng cần phải chuẩn bị một máy tính có cấu hình tương tự (Linux-x86) như tôi. Bạn chọn distro nào cũng được, nhưng tốt nhất là nên chọn Ubuntu như tôi luôn. Các phần mềm khác mà bạn cần phải chuẩn bị:
  • nasm là bộ phần mềm bao gồm một assembler tên nasm và một disassembler mang tên ndisasm. nasm dùng để biên dịch các mã lệnh Assembly sang mã máy, còn ndisasm làm công việc ngược lại.
  • gdb là phần mềm giúp bạn debug (bắt lỗi) hoặc disassemble các chương trình viết bằng C. gdb thường có sẵn trong distro của bạn.
  • objdump là công cụ giúp bạn xem các thông tin quan trọng trong các file object hay file executable. Tương tự như gdb, objdump thường có sẵn trong distro của bạn.
  • strace là công cụ xem các syscall (tôi sẽ giải thích khái niệm này sau) mà một chương trình gọi khi nó được thực thi.
-Thái.

Comments

Anonymous said…
Cám ơn anh Thái. Anh giải thích rất dễ hiểu. Em đang đón chờ phần tiếp theo của anh đây.
Anonymous said…
Thanks anh Thái. Thực sự là em đang tìm hiểu về exploit và shellcode ==> em đang muốn tìm đọc tài liệu Tiếng Việt trước đã, trước khi nhảy sang tiếng Anh. Bài viết hay lắm
Anonymous said…
hi anh Thái !!
em đang học viết shell, nhưng chỉ là shell srcipt thôi chạy với bash shell . Còn bài viết của anh Thái là viết code để tạo ra 1 shell tương tự như terminal, rồi dùng shell đó để tương tác với kernel tương tự như bash shell, csh .. tương tác với kernel ???
Em chưa rõ lắm.
Anonymous said…
shellcode va` shell script la` 2 van de hoan toan khac nhau. Ban nen phan biet diem nay. shell script la automated action, dung de tuong tac voi shell. shell code, la binary code, duoc inject vao mot chuong trinh bi loi overflow va` muc dich chinh la de co the kiem duoc cho attacker mot cai shell de dung.

shell code co the la code run /bin/bash voi quyen root, la code de run netcat listen tren port nao do van van. Cach viet shell code don gian nhat la viet mot chuong trinh, sau do disasm, roi dich lai vao thanh shellcode. Hoac, co the tu viet truc tiep bang asm, hoac tim kiem tren mang. Cai kho khan cua buffer overflow khong phai shellcode, ma la inject the nao de co the overwrite return address vao dung y cua minh

khoai
Anonymous said…
hi khoai !!
thanks đã giải thích cho tui hiểu. "Cai kho khan cua buffer overflow khong phai shellcode, ma la inject the nao de co the overwrite return address vao dung y cua minh" <-- cái nì chưa hiểu lắm nè, với lại bồ nên biết tiếng việt nha, để tránh nhìu khi tui hiểu lầm đó mà.
thanks khoai
p/s: phải khoai bên HVA hông ?? :D
Anonymous said…
"Chúng ta đều biết mã máy là thứ ngôn ngữ duy nhất mà MÁY TÍNH có thể hiểu được"??? -> Nên sửa lại là "mã máy là ngôn ngữ duy nhất VI XỬ LÝ có thể hiểu được"! còn trong đa số các máy dùng kiến trúc CISC, microcode mới là ngôn ngữ phần cứng (phần cứng chính là nói về CÁI MÁY TÍNH) sử dụng. Ở những kiến trúc như x86, nhiều chỉ thị của mã máy được thông dịch thành microcode.
Anonymous said…
Xin lỗi vì hôm trước khoai không type được tiếng Việt có dẫu rõ ràng. Ý nghĩa câu trên là: shellcode không khó để tạo, hoặc để tìm. google với từ shellcode sẽ ra cả tá.
Để có thể exploit được buffer overflow, vấn đề khó là: (1) Tìm ra rằng đoạn code đó bị overflow và (2) tạo được một input thích hợp. (1) có thể được thực hiện bằng cách đọc mã nguồn, hoặc tình vờ phát hiện vân vân. Bước (2) mới thật sự đau đầu. Lý do: Nhiều lỗi buffer overflow có input buffer rất nhỏ. Bạn phải chèn vào sao cho:
1. shellcode của bạn vừa đủ nằm trong input.
2. Ghi lại return address sao cho return address này trỏ về đúng đầu shellcode.
Một kỹ thuật thường được dùng nhất là dùng NOP. ví dụ, bạn muốn tạo input giống như sau:
NNNNNNNNSSSSSSSSSSSSRRRRRRRRRRR
trong đó S..S là đoạn shellcode. R là return address. N là NOP, khi processor gặp cái này, nó sẽ không làm gì cả. Lý do bạn có nhiều R là để tăng sát xuất R sẽ ghi đè lên return address. Lý do bạn có nhiều N là để tăng sát xuất R sẽ trỏ đến N, và từ đó, instruction sẽ "trượt" đến đầu S.

Việc xác định được khoảng địa chỉ nào để dùng cho R, và tạo được input như trên, mới là vấn đề nan giải cho hầu hết các lỗi buffer overflow.

khoai đúng là member Mr.Khoai trên HVA, nhưng ở blog không hề có chức năng nào để xác định việc này. khoai post bài này dưới account annonymous! Không nên ASSume gì cả :~P
Anonymous said…
hi !!
thanks bồ đã giải thích cặn kẽ cho tui hiểu. Đúng là không dễ dàng chút nào, trình độ tui còn yếu kém wá khó mà hiểu sâu hết toàn bộ vấn đề được. Tui sẽ xem kĩ những bài viết này của anh Thái và những gì bồ viết
Unknown said…
Thank a rất nhiều! Blog rất hay!
Mình sẽ ghé thăm thường xuyên!
Unknown said…
Blog rất là hay!
Mình sẽ ghé thường xuyên!
Cảm ơn a Thái!
Unknown said…
i want great thanhk for sharing.this tag is very good
Unknown said…
Bài viết của a rất hay và dễ hiêu, e rất thic.
Dịch vụ thiết kế website
Unknown said…
Anh viết rất hay ạ, giải thích cặn kẽ, đọc 1 lần mà đã hiểu hết rồi :)
Chúc anh có nhiều thành công ạ <3
Anonymous said…
Hihi, nay con đang phải làm bài binary exploit, con bỏ cả tiếng ra mà cái exploit return address nó ko rớt được vào nopsled. mong là đọc xong cái này lấy aura chú Thái mà giải được