皮皮网
皮皮网

【源码精灵粘土】【刀剑神域插件源码】【巨人猎手怎么修改源码】iat注入源码

来源:ygbook最新源码 发表时间:2024-11-25 10:32:56

1.屏幕取词
2.十种注入技巧 | 通用性进程注入技巧研究
3.干货分享丨表哥带你学习导入表及导入表注入

iat注入源码

屏幕取词

       â€œé¼ æ ‡å±å¹•å–词”技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在WINDOWS系统中实现却是非常复杂的,总的来说有两种实现方式:

        第一种:采用截获对部分GDI的API调用来实现,入源如TextOut,TextOutA等。

        第二种:对每个设备上下文(DC)做一分Copy,并跟踪所有修改上下文(DC)的操作。

        第二种方法更强大,但兼容性不好,而第一种方法使用的截获WindowsAPI的调用,这项技术的强大可能远远超出了您的想象,毫不夸张的说,利用WindowsAPI拦截技术,你可以改造整个操作系统,事实上很多外挂式Windows中文平台就是这么实现的!而这项技术也正是这篇文章的主题。

        截WindowsAPI的调用,具体的说来也可以分为两种方法:

        第一种方法通过直接改写WinAPI 在内存中的映像,嵌入汇编代码,使之被调用时跳转到指定的地址运行来截获;第二种方法则改写IAT(Import Address Table 输入地址表),重定向WinAPI函数的调用来实现对WinAPI的截获。

        第一种方法的实现较为繁琐,而且在Win、下面更有难度,这是因为虽然微软说WIN的API只是为了兼容性才保留下来,程序员应该尽可能地调用位的API,实际上根本就不是这样!WIN 9X内部的大部分位API经过变换调用了同名的位API,也就是说我们需要在拦截的函数中嵌入位汇编代码!

        我们将要介绍的是第二种拦截方法,这种方法在Win、和NT下面运行都比较稳定,兼容性较好。由于需要用到关于Windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、PE(Portable Executable)文件格式和IAT(输入地址表)等较底层的知识,所以我们先对涉及到的这些知识大概地做一个介绍,最后会给出拦截部分的关键代码。

        先说Windows虚拟内存的管理。Windows9X给每一个进程分配了4GB的地址空间,对于NT来说,这个数字是2GB,系统保留了2GB到 4GB之间的地址空间禁止进程访问,而在Win9X中,2GB到4GB这部分虚拟地址空间实际上是由所有的WIN进程所共享的,这部分地址空间加载了共享Win DLL、内存映射文件和VXD、内存管理器和文件系统码,Win9X中这部分对于每一个进程都是可见的,这也是Win9X操作系统不够健壮的原因。

        Win9X中为位操作系统保留了0到4MB的地址空间,而在4MB到2GB之间也就是Win进程私有的地址空间,由于 每个进程的地址空间都是相对独立的,也就是说,如果程序想截获其它进程中的API调用,就必须打破进程边界墙,向其它的进程中注入截获API调用的代码,这项工作我们交给钩子函数(SetWindowsHookEx)来完成,关于如何创建一个包含系统钩子的动态链接库,《电脑高手杂志》已经有过专题介绍了,这里就不赘述了。

        所有系统钩子的函数必须要在动态库里,这样的话,当进程隐式或显式调用一个动态库里的函数时,系统会把这个动态库映射到这个进程的虚拟地址空间里,这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈,也就是说动态链接库中的代码被钩子函数注入了其它GUI进程的地址空间(非GUI进程,钩子函数就无能为力了),当包含钩子的DLL注入其它进程后,就可以取得映射到这个进程虚拟内存里的各个模块(EXE和DLL)的基地址,如:

       HMODULE hmodule=GetModuleHandle(“Mypro.exe”);

        在MFC程序中,我们可以用AfxGetInstanceHandle()函数来得到模块的基地址。EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win工程时,VC++链接器使用缺省的基地址0x。可以通过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x处,DLL也随之有不同的基地址,通常被映射到不同进程的相同的虚拟地址空间处。

        系统将EXE和DLL原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样的。即PE (Portable Executable) 文件格式。我们得到了进程模块的基地址以后,就可以根据PE文件的格式穷举这个模块的IMAGE_IMPORT_DESCRIPTOR数组,看看进程空间中是否引入了我们需要截获的函数所在的动态链接库,比如需要截获“TextOutA”,就必须检查“Gdi.dll”是否被引入了。

        说到这里,我们有必要介绍一下PE文件的格式,如右图,这是PE文件格式的大致框图,最前面是文件头,我们不必理会,从PE File Optional Header后面开始,就是文件中各个段的说明,说明后面才是真正的段数据,而实际上我们关心的只有一个段,那就是“.idata”段,这个段中包含了所有的引入函数信息,还有IAT(Import Address Table)的RVA(Relative Virtual Address)地址。

        说到这里,截获WindowsAPI的整个原理就要真相大白了。实际上所有进程对给定的API函数的调用总是通过PE文件的一个地方来转移的,这就是一个该模块(可以是EXE或DLL)的“.idata”段中的IAT输入地址表(Import Address Table)。在那里有所有本模块调用的其它DLL的函数名及地址。对其它DLL的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到DLL真正的函数入口。

        具体来说,我们将通过IMAGE_IMPORT_DESCRIPTOR数组来访问“.idata”段中引入的DLL的信息,然后通过IMAGE_THUNK_DATA数组来针对一个被引入的DLL访问该DLL中被引入的每个函数的信息,找到我们需要截获的函数的跳转地址,然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。

        讲了这么多原理,现在让我们回到“鼠标屏幕取词”的专题上来。除了API函数的截获,要实现“鼠标屏幕取词”,还需要做一些其它的工作,简单的说来,可以把一个完整的取词过程归纳成以下几个步骤:

        1. 安装鼠标钩子,通过钩子函数获得鼠标消息。

        使用到的API函数:SetWindowsHookEx

        2. 得到鼠标的当前位置,向鼠标下的窗口发重画消息,让它调用系统函数重画窗口。

        使用到的API函数:WindowFromPoint,ScreenToClient,InvalidateRect

        3. 截获对系统函数的调用,取得参数,也就是我们要取的词。

        对于大多数的Windows应用程序来说,如果要取词,我们需要截获的是“Gdi.dll”中的“TextOutA”函数。

        我们先仿照TextOutA函数写一个自己的MyTextOutA函数,如:

       BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)

       {

       // 这里进行输出lpszString的处理

       // 然后调用正版的TextOutA函数

       }

        把这个函数放在安装了钩子的动态连接库中,然后调用我们最后给出的HookImportFunction函数来截获进程对TextOutA函数的调用,跳转到我们的MyTextOutA函数,完成对输出字符串的捕捉。

        HookImportFunction的用法:

       HOOKFUNCDESC hd;

       PROC pOrigFuns;

       hd.szFunc="TextOutA";

       hd.pProc=(PROC)MyTextOutA;

       HookImportFunction (AfxGetInstanceHandle(),"gdi.dll",&hd,pOrigFuns);

        下面给出了HookImportFunction的源代码,相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的很难,Ok,Let’s Go:

       ///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////

       #include <crtdbg.h>

       // 这里定义了一个产生指针的宏

       #define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))

       // 定义了HOOKFUNCDESC结构,我们用这个结构作为参数传给HookImportFunction函数

       typedef struct tag_HOOKFUNCDESC

       {

       LPCSTR szFunc; // The name of the function to hook.

       PROC pProc; // The procedure to blast in.

       } HOOKFUNCDESC , * LPHOOKFUNCDESC;

       // 这个函数监测当前系统是否是WindowNT

       BOOL IsNT();

       // 这个函数得到hModule -- 即我们需要截获的函数所在的DLL模块的引入描述符(import descriptor)

       PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);

       // 我们的主函数

       BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule,

       LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)

       {

       /////////////////////// 下面的代码检测参数的有效性 ////////////////////////////

       _ASSERT(szImportModule);

       _ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));

       #ifdef _DEBUG

       if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));

       _ASSERT(paHookFunc.szFunc);

       _ASSERT(*paHookFunc.szFunc != '\0');

       _ASSERT(!IsBadCodePtr(paHookFunc.pProc));

       #endif

       if ((szImportModule == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))

       {

       _ASSERT(FALSE);

       SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

       return FALSE;

       }

       //////////////////////////////////////////////////////////////////////////////

       // 监测当前模块是否是在2GB虚拟内存空间之上

       // 这部分的地址内存是属于Win进程共享的

       if (!IsNT() && ((DWORD)hModule >= 0x))

       {

       _ASSERT(FALSE);

       SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);

       return FALSE;

       }

       // 清零

       if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC));

       // 调用GetNamedImportDescriptor()函数,来得到hModule -- 即我们需要

       // 截获的函数所在的DLL模块的引入描述符(import descriptor)

       PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);

       if (pImportDesc == NULL)

       return FALSE; // 若为空,则模块未被当前进程所引入

       // 从DLL模块中得到原始的THUNK信息,因为pImportDesc->FirstThunk数组中的原始信息已经

       // 在应用程序引入该DLL时覆盖上了所有的引入信息,所以我们需要通过取得pImportDesc->OriginalFirstThunk

       // 指针来访问引入函数名等信息

       PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule,

       pImportDesc->OriginalFirstThunk);

       // 从pImportDesc->FirstThunk得到IMAGE_THUNK_DATA数组的指针,由于这里在DLL被引入时已经填充了

       // 所有的引入信息,所以真正的截获实际上正是在这里进行的

       PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc->FirstThunk);

       // 穷举IMAGE_THUNK_DATA数组,寻找我们需要截获的函数,这是最关键的部分!

       while (pOrigThunk->u1.Function)

       {

       // 只寻找那些按函数名而不是序号引入的函数

       if (IMAGE_ORDINAL_FLAG != (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))

       {

       // 得到引入函数的函数名

       PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,

       pOrigThunk->u1.AddressOfData);

       // 如果函数名以NULL开始,跳过,继续下一个函数

       if ('\0' == pByName->Name[0])

       continue;

       // bDoHook用来检查是否截获成功

       BOOL bDoHook = FALSE;

       // 检查是否当前函数是我们需要截获的函数

       if ((paHookFunc.szFunc[0] == pByName->Name[0]) &&

       (strcmpi(paHookFunc.szFunc, (char*)pByName->Name) == 0))

       {

       // 找到了!

       if (paHookFunc.pProc)

       bDoHook = TRUE;

       }

       if (bDoHook)

       {

       // 我们已经找到了所要截获的函数,那么就开始动手吧

       // 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取

       MEMORY_BASIC_INFORMATION mbi_thunk;

       VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));

       _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,

       PAGE_READWRITE, &mbi_thunk.Protect));

       // 保存我们所要截获的函数的正确跳转地址

       if (paOrigFuncs)

       paOrigFuncs = (PROC)pRealThunk->u1.Function;

       // 将IMAGE_THUNK_DATA数组中的函数跳转地址改写为我们自己的函数地址!

       // 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用

       pRealThunk->u1.Function = (PDWORD)paHookFunc.pProc;

       // 操作完毕!将这一块虚拟内存改回原来的保护状态

       DWORD dwOldProtect;

       _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,

       mbi_thunk.Protect, &dwOldProtect));

       SetLastError(ERROR_SUCCESS);

       return TRUE;

       }

       }

       // 访问IMAGE_THUNK_DATA数组中的下一个元素

       pOrigThunk++;

       pRealThunk++;

       }

       return TRUE;

       }

       // GetNamedImportDescriptor函数的实现

       PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)

       {

       // 检测参数

       _ASSERT(szImportModule);

       _ASSERT(hModule);

       if ((szImportModule == NULL) || (hModule == NULL))

       {

       _ASSERT(FALSE);

       SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

       return NULL;

       }

       // 得到Dos文件头

       PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;

       // 检测是否MZ文件头

       if (IsBadReadPtr(pDOSHeader, sizeof(IMAGE_DOS_HEADER)) ||

       (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE))

       {

       _ASSERT(FALSE);

       SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

       return NULL;

       }

       // 取得PE文件头

       PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);

       // 检测是否PE映像文件

       if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||

       (pNTHeader->Signature != IMAGE_NT_SIGNATURE))

       {

       _ASSERT(FALSE);

       SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);

       return NULL;

       }

       // 检查PE文件的引入段(即 .idata section)

       if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)

       return NULL;

       // 得到引入段(即 .idata section)的指针

       PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,

       pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

       // 穷举PIMAGE_IMPORT_DESCRIPTOR数组寻找我们需要截获的函数所在的模块

       while (pImportDesc->Name)

       {

       PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc->Name);

       if (stricmp(szCurrMod, szImportModule) == 0)

       break; // 找到!中断循环

       // 下一个元素

       pImportDesc++;

       }

       // 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!

       if (pImportDesc->Name == NULL)

       return NULL;

       // 返回函数所找到的模块描述符(import descriptor)

       return pImportDesc;

       }

       // IsNT()函数的实现

       BOOL IsNT()

       {

       OSVERSIONINFO stOSVI;

       memset(&stOSVI, NULL, sizeof(OSVERSIONINFO));

       stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

       BOOL bRet = GetVersionEx(&stOSVI);

       _ASSERT(TRUE == bRet);

       if (FALSE == bRet) return FALSE;

       return (VER_PLATFORM_WIN_NT == stOSVI.dwPlatformId);

       }

       /////////////////////////////////////////////// End //////////////////////////////////////////////////////////////////////

       ä¸çŸ¥é“在之前,有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术,也只有尝试过的朋友才能体会到其间的不易,尤其在探索API函数的截获时,手头的几篇资料没有一篇是涉及到关键代码的,重要的地方都是一笔代过,MSDN更是显得苍白而无力,也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA,微软还隐藏了多少秘密,好在硬着头皮还是把它给攻克了,希望这篇文章对大家能有所帮助。

