DLL注入-创建远程线程

selph
selph
发布于 2020-10-08 / 843 阅读
0
0

DLL注入-创建远程线程

原理介绍

远程线程指的是跨进程,可以在别的进程里创建线程

用到的API函数是 CreateRemoteThread

使用 CreateRemoteThread 创建远程线程的时候,需要提供目标进程的句柄,这个可以通过OpenProcess得到,然后还需要一个函数地址和一个参数地址

这个函数地址是在目标进程里创建线程执行的函数,而这个函数只能带有一个参数

说也巧,加载DLL的函数 LoadLibrary 刚好也只要一个参数,那就是DLL文件的路径

那么,我们只要找到 LoadLibrary 函数的地址,然后把DLL文件的路径写入到目标进程内存空间中去,得到参数的地址,就可以实现创建远程线程的DLL注入了

注意

这里先提一下一个可能会遇到的坑,DLL注入过程中,如果目标进程是64位的,那么我们写的程序也得是64位的,注入的DLL也必须是64位的,不然注入会失败!!

CreateRemoteThread 函数介绍

HANDLE
WINAPI
CreateRemoteThread(
    _In_ HANDLE hProcess,        //目标进程句柄
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,    //安全属性一般NULL
    _In_ SIZE_T dwStackSize,    //缺省堆栈大小 一般NULL
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,    //函数地址指针
    _In_opt_ LPVOID lpParameter,    //函数参数
    _In_ DWORD dwCreationFlags,        //创建后线程的状态,0表示立即执行,暂停CREATE_SUSPENDED需要调用ResumeThread去恢复
    _Out_opt_ LPDWORD lpThreadId    //用来接收新创建线程的线程ID,可以为NULL
    );

这个函数需要用到的进程句柄、函数地址、参数地址这三个参数需要我们提前去获得

获得进程ID

通过OpenProcess获得即可

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
    return -1;
}

进程PID可以通过任务管理器自行查看

当然也可以通过代码来获取,比如通过FindWindow根据窗口名获取窗口句柄,然后使用GetWindowThreadProcessId来获取进程PID,这里就不细讲了

获取函数地址

由于kernel32.dll中的函数地址在windows上是固定的位置,所以在本进程中获取到的地址在别的进程中也适用:

//获取LoadLibraryA函数地址
PTHREAD_START_ROUTINE load_start_addr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

获取参数地址

这里需要打开进程,向目标进程申请一片地址空间,然后向这个空间写入dll文件的路径

//打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
    return -1;
}

//向目标进程申请空间,返回首地址指针
int nDllSize = strlen(dll) + 1;
LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, nDllSize, MEM_COMMIT, PAGE_READWRITE);
if (pDllAddr == NULL) {
    CloseHandle(hProcess);
    return -1;
}

//向目标进程申请的空间写入dll绝对路径
DWORD dwWriteNum = 0;
WriteProcessMemory(hProcess, pDllAddr, dll, nDllSize, &dwWriteNum);

如果不知道dll路径有没有成功写入目标进程,可通过CE软件打开目标进程查看内存来判断

DLL注入

接下来调用CreateRemoteThread进行dll注入即可

//DLL注入
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, load_start_addr, pDllAddr, 0, NULL);

//等待目标dll的加载结束
WaitForSingleObject(hThread, INFINITE);

//关闭刚刚创建的句柄
CloseHandle(hThread);
CloseHandle(hProcess);

效果演示

image-20201007145131935

突破SESSION 0 远线程注入

在Windows 10 下,dll注入只能注入到一些普通的用户进程里,没办法注入到系统服务进程里,这是因为SESSION 0隔离机制所造成的,通过ZwCreateThreadEx函数可以突破SESSION 0 限制实现远线程注入

CreateRemoteThread函数在调用的时候,内部会调用ZwCreateThread来创建远程线程,其中第七个参数为1,导致线程创建完成之后处于挂起状态,无法恢复运行,我们直接调用这个函数然后把第七个参数设置为1即可让线程创建完成后就恢复运行,从而成功注入

关于这个参数的值,网上有个师傅说:

恰巧的是传入值为2的时候,可以绕过DLL_ATTACH_THREAD,大家都懂得。
另外就是传入值为4的时候,貌似OD里对这个线程里的代码下断会飞,似乎是hidefromdbgger的功能。

由于目前能力有限,先把这个信息留在这里,回头懂了再回来进行实验。

ZwCreateThreadEx 函数

利用这个函数和CreateRemoteThread相比,原理是相同的,但这个函数更为底层

由于ZwCreateThreadEx函数在ntdll.dll中没有定义,所以需要使用GetProcAddress从ntdll.dll中获取函数的导出地址

我们要在文件开头自己声明这个函数的参数:

