双11狂欢大促

盲盒赢 定制羽绒服

开启惊喜盲盒,抽取你的专属好礼

— 惊喜奖品抢先看 —

线程注入、HOOK APIs(附VC6源码)

BeanJoy BeanJoy 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区
  工作关系,想HOOK并修改一些API,使得不支持某些设备的第三方工具可以正常运行,因此花时间写了这么个工具。比如ReadFile时,某些设备不支持指定的缓存大小(如512KB),可以HOOK ReadFile,把缓存大小修改为更小,可能ReadFile就能正常工作,第三方工具也能正常使用。其实,只是想借工作这个契机,学习远程线程注入和HOOK API。工作上测试的设备和第三方工具运行在64位机上,还没有时间在64位机上修改并编译。

  运行DEMO说明:
  首先进入TestExe目录,打开MFCDialogApplication.exe,8个按钮分别简单的调用8个API,可一一点击查看效果,标题栏显示进程ID:

  打开DllImport.exe,弹出Console,以下显示的是我输入并HOOK完的界面:

  首先输入需要HOOK的进程ID,这里输入对话框进程ID12600。然后提示选择需HOOK的API,每个API有FLAG值,占一位,可以用位组合,这里输入511,即9个API都需HOOK。然后选择是HOOK还是UNHOOK,这里当然输入1。然后这个程序向对话框程序注入线程,调用DllExport.dll,DllExport.dll中HOOK这9个API。结果全部成功,见上图。HOOK对话框后,会在对话框进程中弹出一个Console窗口,用以显示相关信息,见下图:

  然后在对话框上一一点击各个按钮,并随时查看弹出的Console窗口中内容,单击完后,见下图:

  每点击一个按钮后,在Console中会显示相关信息。这些信息是HOOK时打印的,我只设置了打印简单的信息。还要注意,HOOK MessageBoxA和MessageBoxW时,改变了弹出消息框的标题、文字等。
  然后再打开DllImport.exe,把HOOK的API还原,并从对话框进程中卸载DllExport.dll,输入顺序与HOOK一致,只是第三步时需指定0,见下图:

  UNHOOK API时我选择的全部还原。完后,9个API不再被HOOK,正常执行,之前显示信息的Console关闭,对话框正常运行。所有API UNHOOK后,DllExport.dll被卸载,可改名、删除等。此时,再点击对话框按钮,就不再有任何显示显示。注意点击两个MessageBox后,消息框的标题与文字。

  编译说明:
  MFCDialogApplication是生成被注入的对话框工程;DllWorkspace中,DllExport生成需注入的DllExport.dll,DllImport生成执行注入操作的DllImport.exe。
  DllWorkspace工作空间中,两个项目都是用的安全字符串操作函数,如果出现找不到头文件,需要更新SDK,我的VC6 Include路径包括“C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include”和“C:\Program Files\Microsoft Visual Studio 9.0\VC\include”,即可编译通过。
  DllWorkspace下的两个项目,ANSI、UNICODE编译均可,既四种组合的编译,都可正常注入并HOOK。

  注入及HOOK代码简要说明:
  注入主要代码如下:

// create a remote thread, and start LoadLibrary to load dll that we make, in the dll, we can
// hook apis and can do everything we want
BOOL CreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pfnStartAddr, LPVOID pRemoteMem)
{
BOOL bRet = FALSE;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, pRemoteMem, 0, NULL);
do
{
if(!hThread)
{
PrintError(_T("CreateRemoteThread"), GetLastError(), __MYFILE__, __LINE__);
break;
}
TCOUT << _T("Waiting for the end of the remote thread...") << endl;
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
hThread = NULL;
bRet = TRUE;
} while (FALSE);
return bRet;
}

  用CreateRemoteThread创建一个远程进程下的线程,开始执行pfnStartAddr(为LoadLibrary的地址)地址处的代码,传递的参数为pRemoteMem(远程进程下的一段内存空间,保存的是DllExport.dll的路径),创建的远程线程由LoadLibrary开始执行,然后加载DllExport.dll,在DllExport.dll中可执行我们的处理。注意:DllExport.dll的路径是相对于被注入进程的,因此被注入进程需要能找到这个Dll。程序中szDllPath保存路径。
  dll运行时可以和执行注入的进程交换数据,我使用file-mapping实现,代码如下:

// write infomation to file-mapping, the infomation includes witch apis need to be (un)hooked,
// and includes the (un)hook results and so on
LPVOID WriteFileMapping(HANDLE hMap, CONTENT_FILE_MAPPING content)
{
LPVOID pContent = NULL;
do
{
if(!hMap)
{
PrintMsg(_T("WriteFileMapping fail : hMap is null, file : %s, line : %d\n"),
__FILE__, __LINE__);
break;
}
pContent = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(content));
if(!pContent)
{
PrintError(_T("MapViewOfFile"), GetLastError(), __MYFILE__, __LINE__);
break;
}
memcpy(pContent, &content, sizeof(content));
} while (FALSE);
return pContent;
}

  hMap是名字为宏NAME_FILE_MAPPING定义的一个file-mapping,然后向其中写入数据。dll执行时,再打开这个file-mapping,从中读取数据,再把结果写回。
  然后是卸载被注入进程中DllExport.dll的代码:

// if no apis be hooked, we must free the library
BOOL UnLoadModule(DWORD dwProcesssId, LPCTSTR lpModuleName)
{
BOOL bRet = FALSE;
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
MODULEENTRY32 me32;
HANDLE hProcess = NULL;
HMODULE hModule = NULL;

me32.dwSize = sizeof(me32);

hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwProcesssId);
do
{
if(!hProcess)
{
PrintError(_T("OpenProcess"), GetLastError(), __MYFILE__, __LINE__);
break;
}

hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcesssId);
if(INVALID_HANDLE_VALUE == hModuleSnap)
{
PrintError(_T("CreateToolhelp32Snapshot"), GetLastError(), __MYFILE__, __LINE__);
break;
}

if(!Module32First(hModuleSnap, &me32))
{
PrintError(_T("Module32First"), GetLastError(), __MYFILE__, __LINE__);
break;
}

int nRefCount = 0;
do
{
if(!StrCmpI(me32.szModule, lpModuleName) || !StrCmpI(me32.szExePath, lpModuleName))
{
hModule = me32.hModule;
nRefCount = me32.ProccntUsage;
break;
}
} while(Module32Next(hModuleSnap, &me32));

LPTHREAD_START_ROUTINE pfnStartAddr =
(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "FreeLibrary");
if (!pfnStartAddr)
{
PrintError(_T("GetProcAddress"), GetLastError(), __MYFILE__, __LINE__);
break;
}
for (int i = 0; i < nRefCount; i++)
{
HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, hModule, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CLOSE_HANDLE(hThread);
}
PrintMsg(_T("FreeLibrary %s in the process %d finished!\n"), lpModuleName, dwProcesssId);

bRet = TRUE;
} while (FALSE);

CLOSE_HANDLE(hModuleSnap);
CLOSE_HANDLE(hProcess);

return bRet;
}

  遍历被注入进程加载的模块,如果找到了DllExport.dll,得到被这个进程引用的次数,然后再循环创建远程线程使用FreeLibrary来卸载DllExport.dll。注意,DllExport.dll被加载几次,就必须执行几次FreeLibrary才能完全卸载。至此,DllExport.dll就可以任意操作了。
  HOOK代码简要说明:
  使用的是跳转法,先保存API的前几个字节,再把这几个字节设置为跳转到我们自己函数的地方去。我们自己的函数中,进行相应处理后,再执行保存的API前几个字节的代码,然后跳转到API相应的位置执行。代码如下:

