RSS
Container Icon

Shellcode thần chưởng: luyện assembly

Sau bài viết đầu tiên về shellcode, một người bạn có hỏi tôi là nếu shellcode là bytecode - mã máy, vậy nó chỉ phụ thuộc vào bộ vi xử lí thôi chứ, tại sao nó còn phụ thuộc vào hệ điều hành nữa vậy?


Đây là một câu hỏi rất thường gặp ở những người mới bắt đầu, bản thân tôi cũng đã từng tự hỏi như thế khi mới tìm hiểu shellcode. Thật ra lý do shellcode bắt buộc phải phụ thuộc vào hệ điều hành khá hiển nhiên. Nếu bạn nhìn rộng ra một chút, bạn sẽ thấy rằng không phải chỉ shellcode mới là bytecode, mà tất cả phần mềm, dù được viết bằng ngôn ngữ gì, cuối cùng phải được dịch sang bytecode rồi mới có thể chạy được. Nếu shellcode không phụ thuộc vào hệ điều hành, vậy tất cả các phần mềm cũng sẽ không phụ thuộc vào hệ điều hành, phải không nào? Shellcode xét đến cùng cũng chỉ là một phần mềm, nó buộc phải lệ thuộc vào những gì hệ điều hành cung cấp để thực thi chức năng của nó.


Bạn muốn viết chương trình đọc một file rồi xuất ra màn hình? Dù bạn viết bằng C hay Assembly, chắc chắn mã nguồn của chương trình đó trên Windows và Linux sẽ khác nhau bởi lẽ các hàm mà Windows hay Linux cung cấp cho bạn rất khác nhau. Nói tóm lại, bạn buộc phải có một chương trình hay một shellcode chuyên biệt cho từng loại hệ điều hành mà bạn dự định chạy shellcode trên đó. Nếu bạn vẫn chưa nắm được vấn đề, hãy đọc tiếp, các ví dụ cụ thể trong bài viết này có thể giúp bạn hiểu được tại sao shellcode phải phụ thuộc vào hệ điều hành.


Không có ngôn ngữ nào đơn giản hơn Assembly


Như trong bài trước tôi đã nói, chúng ta sẽ viết shellcode theo hai cách:
viết bằng C, dịch sang Assembly rồi tiếp tục dịch sang mã máy
viết bằng Assembly rồi dịch luôn ra mã máy.
Có thể thấy rằng, dù chọn cách viết nào đi chăng nữa, bạn bắt buộc phải thông hiểu ngôn ngữ Assembly. Rồi coi như xong, tôi bỏ cuộc đây, Assembly phức tạp và khó học thấy mồ, sao tôi học nổi? Ấy ấy bạn đừng vội, hãy dành chút thời gian nghe tôi trình bày rồi bỏ đi cũng chưa muộn.


Chắc hẳn bạn cũng biết, các ngôn ngữ lập trình thường được chia làm hai lớp: lớp ngôn ngữ cấp cao và lớp ngôn ngữ cấp thấp. Lớp ngôn ngữ cấp cao lại được chia làm hai lớp, lớp cao vừa vừa như C/C++ chẳng hạn và lớp cao ngất ngưỡng như Java hay các loại ngôn ngữ scripting kiểu như PHP, Perl, Python, Ruby...Trong khi lớp ngôn ngữ cấp cao đông đúc như vậy thì lớp ngôn ngữ cấp thấp chỉ có một đại diện duy nhất là Assembly (tùy thuộc vào assembler mà cú pháp ngôn ngữ Assembly sẽ có những thay đổi nhất định nhưng xét tổng quát thì chúng ta chỉ có một ngôn ngữ Assembly duy nhất).


Thông thường ngôn ngữ cấp cao sẽ dễ học và dễ sử dụng hơn ngôn ngữ cấp thấp hơn, ví dụ như C/C++ khó học và khó xài hơn Java hay Python rất nhiều. Assembly còn ở cấp thấp hơn cả C/C++, vậy suy ra nó phải cực khó rồi. Tin vui là không đúng như vậy bạn ơi. Assembly là một ngôn ngữ cực kì đơn giản. Bản thân tôi đã sử dụng khá nhiều ngôn ngữ khác nhau và Assembly (cụ thể là NASM Assembly) là ngôn ngữ đơn giản nhất mà tôi từng biết. Theo tôi, sự đơn giản của Assembly thể hiện ở chỗ, ngôn ngữ này gần như không có bất kì khái niệm trừu tượng nào cả. Assembly không có pointer, không có class, không có function, không có int, không có string...Assembly chỉ có một tập lệnh (instruction set), bộ nhớ (stack + register) và tất cả dữ liệu đều được lưu trữ và xử lí theo từng byte. Người mới học Assembly dựa vào tập lệnh là đã có thể viết chương trình được ngay mà không phải tốn thời gian tìm hiểu các khái niệm trừu tượng như trong các ngôn ngữ cấp cao khác. Suy cho cùng, Assembly bắt buộc phải đơn giản, nó không thể phức tạp, bởi lẽ nó (gần như) là phiên bản human-readable của mã máy, thứ ngôn ngữ duy nhất mà vi xử lí có thể hiểu được và chúng ta đều biết, vi xử lí chỉ có thể hiểu được những thứ rất đơn giản như 0 và 1 mà thôi.


Rồi, nói nhiều quá rồi, phải trình diễn thôi!


Sơ lược về Assembly


Trước tiên, bạn download file này về (tạm thời để ở Yousendit, sẽ chuyển sang một host mới trong vài ngày nữa), in ra rồi đọc cho đến khi hiểu rõ nội dung của nó rồi hãy tiếp tục theo dõi bài này. Đây là tài liệu của lena151, một cao thủ về reverse code engineering, tóm gọn khá tốt và đầy đủ những khái niệm quan trọng nhất của Assembly. Mặc dù tài liệu này thiên về phục vụ cho reverse code engineering nhưng bạn hoàn toàn có thể áp dụng những kiến thức này để viết shellcode. Một lần nữa, tôi đề nghị bạn hãy đọc thật kĩ tài liệu này trước khi tiếp tục.


Hello, world!


Chúng ta hãy bắt đầu bằng cách cổ điển, viết một chương trình Hello, world! bằng Assembly. Bạn hãy lưu đoạn chương trình sau đây vào file hello.asm (nhớ bỏ đi các số đầu dòng nhen):
1 global _start
2 _start:
3 xor eax, eax


4 jmp short string
5 code:
6 pop ecx
7 mov edx, 14
8 mov ebx, 1
9 mov al, 4
10 int 0x80


11 xor eax, eax
12 mov al, 1
13 int 0x80


14 string:
15 call code
16 db 'Hello, world!', 0x0a
Trời, có một cái Hello, world! mà đã dài đến 16 dòng vậy mà dám nói Assembly đơn giản!? Bạn ơi, dài hơn không có nghĩa là khó và phức tạp hơn mà nên hiểu rằng nó rõ ràng và rành mạch hơn. Nếu nhìn kĩ vào chương trình trên, bạn sẽ thấy nó chỉ sử dụng một số lệnh đơn giản như int, xor, mov, pop, push, call hay jump...cùng với các register như eax, ebx, ecx, edx hay esp ngoài ra không có bất kì lệnh nào khó hiểu khác. Tôi cũng cam đoan với bạn rằng, hầu hết shellcode mà bạn sẽ viết đều chỉ sử dụng bấy nhiêu đó lệnh mà thôi. Chúng ta sẽ để dành công việc phân tích đoạn chương trình trên ở những bài sau, trước mắt hãy chạy thử chương trình này đi đã:
$ nasm -f elf hello.asm
$ ld -o hello hello.o
$ ./hello
Hello, world!
Ở lệnh đầu tiên, nasm sẽ đọc mã nguồn ở file hello.asm và tạo ra file object có định dạng là ELF mang tên hello.o. Ở lệnh thứ hai, chương trình linker mang tên ld trên Linux sẽ sử dụng file object hello.o để tạo ra file thực thi hello. Hãy xem kích thước của chương trình này là bao nhiêu:
$ cat hello | wc -c
749
Bạn hãy thử viết một chương trình Hello, world! tương tự bằng C rồi so sánh kích thước của hai chương trình xem sao? Chắc chắn rằng chương trình viết bằng C sẽ to gần 10 lần chương trình viết bằng Assembly. Rõ ràng đây là một lợi thế đáng kể của Assembly và chúng ta có thể lợi dụng điều đó để viết những đoạn shellcode có kích thước cực nhỏ.

  • Digg
  • Del.icio.us
  • StumbleUpon
  • Reddit
  • RSS

0 nhận xét:

Đăng nhận xét