#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	ULONG CreateThreadFlags,
	SIZE_T ZeroBits,
	SIZE_T StackSize,
	SIZE_T MaximumStackSize,
	LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
	PHANDLE ThreadHandle,
	ACCESS_MASK DesiredAccess,
	LPVOID ObjectAttributes,
	HANDLE ProcessHandle,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	BOOL CreateSuspended,
	DWORD dwStackSize,
	DWORD dw1,
	DWORD dw2,
	LPVOID pUnkown);
#endif

这是个宏定义的函数,函数地址需要从GetProcAddress获得:

//加载ntdll.dll
HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
//获取ZwCreateThreadEx地址
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll,"ZwCreateThreadEx");

获取到这个函数之后,直接调用即可实现注入:

HANDLE hRemoteThread = NULL;
DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread,PROCESS_ALL_ACCESS,NULL,hProcess,(LPTHREAD_START_ROUTINE)pFunprocAddr,pDllAddr,0,0,0,0,NULL);

注入的话,最后5个参数全是0

代码示例:

BOOL ZwCreateThreadEx_InjectDLLW(DWORD dwPid,LPCWSTR pszDllFileName) {
	//打开进程,获取进程句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid);
	if (hProcess == NULL) {
		return FALSE;
	}
	//在注入进程中申请内存
	DWORD dwSize = 1 + wcslen(pszDllFileName)*2;
	LPVOID pDllAddr = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_READWRITE);
	if (pDllAddr == NULL) {
		return FALSE;
	}
	//在目标内存写入数据
	if (WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL) == NULL) {
		return FALSE;
	}
	//获取LoadLibraryA函数地址
	FARPROC pFunprocAddr = GetProcAddress(GetModuleHandleW(L"kernel32.dll"),"LoadLibraryW");
	if (pFunprocAddr == NULL) {
		return FALSE;
	}
	//加载ntdll.dll
	HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
	if (hNtdll == NULL) {
		return FALSE;
	}
	//获取ZwCreateThreadEx地址
	typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");
	if (ZwCreateThreadEx == NULL) {
		return FALSE;
	}
	//注入
	HANDLE hRemoteThread = NULL;
	DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread,PROCESS_ALL_ACCESS,NULL,hProcess,(LPTHREAD_START_ROUTINE)pFunprocAddr,pDllAddr,0,0,0,0,NULL);
	if (hRemoteThread == NULL) {
		return FALSE;
	}
	//关闭句柄
	CloseHandle(hRemoteThread);
	CloseHandle(hProcess);
	return TRUE;
}

卸载注入的DLL

卸载注入的DLL,和注入DLL的原理相同,都是通过创建远程线程去执行一个只有一个参数的函数来进行

卸载用到的函数是:FreeLibrary,参数为模块句柄,这个模块句柄可通过对目标进程创建快照,然后遍历其中的模块来获得,关于创建快照遍历模块相关内容,可从我以前的笔记中找到,这里就不再啰嗦了。

FreeLibrary 函数介绍

BOOL
WINAPI
FreeLibrary(
    _In_ HMODULE hLibModule    //模块句柄,可通过遍历获得
    );

代码实现

void CInjectDllDlg::UnInjectDll(DWORD dwPid, LPCSTR szDllName)
{
    // TODO: 在此处添加实现代码.
    if (dwPid == 0 || strlen(szDllName) == 0)
    {
        return;
    }

    //创建DLL快照
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
    MODULEENTRY32 me32;
    me32.dwSize = sizeof(me32);
	
    //这里寻找指定的dll信息,找到了后,结构体里的hModule参数里会存有dll模块句柄
    BOOL bRet = Module32First(hSnap,&me32);
    TCHAR szDllNameW[MAX_PATH] = { 0 };
    MultiByteToWideChar(CP_ACP, NULL, szDllName, -1, szDllNameW, strlen(szDllName));
    while (bRet) {
        if (lstrcmp(_wcsupr(me32.szExePath), _wcsupr(szDllNameW)) == 0) {
            break;
        }
        bRet = Module32Next(hSnap, &me32);
    }
    CloseHandle(hSnap);

    char* pFunName = "FreeLibrary";

    // 打开目标进程
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);

    if (hProcess == NULL)
    {
        return;
    }

    // 计算欲注入DLL文件完整路径的长度
    int nDllLen = strlen(szDllName) + sizeof(TCHAR);

    //获得函数的地址
    FARPROC pFunAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), pFunName);

    // 创建远程线程
    HANDLE hThread = CreateRemoteThread(hProcess,
        NULL, 0,
        (PTHREAD_START_ROUTINE)pFunAddr,//LoadLibraryA函数
        me32.hModule,        //参数是dll地址指针
        0, NULL);
    //WaitForSingleObject(hThread, INFINITE);//等待线程执行完毕

    CloseHandle(hThread);
    CloseHandle(hProcess);
}

评论