Tải bản đầy đủ (.docx) (14 trang)

Windows shell code và ứng dụng khai thác lỗi tràn bộ đệm

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (705.96 KB, 14 trang )

Windows Shell Code và Ứng dụng khai thác lỗi
tràn bộ đệm
Windows Shell Code và Ứng dụng khai thác lỗi tràn bộ đệm – Phần 1
Hôm nay tôi xin chia sẻ tới các bạn loạt các bài viết về chủ đề “Windows
shellcode và ứng dụng khai thác lỗi tràn bộ đệm”.
Bài viết của tôi sẽ bao gồm các thành phần sau đây:
I. Khái niệm
II. Shellcode cơ bản
1. System Calls & Shellcode
2. API & Shellcode
3. Lấy địa chỉ hàm trong Shellcode
4. Mã hóa shellcode
III. Shellcode nâng cao
1. Ý tưởng
2. Tìm kiếm địa chỉ Kernel32.dll
3. Process Enviroment Block
4. Structured Exception Handling
5. TOPSTACK
6. Lấy địa chỉ hàm bên trong Kernel32.dll
7. Tối ưu độ dài của Shellcode
IV. Các shellcode phổ biến
1. MessageBox Shellcode Version 1
2. MessageBox Shellcode Version 2
Hi vọng các bạn sẽ thu được những kiến thức hữu ích qua các bài viết này.
Những kiến thức mà tôi đã tích lũy được trong suốt 8 năm nghiên cứu về an
toàn thông tin, khai thác lỗ hổng phần mềm.
1. Khái niệm
Khai thác lỗi tràn bộ đệm cho phép kẻ tấn công chiếm quyền điều khiển
chương trình bị lỗi hay chính xác là có thể điều khiển con trỏ lệnh của
chương trình đó. Như vậy, sau khi đã thực hiện việc khai thác và chiếm được
quyền điều khiển con trỏ lệnh, kẻ tấn công cần phải thực thi thực hiện đoạn


chương trình nào đó cho phép chiếm quyền điều khiển hệ thống. Đoạn
chương trình này chính là Shellcode.
Shellcode là một đoạn mã máy nhỏ cho phép thực hiện chỉ một nhiệm vụ nào
đó bên trong một chương trình bị khai thác. Nhiệm vụ đó có thể đơn giản là
đưa ra thông báo (MessageBox), có thể là tạo User, cài đặt trojan… Bài viết
này sẽ hướng dẫn cách viết một shellcode từ đơn giản đến nâng cao.
Bài viết yêu cầu người đọc phải biết khái niệm về các ngôn ngữ Assembly32
cho bộ vi xử lý 8086 của intel, lập trình API cho Windows 9x và Windows NT.
Một số công cụ yêu cầu :
• NASM32 & MakeShell
• OllyDbg
• Hiew
• Depends (một công cụ trong bộ Visual Studio 6.0)
• MSDN
2. Shellcode cơ bản
System Calls & Shellcode
System calls (SysCall) hay hàm gọi hệ thống là một kỹ thuật cho phép chương
trình user-mode gọi một thủ tục của Kernel cho phép thao tác vào ra trực tiếp
với hệ thống. SysCall có thể hiểu là một cổng trung gian giữa User-mode và
Kernel-mode. Để hiểu về SysCall, ta có thể nhìn hình dưới đây :
Hình 1 : Hoạt động của System Calls
SysCall làm nhiệm vụ trung gian nhận lời gọi API từ ứng dụng User-mode, tìm
kiếm chỉ số hàm Kernel trong System Call Table, thực thi hàm đó rồi trả lại
kết quả cho ứng dụng. Việc nhận lời gọi API từ ứng dụng thực ra là nó nhận
được ngắt từ ứng dụng, SysCall gọi trình phục vụ ngắt thực hiện chuyển đổi
dữ liệu của API (tham số, cờ, thanh ghi…) từ User Stack sang Kernel Stack.
Trong Windows, SysCall cho phép quản lý bộ nhớ, vào ra dữ liệu, quản lý thư
mục, quản lý tiến trình, … Tuy nhiên, đa phần API có giao tiếp SysCall đều là
các hàm Undocument.
Ngắt được sử dụng để yêu cầu SysCall :

