ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit ebook viet code khaithac exploit
Trang 1Exploit writing tutorial part 1 : Stack
Based Overflows
Ngày 14/7/2009, một người với nickname “Crazy_Hacker” thông báo một lỗ hổng trong phần mềm: trong phần mềm: Easy RM to MP3 Conversion Utility ( phiên bản trên Windows XP SP2 EN) trên packetstormsecurity.org ( Tham khảo tại: http://packetstormsecurity.org/0907-exploits/) Tài liệu này mô
tả về phương pháp khai thác, vì lý do nào, exploit có thể không thành công Bạn có thể sao chép PoC exploit code, run it, và thấy rằng nó không hoạt động ( hoặc may mắn nó sẽ hoạt động ) hoặc bạn sẽ cố gắng tìm hiểu quá trình xây dựng một exploit, từ đó bạn có thể sửa chữa broken exploit ( exploit không phù hợp với phiên bản Windows, ví dụ như SP3 chẳng hạn) , hoặc thử xây dựng một exploit của riêng bạn từ đầu
Câu hỏi được đặt ra là: Làm thế nào để xây dựng một exploit? Quá trình
từ phát hiện khả năng có thể exploit đến xây dựng một exploit thự c tế ra sao? Làm thế nào có thể sử dụng những thông tin thu thập được để xây dựng exploit?
Kể từ khi tôi bắt đầu viết tutorial, việc viết một basic tutorial về buffer overflow theo kiểu “to do” luôn có trong danh sách, tuy nhiên tôi chưa thực sự
để dành thời gian để hoàn thành việc đó
Khi tôi đọc được thông báo về vulnerability ( lỗ hổng) này, xem xét exploit, tôi thấy rằng nó có thể là một ví dụ hoàn hảo giới thiệu về khai thác cơ bản Nó khá là đơn giản, cho phép tôi chứng minh một số kỹ thuật về tràn bộ nhớ đệm trên stack - stack based buffer overflows
Tôi sử dụng lỗ hổng trong “Easy RM to MP3 conversion utility” làm ví
dụ và trình bày các bước để xây dựng một exploit Có nghĩa, chúng ta sẽ xây dựng nó từ đầu
Trước khi bắt đầu, tôi xin được lưu ý Tài liệu này dành cho mục đích giáo dục Tôi không muốn một ai sử dụng nó để tấn công vào một máy tính Vì vậy, tôi không thể chịu trách nhiệm cho các hành vi của người khác, những sử dụng nó cho các mục đích bất hợp pháp
Những thông tin bạn nhận được từ báo cáo lỗ hổng mô tả cơ bản về lỗ
Trang 2universal buffer overflow exploit that creates a malicious m3u file” Nói cách khác, bạn có thể tạo một file chữa mã độc ( malicious): *.m3u, đưa cho phần mềm xử lý và trigger – kích hoạt exploit Bạn có thể mô phỏng để làm crash – hoặc làm phần mềm có hành vi lạ
“Trước khi bắt đầu phần đầu tiên của loạt bài tutorial về exploit writing, cho phép tôi giới thiệu diễn đàn mà ở đó chúng ta có thể thảo luận về exploit writing – question – tip – trick Bạn có thể truy cập vào: https:// www.corelan.be/index.php/forum/writing-exploits/ ”
1. Verify the bug – Xác nhận bug
Đầu tiên, cần xác nhận rằng ứng dụng không thực sự crash – sụp đổ khi
mở định dạng m3u – hoặc ứng dụng bị treo khi mở dữ liệu thủ công đặc biệt - specifically crafted data
Hãy lấy một phiên bản Easy RM to MP3 có lỗi và cài đặt nó trên Windows
XP Báo cáo lỗi đưa ra exploit trên Windows XP SP2, tuy nhiên chúng ta sẽ làm cho nó hoạt động trên Windows SP3
Phiên bản có lỗi có thể download tại: https://www.corelan.be/?dl_id=37
Note: Bạn có thể tìm các phiên bản cũ hơn tại oldversion.com và
oldversion.com Exploit có thể tìm kiếm trên exploit-db.com.
Chúng ta sẽ sử dụng một đoạn code perl để tạo ra file crash.m3u giúp chúng ta thu thập được nhiều thông tin về lỗ hổng này
print "m3u File Created successfully\n" ;
Chạy đoạn script này sẽ tạo file m3u, được lấp đẩy bởi 10000 ký tự A (\x41 mà mã hexa của A) và mở nó bằng phần mề Easy RM to MP3 Ứng dụng
Trang 3đưa ra một thông báo lỗi như có vẻ được xử lý chính xác và ứng dụng không bị crash:
Thử thay đổi script với 20000 A và thử lại, vẫn như vậy ( ngoại lệ được
xử lý chính xác và chúng ta vẫn chưa thể ghi đè được những thông tin có ích – chi tiết ở phía sau) Bây giờ thử thay đổi với 30000 A và mở bằng phần mềm:
Boom – application dies
Vậy là ứng dụng bị crash nếu file có 20000 đến 30000 A Nhưng ta có thể làm gì với nó
Verify the bug – và xem có những gì thú vị ở đó
Rõ ràng, không phải tất cả ứng dụng bị crash đều khai thác được Trong nhiều trường hợp, một ứng dụng bị crash sẽ không dẫn đến exploit Nhưng một số lại có thể Với “exploit”, chúng ta sẽ bắt ứng dụng làm một cái gì đó mà không có ý định làm, ví dụ như chạy một đoạn code của bạn chẳng hạn Đơn giản nhất để làm ứng dụng làm gì đó khác bằng cách điều khiển luồng của ứng
Trang 4hướng dẫn - Instruction Pointer hoặc Program Counter, là một thanh ghi của CPU chứa con trỏ chỉ đến lệnh tiếp theo sẽ được thực hiện.
Giả sử ứng dụng gọi một hàm với một tham số Trước khi đến hàm đó, nó sẽ lưu lại vị trí hiện tại ( thường được biết đến là địa chỉ quay về khi hàm kết thúc) Nếu bạn có thể thay đổi giá trị của con trỏ này trỏ nó đến một chỗ khác trong bộ nhớ mà chứa phần code của bạn, tiếp theo bạn có thể thay đổi dòng xử lý của ứng dụng và làm cho nó thực thi một cái gì đó khác ( thay vì trỏ về vị trí ban đầu) Đoạn code mà bạn muốn được thực thi sau khi điều khiển được con trỏ thường được gọi là “shellcode” Vì vậy, nếu chúng ta làm cho ứng dụng chạy shellcode của chúng ta, chúng ta có thể goi nó là một exploit Trong hầu hết trường hợp, con trỏ này được tham chiếu bởi thanh ghi EIP Thanh ghi có độ dài
4 bytes Cho nên nếu bạn có thể thay đổi 4 bytes này, bạn sẽ làm chủ được ứng dụng – và computer chạy ứng dụng đó
2 Trước khi tiến hành, có một số lý thuyết sau:
Một vài thuật ngữ bạn sẽ cần:
Mọi ứng dụng Windows sử dụng các phần của bộ nhớ Trong đó gồm 3 thành phần chính là:
● Code segment: mã lệnh hướng dẫn bộ xử lý thực thi (EIP trỏ đến
mã lệnh sẽ được thực thi tiếp theo)
● Data segment: biến – varible, dynamic buffer
● Stack segment: được sử dụng để truyền data ( dữ liệu) – tham số ( agrument) vào trong hàm, và được sử dụng như là một nơi lưu trữ biến Stack bắt đầu ( đáy stack) tại vị trí kết thúc ( very end) của trang bộ nhớ ảo ( virtual memory) và giảm dần Lệnh PUSH thêm vào đỉnh stack, POP thì lấy nó ra ( 4bytes) và chuyển vào thanh ghi
Nếu muốn truy cập stack trực tiếp, có thể sử dụng thanh ghi ESP (Stack Pointer) Thanh ghi này luôn trỏ vào đỉnh stack - địa chỉ thấp nhất của stack
Sau khi PUSH, ESP sẽ trỏ đến địa chỉ thấp hơn ( địa chỉ sẽ được giảm bằng size của dữ liệu được push vào stack – thường là 4 bytes với địa chỉ / thanh ghi) Việc giảm địa chỉ thường được thực hiện trước khi đặt dữ liệu vào stack ( tùy thuộc vào quá trình thực hiện – nếu ESP chỉ vào vị trí tiếp theo trong stack, việc giảm sẽ tiến hành sau khi đặt dữ liệu vào stack)
Trang 5Sau khi POP, ESP trỏ đến địa chỉ cao hơn ( địa chỉ được tăng, thường là 4bytes) Việc tăng địa chỉ xảy ra khi sau khi gỡ bỏ thành phần ra khỏi stack.
Khi một hàm/ chương trình con bắt đầu, một frame stack được tạo ra Frame này sẽ lưu các thông số của thủ tục trước đó và được sử dụng để chuyển tham số cho chương trình con Vị trí hiện tại của con trỏ có thể truy cập qua ESP - stack pointer Cơ sở bắt đầu của hàm hiện tại được chứa trong thanh ghi
cơ sở - base pointer (EBP) hoặc frame pointer
Các thanh ghi phổ biến (Intel, x86) là:
● EAX – accumulator: được sử dụng cho việc tính toán, lưu trữ dữ liệu ( trong function call chẳng hạn) Sử dụng trong các toán tử cơ bản như add, subtract, compare
● EBX : base: ( không có bất kỳ điều gì cần làm với thanh ghi cơ sở) không có mục đích chính xác và được sử dụng để lưu dữ liệu
● ECX : counter: được sử dụng để lặp – ECX giảm dần
● EDX : data : thanh ghi mở rộng của EAX Cho phép các tính toán phức tạp hơn ( multiply – divde) bằng cách cho phép mở rộng lưu trữ dữ liệu tạo điều kiện cho tính toán ( như lưu thương số vào EAX, phần dư vào EDX chẳng hạn)
● ESP : stack pointer
● EBP : base pointer
● ESI : source index : lưu giữ vị trí của input data
● EDI : destination index : chỉ đến vị trí kết quả của toán tử được lưu trữ
● EIP : instruction pointer
3 Process Memory
Khi ứng dụng bắt đầu trong môi trường Win32, tiến trình được tạo và bộ nhớ ảo (virtual memory) được gán Với tiến trình 32 bit, địa chỉ bắt đầu từ 0×00000000 đến 0xFFFFFFFF Trong đó từ 0×00000000 đến 0x7FFFFFFF
được gán cho “user-land”, còn từ 0×80000000 đến 0xFFFFFFFF được gán cho
“kernel-land” Windows sử dụng flat memory model – điều đó có nghĩa CPU
có thể trực tiếp / tuần tự / tuyến tính địa chỉ tất cả vị trí địa chỉ có sẵn mà không cần phải sử dụng phân đoạn – phân trang
Bộ nhớ Kernel land chỉ được truy cập bởi OS
Trang 6Khi tiến trình được tạo , PEB (Process Execution Block) và TEB (Thread Environment Block) cũng được tạo.
PEB bao gồm tất cả user land parameters ( tham số của user land) gắn với tiến trình hiện tại:
1. Vị trí của main excute
2. Trỏ đến loader data ( sử dụng để hiển thị tất cả dll / module được load trong tiến trình)
3. Trỏ đến thông tin về heap
TEB mô tả trạng thái của tiến trình, bao gồm:
1. Vị trí của PEB trong bộ nhớ
2. Vị trí của stack trong tiến trình mà nó sở hữu
3. Trỏ đến entry đầu tiên của SEH chain
Mỗi luồng (thread) bên trong tiến trình (process) có một TEB
Sơ đồ bộ nhớ trong tiến trình của Win32:
Trang 7Phân đoạn text ( text segment) trong program image là read-only, và chỉ bao gồm application code Điều này hạn chế sửa đổi application code Data segment được sử dụng để lưu trữ biến toàn cục (global) và biến tĩnh (static) Data segment được sử dụng để khởi tạo global variables, strings, constants.
Data segment có khả năng ghi và có size cố định Heap segment được sử
Trang 8hoặc nhỏ hơn thiết kế.Tất cả bộ nhớ trong heap được quản lý bởi thuật toán cấp phát và thuật toán thu hồi Một vùng nhớ được dành riêng bởi thuật toán Heap
sẽ phát triển địa chỉ lên cao hơn
Trong dll, các mã, đầu vào (danh sách các hàm được sử dụng bởi dll hoặc dll khác và ứng dụng), đầu ra là một phần của text segment
4 Stack
Stack là một phần của tiến trình bộ nhớ, một cấu trúc dữ liệu hoạt động theo
mô hình LIFO (Last in first out) Stack được cấp phát bởi OS cho mỗi thread – khi thread được tạo Khi thread kết thúc, stack sẽ được clear.Size của stack được định nghĩa khi được tạo và không thể thay đổi Kết hợp với LIFO không đòi hỏi
cơ chế quản lý phức tạp nên stack khá nhanh – tuy nhiên bị giới hạn trong kích cỡ
LIFO có nghĩa là dữ liệu được đặt vào gần nhất sẽ là dữ liệu đầu tiên được lấy ra
Khi stack được tạo, con trỏ stack trỏ về đỉnh của stack ( bằng địa chỉ cao nhất của stack) Ngay khi dữ liệu được push vào stack, con trỏ stack giảm ( tới địa chỉ thấp hơn) Vì vậy, stack phát triển xuống vùng địa chỉ thấp hơn
Stack lưu local variables, function call và những thông tin khác mà không cần lưu trữ trong thời gian lớn Mỗi lần gọi hàm ( function call), các tham số của hàm được push vào stack, và các giá trị được lưu vào các thanh ghi (EIP, EBP) Khi hàm kết thúc , giá trị đã lưu của EIP được lấy ra từ stack và đặt trở lại EIP, từ đó ứng dụng có thể trở lại bình thường
Hãy sử dụng đoạn code sau để chứng minh điều đó:
Trang 9ứng dụng này "stacktest.exe AAAA" Không có gì cả”
Ứng dụng lấy 1 agrument – tham số ( argv[1] và truyền nó vào hàm do_something) Trong hàm này, agrument sẽ được copy tới biến cục bộ có độ dàu tối đa 128bytes Vậy nếu agrument dài hơn 127bytes ( 1 Null byte để ngắt xâu) bộ đệm có thể bị tràn
Khi hàm do_something() được gọi từ trong hàm main(), có những điều sau xảy ra:
Một stack frame được tạo ra, ở đỉnh của stack “cha” – parent stack Con trỏ stack - stack pointer (ESP) trỏ vào địa chỉ cao nhất của stack mới được tạo Đây là đỉnh của stack
Trước khi do_something() được gọi, con trỏ trỏ đến agrument vừa được push vào stack Trong trường hợp này là trỏ tới argv[1]
Trang 10Stack sau khi thực hiện lệnh MOV
Tiếp theo, hàm do_something được gọi Hàm CALL đầu tiên đặt con trỏ lệnh hiện thời vào stack ( đây được biết là nơi mà trở lại khi hàm hết thúc) và nhảy tới function code ( đoạn code của hàm)
Stack sau khi thực hiện hàm CALL:
Trang 11Sau khi push, ESP sẽ giảm 4bytes và trở về địa chỉ thấp hơn:
ESP trỏ đến 0022FF5C, ở địa chỉ này, chúng ta thấy địa chỉ đã lưu của EIP (Return to…) , tiếp theo là trỏ đến tham số ( AAAA trong ví dụ này) Con trỏ đã được lưu trên stack trước khi hàm CALL được thực thi
Tiếp theo, hàm prolog thực thi Về cơ bản, thanh ghi cơ sở - frame pointer (EBP) được đặt vào stack Vì vậy nó có thể được phục hồi khi hàm trở
về Lệnh để lưu frame pointer là “push ebp” ESP lại giảm 4bytes lần nữa
Trang 12Sau khi push ebp, con trỏ stack hiện tại (ESP) đặt vào EBP Tại điểm này,
cả ESP và EBP đều trỏ vào đỉnh của stack Từ thời điểm đó, stack được tham chiếu bởi ESP ( luôn ở đỉnh của stack bấy kỳ lúc nào) và EBP, con trỏ cơ sở của stack hiện tại Bằng cách này, ứng dụng có thể tham chiếu đến các biến bằng các sử dụng offset với EBP
“Hầu hết các hàm đều bắt đầu với: PUSH EBP Theo sau là: MOV EBP,ESP”
Vì vậy, nếu bạn push 4bytes nữa vào stack ESP sẽ giảm một lần nữa còn EBP vẫn ở lại đó Bạn có thể tham chiếu 4bytes này bằng cách sử dụng EBP – 0x8
Tiếp theo, chúng ta sẽ xem làm thế nào stack phân bổ khoảng trống cho biến MyVar (128bytes) Đễ giữ các dữ liệu, một số không gian trên stack được phân bố để lưu giữ biến, ESP sẽ giảm một số bytes Con số này có thể là hơn 128bytes, tùy thuộc vào trình biên dịch Trong trường hợp của Devcpp, sẽ là 0×98 bytes, cho nên bạn sẽ nhìn thấy lệnh SUB ESP,0×98 Bằng cách đó, sẽ có không gian cho biến:
Trang 13Disassembly của hàm giống như sau:
00401290 /$ 55 PUSH EBP
00401291 | 89E5 MOV EBP,ESP
00401293 | 81EC 98000000 SUB ESP,98
00401299 | 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; |
0040129C | 894424 04 MOV DWORD PTR SS:[ESP+4],EAX ; |
004012A0 | 8D85 78FFFFFF LEA EAX,DWORD PTR SS:[EBP-88] ; |
004012A6 | 890424 MOV DWORD PTR SS:[ESP],EAX ; | 004012A9 | E8 72050000 CALL ; \strcpy
004012AE | C9 LEAVE
004012AF \ C3 RETN
Đừng lo lắng vì quá nhiều code Bạn có thể thấy rõ chức năng prolog: PUSH EBP và MOV EBP,ESP Tiếp theo bạn sẽ thấy cấp phát khoảng trống cho biến Myvar: SUB ESP,98 Và bạn sẽ thấy một số hàm MOV và LEA ( cơ bản là thiết lập các tham số cho lời gọi hàm strcpy) Có thể giải thích là: đặt con trỏ về argv[1] ( chính là EBP+8 – sao chép nó vào EAX), sau đó sao chép EAX vào biến Myvar ( có vị trí là ESP+4)
Cụ thể như sau:
Trang 14PUSH EBP: Tiến hành lưu EBP rồi MOV EBP,ESP: ESP và EBP cùng trỏ tới đỉnh stack, là EBP vừa được push vào.
SUB ESP,98: Tiến hành cấp phát một khoảng nhớ là 152bytes ( 98 hexa
to decima)
MOV EAX,DWORD PTR SS:[EBP+8] : EBP cộng 8 chính là ptr to argv[1] Bước này sao chép địa chỉ trỏ tới argv[1] vào EAX Lưu ý rằng địa chỉ
có độ dài bằng 1 thanh ghi 32bit tức 4bytes
MOV DWORD PTR SS:[ESP+4],EAX: Sao chép EAX ( tức địa chỉ argv[1] tới ESP cộng 4 Nhớ rằng sau khi SUB ESP,98, ESP được giảm đi, ở đỉnh stack ( như hình trên) chứ không còn cùng trỏ vào EBP nữa ESP cộng 4 là
từ đỉnh giảm xuống 4 ( stack phát triển từ cao xuống thấp) Lúc này, 4bytes trên đỉnh stack chứa địa chỉ argv[1]
LEA EAX,DWORD PTR SS:[EBP-88] : Lệnh này sẽ lưu địa chỉ ô nhớ EBP trừ 88 vào EAX:
Trang 15MOV DWORD PTR SS:[ESP],EAX: Địa chỉ này sau đó được ghi vào ESP ESP lúc này trỏ vào EBP trừ 88, tức bắt đầu của nơi strcpy() lưu giá trị của argv[1].
Kết thúc bước này hoàn tất quá trình chuẩn bị gọi hàm strcpy()
CALL ; \strcpy
Sau khi thực hiện xong, tiến hành LEAVE để lấy lại EBP đã lưu, RET lấy lại EIP đã lưu, chuyển về hàm main
Nếu như không có hàm strcpy() trong hàm này, hàm sẽ kết thúc và
“unwind” stack Cơ bản là, nó sẽ di chuyển ESP lại ESP đã lưu, sau đó thực hiện lênh RET RET trong trường hợp này sẽ lấy con trỏ ESP từ stack và nhảy đến đó Sau đó, nó sẽ quay lại trở lại chương trình chính, nơi mà hàm do_something() đã được gọi Hướng dẫn epilog được thực hiện bởi lệnh LEAVE, mà ở đó sẽ hồi phục framepointer và EIP Trong ví dụ của chúng ta, có hàm strcpy()
Trang 16Hàm này sẽ đọc dữ liệu, từ địa chỉ trỏ bởi [Buffer], và lưu trữ nó trong
<space for Myvar> ( trong sơ đồ trên), đọc tất cả dữ liệu cho tới khi null byte (string terminator) Trong khi sao chép dữ liệu, ESP ở nơi nó trỏ tới Strcpy() không sử dụng PUSH để đưa dữ liệu vào stack, nó sẽ đọc 1 bytes và đưa vào stack, sử dụng index ( như ESP, ESP+1, ESP+2) Sau khi copy, ESP trỏ về đầu chuỗi
Có nghĩa là, nếu [buffer] lớn hơn 0x98bytes, strcpy() sẽ ghi đè EBP được lưu và cả EIP Sau đó, nó chỉ đọc và ghi cho đến khi gặp được null byte trong chuỗi nguồn
Trang 17ESP vẫn trỏ vào điểm bắt đầu chuỗi Hàm strcpy() kết thúc nếu không
có gì sai, sau khi strcpy(), hàm kết thúc ( do_something()) Và đây là mọi thứ trở nên thú vị Chức năng epilog được kích hoạt Cơ bản, nó sẽ di chuyển ESP
về nơi EIP đã được lưu, rồi tiến hành RET Nó sẽ lấy con trỏ (AAAA hoặc 0×41414141 tùy trường hợp) và nhảy đến địa chỉ đó
Vì vậy, bạn kiểm soát được EIP Bằng cách điều khiển EIP, bạn thay đổi địa chỉ trở về ( return address) để chương trình tiếp tục bình thường
Đương nhiên, bạn có thể thay đổi địa chỉ trở về bằng cách tận dụng buffer overflow
Vì vậy, giả sử bạn có thể ghi đè buffer trong Myvar, EBP, EIP và bạn có một đoạn code của riêng bạn, ở vùng trước và sau khi EIP được lưu Sau khi ghi
đè, EIP sẽ trỏ về đoạn code của bạn Và bạn đã làm cho EIP trỏ tới đoạn code của bạn, và bạn đã nắm quyền điều khiển
Trang 18Note: Khi một bộ đệm tràn trên stack, thuật ngữ: "stack based overflow" hoặc "stack buffer overflow" được sử dụng Khi bạn cố thay đổi stack frame, thuật ngữ "stack overflow" được sử dụng Đừng nhẫm lẫn hai thuật ngữ, vì
Chúng ta sẽ sử dụng Windbg Cài đặt Windbg (Full install) và đăng ký như
là một “post-mortem” debugger bằng việc sử dụng windbg –I
Bạn có thể disable “xxxx has encountered a problem and needs to close” popup bằng chỉnh sửa register key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto : Đặt
là 0
Để tránh Windows thông báo về “Symbol files not found”, tạo một thư mục trên ổ đĩa của bạn ( ví dụ là c:\windbgsymbols) Trong Windbg, tới “File” – “Symbol File Path” và thêm dòng sau:
SRV*C:\windbgsymbols*http://msdl.microsoft.com/download/symbols