NDIS HOOK 防火墙实现关键技术

    技术2022-05-19  23

    谈到网络安全不能不提到防火墙。目前国内防火墙多数是采用 TDI 技术, NDIS 可以算是较先进的技术了(如果您认为不是的话, 只能说明我落伍了, 呵呵) 网上缺少 NDIS 防火墙实现的技术细节说明.  经过2天的查找资料和分析已有的源代码再加上以前 NDIS 编程的基础很快对该技术有了初步的了解。

    该技术细节分析只涉及到怎样 Hook 到 Ndis.sys 中导出的内核函数。至于在 Hook 函数中要进行怎样的处理就要取决于具体功能要求了. 希望下面的技术细节分析可以解决您入手难的问题.

    实现思路:(1) 类似于 User-Mode Application, 我们需要得到被挂钩函数所在文件的内存基地址。

    (2) 判断该基地址开始前2个字节是否是 'MZ', 然后通过 DOS 头部结构的最后成员 e_lfanew. 进一步得到 PIMAGE_NT_HEADERS, 然后得到    函数导出目录的地址.

    (3) 在 ndis.sys 的导出目录中查找要替换的目标函数 NdisRegisterProtocol, 找到目标函数后得到目标函数地址. 然后保存原先函数地址并    用我们自己的 New_NdisRegisterProtocol 替换原函数地址。

    (4) 根据具体的功能需求进行不同的过滤实现.

    没有什么讲解方法比展示实现代码更丰富更吸引人的, 下面就给出实现代码.

    (1)例如: 通过 depends.exe 工具查看 ndis.sys 导出的函数, 可以发现其中包括 NdisRegisterProtocol, 我们就挂钩该函数

    首先需要得到 ndis.sys 的内存基地址这里使用 Native API  ZwQuerySystemInformation 来获得系统已经加载内核模块的信息。

    系统模块信息结构体如下:

    NDIS HOOK 防火墙实现关键技术www.firnow.com    时间 : 2008-11-25  作者:佚名   编辑:辉辉 点击:  2360 [ 评论 ]--typedef struct _SYSTEM_MODULE_INFORMATION { ULONG Reserved[2]; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; USHORT Unknown; USHORT LoadCount; USHORT ModuleNameOffset; CHAR ImageName[255];} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

    查找指定模块的内存基址函数如下:

    void * find_system_dll(const char *name){ ULONG i, n, *q; PSYSTEM_MODULE_INFORMATION p; void *base;  /*         * 得到系统模块信息需要的内存数量         */ ZwQuerySystemInformation( SystemModuleInformation, &n, 0, &n); q = (ULONG *)ExAllocatePool(PagedPool, n); ZwQuerySystemInformation(SystemModuleInformation, q, n * sizeof (*q), 0);

            /*         * ZwQuerySystemInformation 在改内存中返回模块数量和每个模块的信息.   * 模块数量是内存的前4个字节, 后面是所有模块信息的排列         */ p = (PSYSTEM_MODULE_INFORMATION)(q + 1);

     base = NULL; for (i = 0; i < *q; i++)  {                  /*                 * 例如: ImageName: windows/system32/ndis.sys, 那么 ModuleNameOffset 就是 0x11                 */  if (_stricmp(p[i].ImageName + p[i].ModuleNameOffset, name) == 0)   {   /*                         * 得到 ndis.sys 模块的内存基址                         */   base = p[i].Base;

       KdPrint(("[ndis_hk] find_system_dll: %s; base = 0x%x; size = 0x%x/n", name, base, p[i].Size));   break;  } }   ExFreePool(q);

     return base;} 

    (2) 我们已经得到了ndis.sys 模块的内存基址, 下面就根据 PE 文件格式来得到导出函数目录的虚拟地址

    (3) 查找目标函数 NdisRegisterProtocol, 得到目标函数的在系统内核中的地址, 保存并替换原地址

    /* * base :  ndis.sys 模块的内存基地址 *   * fn:     被 Hook 的函数名 * * new_fn: 新的函数地址 */

    void * fix_export(char *base, const char *fn, void *new_fn){ PIMAGE_DOS_HEADER dos_hdr; PIMAGE_NT_HEADERS nt_hdr; PIMAGE_EXPORT_DIRECTORY export_dir; ULONG *fn_name, *fn_addr, i;

     /*         * 检查文件的有效性, 开始的2个字节是否是 'MZ', 按照 little-endian 顺序值是 0x5A4D.         */ dos_hdr = (PIMAGE_DOS_HEADER)base;

     if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE)  return NULL;

     /*         * 通过DOS头部的最后一个成员得到 NT 头部相对于文件的偏移, 然后计算出 NT 头部的虚拟地址          */ nt_hdr = (PIMAGE_NT_HEADERS)( base + dos_hdr->e_lfanew );

     export_dir = (PIMAGE_EXPORT_DIRECTORY)( base + nt_hdr->OptionalHeader.DataDirectory                                                    [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress );   /*         * 得到导出函数名字地址数组的地址, 数组中的每个元素是导出函数名的地址(指向导出函数名的指针)         */ fn_name = (ULONG *)(base + export_dir->AddressOfNames); /*         * fn_addr 数组中每个元素是导出函数地址的地址         */ fn_addr = (ULONG *)(base + export_dir->AddressOfFunctions);   for ( i = 0;  i < export_dir->NumberOfNames;  i++, fn_name++, fn_addr++ )  {  if ( strcmp(fn, base + *fn_name) == 0 )   {   /*                         * 取得该函数名对应的虚拟内存地址                         */   void *old_addr = base + *fn_addr;

       /*                  *  用我们自己新的函数地址(相对于导出模块的基地址) 来代替原函数的地址                         */    replace_value_safe(fn_addr, (char *)new_fn - base);

     return old_addr;  } }

     return NULL;}

    BOOLEAN replace_value_safe( ULONG *addr, ULONG value){ MDL *mdl; ULONG *virt_addr;

     mdl = IoAllocateMdl(addr, sizeof(value), FALSE, FALSE, NULL); if ( mdl == NULL )  return FALSE;  /*         * 检测指定的操作是否被支持, 锁定页面避免被换出从而造成缺页错误.         * 如果检测的操作不被支持该函数会抛出异常, 因此必须用 try/except 异常处理.         */ __try  {  MmProbeAndLockPages( mdl, KernelMode, IoModifyAccess );  } __except(EXCEPTION_EXECUTE_HANDLER)  {  KdPrint( ("[ndis_hk] replace_value_safe: MmProbeAndLockPages!/n") );  return FALSE; }

     virt_addr = (ULONG *)MmGetSystemAddressForMdl(mdl);

     /*         * 修改函数地址         */ *(ULONG *)virt_addr = value;

     MmUnlockPages(mdl); IoFreeMdl(mdl); return TRUE;}

    (4) 在新函数中进行过滤操作

    文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/webjsh/osgl/20071028/80742_4.html


    最新回复(0)