十种注入技巧 | 通用性进程注入技巧研究

       进程注入是恶意软件和无文件攻击中的一种逃避技术,允许在另一个进程的入源地址空间内运行自定义代码。这种隐蔽性提高了攻击的入源效率。本文将介绍十种常见的入源进程注入技术,以及如何通过这些技术在现实场景中注入恶意代码。入源

       1. **DLL注入方式**:利用`CREATEREMOTETHREAD`和`LOADLIBRARY`进行注入是入源源码精灵粘土常用方法。恶意软件将恶意动态链接库(DLL)路径写入目标进程的入源虚拟地址空间,并通过创建远程线程确保其加载。入源图1显示了此过程的入源屏幕截图。

       2. **PE文件注入**:此技术允许恶意软件直接在其代码中注入PE文件,入源而无需在磁盘上创建额外的入源DLL。内存中执行的入源刀剑神域插件源码优势在于无需额外文件,但缺点是入源PE文件的新基址可能不可预测,需要动态计算其绝对地址。入源图3展示了这一技术的入源结构。

       3. **进程HOLLOWING**:恶意软件可以直接替换宿主进程的内存空间,用恶意可执行文件覆盖合法代码,从而实现隐蔽的注入。图5演示了Ransom.Cryak如何实现进程hollowing。

       4. **线程执行劫持**:通过劫持现有线程避免进程或线程创建操作的噪声,提高隐蔽性。图7给出了通用木马程序利用此技术的实例。

       5. **SETWINDOWSHOOKEX注入**:利用挂钩技术拦截特定线程事件,巨人猎手怎么修改源码加载恶意DLL。Locky Ransomware是使用此技术的一个例子。图9展示了其注入过程。

       6. **注册表注入**:通过修改`AppInit_DLLs`、`AppCertDlls`和`IFEO`注册表项实现注入和维持注入。图和分别展示了木乃伊和Diztakun木马如何利用这些技术。

       7. **APC注入与AtomBombing**:通过异步过程调用控制线程执行,注入自定义代码。图展示了Amanahe恶意软件使用APC注入的实例。

       8. **窗口内存注入 (EWMI)**:PowerLoader等恶意软件利用资源管理器托盘窗口的额外窗口内存注入代码。图显示了注入过程。通达信 缺口 源码

       9. **SHIMS注入**:恶意软件利用Shims技术实现注入,通过修改API调用路径实现隐蔽。图展示了广告软件“Search Protect by Conduit”使用Shims进行注入和维持的例子。

       . **IAT和INLINE HOOKING**:通过修改导入地址表或直接修改API函数本身,实现隐蔽的注入。图演示了FinFisher通过IAT HOOKING技术注入恶意代码。

       总结,本文详细介绍了十种不同的进程注入技术,涵盖了直接代码注入、DLL加载、内存注入、绝地求生源码免费注册表修改、API钩子和隐藏执行等多种方法。这些技术在恶意软件中广泛使用,以提高其隐蔽性和持久性。通过理解这些技术,研究人员和安全专家可以更有效地检测和防御针对这些技术的攻击。

干货分享丨表哥带你学习导入表及导入表注入

       导入表概述

       导入表在逆向和病毒分析中扮演重要角色,尤其在病毒分析初期,快速查看导入表内容能帮助判断程序使用了哪些功能。导入表位于数据目录项第二位置,程序每次调用DLL都会生成一张表,记录调用的DLL中函数地址。

       导入表结构体 IMAGE_IMPORT_DESCRIPTOR

       导入表结构包括了调用的DLL信息。

       导入绑定技术

       程序加载前,IAT表与INT表内容相同,都是DLL中函数名或序号。加载后,IAT表更新为函数真实地址。若在DLL重定位时地址未占据ImageBase,需修复绝对地址。dll修改后,IAT表中函数地址可能变化,需再次修复。

       绑定导入技术在Windows记事本中应用。

       IMAGE_THUNK_DATA

       标识函数以序号或字符串导入,序号表示函数名,字符串指向IMAGE_IMPORT_BY_NAME结构。

       IMAGE_IMPORT_BY_NAME

       存储输入函数信息。

       导入地址表IAT

       PE装载器重写IAT表,搜索INT表找到输入函数地址,替换IAT表元素。

       非导入绑定情况下,INT与IAT表内容一致。

       PE文件加载后,IAT表更新为函数入口地址。

       定位导入表

       通过数据目录项第二项的VirtualAddress转为FOA,定位导入表结构,打印名称,获取DLL名。

       遍历INT表

       从导入表定义到INT表,判断是否序号导入,定位并输出函数名称。

       遍历IAT表

       定义到IAT表,遍历输出函数名称。

       实现代码

       导入表注入

       通过修改exe导入表,将自定义DLL添加,实现加载。

       实现步骤

       1. 新增节表存储移动后的导入表;

       2. 根据目录项获取导入表信息;

       3. 将VirtualAddress转为FOA定位导入表;

       4. 复制原导入表到空白区;

       5. 追加新导入表;

       6. 追加INT表、IAT表及IMAGE_IMPORT_BY_NAME结构;

       7. 填充结构RVA与DLL名称;

       8. 修正IMAGE_DATA_DIRECTORY结构。

       手动实现

       根据目录项定位导入表,寻找空白区复制原表,追加新表和结构,填充信息,修正表元素。

       代码实现

       通过以上步骤实现导入表注入,放置DLL并运行程序,成功弹出窗口。

相关栏目:知识

.重点关注