roger 发表于 2020-9-1 10:54:04

【windows代码注入教程】第五课 代码注入(C语言)

  下面用一个代码注入示例向notepad.exe进程注入简单的代码,注入后会弹出消息框,显示消息
  首先,运行notepad.exe 查看notepad.exe的PID,我这里显示的是2608
·  在cmd窗口中运行CodeInjection.exe 并输入命令与参数
  然后可以看到一个信息框被注入到notepad.exe进程当中
  下面展示一下CodeInjection.exe的源码(用VS2017编译通过)

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

typedef struct _THREAD_PARAM
{
FARPROC pFunc;               // LoadLibraryA(), GetProcAddress()
char    szBuf;          // 接收传递过来的"user32.dll", "MessageBoxA", "www.xuenixiang.com", "Shin"这些字符串
} THREAD_PARAM, *PTHREAD_PARAM;

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

typedef FARPROC(WINAPI *PFGETPROCADDRESS)//如果函数调用成功,返回值是DLL中的输出函数地址
(
HMODULE hModule,//dll模块句柄
LPCSTR lpProcName;//函数名
);

typedef int (WINAPI *PFMESSAGEBOXA)//MessageBoxA传参结构体
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

DWORD WINAPI ThreadProc(LPVOID lParam)//线程的起始地址,线程创建成功,返回非零值,否则为0
{
PTHREAD_PARAM   pParam = (PTHREAD_PARAM)lParam;//接收线程传递给函数使用的CreateThread函数lpParameter参数数据
HMODULE         hMod = NULL;
FARPROC         pFunc = NULL;

// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc)(pParam->szBuf);    // "user32.dll"
if (!hMod)
return 1;

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

// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf, pParam->szBuf, MB_OK);

return 0;
}

BOOL InjectCode(DWORD dwPID)//代码注入的核心部分
{
HMODULE         hMod = NULL;
THREAD_PARAM    param = { 0, };
HANDLE          hProcess = NULL;
HANDLE          hThread = NULL;
LPVOID          pRemoteBuf = { 0, };
DWORD         dwSize = 0;

hMod = GetModuleHandleA("kernel32.dll");//获取kernel32.dll动态链接库的模块句柄

// set THREAD_PARAM
param.pFunc = GetProcAddress(hMod, "LoadLibraryA");//返回值是DLL中LoadLibraryA函数的地址
param.pFunc = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf, "user32.dll");
strcpy_s(param.szBuf, "MessageBoxA");
strcpy_s(param.szBuf, "www.xuenixiang.com");
strcpy_s(param.szBuf, "Shin");

// 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 = VirtualAllocEx(hProcess,          // hProcess
NULL,               // lpAddress
dwSize,               // dwSize
MEM_COMMIT,         // flAllocationType
PAGE_READWRITE)))    // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
/*hProcess 由OpenProcess返回的进程句柄。
如参数传数据为 INVALID_HANDLE_VALUE 【即 - 1】目标进程为自身进程
lpBaseAddress要写的内存首地址
再写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据。
lpBuffer
指向要写的数据的指针。
nSize
要写入的字节数。
返回值
非零值代表成功。
可用GetLastError获取更多的错误详细信息。*/
if (!WriteProcessMemory(hProcess,                     // hProcess
pRemoteBuf,                  // lpBaseAddress
(LPVOID)&param,               // lpBuffer
dwSize,                         // nSize
NULL))                         // lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for ThreadProc() 为线程函数申请内存空间 执行成功就返回分配内存的首地址,不成功就是NULL
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if (!(pRemoteBuf = VirtualAllocEx(hProcess,          // hProcess
NULL,               // lpAddress
dwSize,               // dwSize
MEM_COMMIT,         // flAllocationType
PAGE_EXECUTE_READWRITE)))    // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if (!WriteProcessMemory(hProcess,                     // hProcess
pRemoteBuf,                  // lpBaseAddress
(LPVOID)ThreadProc,             // lpBuffer
dwSize,                         // nSize
NULL))                         // lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if (!(hThread = CreateRemoteThread(hProcess,            // hProcess
NULL,                // lpThreadAttributes
0,                   // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf,   // dwStackSize
pRemoteBuf,       // 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;
//要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,
//只要当前进程具有SeDeDebug权限就可以了。要是一个用户是Administrator或是被给予了相应的权限,
//就可以具有该权限。可是,就算我们用Administrator帐号对一个系统安全进程执行
//OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID)还是会遇到“访问拒绝”的错误。什么原因呢?
//原来在默认的情况下进程的一些访问权限是没有被启用(Enabled)的,所以我们要做的首先是启用这些权限。
//与此相关的一些API函数有OpenProcessToken、LookupPrivilegevalue、AdjustTokenPrivileges。
//我们要修改一个进程的访问令牌,首先要获得进程访问令牌的句柄,这可以通过OpenProcessToken得到
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.Luid = luid;
if (bEnablePrivilege)
tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges.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);
return 1;
}

// change privilege
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;

// code injection
dwPID = (DWORD)atol(argv);
InjectCode(dwPID);

return 0;
}
  InjectCode()是代码注入技术的核心部分。两次调用VirtualAllocEx和WriteProcessMemory,第一次调用分配内存空间并写入远程线程的数据,第二次调用分配内存空间并写入远程线程的代码。这是代码注入的一个非常重要的特征
  下面动态调试代码注入的过程
  首先用OD调试notepad.exe ,F9跑起来
  然后设置OD,选项 调试设置 勾选中断于新线程
  现在开始,每当notepad.exe进程中生成新的线程,调试器就暂停在线程函数开始的代码位置,现在运行CodeInjection.exe,输入相应的PID
  就是Iparam参数
  插入到notepad.exe进程的汇编代码(宏观)
  高级语言中函数大小的计算方法类似于汇编语言中代码所占空间的计算方法(详情可以百度或者查阅《计算机组成原理和汇编语言》一书)
            
页: [1]
查看完整版本: 【windows代码注入教程】第五课 代码注入(C语言)