• Windows 2000 : “int 0x2e”
• Windows XP : SYSENTER
Như vậy, Dựa vào cơ chế của System Call, ta có thể viết Shellcode bằng cách
gọi hàm bởi chỉ số của hàm bên trong bảng System Call Table. Đoạn asm dưới
đây thực hiện kết thúc tiến trình bằng cách gọi API ntTerminalProcess với số
SysCall của nó:
MOV EAX, 0x000000bb ; 0xbb là số SysCall hàm ntTerminalProcess
LEA EDX, [ESP+4] ; EDX lưu tham số của hàm
INT 0x2E ; Gọi Syscall thực hiện hàm
Với số lượng SysCall tương đối trong Windows, ta có thể thực hiện viết
Shellcode chỉ thông qua SysCall. Tuy nhiên, Việc viết shellcode như vậy có cả
ưu điểm và hạn chế:
Ưu điểm:
• Không cần sử dụng của hàm API, cụ thế là không cần xác định địa chỉ
của các hàm API.
• Có thể sử dụng để vượt qua cơ chế bảo vệ chống tấn công tràn bộ đệm
ở user-mode.
Nhược điểm:
• Giới hạn khả năng làm việc của Shellcode vì số lượng SysCall là có hạn
(trong Windows khoảng hơn 1000)
• Vì các hàm socket API không được Windows hỗ trợ giao tiếp SysCall
nên Shellcode sử dụng SysCall không có khả năng làm việc với mạng.
2. API & Shellcode
Windows API là thư viện các hàm cho phép phát triển các ứng dụng khác
nhau trên nền hệ điều hành của Microsoft. Shellcode cũng coi như là một ứng
dụng trên nền Windows, do đó nó cũng có thể được viết thông qua các hàm
API. Khác với viết Shellcode bằng SysCall, Shellcode viết bằng hàm API có khả
năng rất đa dạng, đặc biệt nó có thể làm việc với các hàm socket API.
Với ưu điểm trên, hầu hết các Shellcode được sử dụng cho các cuộc tấn công
ngày nay đều được viết bằng các hàm API và thực hiên các nhiệm vụ như

connect back,download, send mail, cài đặt trojan … Tuy nhiên, cũng giống
như SysCall, địa chỉ của các hàm API bị thay đổi cho mỗi phiên bản khác nhau
của Windows nên một Shellcode có địa chỉ API cố định sẽ chỉ có thể thực thi ở
đúng phiên bản Windows. Trước tiên, chúng ta sẽ viết một đoạn Shellcode
đơn giản, chỉ sử dụng địa chỉ cố định của hàm API.
Công cụ sử dụng :
• Depends (Công cụ trong bộ VS6.0).
• Text Editor (EditPlus, NotePad…)
• NASM&MakeShell
Trước tiên, ta biết các ứng dụng khi thực thi đều phải nạp thư viện
Kernel32.dll vào không gian bộ nhớ. Do đó, Shellcode đầu tiên này sẽ sử dụng
2 hàm API trong Kernel32.dll với nhiệm vụ gọi “notepad” bắt hàm
“Winexec()”, sau đó kết thúc chương trình bằng hàm “ExitProcess()”.
Sử dụng Depends để xác định địa chỉ hàm “Winexec()” cho Windows XP SP2
ta nhận được địa chỉ cơ sở của Kernel32.dll (0x7C800000) và địa chỉ tương
đối của hàm (0x6114D). Như vậy, địa chỉ tuyệt đối Shellcode sử dụng là
0x7C86114D. Tương tự như vậy ta có địa chỉ hàm “ExitProcess()” là
0x7C81CAA2.

Hình 2 : Sử dụng Depends xác định địa chỉ
hàm
Biên dịch đoạn asm sau ra file nhị phân rồi chuyển sang Shellcode :
nasmw.exe -s -fbin file.asm
makeshell.exe file
Kết quả là đoạn Shellcode có thể thực hiện trên Windows XP SP2:
\
xBB\x4D\x11\x86\x7C\xE9\x0E\x00\x00\x00\x58\x68\x05\x00\x00\x00\
x50\xFF\xD3\xE9\x0D\x00\x00\x00\xE8\xED\xFF\xFF\xFF\x6E\x6F\x74\
x65\x70\x61\x64\x00\x68\x01\x00\x00\x00\xBB\xA2\xCA\x81\x7C\xFF\
xD3

