尝试使用C语言来进行dll注入和inline hook

尝试使用C语言来进行dll注入和inline hook

第一次写博客嘿嘿。
关于inline hook,网上很多文章都有很详尽的介绍了。大致就是针对一个API,在本进程的地址空间中获取其函数地址以后,对开头的五个字节进行一个修改,把它修改为一个0xe9机器码开头的长转移指令。令它转移到我们的目的函数,从而完成一次钩取。

这个例子就尝试钩取ntdll.dll中的ZwSetInformationThread函数,这个函数可以用作反调试,通过给第二个参数传入对应枚举:THREADINFOCLASS中的0x11来完成一个对调试端口的置零,让正在调试本进程的调试器无法继续接受调试事件从而完成一个简单的反调试机制:

typedef NTSTATUS (_stdcall* PSETINFORMATIONTHREAD)(HANDLE, BYTE, PVOID, ULONG);

为了简单操作,在函数定义时把第二个参数写成BYTE类型,然后传入0x11就好了(但是不规范)

然后我的钩取实验想法是自己随意编写一个C程序,然后再编写注入代码来注入包含下钩代码的dll。所以大体上就需要三个c程序:
一个是用来被注入的实验程序
一个是dll
最后一个是用来将dll注入实验程序的程序。

实验程序的代码可以随意写,只要包含对ntdll.ZwSetInformationThread函数的调用就行了。
代码段如下:

typedef LONG (_stdcall *MYTHREADHIDE)(HANDLE, BYTE, PVOID, ULONG);
int main(){
	int num;
	scanf("%d", num);
	HMODULE hmod = LoadLibrary(TEXT("Ntdll.dll"));
	if(hmod == NULL){
		printf("getdllfailed");
		return -1;
	}
	MYTHREADHIDE pmyhide = (MYTHREADHIDE)GetProcAddress(hmod, "ZwSetInformationThread");
	if(pmyhide == NULL){
		printf("getprocfailed");
		return -1;
	}
	pmyhide(GetCurrentThread(), 0x11, NULL, NULL);//调用ZwSetInformationThread
	return 0;
}

开头的scanf是希望在调用ZwSetInformationThread函数之前就钩取到这个函数。(即在od中测试运行,不输入这个num,直到我们将dll注入后在执行后面的代码,以此来验证钩取效果)

然后写dll程序:
全局变量的定义:

BYTE pOrgByte[5] = {0, };

首先我们先要编写dllmain函数,我们希望在dll被注入进实验程序的立刻进行下钩,所以代码可以这样写:

BOOL __stdcall DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved){
	HANDLE hThread = NULL;
	switch(fdwReason){
	case DLL_PROCESS_ATTACH:
		hook(pOrgByte);
		break;
	case DLL_PROCESS_DETACH:
		unhook(pOrgByte);
		break;
	}
	return TRUE;
}

那个hook函数即是我们的下钩函数。至于为什么要有pOrgByte变量。是为了保存原本api的前五个字节。对于inline hook 我们如果希望在替换的函数中还能调用原本的api的话需要pOrgByte,同时也可以用于脱钩,不影响其他地方调用原始api。
所以我的想法就是在替换原始ZwSetInformationThread的函数再次调用这个原始函数,但是传入的第二个参数是0而不是0x11,这样一来就可以达到反反调试。当然大家也可以不调用并直接返回,我这里这么做是想完成一个完整的下钩和脱钩过程。
所以替换函数我这样写:

#pragma optimize("", off)
NTSTATUS _stdcall MYSETINFORMATIONTHREAD(HANDLE hThread, BYTE ThreadInforClass, PVOID ThreadInfor, ULONG ThreadInforLen){
	unhook(pOrgByte);
	HINSTANCE hLib = LoadLibrary(L"Ntdll.dll");
	PSETINFORMATIONTHREAD psit = (PSETINFORMATIONTHREAD)GetProcAddress(hLib, "ZwSetInformationThread");
	printf("GetProc:%d\n", GetLastError());
	NTSTATUS status = (psit)(hThread, 0, 0, 0);
	hook(pOrgByte);
	return status;
}
#pragma optimize("", on)

代码一开是要进行脱钩(unhook),因为我接下里要调用原来的ZwSetInformationThread API,所以如果我不进行脱钩的话,那就会形成一个死调用。因为我原本hook了以后,那个原来的api的开头五个直接被我改成长转移到这个函数,然后这个函数在继续调用的话就还是跳转到我这个函数而形成一个死循环。所以这里大家可以根据自己的hook需求来决定要不要编写这个脱钩函数,如果你hook 的过程只是想单纯的拦截程序对这个函数的所有的调用那就不需要脱钩。

然后是hook函数:

BOOL hook(PBYTE	pOrgBytes){
	HMODULE hMod = LoadLibrary(TEXT("Ntdll.dll"));
	FARPROC pOrgFunc = GetProcAddress(hMod, "ZwSetInformationThread");
	//printf("%d\n", (DWORD)pOrgFunc);
	DWORD dwOldProtect, dwAddress;
	BYTE pBuf[5] = {0xe9, 0, };
	::VirtualProtect((LPVOID)pOrgFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	memcpy(pOrgBytes, pOrgFunc, 5);
	dwAddress = (DWORD)MYSETINFORMATIONTHREAD - (DWORD)pOrgFunc - 5;
	memcpy(&pBuf[1], &dwAddress, 4);
	memcpy(pOrgFunc, pBuf, 5);
	::VirtualProtect((LPVOID)pOrgFunc, 5, dwOldProtect, NULL);
	return TRUE;
}

前两行用于获取原API的地址,然后后面的pBuf用来生成我们要用于长跳转的五个字节,注意到第一个字节就是长跳转的机器码。
然后通过使用virtualprotect函数来让原API的地址可写,然后dwAddress用来保留计算要跳转的后四个字节,因为跳转的机器码是按照相对地址来的而不是绝对地址,所以我要用我们的替换函数的地址减掉原API的地址在减掉5个字节,因为长转移本身就占五个字节。
计算好的地址通过memcpy传到pBuf里面,然后后面一个memcpy就是用来将原始的五个直接保留到全局变量里面,以便于我们的脱钩。

脱钩函数unhook:

BOOL unhook(PBYTE	pOrgBytes){
	HMODULE hMod = LoadLibrary(TEXT("Ntdll.dll"));
	FARPROC pOrgFunc = GetProcAddress(hMod, "ZwSetInformationThread");
	DWORD dwProtect;
	::VirtualProtect((LPVOID)pOrgFunc, 5, PAGE_EXECUTE_READWRITE, &dwProtect);
	memcpy(pOrgFunc, pOrgBytes, 5);
	::VirtualProtect((LPVOID)pOrgFunc, 5, dwProtect, NULL);
	return TRUE;
}

代码很好理解,参照hook就可以了。

这样我们就完成了dll 的编写,通过使用vs来生成dll就ok了。

然后就是我们需要将这个dll注入到实验程序:
我们采用最常用的方法就是通过CreateRemoteThread这个api,来在实验程序中建立以远程线程,命令这个远程线程来执行loadlibrary函数,来在进程中加载编写好的dll,从而达到dll注入的目的。

int main(int argc, CHAR* argv[]){
	BOOL INX = SetPrivilege(SE_DEBUG_NAME, TRUE);
	if(argc != 2){
		printf("arg wrong\n");
		return -1;
	}
	CHAR* szDllPath = "myhack.dll";
	DWORD dwPid;
	dwPid = atoi(argv[1]);
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if(hProcess == NULL){
		printf("open process failed:%d", GetLastError());
		return -1;
	}
	DWORD dwBufSize = strlen(szDllPath) + 1;
	LPVOID pRemoteBuf = ::VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
	::WriteProcessMemory(hProcess, pRemoteBuf, (LPCVOID)szDllPath, dwBufSize, NULL);
	LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA");
	HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	::WaitForSingleObject(hThread, INFINITE);
	::VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return 0;
}

采用pid的方式获取进程句柄,所以我们执行这个程序的时候可以使用命令行来带参数执行,通过输入实验程序的pid来完成注入。

关于注入过程Jeffrey Richter在其《windows核心编程》一书中有着非常详尽的介绍。这里就不赘述了。

然后就来验证一下钩取过程:
在这里插入图片描述
第一个程序是用来注入的,第二个是dll,第三个是实验程序。

我们用od打开第三个程序:
断在我们想要钩取的函数这里
在这里插入图片描述
然后跟进,看看是怎样的:
在这里插入图片描述
跟进是这样的,前五个字节是这样的,然后我们执行完这个函数,程序被终止,代表反调试成功了,现在我们就要注入dll,然后hook掉这个函数。(ctf的同学碰到这个反调试就可以直接改第二个参数为0就可以过反调试了。)

重启这个程序,F9跑起来,然后我在命令行不输入这个scanf,先让dll注入进去。
可以打开任务管理器或者process explorer来查看这个实验程序的pid,然后进行注入:
在这里插入图片描述
在这里插入图片描述
这个时候我们在od中alt+m查看内存:可以看到myhack.dll已经被注入进去了:
在这里插入图片描述
process explorer也可以看到:
在这里插入图片描述

然后我们就可以在实验程序中随意输入一个数字并跟进ZwSetInformationThread,看看发生了什么:
在这里插入图片描述
原来的指令就这样被修改了。所以hook成功了,然后可以跟进这个长转移,然后单步跟进直到再一次调用ZwSetInformationThread函数,来验证脱钩效果:
在这里插入图片描述
脱钩成功了,然后继续执行下去,也不会因为反调试而导致程序直接终止。
inline hook非常好用,但是如果想针对一些大型的程序通过inline hook关键的api来完成对程序功能的篡改,就要斟酌一下,因为如果程序本身会大量的调用这个api的话,使用inline hook就会拖慢程序的执行效率,因为程序会不断的进行一个下钩和脱钩的过程。

参考书目:李承远教授《逆向工程核心原理》
(完)

  • 0
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论
<p> 现在的网络在线教学会教你写驱动、写应用层代码、写界面、还会教你玩逆向、以及一些调试技巧。但在安全软件开发领域,却没有教程会教你从实际的项目角度去切入,如何灵活的应用这些知识,最终打造一款安全产品出。它涵盖了通过对安全趋势感受选择方向,确定后分析威胁(分析大部分样本)归纳其共性后,进而推导出防御思路,单调的防御思路还不足以形成产品,此时需要思考还需添加哪些功能才能使其足够成熟,能够满足基本产品级要求。再对所有功能进行归纳划分,最后产生成熟的架构。再根据架构开始进行驱动、应用层、界面的代码编写,这中间踩过的坑,遇到的坎,我都将一一为大家呈现。看到这里,估计你的心中也有点了然了。<br /> 是的,这真的可能是目前网络安全开发领最硬核的在线教程了。那么你准备好了吗?<br /> <br /> 本课程的前面两章,讲解如何感受与判断当前的安全趋势,如何判断某一种威胁是否具有流行的潜力;如何产生对抗思路;如何产生最终安全产品的技术架构并梳理出核心流程。虽然只有两章,但其实关于设计模式,技术选型的批判与自我批判将贯穿本课程所有章节。<br /> <br /> 第三章讲解应用层核心服务模块设计与实现,包括与各模块间的互交通讯、握手处理等;也将重点讲解在安全产品通讯设计中,如何抉择哪些通讯需加密,哪些无需,为什么?当然还包括核心服务模块的日常事物的处理等。核心模块是整个产品架构的通讯中枢,乃重中之重,通过学习核心模块设计实现便可掌握整个安全软件的具体运作流程(套路)。为自己将设计安全软件打个基础。同时也会涉及到逆向工程,讲解当你需要一个功能但手上没有相关代码,并且网络上给出的代码都不成熟的情况下,如何去寻找合适的成熟产品进行逆向。<br /> <br /> 第四章的重点在于探讨在加密算法的安全性问题。在特定的环境下,原安全的加密算法可能变得不安全,同样的原不安全的加密算法也可能变得安全。本节会告诉你如何灵活的去理解并运用。<br /> <br /> 第五章前十节是讲解驱动基本知识,开发环境搭建、windbg使用(有彩蛋)、编写windbg高级调试脚本、Verifier使用技巧等。然后对内核hook安装与卸载的稳定性进行深入探讨与论证实验(包括了普通hookinlinehook),最后得出第一手结论(说它是第一手是因为目前网络上的资料还没有人给出这样的结论),此过程中深入分析了360对KiFastCallEntry(高频函数)进行hook时分别使用两种模式的原因。前十章能使你的驱动代码稳定性上升到一个新台阶。后面则讲解终结者的sfilter框架,包括如何在驱动中使用各种数据结构:链表、延展树、资源锁、消息传递注意点等等。因为目前网络上的资料,对内核中的各种小花招讲的很多,但是对各种数据结构设计、消息的处理与衔接之类的讲的太少,或者说压根就没讲。而本章会也会重点讲解这一块,因为这才是安全软件稳定性的第一保障!<br /> <br /> 第六章是探讨面对安全威胁时,如何针对实际情况设计相应的清除思路,然后讲解守护模块在安装时如何启动服务,以及在安装后是如何与服务实现互相守护。<br /> <br /> 第七章前五节是对SOUI这个界面库进行介绍,并且讲解了布局,系统控件,自定义控件的使用方法。同时也会讲解选择SOUI界面库的原因,这是一个及其简单易用的界面库(比qt之类的简单多了),你只要会点MFC的基本知识就能写个炫酷界面。然后讲解终结者【引导页】的布局设计与源码剖析、【主界面】与【威胁消息回调页面】布局与源码剖析;最后讲多种启动模式的设计与处理,接口的处理等。<br /> <br /> 第八章讲解安全类软件在编写卸载程序与安装包时的一些注意事项,当然也包括了这两类模块的编写方法。<br /> <br /> <br /> <br /> 预备知识<br /> <br /> 预备知识的最低要求是必须熟练掌握C语言。<br /> 什么?你对windows api不熟,没事,我们有源码!<br /> 什么?你对内核api不熟,没事,我们有源码!<br /> 什么?你对界面编程不熟,没事,我们有源码!<br /> 我们不仅有源码,我们还管售后,我们是三人团队,一个日常事务,两个技术售后(包括我)。大家购买课程以后,可以加下这个qq群:698220527【安全软件开发实战】 群主是【铁汉】,QQ号:66854746 看仔细了哈。<br /> 我们首先会把源码发你,如果你有学不明白的地方可以在里面提出,大家互相切磋。<br /> <br /> 注意<br /> <br /> 每位学员送一套【勒索软件终结者】源码,可以看着源码跟着课程进行学习!关键章节都带有课后作业,分为必做题非必做题。我对必做题也进行了“爱”设计,它可以确保一个事情:如果你做得出,就代表你一定看懂了整体框架运作流程。做不出那一定是没看懂!不会存在看懂了但做不出,或者做出了但没看懂的情况。作业的设计,本身也是一种攻防嘛。<br /> <div> <br /> </div> </p> <p> <span></span> </p>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页
评论

打赏作者

weixin_43350252

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值