// hook the specify api
// pRecallApiInfo : infomation of the api
BOOL HookSpecifyApi(PRECALL_API_INFO pRecallApiInfo)
{
BOOL bRet = FALSE;

do
{
if (!pRecallApiInfo)
{
break;
}
if (pRecallApiInfo->pOrgfnMem)
{
bRet = TRUE;
break;
}
HMODULE hModule = LoadLibrary(pRecallApiInfo->lpDllName);
if (!hModule)
{
PrintError(_T("LoadLibrary"), GetLastError(), __MYFILE__, __LINE__);
break;
}
USES_CONVERSION;
FARPROC pfnStartAddr = (FARPROC)GetProcAddress(hModule, T2CA(pRecallApiInfo->lpFunctionName));
pRecallApiInfo->lpApiAddr = pfnStartAddr;
if (!pfnStartAddr)
{
PrintError(_T("GetProcAddress"), GetLastError(), __MYFILE__, __LINE__);
break ;
}

// we must save the first few bytes of the api(at least five, and these few bytes must complete
// the assembly codes), then make the 5 bytes in front of api to jump to our function, and our
// function must execute the few bytes saved before, and then jump to the api to execute
// the rest code in the api
int nSize = 0;
int nDisassemblerLen = 0;
while(nSize < 5)
{
// GetOpCodeSize can get the assembly code size
nDisassemblerLen = GetOpCodeSize((BYTE*)(pfnStartAddr) + nSize);
nSize = nDisassemblerLen + nSize;
}

DWORD dwProtect = 0;
if (!VirtualProtect(pfnStartAddr, nSize, PAGE_EXECUTE_READWRITE, &dwProtect))
{
PrintError(_T("VirtualProtect"), GetLastError(), __MYFILE__, __LINE__);
break ;
}

// be sure that we must change pOrgfnMem's protect, because the code in pOrgfnMem
// also need to execute
pRecallApiInfo->pOrgfnMem = new BYTE[5 + nSize];
DWORD dwMemProtect = 0;
if (!VirtualProtect(pRecallApiInfo->pOrgfnMem, 5 + nSize, PAGE_EXECUTE_READWRITE, &dwMemProtect))
{
delete [] pRecallApiInfo->pOrgfnMem;
pRecallApiInfo->pOrgfnMem = NULL;
PrintError(_T("VirtualProtect"), GetLastError(), __MYFILE__, __LINE__);
break ;
}
pRecallApiInfo->nOrgfnMemSize = 5 + nSize;

memcpy(pRecallApiInfo->pOrgfnMem, pfnStartAddr, nSize);
*(BYTE*)(pRecallApiInfo->pOrgfnMem + nSize) = 0xE9;
*(DWORD*)(pRecallApiInfo->pOrgfnMem + nSize + 1)
= (DWORD)pfnStartAddr + nSize - (DWORD)(pRecallApiInfo->pOrgfnMem + 5 + nSize);
*(BYTE*)(pfnStartAddr) = 0xE9;
*(DWORD*)((BYTE*)pfnStartAddr + 1) = (DWORD)pRecallApiInfo->lpRecallfn - ((DWORD)pfnStartAddr + 5);
memset((BYTE*)pfnStartAddr + 5, 0x90, nSize - 5);
// be sure that we must set the rest to 0x90(assembly code for nop, do nothing,
// and occupy one byte), because we should't change the assembly code

VirtualProtect(pfnStartAddr, nSize, dwProtect, &dwProtect);

bRet = TRUE;
} while (FALSE);

return bRet;
}

  需要注意的是:保存的可能并不是API的前5个字节,因为前5个字节可能不是一个或几个完整的汇编指令,比如第5、6个字节合起来才是一个指令,我们就不能只保存前5个字节,最后执行这5个字节,再跳转到第6个字节处执行。这样破坏了指令,必然造成崩溃。这时需要保存前6个字节才行。程序中,我使用了从网上找到的一段代码GetOpCodeSize,GetOpCodeSize可以得到当前地址处的汇编指令长度。然后保存API前至少5个字节,并且这些字节可以组成完整的汇编指令。实际也可以不用这样,可以用另一方式,我们函数中先恢复API的前5个字节,然后再调用API,调用完后再改API前5个字节为跳转到我们函数的指令。但是,这种方式并不好,如果调用API时,API的前5个字节正常,如果再有进程中其他线程调用API,这时流程完全正常,没有被HOOK。
  另外,还需要修改保存API前几个字节内存的属性,因为这些内存是需要执行的,因此修改为可读、可写、可执行。代码修改pRecallApiInfo->pOrgfnMem段内存在属性。
  最后,如果保存的API前5个以上的字节,比如保存的6个字节,还需要把第6个字节修改为0x90,编译指令为NOP,不执行任何操作。否则,第6个字节可能和后面的几个字节组合成新的指令,也是不正确的。其实,这里也可以不修改,因为我们是直接跳到第7个字节执行的,既使第6、7个字节组合成一个新指令也没关系,因为不是从第6个指令开始执行的。但是,这样处理后,调试方便,打开汇编窗口,一目了解。

  还原就简单了,直接用之前保存的字节恢复即可。
  我们替换API的函数代码如下:

int WINAPI MyMessageBoxA(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType)
{
int nOrderHookApi = ORDER_MESSAGEBOXA;

int nRet = 0;
static int i = 1;
if (g_arHookAPIs[nOrderHookApi].pOrgfnMem)
{
USES_CONVERSION;
PrintMsgA("%-18s %08d : 0x%08x \"%s\" \"%s\" 0x%08x\n",
T2CA(g_arHookAPIs[nOrderHookApi].lpFunctionName),
i++, hWnd, VALID_CHAR(lpText), VALID_CHAR(lpCaption), uType);

nRet = ((pfnMessageBoxA)(LPVOID)g_arHookAPIs[nOrderHookApi].pOrgfnMem)(
hWnd, "HelloWorld", "Caption", MB_OKCANCEL);
}
return nRet;
}

  最后一定要从保存的API的地址处开始执行。
  代码中已实现同时HOOK9个API(MessageBoxA、MessageBoxW、DeviceIoControl、CreateFileA、CreateFileW、ReadFile、ReadFileEx、WriteFile、WriteFileEx),稍加修改,即可实现HOOK更多的API。
  编译环境:Windows XP SP3、VC++ 6.0 SP6

  博客原文:线程注入、HOOK APIs(附VC6源码)
  源码及DEMO下载地址:线程注入、HOOK APIs(附VC6源码)
...全文

相关推荐

【信息科学与工程学】【安全领域】第一百四十二篇 零信任网络解决方案中的算法04

对于节点 i, 有功和无功功率平衡方程为: Pi​=Vi​∑j=1N​Vj​(Gij​cosθij​+Bij​sinθij​), Qi​=Vi​∑j=1N​Vj​(Gij​sinθij​−Bij​cosθij​)。下风向某点 (x,y,z)的浓度 C为: C=2πuσy​σz​Q​exp(−2σy2​y2​)[exp(−2σz2​(z−H)2​)+exp(−2σz2​(z+H)2​)], 其中 Q是源强, u是风速, H是有效源高, σy​,σz​是水平和垂直扩散参数。

flyair_China

hook dx

一个hook dx的程序,很不错的,值得学习

azhe530

前端开源库-sails-hook-autoreload-fork

前端开源库-sails-hook-autoreload-fork帆钩自动加载叉,帆JS钩自动加载控制器和模型时,改变。

weixin_38743506

madCollection 2.5.6.0 安装版(无源码)

madCollection 2.5.6.0 安装版(无源码) MadCollection,是一款集成在 Delphi (D5, D6, D7, D8, D9, BDS2006, D2007) IDE 下的辅助工具,能快速生成带 Error Report 功能的应用程序。能将错误报告发送到指定邮箱、服务器、记录在本地文件中等。其报告可具体指出出错的代码行,以及机器当前信息、寄存器信息等诸多重要诊断信息。还可以完全自定义出错后的界面。The "madBasic" package contains several units, which implement a lot ofbasic functionality like standard type definitions (madTypes), string handling (madStrings), simplified message handling (madTools), compression(madZip) and crypting and encoding (madCrypt). Then the base interfaces for the other mad* packages are defined (unit madBasic). Finally some lists are implemented (madLists). As a bonus I've recently added a unit which can modify the resources of dlls/exes (unit madRes).The package "madCodeHook" offers everything you need to hook code (mostly APIs). Usually such hooks show effect only in the current process. But madCodeHook also can inject a specified DLL into any desired 32bit process or even "system wide" (that is into all currently running and into all in the future created processes). So by putting your hooking stuff into a little DLL and injecting it "system wide", you can realize effective and easy to use system wide API hooking. Of course everything works just as fine under both OS families (9x/NT).The package "madDisAsm" features a full x86 disassembler including MMX, 3dNow enhanced, SSE and SSE2 support. The isassembler can examine a single x86 instruction (see ParseCode) or a full function (see ParseFunction) and either return a short analysis or a full text disassembly. Register contents are watched/followed if possible, this improves the analyses forjump/call targets. Case/switch jump tables are automatically detected and handled correctly.The package "madExcept" was exactly build for that purpose, namely to replace Delphi's standard exception handling with a new logic, which finds out the full callstack with as much details and infos as possible. The exact behaviour of ma

冷月宫主

(源码)基于Flask和React的数据库CRUD管理系统.zip

# 基于Flask和React的数据库CRUD管理系统 ## 项目简介 本项目是基于Flask后端框架和React前端框架搭建的数据库CRUD管理系统。采用前后端分离的三层架构模式,功能完整,支持多种数据库,并已部署至Heroku。用户输入数据库配置信息后,可对相应数据表和字段进行前端显示和增、删、改、查操作,同时具备登录注册、查看系统操作日志、用户权限管理等功能。 ## 项目的主要特性和功能 1. 自动获取表和字段信息利用Sqlalchemy中的Automap自动映射数据库中已有的表和字段信息。 2. CRUD操作基于flaskrestful建立REST APIs,借助flasksqlalchemy实现对选定数据表中记录的增、删、改、查。 3. 用户认证前端使用React Hook Form构建登录注册表单,通过React Token Auth管理用户登录状态和Token后端采用flaskjwtextended进行Token校验。

静默小音箱
评论 74
收藏 已收藏
分享
返回顶部
BeanJoy
进程/线程/DLL 15,466 成员 49,169 内容