Như vậy, ta đã có được đoạn Shellcode thực hiện một nhiệm vụ nhỏ. Tuy
nhiên, vì chỉ sử dụng các hàm trong Kernel32.dll nên khả năng của Shellcode
rất hạn chế vì thế ta cần phải nạp thêm các thư viện.
Trong bài viết tới tôi sẽ tiếp tục với việc “Lấy địa chỉ hàm trong Shellcode” và
“Mã hóa shellcode“.
#LƯU Ý: Các ví dụ trên đều được thực hiện trên Windows XP.
Windows Shell Code và Ứng dụng khai thác lỗi tràn bộ đệm – Phần 2
Chào các bạn, tiếp tục chủ đề về “Windows Shell Code và Ứng dụng khai thác
lỗi tràn bộ đệm”. Hôm nay chúng ta tiếp tục giải quyết các vấn đề liên quan
tới việc lấy địa chỉ hàm, nạp thêm thư viện và việc mã hóa shellcode.
Nếu bạn nào chưa đọc bài viết trước của tôi, bạn có thể tham khảo tại đây.
Lấy địa chỉ hàm trong Shellcode
Để đa dạng hóa khả năng của Shellcode, ta sẽ nạp thêm các thư viện khác
như User32.dll… bằng 2 hàm API là LoadLibrary & GetProcAddress
Nạp thư viện bằng hàm LoadLibrary:
HINSTANCE LoadLibrary ( LPCTSTR lpLibFileName);
Lấy địa chỉ hàm bằng hàm GetProcAddress:
FARPROC GetProcAddress( HMODULE hModule, LPCWSTR lpProcName);
Như vậy, ta sẽ phải biết địa chỉ của hàm LoadLibrary & GetProcAddress bên
trong thư viện Kernel32.dll. Đoạn asm dưới đây sẽ thực hiện gọi MessageBox
từ thư viện User32.dll:
4. Mã hóa shellcode
Nhắc lại một chút về tấn công tràn bộ đệm (Buffer Overflow), ta biết lỗi tràn
bộ đệm về cơ bản là do lập trình cẩu thả, không kiểm soát các giá biên của dữ
liệu. Do vậy, khi ta sử dụng các hàm ghi dữ liệu vào vùng nhớ như printf(),
strcpy()… vùng nhớ sẽ bị tràn. Kẻ tấn công dựa vào các hàm này để đưa mã
độc vào các vùng nhớ đó. Tuy nhiên, các hàm trên đưa dữ liệu vào vùng nhớ
dưới dạng chuỗi ký tự (string), nghĩa là tất cả các ký tự NULL hay 0x00 đều
là điểm ngắt của chuỗi ký tự và Shellcode chứa ký tự 0x00 sẽ không được đưa
vào vùng nhớ. Như vậy, mã hóa Shellcode là cần thiết để loại bỏ các giá trị

