远线程注入
每个进程都有自己的虚拟地址空间,对32位进程来说,这个地址空间的大小为4GB。因为每个进程都有自己专有的地址空间,当进程的各个线程运行的时候,它们只能够访问属于自己进程的内存。这样做的一个好处是维护系统的安全,防止进程的私有空间被入侵。世界上有了矛就有了盾,windows也撕开了一个小口,提供了一些函数来让其它进程对另一个进程进行操作,当然亦邪亦正,全在于你。大名鼎鼎的CreateRemoteThread就是属于这样的函数。
远线程注入的基本原理就是通过在另一个进程中创建远程线程的方法进入目标进程的内存地址空间。使用插入到目标进程中的远程线程将该DLL插入到目标进程的地址空间,即利用该线程通过调用Windows API LoadLibrary函数来加载DLL,从而实现获取目标进程空间的使用权。如下摘自ReactOS 3.14的代码所示,CreateRemoteThread实际实现的功能就是调用NtCreateThread创建一个属于目标进程的线程。
HANDLE
WINAPI
CreateRemoteThread(HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId)
{
/* Clear the Context */
RtlZeroMemory(&Context, sizeof(CONTEXT));
/* Write PID */
ClientId.UniqueProcess = hProcess;
/* Create the Stack */
Status = BaseCreateStack(hProcess,
dwStackSize,
dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION ?
dwStackSize : 0,
&InitialTeb);
/* Create Initial Context */
BaseInitializeContext(&Context,
lpParameter,
lpStartAddress,
InitialTeb.StackBase,
1);
/* initialize the attributes for the thread object */
ObjectAttributes = BaseFormatObjectAttributes(&LocalObjectAttributes,
lpThreadAttributes,
NULL);
//前面部分都是在初始化新线程的环境,紧接着就是创建线程了
/* Create the Kernel Thread Object */
Status = NtCreateThread(&hThread,
THREAD_ALL_ACCESS,
ObjectAttributes,
hProcess,
&ClientId,
&Context,
&InitialTeb,
TRUE);
//...............其它操作
}
CreateRemoteThread在MSDN的声明如下:
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess, //新线程所属的进程句柄
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress, //新线程函数指针
__in LPVOID lpParameter, //新线程函数所需传入的参数
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);
接下来用一个简单的例子来分析说明,本示例所实现的功能就是创建远线程,把DLL注入到目标进程中去。
1) 获取一个需要被注入远线程的进程句柄。本例的目标进程为explorer.exe,这里通过toolhelp32函数来找到该进程的PID,进而用OpenProcess获取该进程的句柄。
HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if( hSnapshot == NULL )
{
cout<<"Create Snapshot false."<<endl;
cin.get();
return;
}
PROCESSENTRY32 stProcessEntry32 = {0};
stProcessEntry32.dwSize = sizeof(PROCESSENTRY32);
Process32First( hSnapshot, &stProcessEntry32 );
bool bFind = false;
do
{
if( strncmp( stProcessEntry32.szExeFile, "explorer.exe", strlen("explorer.exe") ) == 0 )
{
bFind = true;
break;
}
}while( Process32Next( hSnapshot, &stProcessEntry32 ) );
CloseHandle( hSnapshot );
if( !bFind )
{
cout<<"查找explorer进程失败."<<endl;
cin.get();
return;
}
DWORD dwPId = stProcessEntry32.th32ProcessID;
HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, false, dwPId );
if( hProcess == NULL )
{
cout<<"打开explorer进程失败."<<endl;
cin.get();
return;
}
2) 把进程的参数数据写入到目标进程的地址空间。
char lpDllName[100] = {0};
GetCurrentDirectory( 100, lpDllName );
strcat( lpDllName, "\\InjectProcessDll.dll" );
LPVOID lpDllNameAddr = VirtualAllocEx( hProcess, NULL, strlen(lpDllName)+1, MEM_COMMIT, PAGE_READWRITE );
if( lpDllNameAddr == NULL )
{
cout<<"explorer进程中申请内存失败."<<endl;
CloseHandle(hProcess);
cin.get();
return;
}
cout<<"在目标进程:"<<stProcessEntry32.szExeFile<<"中申请的空间地址:"<<hex<<lpDllNameAddr<<endl;
DWORD dwRes = 0;
bool bRet = WriteProcessMemory( hProcess, lpDllNameAddr, lpDllName, strlen(lpDllName), &dwRes );
if( !bRet )
{
cout<<"explorer进程写信息失败."<<endl;
VirtualFreeEx( hProcess, lpDllNameAddr, strlen(lpDllName)+1, MEM_DECOMMIT );
CloseHandle(hProcess);
cin.get();
return;
}
3) 用 CreateRemoteThread启动远程的 ThreadFunc 拷贝。
HMODULE hModuleKernel32 = GetModuleHandle("kernel32.dll");
DWORD lp = (DWORD)LoadLibraryA;
LPTHREAD_START_ROUTINE lpLoadLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress( hModuleKernel32, "LoadLibraryA" );
if( lpLoadLibraryAddr != NULL )
{
cout<<"获得函数地址:"<<hex<<lpLoadLibraryAddr<<endl;
HANDLE hRemote = CreateRemoteThread( hProcess, NULL, 0, lpLoadLibraryAddr, lpDllNameAddr, 0, NULL );
4) 等待远程线程终止,判断注入是否成功
if( hRemote != NULL )
{
cout<<"创建远程线程成功,句柄:"<<hex<<hRemote<<endl;
WaitForSingleObject( hRemote, INFINITE );
CloseHandle( hRemote );
cout<<"远程线程运行结束"<<endl;
cin.get();
}
else
cout<<"创建远程线程失败."<<endl;
至此,该DLL注入到目标进程中。有些人在使用CreateRemoteThread的时候,会把它分为两种,一种是加载DLL的时候,一种是加载一段代码。其实仔细观察,加载DLL,也是加载一段代码形式的特例,只不过这段代码就是LoadLibraryA。
在另外两篇文章中讲到的RING3下的IAT HOOK和INLINE HOOK,都是非常常用的API拦截方法。但是没有好的DLL注入方法,无法显示它们的功力,下面用一个例子来演示:
功能简介:HOOK explorer.exe中的CreateProcessW,当遇到每一个打开记事本程序的时候,注入一个TestDll。
测试环境:XP SP3
a) InjectDll.exe
即上面实现远线程注入的代码
b) RemoteThreadDll.dll
远线程要注入的DLL,其功能是IAT HOOK函数CreateProcess,当打开的是NOTEPAD时候,使用INLINE hook注入一段代码,令记事本加载一个DLL模块。
IAT HOOK 函数CreateProcess
特别注意的是,当我们鼠标去打开一个记事本程序的时候,系统会调用ShellExecuteEx-CreateProcessW,这里需要HOOK的是SHELL32.DLL的输入表。
BOOL ImportAddressTableHook
(
IN HMODULE hModule,
IN LPCTSTR pImageName,
IN LPCVOID pTargetFuncAddr,
IN LPCVOID pReplaceFuncAddr
)
{
IMAGE_DOS_HEADER* pImageDosHearder = (IMAGE_DOS_HEADER*)hModule;
IMAGE_OPTIONAL_HEADER* pImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((DWORD)hModule+pImageDosHearder->e_lfanew+24);
IMAGE_IMPORT_DESCRIPTOR* pImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)
((DWORD)hModule+
pImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
IMAGE_THUNK_DATA* pImageThunkData = NULL;
string TargetLibraryName;
DWORD Value = 0, OldProtect = 0, NewProtect = 0;
LPDWORD FunctionAddress = NULL;
MEMORY_BASIC_INFORMATION InforMation;
while(pImageImportDescriptor != 0)
{
TargetLibraryName.clear();
TargetLibraryName = (LPCTSTR)((DWORD)hModule + pImageImportDescriptor->Name);
transform(TargetLibraryName.begin(), TargetLibraryName.end(), TargetLibraryName.begin(), ::toupper);
if(TargetLibraryName.compare(pImageName) == 0)
{
pImageThunkData = (IMAGE_THUNK_DATA*)((DWORD)hModule + pImageImportDescriptor->FirstThunk);
while(pImageThunkData->u1.Function)
{
FunctionAddress = (LPDWORD)&(pImageThunkData->u1.Function);
//_asm int 3
//char pLog[100] = {0};
//sprintf(pLog, "library name is %s, FunctionAddress is 0x%x , pTargetFuncAddr is 0x%x\n", TargetLibraryName.c_str(), *FunctionAddress, (DWORD)pTargetFuncAddr);
////MessageBox(NULL, pLog, "", MB_OK);
//OutLog(pLog);
if(*FunctionAddress == (DWORD)pTargetFuncAddr)
{
if (!VirtualProtect(FunctionAddress, sizeof(DWORD), PAGE_READWRITE, &OldProtect)) return FALSE;
if(!(WriteProcessMemory((HANDLE)-1, FunctionAddress, &pReplaceFuncAddr, 4, NULL)
&& VirtualProtect(FunctionAddress, sizeof(DWORD), OldProtect, &NewProtect)))
{
return FALSE;
}
//MessageBox(NULL, "end", "", MB_OK);
return TRUE;
}
pImageThunkData++;
}
break;
}
pImageImportDescriptor++;
}
if (pImageThunkData == NULL)
{
return FALSE;
}
//MessageBox(NULL, "hello world", "", MB_OK);
return FALSE;
}
在NOTEPAD.EXE注入一段SHELLCODE
001D0000 60 pushad
001D0001 9C pushfd
001D0002 68 00001B00 push 0x1B0000; ASCII "C:\WINDOWS\TestDll.dll"
001D0007 E8 6F1D637C call kernel32.LoadLibraryA
001D000C 9D popfd
001D000D 61 popad
001D000E - E9 5575E300 jmp notepad.01007568
char lpShellCode[] = {
0x60,
0x9c,
0x68,0x90,0x90,0x90,0x90,
0xe8,0x90,0x90,0x90,0x90,
0x9d,
0x61,
0xe9,0x90,0x90,0x90,0x90};
char lpCurrentDirectory [100] = {0};
GetCurrentDirectory( 100, lpCurrentDirectory );
strncat( lpCurrentDirectory, "\\TestDll.dll", strlen("\\TestDll.dll") );
LPVOID lpDllPathAddr = VirtualAllocEx( lpProcessInformation->hProcess, NULL, strlen(lpCurrentDirectory)+1, MEM_COMMIT, PAGE_READWRITE );
if (NULL == lpDllPathAddr) goto EndFunc;
BOOL bOk = WriteProcessMemory( lpProcessInformation->hProcess, lpDllPathAddr, lpCurrentDirectory, strlen(lpCurrentDirectory), NULL);
if( !bOk )
{
VirtualFree( lpDllPathAddr, strlen(lpCurrentDirectory)+1, MEM_DECOMMIT );
goto EndFunc;
}
LPVOID lpFuncAddr = GetProcAddress( GetModuleHandle("kernel32.dll"), "LoadLibraryA" );
LPVOID lpShellCodeAddr = VirtualAllocEx( lpProcessInformation->hProcess, NULL, strlen(lpShellCode)+1, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if( lpShellCodeAddr == NULL )
{
VirtualFree( lpDllPathAddr, strlen(lpCurrentDirectory)+1, MEM_DECOMMIT );
goto EndFunc;
}
//组合shellcode
//push XXXXXXXX
memcpy( lpShellCode + 3, (char*)&lpDllPathAddr, 4 );
//call XXXXXXXX,先计算偏移
DWORD dwFuncAddr = (DWORD)lpFuncAddr - ((DWORD)lpShellCodeAddr + 7) - 5;
memcpy( lpShellCode + 8, (char*)&dwFuncAddr, 4 );
//jmp XXXXXXXX,先计算偏移
DWORD dwJmpEnd = dwChangedFuncAddr - ((DWORD)lpShellCodeAddr + 14) -5;
memcpy( lpShellCode + 15, (char*)&dwJmpEnd, 4 );
//写入shellcode,及改变原来函数入口
bOk = WriteProcessMemory( lpProcessInformation->hProcess, lpShellCodeAddr, lpShellCode, 30, NULL );
if( !bOk )
{
VirtualFreeEx( lpProcessInformation->hProcess, lpDllPathAddr, strlen(lpCurrentDirectory)+1, MEM_DECOMMIT );
VirtualFreeEx( lpProcessInformation->hProcess, lpShellCodeAddr, strlen(lpShellCode)+1, MEM_DECOMMIT );
goto EndFunc;
}
//计算call的新地址
DWORD dwNewCallAddr = (DWORD)lpShellCodeAddr - (dwReadAddrPtr + 4);
//写入写call地址
MEMORY_BASIC_INFORMATION stMemBasicInfor = {0};
VirtualQueryEx( lpProcessInformation->hProcess, (PVOID)dwReadAddrPtr, &stMemBasicInfor, sizeof(MEMORY_BASIC_INFORMATION) );
DWORD dwOldProtect = 0;
VirtualProtectEx( lpProcessInformation->hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect );
bOk = WriteProcessMemory( lpProcessInformation->hProcess, (PVOID)dwReadAddrPtr, &dwNewCallAddr, 4, NULL );
VirtualProtectEx( lpProcessInformation->hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, dwOldProtect, NULL );
if( !bOk )
{
VirtualFreeEx( lpProcessInformation->hProcess, lpDllPathAddr, strlen(lpCurrentDirectory)+1, MEM_DECOMMIT );
VirtualFreeEx( lpProcessInformation->hProcess, lpShellCodeAddr, strlen(lpShellCode)+1, MEM_DECOMMIT );
goto EndFunc;
}
[课程]Android-CTF解题方法汇总!