今天整理下代码注入(远程线程注入),所谓代码注入,可以简单的理解为是在指定内进程里申请一块内存,然后把我们自己的执行代码和一些变量拷贝进去(通常是以启线程的方式),然后直接调用对方内存里我们拷贝进去的那部分代码(创建一个线程)。这样就行了,此时我们的这个线程就是目标进程的子线程了。但是要注意一点,代码注入之所以能成功重点是:有些系统常用的dll里的某些函数,在不同的进程里面获取到的地址是一样的(此处注意,一样的概念是指数值一样,但是这个数值存的地方不一样。也就是A里面的b变量的值等于C里面的d变量的值。这个要清楚,不然在学习API劫持的时候可能会迷茫)。
首先先介绍几个需要的API
1.LoadLibrary() 和 GetProcAddress()这两个函数,一个是加载dll库,一个是从已经加载的库句柄里拿出来某个函数的地址,可以理解成是把一个dll加到内存里,然后获取里面某个函数的地址,得到这个地址后就可以直接调用了,这两个简单的函数经常用到,无论是常规调用还是静态免杀都经常用。
2.OpenProcess()
根据进程id获取进程的句柄,也就是获取进程操控权。
3.VirtualAllocEx()
在指定进程里开辟一块内存,用于存放自己的代码和参数。
4.WriteProcessMemory()
3里面的函数会在一个进程里开辟一块内存,然后在那个内存里直接用本函数4进行数据写入,就是在别人那开一块内存然后写自己的东西。
5.CreateRemoteThread()
最核心的函数,在指定进程的某个内存位置存储的函数为线程函数,启动一个线程,当然被启动的这个线程属于指定的这个进程。
OK上面那5个API就是代码注入需要的几个基本的了,接下来我说一下我对线程注入的理解:
线程注入其实很好理解,就是说我们通过一定的手段在宿主也就是需要被注入的进程那获取权限,得到权限之后我们要在这个进程上开辟一定的内存,然后把自己的线程函数内容以及参数什么的全都拷贝过去,这样目标进程上有我们的函数,我们的参数,我们这个时候只是需要"帮"它启动一下这个线程就OK了,直接用CreateRemoteThread函数在对方进程的某个内存位置的某个线程函数作为线程函数启动。
然后上面的那个解释是宏观的,也就是泛泛而谈,最最核心的思路是这样:对于每个进程,他们调用的API大多都是自己随时调用随时获取的,地址也不一样,但是只有极少数的几个系统核心dll他们不一样,系统为了优化和统一管理,使得每个人load的dll然后从里面获取的函数地址是一样的,也就是大家共用一套已经被载入的dll地址,可以用的也是用的最多的就是User32.dll,kernel32.dll(我目前就知道这两个,但是不重要只要知道一个kernel32.dll然后在里面做一个工厂就行了),既然大家用的地址都一样,那么也就是说我在我本地直接获取里面某个函数的地址,那么你的地址也是这个,那我直接帮你“怂恿”你去执行就行了呗。这样代码注入的思路可以总结成是:我们以所有进程加载的某些DLL地址是相同的,通过这个相同为跳板,“跳过去,然后帮宿主进程做事”,这样想来有点局限,因为只有那么可怜的几个dll大家的地址一样,但是不用担心,我下面会介绍一个我自创的思路,就是在宿主里面建造一个工厂,然后加载任意想加载的东西,如果我们每个产品都要自己生产,然后送货,但是因为有很多货是不合格的,都被扣押了,那么我们为什么不直接生产一个合格的工厂送过去,只要工厂被接受了,那么他所生产的所有产品都被接受了,也就是在宿主进程上加载的dll里面的api肯定和他自己加载的一样啊,这样突破了常规代码注入函数受限制的弊端,下面注意问题的4还会说这个问题。
需要注意的问题:1.
参数结构体的结构体名字有说道,我之前没注意这个问题,造成一定概率的注入失败。
2.
注意64位32位问题,目前的经验是64位的话就用64位程序注入,32位就用32位程序注入,这个应该可以解决,问题出现在获取进程pid那。
3.
注意一个非常关键的问题,在代码注入的时候,你所需要的任何变量最好自己以内存拷贝的方式传过去,比如我直接在线程函数里开了一个变量 char[512] = "00000",当你把这个函数地址拷贝到指定进程里面去的时候,个"00000"所占的内容并没有被拷贝过去,这样你在调用的时候内存就会出错。
4.
代码注入限制非常多,被注入的线程函数里面内容非常有讲究,大小有限制,存储方式有限制,不能随便调用API函数,比如直接在里面来了一个Sleep(1000),你觉得这个函数是系统的,在那都能调用,然而并不是,直接就内存错误,如果需要,你需要找到对应的API然后加载进去,然后调用,但是又不是所有的AIP函数地址都是统一的,那么怎么办?我之前有一个解决办法是直接导入两个固定的地址LoadLibrary() 和 GetProcAddress(),通常的代码注入者的固定思路是靠这两个函数获取地址然后传过去,然而大家很容易忽略一个思路,就是既然我们知道所有程序的LoadLibrary() 和 GetProcAddress() 地址是一样的,那么我们就直接本地获取这两个函数的地址,然后把这两个地址传到线程函数里,然后在里面在本地生产相应的函数,这样我们加载的API都是目标线程能接受的API地址了,这个思路是当时睡觉时候想的,妈的激动地差点失眠(虽然现在回头整理这些的时候已经不怎么激动了)。
5.
还有很多限制和注意问题,这里就不多说了,需要的可以自己去查一查,我知道的也不多,有的时候也是现用现查。
OK上面是需要知道的思路和基础,下面就直接说实现。
思路大体分这么几个阶段
1.提权 权限不够的话可能到导致敏感函数执行失败。
2.获取pid 获取宿主进程的pid
3.打开宿主进程 可以理解成是获取宿主进程的操控权,当然这一步依赖于1的提权和2获取的pid
4.初始化参数数据 这一步就是得到一些AIP地址了,参数了什么的,然后把它存在参数的结构体里。
5.在宿主进程里分配内存用于存放参数
6.把需要的参数全都拷贝到宿主继承内存里(第4步获取的内存位置)
7.在宿主进程里分配内存用于存放线程函数代码
8.把线程函数直接拷到7获取的内存里
9.启动注入宿主进程的线程
10.善后,释放一下垃圾什么的。
下面我给出一个最基本的代码注入的代码,功能是在qq或者explorer里面注入一个线程,线程就只是弹出来一个对话框(我资源里整理了一个测试项目http://download.csdn.net/detail/u013761036/9603026)。
.H
#pragma once
#include <Windows.h>
#include <stdlib.h>
#include <tlhelp32.h>
#include <Psapi.h>
#include <string>
using std::string;
using std::wstring;
#pragma comment (lib,"Psapi.lib")
#pragma warning(disable:4996)
class CInjection
{
private:
bool AdjustProcessTokenPrivilege();//提权
bool Camp2str(wstring wsStrA ,wstring wsStrB);
DWORD GetProcessIdByName(const wstring &wsProcessName);//获取pid
public:
bool InjectionExeAndShowMessage(const wstring &wsProcessName);
};
.CPP
#include "stdafx.h"
#include "Injection.h"
typedef struct _REMOTE_PARAMETER
{
CHAR cTitle[64];
CHAR cBody[64];
DWORD dwMessAgeBoxShowAddress;
}RemotePara ,* PRemotePara;
bool CInjection::AdjustProcessTokenPrivilege()
{
LUID luidTmp;
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidTmp))
{
CloseHandle(hToken);
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luidTmp;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
{
CloseHandle(hToken);
return FALSE;
}
return true;
}
bool CInjection::Camp2str(wstring wsStrA ,wstring wsStrB)
{
int nSize = wsStrA.length();
for(int i = 0 ;i < nSize ;i ++)
{
if(wsStrA[i] >= 'A' && wsStrA[i] <= 'Z')
wsStrA[i] += 'a'- 'A';
}
nSize = wsStrB.length();
for(int i = 0 ;i < nSize ;i ++)
{
if(wsStrB[i] >= 'A' && wsStrB[i] <= 'Z')
wsStrB[i] += 'a'- 'A';
}
return wsStrA == wsStrB;
}
DWORD CInjection::GetProcessIdByName(const wstring &wsProcessName)
{
HANDLE hProcess = 0;
DWORD dwProcess[2048] ,dwNeeded;
TCHAR tcProcName[MAX_PATH] = {0};
wstring wsNowProcessName = L"";
int nTempSize = 0;
int nPos = 0;
EnumProcesses(dwProcess, sizeof(dwProcess), &dwNeeded);
for(int i = 0 ;i < dwNeeded / sizeof(DWORD) ;i++)
{
if(0 != dwProcess[i])
{
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcess[i]);
GetModuleFileNameEx(hProcess, NULL, tcProcName, MAX_PATH);
nPos = wstring(tcProcName).find_last_of(L'\');
if(nPos != wstring::npos)
{
wsNowProcessName = wstring(wstring(tcProcName).substr(nPos + 1));
if(Camp2str(wsProcessName ,wsNowProcessName))
{
DWORD aa = dwProcess[i];
return aa;
}
//if(wsProcessName == wsNowProcessName)
// return dwProcess[i];
}
}
}
return 0;
}
//注入的线程函数
static DWORD __stdcall RemoteThread(PRemotePara myData)
{
typedef int (WINAPI *_MessageBoxA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType
);
_MessageBoxA mbAdd = (_MessageBoxA)myData->dwMessAgeBoxShowAddress;
mbAdd(NULL ,myData->cBody ,myData->cTitle ,MB_OK);
return 0;
}
bool CInjection::InjectionExeAndShowMessage(const wstring &wsProcessName)
{
//1.提权
if(!AdjustProcessTokenPrivilege())
return false;
//2.获取pid
DWORD dwProPID = 0;
if((dwProPID = GetProcessIdByName(wsProcessName)) == 0)
return false;
//3.打开进程
HANDLE hProcess = NULL;
if((hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE ,dwProPID)) == NULL)
return false;
//4.初始化参数数据
RemotePara rpData = {0};
ZeroMemory(&rpData,sizeof(RemotePara));
HINSTANCE hInst=NULL;
hInst=LoadLibrary(L"User32.dll");
if(hInst == NULL)
return false;
rpData.dwMessAgeBoxShowAddress = 0;
rpData.dwMessAgeBoxShowAddress = (DWORD)GetProcAddress(hInst,"MessageBoxA");
if(rpData.dwMessAgeBoxShowAddress == 0)
return false;
FreeLibrary(hInst);
strcat(rpData.cTitle ,"title");
strcat(rpData.cBody ,"body");
//RemoteThread(&rpData);
//5.在宿主进程里分配内存,用于存参数
PRemotePara pPara = NULL;
pPara = (PRemotePara)VirtualAllocEx(hProcess , 0 ,sizeof(RemotePara) ,MEM_COMMIT,PAGE_READWRITE);
if(pPara == NULL) return false;
//6.把参数写入宿主进程里,注意结构体的命名(_REMOTE_PARAMETER)
if(!WriteProcessMemory(hProcess ,pPara ,&rpData ,sizeof(RemotePara) ,0))
return false;
//7.在宿主进程里分配内存,用于写线程函数 1024这个值是我随意填写的,我觉得这么大可以存下上面那个线程函数
void *pRemoteThr = VirtualAllocEx(hProcess , NULL ,1024 ,MEM_COMMIT | MEM_RESERVE ,PAGE_EXECUTE_READWRITE);
if(pRemoteThr == NULL) return false;
//8.把进程函数写入分配的内存里
if(!WriteProcessMemory(hProcess ,pRemoteThr ,&RemoteThread ,1024 ,0))
return false;
//9.启动注入宿主进程的进程
DWORD dwThreadId = 0;
HANDLE hThread = CreateRemoteThread(hProcess ,0 ,0 ,(DWORD (WINAPI *)(LPVOID))pRemoteThr ,pPara ,0 ,&dwThreadId);
if(!hThread) return false;
//10.等待线程结束,然后清理内存
WaitForSingleObject(hThread ,INFINITE);
CloseHandle(hThread);
VirtualFreeEx(hProcess ,pPara ,0 ,MEM_RELEASE);
VirtualFreeEx(hProcess ,pRemoteThr ,0 ,MEM_RELEASE);
CloseHandle(hProcess);
return true;
}
USER
#include "stdafx.h"
#include "ZZZTest.h"
#include "Injection.h"
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
CInjection *pciTest = new CInjection();
pciTest->InjectionExeAndShowMessage(L"qq.exe"); //explorer.exe
delete pciTest;
return 0;
}