không mong muốn mà vẫn cho phép được thực thi bình thường.
Bài viết này sử dụng phương phép XOR đoạn mã nhị phân với một giá trị cụ
thể (ví dụ như 0x02). Dưới đây là đoạn mã nhị phân chưa được XOR và đoạn
mã nhị phân sau khi XOR :
\x55\x89\xe5\x57\x56\x53\xe8\x00\x00\x00\x00\x5b…
\x57\x8b\xe7\x55\x54\x51\xea\x02\x02\x02\x02\x59…
Mã hóa tất nhiên sẽ phải có cơ chế giải mã, đoạn mã sau sẽ thực hiện giải mã
đoạn Shellcode trên.
Đoạn Shellcode sau đây bao gồm cả mã hóa và giải mã sẽ không còn các giá
trị không mong muốn (0x00) :
\
xeb\x0d\x5e\x31\xc9\xb1\x66\x80\x36\x02\x46\xe2\xfa\xeb\x05\xe8\xee
\xff\xff\xff\x57\x8b\xe7\x55\x54\x51\xea\x02\x02\x02\x02\x59…
Phương pháp mã hóa trên có hạn chế là phải có cơ chế giải mã tương ứng.
Ngoài phương pháp trên đây còn nhiều phương pháp khác để tránh cho
Shellcode gặp những giá trị đặc biệt, không mong muốn :
• Viết Shellcode bằng ký tự.
• Unicode Shellcode.
Trong các bài tới tôi sẽ tiếp tục với các chủ đề còn lại bao gồm:
III. Shellcode nâng cao
1. Ý tưởng
2. Tìm kiếm địa chỉ Kernel32.dll
3. Process Enviroment Block
4. Structured Exception Handling
5. TOPSTACK
6. Lấy địa chỉ hàm bên trong Kernel32.dll
7. Tối ưu độ dài của Shellcode
IV. Các shellcode phổ biến
1. MessageBox Shellcode Version 1
2. MessageBox Shellcode Version 2

Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 3
Tiếp tục với chủ đề “Windows Shellcode và ứng dụng khai thác lỗi tràn bộ
đệm” hôm nay chúng ta sẽ tiếp tục với “Shellcode nâng cao”.
Bạn có thể theo dõi lại các bài viết tại đây:
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 1
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 2
Ý tưởng
Phần trên ta đã trình bày các bước để viết một Shellcode đơn giản. Tuy nhiên
Shellcode trên vẫn còn rất hạn chế, đặc biệt là nó không có khả năng làm việc
trên các phiên bản khác nhau của Windows. Một Shellcode hoàn thiện phải có
đầy đủ các chức năng sau:
• Phải có khả năng đặt vào vùng nhớ có khả năng thực thi.
• Phải ngắn nhất có thế.
• Phải loại bỏ được tất cả các giá trị đặc biệt.
• Không bị phụ thuộc vào phiên bản hệ điều hành.
Tìm kiếm địa chỉ Kernel32.dll
Để Shellcode không phụ thuộc vào phiên bản của hệ điều hành Windows, ta
phải có cơ chế cho phép tìm kiếm địa chỉ hàm bên trong Shellcode. Kỹ thuật
đặt ra gồm 2 bước, thứ nhất là tìm kiếm địa chỉ thư viện Kernel32.dll, thứ hai
là tìm kiếm địa chỉ hai hàm API LoadLibrary() và GetProcAddress() trong thư
viện Kernel32.dll. Từ đó ta dễ dàng xác định địa chỉ của các hàm API khác của
Windows. Sau đây sẽ là 3 phương pháp cho phép tìm kiếm địa chỉ thư viện
Kernel32.dll.
a. Process Enviroment Block
Process Enviroment Block (PEB) là một cơ chế quản lý một số thông tin về
tiến trình như process heap, threadlocal storage, danh sách module đã nạp…
thông qua một cấu trúc là cấu trúc PEB. May mắn thay, tất cả các thông tin
về module đã nạp (bao gồm cả địa chỉ cơ sở) được lưu trữ trong cấu trúc PEB
này và địa chỉ của cấu trúc này lại không đổi, không phụ thuộc phiên bản của
Windows. Sơ đồ sau sẽ mô tả thuật toán tìm ra được địa chỉ của thư viện

Kernel32.dll:
Hình 3 : Tìm kiếm địa chỉ Kernel32.dll qua cấu trúc PEB
Dựa vào sơ đồ địa chỉ và các cấu trúc ở hình 3, ta sẽ viết đoạn mã asm cho
phép lấy địa chỉ của Kernel32.dll từng bước như sau.
b. Structured Exception Handling
Structured Exception Handling (SEH) là một cấu trúc điều khiển ngoại lệ, cho
phép xử lý một số tình huống bất thường xảy ra khi thực thi chương trình.
Cấu trúc này đặt trên Stack (hình 4) gồm một trường con trỏ tới entry SEH
tiếp theo và một trường trỏ tới hàm xử lý ngoại lệ. Điều đặc biệt mà ta muốn
biết đến là cấu trúc cuối cùng được chỉ định có con trỏ 0xFFFFFFFF và hàm
xử lý ngoại lệ này mặc định là một hàm bên trong Kernel32.dll. Từ địa chỉ của
hàm này đi ngược lên ta sẽ tìm thấy địa chỉ cơ sở của Kernel32.dll. Phương
pháp này có thể vẫn không tìm thấy địa chỉ Kernel32.dll vì cấu trúc SEH có
thể bị chương trình ứng dụng thay đổi. Tuy nhiên, điều này hiếm khi xảy ra
nên kỹ thuật trên có thể sử dụng khá ổn định và cho phép chạy trên cả
Windows 9x.
Trong cấu trúc của file DLL (hay file PE), khoảng cách về địa chỉ giữa các
sections là 1000h và mỗi địa chỉ tương ứng với 4-byte nên DLL xác định biên
cho các sections là 64KB. Do vậy, từ một hàm trong thư viện, ta đi ngược n
lần với bước nhảy 1000h cộng với Offset của hàm trong Section chứa nó cho
đến khi gặp chữ ký “MZ” thì đó chính là địa chỉ cơ sở của Kernel32.dll.
c. TOPSTACK
Phương pháp thứ 3 sử dụng Stack và SEH, nhưng hạn chế của phương pháp
này là không áp dụng được cho Windows 9x
Hình 5 : Tìm kiếm địa chỉ Kernel32 theo Stack và SEH
Dựa vào hình 5, ta thấy kỹ thuật trên đơn giản là đưa con trỏ tới SEH mặc
định trong Stack, lấy hàm xử lý ngoại lệ bên trong Kernel32.dll, từ đó việc lấy
địa chỉ cơ sở của thư viện trên giống phương pháp SEH.
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần cuối
Hôm nay tôi sẽ kết thúc loạt bài viết về chủ đề “Windows ShellCode và ứng

dụng khai thác lỗi tràn bộ đệm”. Tôi hi vọng đã mang lại những kiến thức
hữu ích khi nghiên cứu về shellcode và lỗi tràn bộ đệm.
Bạn có thể theo dõi các bài viết trước tại đây:
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 1
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 2
Windows Shellcode và ứng dụng khai thác lỗi tràn bộ đệm – Phần 3
Lấy địa chỉ hàm bên trong Kernel32.dll
Ta đã có địa chỉ của Kernel32.dll, bước tiếp theo ta phải xác định địa chỉ của
các hàm cần tìm bên trong Kernel32.dll. Cơ chế dưới đây cho phép tìm kiếm
địa chỉ hàm theo tên hàm trong bàng Export Directory Table.
Hình 6 : Tìm kiếm hàm trong thư viện
Trong cấu trúc của DLL PE luôn phải có bảng chứa thông tin về các hàm
(symbols) xuất. Kỹ thuật sử dụng là truy nhập vào bảng chứa tên các hàm, so
sánh tên từng hàm với hàm tên hàm muốn tìm. Từ đó xác định địa chỉ tuyệt
đối của hàm.
Đoạn asm dưới đây cho phép tìm kiếm địa chỉ hàm “GetProcAddress()”
Như vậy, ta đã xác định được địa chỉ của hàm GetProcAddress(), địa chỉ cơ sở
của Kernel32.dll. Dựa vào 2 thông tin trên, ta dễ dàng xác định địa chỉ hàm
LoadLibrary() thông qua hàm GetProcAddress(). Như vậy, ta đã có đủ điều
kiện để viết được một Shellcode không phụ thuộc vào phiên bản của hệ điều
hành. Trong phần cuối là mã nguồn asm cho phép thực hiện gọi một
MessageBoxA trong User32.dll bằng các phương pháp đã nêu.
Tối ưu độ dài của Shellcode
Với phương pháp tìm kiếm địa chỉ đã nêu trên, Shellcode đã có thể thực thi
mà không phụ thuộc vào phiên bản của hệ điều hành. Nhưng nếu Shellcode
sử dụng nhiều hàm API để thực hiện nhiệm vụ phức tạp thì bộ nhớ lưu trữ
tên của các hàm sử dụng sẽ rất lớn. Để giảm kích thước của Shellcode ta sẽ
phải giảm kích thước lưu trữ tên các hàm cần tìm. Điều này còn cho phép ta
không cần sử dụng GetProcAddress() để lấy địa chỉ hàm.
Kỹ thuật được sử dụng là ta sẽ băm (hash) chuỗi tên hàm trong 4-byte bằng

thuật toán sau trong C :
Đoạn asm sau sẽ minh họa việc tìm kiếm hàm LoadLibraryA trong
Kernel32.dll bằng cách thông qua cấu trúc Export Directory Table, lấy lần
lượt từng tên hàm trong bảng chứa tên hàm, băm (hash) cái tên đó rồi so
khớp với 0xec0ee48e (LoadLibrary). Việc so khớp sẽ dừng lại nếu khớp kết
quả băm, từ đó tìm ra địa chỉ hàm LoadLibrary.
Bên dưới là mã asm của chương trình gọi MessageBox đã cải tiến của
version1 bằng các hàm băm, nên shellcode kết quả có kích thước cho nhỏ
hơn.
Các shellcode phổ biến
MessageBox1
MessageBox2

×