代码注入(线程注入)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SKI_12/article/details/82947635

代码注入概念

代码注入是一种向目标进程插入独立运行代码并使之运行的技术,其一般调用CreateRemoteThread() API以远程线程的形式运行插入的代码,亦称为线程注入。代码以线程过程(ThreadProcedure)形式插入,而代码中使用的数据则以线程参数的形式插入,即代码和数据是分别注入的。

 

代码注入与DLL注入的区别

DLL注入适用于代码量大且复杂的情况,而代码注入适用于代码量少且简单的情况。

DLL注入需要先把注入代码放入某个DLL文件,再将整个DLL文件注入目标进程。DLL代码中使用的所有数据位于DLL的数据区域,整个DLL插入目标进程时,代码和数据是共存于内存,因而代码能够正常执行。

代码注入仅向目标进程注入必要的代码,要想使注入代码正常运行,还必须将代码中使用的数据一同注入。

另外,代码注入的优点为:占用内存少;难以查找痕迹;无需另外的DLL文件。

 

DLL注入示例

在之前写的文章《DLL注入》中有简单的讲解。这里先看看DLL注入的常规做法,即将代码写入DLL文件,然后将DLL注入到目标进程后会执行DLL中的程序,这里以MsgBox.dll注入到notepad进程中弹框为例。

MsgBox.cpp

// MsgBox.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include "windows.h"

DWORD WINAPI ThreadProc(LPVOID lParam){
	MessageBoxA(NULL, "DLL注入 By SKI12", "blog.csdn.net/ski_12", MB_OK);
	return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
    switch( fdwReason ){
        case DLL_PROCESS_ATTACH : 
            CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
            break;
    }
    return TRUE;
}

如果在编译中出现如下错误时,只需到dllmain.cpp中删除DllMain()函数即可:

生成成功后,将得到MsgBox.dll文件。

另外还需要编写DLL注入的程序,如之前《DLL注入》文章中所示,这里不再累赘。

打开notepad程序,查看其PID,再到cmd中进行DLL注入,:

接着跟踪调试。在完成MsgBox.dll注入notepad进程后,用OllyDbg查看DLL注入代码:

可以看到两条PUSH指令中两个地址为MessageBoxA()要使用的两个字符串参数,将其存储在栈中。

接着call指令调用的MessageBoxA()函数的地址如下:

可以看到62B82080地址为DLL的IAT区域,DLL代码中使用的所有数据都位于DLL的数据区域。

小结一下,DLL注入就是把代码和数据都写到DLL文件中,再将该DLL文件注入到目标进程中,从而代码与数据共存于内存,使得代码可以正常运行。

 

代码注入示例

编写实现和上述DLL注入一样弹框功能的代码,只是改为用代码注入的方式进行。

CodeInject.cpp

// CodeInject.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

//该结构体用于接收API和4个字符串
typedef struct _THREAD_PARAM {
	FARPROC pFunc[2];
	char szBuf[4][128];
} THREAD_PARAM, *PTHREAD_PARAM;

typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
    LPCSTR lpLibFileName
);

typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
    HMODULE hModule,
    LPCSTR lpProcName
);

typedef int (WINAPI *PFMESSAGEBOXA)
(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
);

DWORD WINAPI ThreadProc(LPVOID lParam){
	PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
    HMODULE hMod = NULL;
    FARPROC pFunc = NULL;

	//未直接调用相关API和未直接定义使用字符串,而通过THREAD_PARAM结构体以线程参数的形式传递使用
    // LoadLibrary()
    hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);    // "user32.dll"
    if( !hMod ){
		return 1;
	}
        

    // GetProcAddress()
    pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);  // "MessageBoxA"
    if( !pFunc ){
		return 1;
	}

    // MessageBoxA()
    ((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);

    return 0;
}

BOOL InjectCode(DWORD dwPID){
	HMODULE hMod = NULL;
    THREAD_PARAM param = {0,};
    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    LPVOID pRemoteBuf[2] = {0,};
    DWORD dwSize = 0;

    hMod = GetModuleHandleA("kernel32.dll");

    //设置THREAD_PARAM结构体
	//加载到所有进程的kernel32.dll的地址都相同,因此从本进程获取的API与在notepad进程中获取的API地址是一样的
    param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
    param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
    strcpy_s(param.szBuf[0], "user32.dll");
    strcpy_s(param.szBuf[1], "MessageBoxA");
    strcpy_s(param.szBuf[2], "代码注入 By SKI12");
    strcpy_s(param.szBuf[3], "blog.csdn.net/ski_12");

    // 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
    //注入数据部分到进程
    dwSize = sizeof(THREAD_PARAM);
    if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // dwSize
                                      MEM_COMMIT,           // flAllocationType
                                      PAGE_READWRITE)) )    // flProtect
    {
        printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }
	//param参数值为数据
    if( !WriteProcessMemory(hProcess,                       // hProcess
                            pRemoteBuf[0],                  // lpBaseAddress
                            (LPVOID)&param,                 // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    // Allocation for ThreadProc()
	//注入代码部分到进程
    dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
    if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // dwSize
                                      MEM_COMMIT,           // flAllocationType
                                      PAGE_EXECUTE_READWRITE)) )    // flProtect
    {
        printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }
	//ThreadProc()函数为操作代码
    if( !WriteProcessMemory(hProcess,                       // hProcess
                            pRemoteBuf[1],                  // lpBaseAddress
                            (LPVOID)ThreadProc,             // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

	//将数据与代码注入到进程内存后,调用CreateRemoteThread()执行远程线程
    if( !(hThread = CreateRemoteThread(hProcess,            // hProcess
                                       NULL,                // lpThreadAttributes
                                       0,                   // dwStackSize
                                       (LPTHREAD_START_ROUTINE)pRemoteBuf[1],     // dwStackSize
                                       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;
}

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;
}

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;
}

编译生成exe文件后,同样运行notepad进程,查看进程PID,在cmd中进行代码注入,可以看到和上述DLL注入一样的效果:

接着使用OllyDbg跟踪调试。

因为代码注入是通过线程的方式实现注入的,因此先设置注入新线程时进行断点:

命令行进行代码注入后,停留在被注入线程代码的起始位置,即ThreadProc()函数:

可以看到002C0004地址处的MOV指令中,[EBP+8]地址就是ThreadProc()函数的lParam参数,而参数lParam则指向被一同注入的THREAD_PARAM结构体:

小结一下,代码注入即线程注入,先注入数据与代码到进程的内存再通过调动CreateRemoteThread()函数来执行远程线程,其中代码和数据都是通过THREAD_PARAM结构体以线程参数的形式传递使用的。

猜你喜欢

转载自blog.csdn.net/SKI_12/article/details/82947635