這裏主要藉助OllyDbg的彙編功能,使用匯編語言編寫注入代碼即ThreadProc()函數。與上一篇文章的CodeInject.cpp代碼類似,但區別在於THREAD_PARAM結構體不包含字符串成員,且使用指令字節數組替代了ThreadProc()函數(因爲代碼本身同時包含所需的字符串數據)。
彙編語言常用的開發工具(Assembler)有MASM、TASM和FASM等。
編寫簡單的待修改程序
編寫一個並無啥功能的asmtest.exe程序以用於通過OllyDbg使用匯編編寫注入代碼。
asmtest.cpp
#include "stdio.h"
int test(){
return 0;
}
int main(int argc, char* argv[]){
return test();
}
編譯生成asmtest.exe。
使用OllyDbg的Assemble功能修改彙編代碼
使用OllyDbg打開asmtest.exe,劃到代碼區域的頂端位置,右鍵New origin here,將EIP更改爲改地址401000:
注意,New origin here命令僅用來修改EIP寄存器值,與直接通過調用方式轉到指定地址是不一樣的,因爲寄存器與棧中內容並未改變。
空格鍵即可彈出輸入彙編命令的窗口,將其中的複選“Fill with NOP's”取消掉(若處於複選狀態,輸入代碼長度短於已有代碼時,剩餘長度會填充爲NOP指令,以整體對齊代碼):
接下來使用匯編編寫ThreadProc()函數。與C語言編寫的不同在於,需要的Data(字符串)已包含在Code中。
自EP處輸入代碼至40102E地址的call指令處(後續再說明爲啥call地址爲40103E):
簡單地說一下,就是先建立棧幀,401001地址處的MOV指令將ESI指向LoadLibraryA(),接下來3條PUSH指令爲“User32.dll”的ASCII碼(這種將字符串壓入棧的方法僅適用於彙編編寫的程序),接着PUSH ESP將上述字符串壓入棧中,再CALL指令調用LoadLibraryA(‘User32.dll’);接着3條PUSH爲“MessageBoxA”,壓入棧後PUSH EAX將use32.dll地址壓入棧,再CALL指令調用GetProcAddress();PUSH 0指令壓入MessageBoxA()第四個參數uType值0,最後的CALL指令相當於PUSH+JMP指令,即將下列的字符串壓入棧後再跳轉到下一個位置執行。
接着在緊跟着的地址401033中輸入字符串,Ctrl+E打開Edit窗口,在ASCII欄輸入字符串“SKI12”,再到HEX欄最後輸入00即NULL結束字符串,其爲MessageBoxA()第三個參數lpCaption:
接着在40103E地址處填寫call指令,可以看到之前的call指向現在的call指令,中間保存的是輸入的‘SKI12’字符串,之所以顯示奇怪的命令在於OllyDbg將字符串誤認爲IA-32指令,是由於輸入者在Code位置輸入字符串引起的:
接着在該call指令下面打開Edit窗口輸入字符串‘Assemble Code Inject By SKI12’再輸入NULL如紫框所示,其爲MessageBoxA()第二個參數lpText,可以看到指令輸入到了401060地址處,修改該處Hex,前面的00爲之前字符串的NULL、後面修改爲6A00即PUSH 0指令(壓入MessageBoxA()第一個參數hWord)如綠框所示,接着通過Edit框輸入Hex值的方式分別往下輸入命令,call eax指令調用MessageBoxA(),XOR指令將線程函數的返回值設置爲0,最後刪除棧幀再return。完整的彙編代碼如下圖:
保存文件,右鍵》Copy to executable》All modifications》Copy all》Save file:
打開新保存的文件asmtest_ski12.exe,在Dump窗口中選中之前寫入的代碼部分,即從401000至40106A地址處的內容:
右鍵選中的內容》Copy》To file,保存爲本地文件ski12.txt:
將該文件內容去掉多餘的部分,保存中間的ASCII字節部分,在每個字節前面加上0x前綴,各個字節之間以逗號分隔,這就是用匯編語言編寫的代碼注入彈框payload:
編寫代碼注入程序
CodeInject_Assemble.cpp
// CodeInject_Assemble.cpp
#include "windows.h"
#include "stdio.h"
//不像之前那樣包含字符串成員,因爲注入代碼中包含所需字符串數據
typedef struct _THREAD_PARAM {
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
} THREAD_PARAM, *PTHREAD_PARAM;
//彙編語言編寫的彈框payload
BYTE g_InjectionCode[] = {
0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00, 0x00, 0x68, 0x33, 0x32, 0x2E, 0x64,
0x68, 0x75, 0x73, 0x65, 0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41, 0x00, 0x68, 0x61, 0x67,
0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54, 0x50, 0xFF, 0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0B,
0x00, 0x00, 0x00, 0x53, 0x4B, 0x49, 0x31, 0x32, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0xE8, 0x1E,
0x00, 0x00, 0x00, 0x41, 0x73, 0x73, 0x65, 0x6D, 0x62, 0x6C, 0x65, 0x20, 0x43, 0x6F, 0x64, 0x65,
0x20, 0x49, 0x6E, 0x6A, 0x65, 0x63, 0x74, 0x20, 0x42, 0x79, 0x20, 0x53, 0x4B, 0x49, 0x31, 0x32,
0x00, 0x6A, 0x00, 0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3
};
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ){
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}
if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ){
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
BOOL InjectCode(DWORD dwPID){
HMODULE hMod = NULL;
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = {0,};
hMod = GetModuleHandleA("kernel32.dll");
// set THREAD_PARAM
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
// Open Process
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID)) ) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for THREAD_PARAM
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
sizeof(THREAD_PARAM), // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)¶m, // lpBuffer
sizeof(THREAD_PARAM), // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// Allocation for ThreadProc()
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
sizeof(g_InjectionCode), // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//g_InjectionCode替換了之前的ThreadProc()
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)&g_InjectionCode, // lpBuffer
sizeof(g_InjectionCode), // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL)) ) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int main(int argc, char *argv[]){
DWORD dwPID = 0;
if( argc != 2 ){
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}
// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;
// code injection
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);
return 0;
}
隨意注入的cmd進程:
跟蹤調試分析
調試分析,使用OllyDbg打開notepad.exe運行,設置生成新進程時進行斷點,查看進程PID並進行代碼注入,可看到調試器暫停在線程代碼起始點,先是生成棧幀,[EBP+8]是傳入函數的第一個參數,這裏指THREAD_PARAM結構體指針,執行完該MOV指令後ESI存儲的地址是CodeInject_Assemble.exe進程爲THREAD_PARAM結構體在notepad.exe進程內存空間中分配的內存緩衝區地址;可以看到該地址存儲着2個4字節的值,即LoadLibraryA()和GetProcAddress()的起始地址:
接着將“user32.dll”字符串以ASCII碼逆序壓入棧中,PUSH ESP中爲該字符串的起始地址,最後CALL指令調用LoadLibraryA(‘user32.dll’),函數的返回地址保存在EAX中:
使用ALT+E查看加載到進程內存中的所有DLL:
同樣,將“MessageBoxA”字符串壓入棧作爲GetProcAddress()函數的第二個參數值,接着PUSH EAX指令將上述的user32.dll返回地址壓入棧作爲GetProcAddress()函數的第一個參數hMod的值,再CALL指令調用GetProcAddress(hMod, ‘MessageBoxA’):
接着是依次壓入MessageBoxA()函數的第四個參數uType值0、第三個參數lpCaption值“SKI12”、第二個參數lpText值“Assemble Code Inject By SKI12”和第一個參數hWnd值0,最後調用MessageBoxA():
使用CALL指令將包含在代碼間的字符串數據地址壓入棧:
僅適用於使用匯編語言編寫的程序。簡單地說,CALL相當於PUSH+JMP。執行1A002E地址處的CALL指令後,函數(1A003E)終止並將返回地址(1A0033)壓入棧中,再JMP到相應的函數地址(1A003E)。這裏1A003E實際並不是函數,不具有RETN指令返回的形態。此處的CALL指令只是用來將緊接其後的“SKI12”字符串地址壓入棧,然後轉到下一條代碼指令。
執行完call指令後便彈框顯示注入內容。
之後便是設置ThreadProc()函數的返回值爲0(使用XOR EAX,EAX指令比使用MOV EAX,0指令更快捷),再刪除棧幀及函數返回: