使用汇编语言编写注入代码
这里主要借助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指令更快捷),再删除栈帧及函数返回: