Anti-Rootkit(ARK)内核级系统防护软件KsBinSword的设计与实现

LastRain

发布日期: 2018-10-05 22:02:18 浏览量: 1533
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

KsBinSword是一斩断黑手的利刃,它适用于Windows 2000/XP/2003操作系统,用于查探系统中的幕后黑手(木马后门)并作出处理,当然使用它需要用户有一些操作系统的知识。

KsBinSword内部功能是十分强大的。它有着自己的独创核心态进程管理方案、简洁而不失效率的网络防火墙、强大而稳定的文件过滤驱动、深入磁盘底层甚至穿透还原软件的磁盘微端口驱动。可能您也用过很多类似功能的软件,比如一些进程工具、端口工具,但是现在的系统级后门功能越来越强,一般都可轻而易举地隐藏进程、端口、注册表、文件信息,一般的工具根本无法发现这些“幕后黑手”。KsBinSword使用大量新颖的内核技术,使得这些后门躲无所躲。

本程序分为应用态与核心态两部分。

1 应用层

程序采用VS2005编写,负责与核心态交互通信,将核心态的信息处理后反馈至用户。应用层使用标准的MFC框架,分为:

  • CAboutDlg:About对话框所在类

  • CEditEx:重载了的编辑框类

  • CHexEdit:重载了的十六进制编辑框类

  • CKsBinSwordApp:程序主应用框架类

  • CKsBinSwordDlg:程序主界面类

  • CMyList:重载了的列表框类

  • CMySystem:静态系统函数类

  • CPage1:进程管理页的类

  • CPage2:监控管理页的类

  • CPage3:驱动模块枚举页的类

  • CPage4:列举LSP页的类

  • CPage5:SSDT页的类

  • CPage6:文件管理页的类

  • CPage7:磁盘编辑器页的类

  • CPage8:防火墙页的类

  • CPage9:PE文件查看页的类

  • CRuleDlg:防火墙规则对话框类

  • CTrayIcon:系统托盘类

其中CMysystem类比较重要,它封装了对驱动操作、各个系统函数调用等操作,各个类都要用到。程序主界面如下图所示:

主界面上,又划分为九个子界面。分别为:进程管理、系统监控、LSP管理、SSDT检测部分、文件管理、磁盘编辑、防火墙部分、PE信息查看和系统模块列举。

1.1 进程管理

  • 内核级进程、线程检测,顺利查找各种病毒隐藏的进程、线程

  • 细致的内核级模块检测,深刻扫描系统潜在危险模块

  • 强大的内核级进程、线程结束,尚无病毒能够抵挡

1.2 系统监控

  • 使用当前杀毒软件最新HIPS技术(主动防御),防范病毒于未然

  • U盘扫描、脚本木马查杀,确保用户中毒后第一时间清理系统

1.3 LSP管理

  • 列举系统socket所依赖的动态链接库,揭发病毒隐藏之处

1.4 SSDT检测部分

  • 完全彻底扫描系统所有SSDT(系统服务派发表),病毒无遁形之处

  • 从系统原生文件还原SSDT,确保系统未修改,阻断病毒自我防护

1.5 文件管理

  • 强大的文件过滤驱动技术,彻底检测所有隐藏文件,安全可靠。
  • 独创的强制删除文件功能,绕过FSD(文件系统驱动),底层删除文件。

1.6 磁盘编辑

  • 方便的十六进制编辑器,完美模拟WinHex功能

  • 强大的底层磁盘编辑,甚至穿透还原卡,读取写入任何被保护扇区

1.7 防火墙部分

  • 自定义安全规则,拦截一切未知数据包,更灵活的保护系统

  • 实时反馈系统网络数据流,提供网络详细信息

1.8 PE信息查看

  • 详细列举PE文件信息,如导入表,区块等,方便了解未知文件结构。

1.9 系统模块列举

  • 列举系统所有模块,查杀潜在内核级病毒威胁

2 内核层

驱动层采用DDK 2003编写,共四个NT驱动程序,分别为:

  • KsBinSword.sys:负责处理进线程相关检测、查杀

  • SIoctl.sys:负责处理硬盘编程

  • DrvFltIp.sys:负责处理防火墙相关部分

  • Explore.sys:负责处理文件编辑相关部分

3 应用层各个功能实现原理

下面结合代码详细介绍应用层各个功能及其实现。

3.1 进程管理

3.1.1 普通列举进程

本功能封装在消息响应函数CPage1::OnBnClickedListProcess()中。采用系统PSAPI.LIB库导出的函数CreateToolhelp32Snapshot()、Process32First()、Process32Next()等函数工作。属于应用态列举进程。取得进程PID后,将使用我们独创的My_OpenProcess()打开进程句柄。

My_OpenProcess()的功能类似于普通的OpenProcess()作用,但功能更为强大。我们知道一些病毒等软件为了防止自己被杀毒软件结束,会采用一定的防御手段。为了关闭进程,进程句柄是必不可少的。所以很多病毒会在OpenProcess()上采用API HOOK技术保护自己不被结束。而我们的CMySystem::My_OpenProcess()将完美绕过,并且采用了一定的新颖的微软未公开技术。

My_OpenProcess()实现原理简介

Windows在子系统进程CSRSS.EXE里维护了一张整个应用层句柄表。通过系统未文档函数ZwQuerySystemInformation()将得到这张表。然后遍历所有句柄,如果是进程句柄则通过ZwDuplicateObject()将句柄复制到本进程,并调用ZwQueryInformationProcess()查询是否为我们需要打开的进程句柄。如果是则返回,否则继续遍历。

通过以上手段,应用层很少病毒能躲过KsBinSword的扫描。但不排除一些内核级的ROOTKIT(既一些底层核心态病毒)使用篡改系统内核技术逃过杀毒软件的检测。这时我们可以采用驱动列举进程功能。

3.1.2 驱动列举进程

本功能封装在函数CMySystem::ListProcessByDrive()中。

实现原理简介

在Windows下,所有资源都是以对象方式进行管理。我们要访问一个对象时,系统就会创建一个对象句柄。句柄和对象之间是通过句柄表来完成的。准确来说,一个句柄是它所对应的对象在句柄表中的索引。PspCidTable是Windows系统上一个特殊的句柄表。它不链接在系统句柄表上,也不属于任何进程。通过它可以访问系统任何对象。

在Windows XP中,为了节省系统空间,采用了动态扩展的表结构。当句柄表数目少的时候仅采用下层表。数目增大后才采用更多的层。最多有三层句柄表。当我们获得三层句柄表后,我们就可以通过句柄来访问对象了。

利用PspCidTable来检测隐藏进程的基本原理正是如此,系统内所有进程对象的对象类型是一样的,先取得任一进程对象的对象类型,然后访问所有句柄值,是进程对象则记录下来。下面是实现代码:

  1. VOID IsValidProcess()
  2. {
  3. //判断是否是进程对象,是则记录,不是则放弃
  4. ULONG PspCidTable;
  5. ULONG TableCode;
  6. ULONG table1,table2;
  7. ULONG object,objectheader;
  8. ULONG NextFreeTableEntry;
  9. ULONG processtype,type;
  10. ULONG flags;
  11. ULONG i;
  12. PspCidTable=GetCidAddr();
  13. processtype=GetProcessType();
  14. if(PspCidTable==0)
  15. {
  16. return ;
  17. }
  18. else
  19. {
  20. //TableCode的最后两位在XP中决定了句柄表的层数
  21. TableCode=*(PULONG)(*(PULONG)PspCidTable);
  22. if((TableCode&0x3)==0x0)
  23. {
  24. table1=TableCode;
  25. table2=0x0;
  26. }
  27. if((TableCode&0x3)==0x1)
  28. {
  29. TableCode=TableCode&0xfffffffc;
  30. table1=*(PULONG)TableCode;
  31. table2=*(PULONG)(TableCode+0x4);
  32. }
  33. //对cid从0x0到0x4e1c进行遍历
  34. for(i=0x0;i<0x4e1c;i++)
  35. {
  36. if(i<=0x800)
  37. {
  38. if(MmIsAddressValid((PULONG)(table1+i*2)))
  39. {
  40. object=*(PULONG)(table1+i*2);
  41. if(MmIsAddressValid((PULONG)(table1+i*2+NEXTFREETABLEENTRY)))
  42. {
  43. NextFreeTableEntry=*(PULONG)(table1+i*2+NEXTFREETABLEENTRY);
  44. if(NextFreeTableEntry==0x0)//正常的_HANDLE_TABLE_ENTRY中NextFreeTableEntry为0x0
  45. {
  46. object=((object | 0x80000000)& 0xfffffff8);//转换为对象(体)指针
  47. objectheader=(ULONG)OBJECT_TO_OBJECT_HEADER(object);//获取对象(头)指针
  48. if(MmIsAddressValid((PULONG)(objectheader+TYPE)))
  49. {
  50. type=*(PULONG)(objectheader+TYPE);
  51. if(type==processtype)
  52. {
  53. flags=*(PULONG)((ULONG)object+FLAGS);
  54. if((flags&0xc)!=0xc)
  55. RecordInfo(object);//flags显示进程没有退出
  56. }
  57. }
  58. }
  59. }
  60. }
  61. }
  62. else
  63. {
  64. if(table2!=0)
  65. {
  66. if(MmIsAddressValid((PULONG)(table2+(i-0x800)*2)))
  67. {
  68. object=*(PULONG)(table2+(i-0x800)*2);
  69. if(MmIsAddressValid((PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY)))
  70. {
  71. NextFreeTableEntry=*(PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY);
  72. if(NextFreeTableEntry==0x0)
  73. {
  74. object=((object | 0x80000000)& 0xfffffff8);
  75. objectheader=(ULONG)OBJECT_TO_OBJECT_HEADER(object);
  76. if(MmIsAddressValid((PULONG)(objectheader+TYPE)))
  77. {
  78. type=*(PULONG)(objectheader+TYPE);
  79. if(type==processtype)
  80. {
  81. flags=*(PULONG)((ULONG)object+FLAGS);
  82. if((flags&0xc)!=0xc)
  83. RecordInfo(object);
  84. }
  85. }
  86. }
  87. }
  88. }
  89. }
  90. }
  91. }
  92. }
  93. }

上面解决了检测进程功能。但PspCidTable是未被Windows导出的,属于未文档结构。下面的代码负责查找PspCidTable:

  1. //通过搜索PsLookupProcessByProcessId函数,获取PspCidTable的地址
  2. ULONG GetCidAddr()
  3. {
  4. PUCHAR addr;
  5. PUCHAR p;
  6. UNICODE_STRING pslookup;
  7. ULONG cid;
  8. RtlInitUnicodeString (&pslookup,
  9. L"PsLookupProcessByProcessId");
  10. addr = (PUCHAR) MmGetSystemRoutineAddress(&pslookup);//MmGetSystemRoutineAddress可以通过函数名获得函数地址
  11. for (p=addr;p<addr+PAGE_SIZE;p++)
  12. {
  13. if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))
  14. {
  15. cid=*(PULONG)(p+2);
  16. return cid;
  17. break;
  18. }
  19. }
  20. return 0;
  21. }

具体细节的补充说明:

  • 本程序所使用的结构都是在Windows xp sp2下实现的,所以移植性比较差

  • 这种检测方式是针对系统句柄 ,所以可以从结果看出不存在系统句柄的System IDIE Process 进程无法列举

  • 因为进程的退出也是基于句柄的,所以存在进程已经退出而进程对象仍然存在的情况。这种情况可以通过EPROCESS结构中的ProcessExiting等标志位来判断是否退出

3.1.3 结束进程

结束进程我们提供了三种方式:

  • 普通TerminateProcess()法结束进程,封装在CPage1::OnMenuKillProcessByTer()

  • 强制清零法结束进程封装在CMySystem::KillProcess()

  • 驱动调用PspTerminateProcess()结束进程,封装在CMySystem::ForceKillProcess()中

清零法

程序调用ZwProtectVirtualMemory()和ZwWriteVirtualMemory() 等函数,强制将目标进程的ring3层的地址空间清除为零。由于连异常处理等Windows特定结构都被清除,故目标进程甚至连异常对话框都不会出现便自动被Windows内存进程管理器消除进程执行体等进程标志,此为目前ring3层最强的结束进程法。

驱动层结束进程将在下面的驱动部分再行介绍。

3.1.4 模块列举

本功能封装在CPage1::ListProDllByQueryVirMem()中。

实现方法

通过ZwQueryVirtualMemory()函数暴力搜索目标进程应用层任何一处位置,并得到响应的地址信息,如果是模块的话列举出来。目前绝大多数工具查找模块也是通过Toolhlp32、psapi,前者会调用RtlDebug***函数向目标注入远线程,后者会用调试api读取目标进程内存,本质上都是对PEB的枚举,通过修改PEB就轻易让这些工具找不到北了。而KsBinSword的核心态方案原原本本地将模块展示,病毒无所逃匿。

3.1.5 线程列举

本功能封装在CMySystem::ListThread(void) 中。

线程列举完全使用了内核态方案,在驱动中遍历线程结构体ETHREAD,通过ETHREAD中的双向链表完成线程列举。完全杜绝了病毒的一些常规拦截操作。

3.1.6 线程结束

线程结束我们提供了两种方案:

  • 基于应用层的TerminateThread()的结束进程。封装在CMySystem::KillProcess()中。原理是创建远程线程,注入目标进程中,再调用TerminateThread()的结束进

  • 基于核心态的PspTerminateThread()结束进程。原理是内核态搜索未导出函数PspTerminateThread()结束进程。

3.2 监控配置

3.2.1 进程监控

本功能封装在CPage2::OnBnClickedOk()中。应用层传递消息控制字给内核层,内核层SSDT挂钩了内核函数ZwCreateProcess(),对每个新创建进程进行用户询问。

3.2.2 注册表监控

本功能封装在CPage2::OnBnClickedCancel()中。应用层传递消息控制字给内核层,内核层SSDT挂钩了内核函数ZwSetValueKey(),对每个注册表访问进行用户询问。

3.2.3 模块监控

本功能封装在CPage2:: OnBnClickedButton1 ()中。应用层传递消息控制字给内核层,内核层SSDT挂钩了内核函数ZwLoadDriver(),对每个内核模块加载进行用户询问。

3.2.4 U盘辅助插件

本功能对U盘可疑文件(如AUTORUNS.INF)进行彻底查杀,在源头上封堵了U盘病毒的来源

3.2.5 脚本木马查杀

本功能采用特征码杀毒方式,能全盘扫描脚本木马,速度快,稳定性高,可靠性好。

3.3 驱动模块检测

本功能采用两种不同的方式列举系统驱动:ZwQuerySystemInformation()和ZwQueryDirectoryObject()方式。前者属于常规法,容易遭到病毒拦截,而后者列举了系统的对象目录,极少数病毒会注意到这个地方,所以采用这种方式查找病毒安全又可靠。本来我们打算移进内核态。但由于这两个函数能在应用态调用,为了增强稳定性,就在应用层实现了。

3.4 LSP枚举

LSP枚举我们采用了API: WSCEnumProtocols()、WSCGetProviderPath()

遍历每个socket协议链得到相应模块路径。病毒有可能更改这个协议链表,所以这里列举出来给用户自行判断。

3.5 SSDT操作

3.5.1 SSDT枚举

本部分操作比较多。分别封装两个函数:CPage5::ShowSSDT(void), OnReShowSSDT()中。CPage5::ShowSSDT(void) 调用BOOL CMySystem::EnumSSDT(IN HANDLE hDriver ) 枚举SSDT。

实现原理

从系统内核读取出SSDT表,然后使用PE文件操作,从系统内核文件ntoskrnl.exe中分别读取PE头部->数据目录->导出表->导出目录表->函数名数组指针。再通过内核中得到ntoskrnl.exe在内存中的基址,根据上述各数据得到相应的SSDT函数在内存中的正确地址,通过与前述得到的数据相对比,判断是否SSDT被更改。关键函数代码:

  1. //枚举SSDT
  2. BOOL CMySystem::EnumSSDT( IN HANDLE hDriver )
  3. {
  4. HINSTANCE hNtDllInst = NULL;
  5. ULONG ulNtDllOffset;
  6. ULONG ulFuncNameCount = 0;
  7. PIMAGE_EXPORT_DIRECTORY pImgExpDir = NULL;
  8. PULONG pFuncNameArray = NULL;
  9. ULONG i;
  10. BOOL bOK = TRUE;
  11. do
  12. {
  13. RealCount = 0; //个数清
  14. if( pList ) //还有存没有释放
  15. {
  16. DestroyModList( pList ); //释放它
  17. pList = NULL;
  18. }
  19. pList = CreateModList( &NTBase ); //创建模块信息链表,顺便得到NT基址
  20. if( pList == NULL ) //创建失败
  21. {
  22. bOK = FALSE;
  23. break;
  24. }
  25. if( !( hNtDllInst = LoadLibrary( L"ntdll" ) ) )
  26. {
  27. bOK = FALSE;
  28. break;
  29. }
  30. /////////////////////////////////////////////////////////
  31. //分配SSDT保存缓冲表
  32. //得到SSDT个数
  33. SSDT ssdt;
  34. if( !GetSSDT( hDriver, &ssdt ) )
  35. {
  36. bOK = FALSE;
  37. break;
  38. }
  39. if( TotalSSDTCount == -1 ) //得到SSDT个数失败
  40. {
  41. bOK = FALSE;
  42. break;
  43. }
  44. if( pSSDTST ) //pSSDTST已有值,先释放它
  45. {
  46. free( pSSDTST );
  47. pSSDTST = NULL;
  48. }
  49. pSSDTST = (pSSDTSaveTable)malloc( TotalSSDTCount * sizeof( SSDTSaveTable ) );
  50. if( pSSDTST == NULL )
  51. {
  52. bOK = FALSE;
  53. break;
  54. }
  55. for( i = 0; i < TotalSSDTCount; i ++ ) //初始化它
  56. {
  57. ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulServiceNumber = -1;
  58. ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress = 0L;
  59. ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulOriginalFunctionAddress = 0L;
  60. memset( ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ServiceFunctionName, \
  61. 0, \
  62. sizeof(((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ServiceFunctionName));
  63. memset( ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName, \
  64. 0, \
  65. sizeof(((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName));
  66. }
  67. /////////////////////////////////////////////////////////
  68. //枚举
  69. ulNtDllOffset = (ULONG)hNtDllInst;
  70. //PE头部
  71. ulNtDllOffset += ((PIMAGE_DOS_HEADER)hNtDllInst)->e_lfanew + sizeof( DWORD );
  72. //数据目录
  73. ulNtDllOffset += sizeof( IMAGE_FILE_HEADER ) + sizeof( IMAGE_OPTIONAL_HEADER )
  74. - IMAGE_NUMBEROF_DIRECTORY_ENTRIES * sizeof( IMAGE_DATA_DIRECTORY );
  75. //导出表
  76. ulNtDllOffset = (DWORD)hNtDllInst + ((PIMAGE_DATA_DIRECTORY)ulNtDllOffset)->VirtualAddress;
  77. //导出目录表
  78. pImgExpDir = (PIMAGE_EXPORT_DIRECTORY)ulNtDllOffset;
  79. //得到函数个数
  80. ulFuncNameCount = pImgExpDir->NumberOfNames;
  81. //函数名数组指针
  82. pFuncNameArray = (PULONG)( (ULONG)hNtDllInst + pImgExpDir->AddressOfNames );
  83. /////////////////////
  84. //循环找函数名
  85. for( i = 0; i < ulFuncNameCount; i ++ )
  86. {
  87. //函数名
  88. PCSTR pszName = (PCSTR)( pFuncNameArray[i] + (ULONG)hNtDllInst );
  89. if( pszName[0] == 'N' && pszName[1] == 't' ) //Nt 开头的函数
  90. {
  91. //查找表
  92. LPWORD pOrdNameArray = (LPWORD)( (ULONG)hNtDllInst + pImgExpDir->AddressOfNameOrdinals );
  93. //函数地址
  94. LPDWORD pFuncArray = (LPDWORD)( (ULONG)hNtDllInst + pImgExpDir->AddressOfFunctions );
  95. //函数代码
  96. LPCVOID pFuncCode = (LPCVOID)( (ULONG)hNtDllInst + pFuncArray[pOrdNameArray[i]] );
  97. //获取服务号
  98. SSDTEntry EntryCode;
  99. memcpy( &EntryCode, pFuncCode, sizeof( SSDTEntry ) );
  100. if( EntryCode.byMov == 0xB8 ) // MOV EAX, XXXX
  101. {
  102. ULONG ulAddr = 0;
  103. if( !GetHook( hDriver, EntryCode.ulIndex, &ulAddr ) )
  104. {
  105. bOK = FALSE;
  106. break;
  107. }
  108. ////////////////////////
  109. //通过地址得到模块名
  110. char ModNameBuf[MAX_PATH+1];
  111. memset( ModNameBuf, 0, sizeof( ModNameBuf ) );
  112. if( GetModuleNameByAddr( ulAddr, pList, ModNameBuf, sizeof( ModNameBuf )-1 ) )
  113. {
  114. memcpy( \
  115. ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ModuleName, \
  116. ModNameBuf, \
  117. sizeof( ModNameBuf ) \
  118. );
  119. }
  120. ////////////////////////////////////////////////////
  121. //保存SSDT信息到缓冲表中
  122. ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ulServiceNumber = \
  123. EntryCode.ulIndex; //服务号
  124. ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress = \
  125. ulAddr; //当前函数地址
  126. memcpy( \
  127. ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ServiceFunctionName, \
  128. pszName, \
  129. sizeof( ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ServiceFunctionName )
  130. );
  131. RealCount ++;
  132. }
  133. }
  134. }
  135. } while( FALSE );
  136. ::FreeLibrary( hNtDllInst );
  137. if( bOK ) //成功
  138. {
  139. //获取剩下的服务号
  140. for( i = RealCount; i < TotalSSDTCount; i ++ )
  141. {
  142. if( !GetHook( hDriver, i, &((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress ) )
  143. {
  144. bOK = FALSE;
  145. break;
  146. }
  147. ////////////////////////
  148. //通过地址得到模块名
  149. char ModNameBuf[MAX_PATH+1];
  150. memset( ModNameBuf, 0, sizeof( ModNameBuf ) );
  151. if( GetModuleNameByAddr( \
  152. ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress, \
  153. pList, ModNameBuf, sizeof( ModNameBuf )-1 ) )
  154. {
  155. memcpy( \
  156. ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName, \
  157. ModNameBuf, \
  158. sizeof( ModNameBuf ) \
  159. );
  160. }
  161. }
  162. //按服务号进行排序
  163. SSDTSTOrderByServiceNum( pSSDTST );
  164. //获取原始函数地址
  165. GetOldSSDTAddress();
  166. }
  167. if( pList )
  168. {
  169. DestroyModList( pList ); //释放模块链表
  170. pList = NULL;
  171. }
  172. return bOK;
  173. }

3.5.2 SSDT恢复

该功能封装在CMySystem::SetSSDT()中。这个函数只是一个用户层通信函数,真正恢复函数在内核态中。内核态将用户层传来的SSDT函数正确地址,写入到内存中。其中为了防止被中断打乱,关闭了一次中断,操作完成后恢复中断。

3.6 文件管理器

本文件管理采用了独创的文件过滤驱动技术,未使用windows API,能查探、删除目前已知的绝大多数病毒、Rootkit等。

文件管理应用层负责与核心态通信。下面分析应用层框架:

3.6.1 列目录、文件

CPage6::OnItemexpanding(NMHDR *pNMHDR, LRESULT *pResult) 函数负责响应文件管理器中树型控件的节点展开消息。然后将点击的路径传送至内核,由文件过滤驱动构造IRP包发送至相应卷驱动。根据卷驱动返回的数据包传递给应用层。应用层通过CPage6::IsMediaValid()、CPage6::IsPathValid()、CPage6::AddDirectoryNodes()判断路径下是否有文件,是何种文件,并将信息添加至树型控件节点。相应的消息控制字为:IOCTL_MT_GETDIRINF、IOCTL_MT_GETDIRNUMINF。其中最重要的函数代码如下:

  1. UINT CPage6::AddDirectoryNodes(HTREEITEM hItem, CString &strPathName)
  2. {
  3. WCHAR wBuf[60];
  4. DWORD bytesReturned=0;
  5. ULONG num=0;
  6. PDIRECTORY_INFO temp={0};
  7. DIRECTORY_INFO_EX DIRECTORY_INFO_b;
  8. CString str,str1,strFileSpec = strPathName;
  9. if (strFileSpec.Right (1) != "\\")
  10. strFileSpec += "\\";
  11. char a[MAX_PATH];
  12. str1=strFileSpec;
  13. CMySystem::WCHARToChar(a,strFileSpec.GetBuffer(strFileSpec.GetLength()));
  14. strFileSpec += "*.*";
  15. DeviceIoControl(hDevice,(DWORD)IOCTL_MT_GETDIRNUMINF,a,sizeof(a),&num,sizeof(ULONG),&bytesReturned,NULL);
  16. if(num==0)
  17. {
  18. AfxMessageBox(L"驱动未加载,列举出错!");
  19. return 0;
  20. }
  21. temp=(PDIRECTORY_INFO)calloc(num,sizeof(DIRECTORY_INFO));
  22. if(temp==NULL)
  23. {
  24. return 0;
  25. }
  26. DeviceIoControl(hDevice,(DWORD)IOCTL_MT_GETDIRINF,a,sizeof(a),temp,num*sizeof(DIRECTORY_INFO),&bytesReturned,NULL);
  27. CWaitCursor wait;
  28. WCHAR wTemp[MAX_PATH]={'\0'};
  29. m_FileList.DeleteAllItems();
  30. index=0;
  31. SetPath(str1,hDevice);
  32. for(ULONG i=0;i<num;i++)
  33. {
  34. TRACE("AddDirectoryNode:%d\n",i);
  35. CMySystem::CharToWCHAR(wTemp,temp[i].FileName);
  36. str.Format(L"%s",wTemp);
  37. str=str1+str;
  38. CString strFileName = (LPCTSTR) &temp[i].FileName;
  39. if(PathIsDirectory(str))
  40. {
  41. if(strcmp(temp[i].FileName,"."))
  42. {
  43. if(strcmp(temp[i].FileName,".."))
  44. {
  45. CMySystem::CharToWCHAR(wTemp,temp[i].FileName);
  46. HTREEITEM hChild =
  47. m_FileTree.InsertItem ((LPCTSTR) wTemp,//&fd.cFileName,
  48. ILI_CLSDFLD , ILI_OPENFLD , hItem , TVI_SORT);
  49. CString strNewPathName = strPathName;
  50. if (strNewPathName.Right (1) != "\\")
  51. {strNewPathName += "\\";}
  52. CMySystem::CharToWCHAR(wBuf,temp[i].FileName);
  53. strNewPathName += wBuf;//fd.cFileName;
  54. SetButtonState (hChild, strNewPathName);
  55. }
  56. }
  57. }
  58. else
  59. {
  60. DIRECTORY_INFO_b.DirectoryInfo=temp[i];
  61. DIRECTORY_INFO_b.path=str1;
  62. AddToListView(&DIRECTORY_INFO_b);
  63. }
  64. }
  65. delete temp;
  66. return num;
  67. }

3.6.2 文件删除

文件删除分为普通删除与驱动删除。

  • 普通删除:调用Win32 API DeleteFile()删除文件。对付普通病毒这种方式有效。但某些病毒会采用文件占用式保护本体不被删除,甚至采用驱动形式保护,此时普通删除无效

  • 驱动删除:传递控制字IOCTL_MT_KILLFILE至驱动Explorer.sys,驱动删除病毒,此方式有一定危险性,但对病毒有奇效。极少数病毒能逃离此法删除

3.7 磁盘编辑器

3.7.1 十六进制编辑器界面处理

我们的磁盘编辑器界面采用了重载后的CEdit类。新类CHexEdit响应下列消息:

  1. WM_CHAR
  2. WM_KILLFOCUS
  3. WM_PAINT
  4. WM_SETFOCUS
  5. WM_SIZE
  6. WM_VSCROLL
  7. WM_HSCROLL
  8. WM_GETDLGCODE
  9. WM_ERASEBKGND
  10. WM_LBUTTONDOWN
  11. WM_LBUTTONDBLCLK
  12. WM_MOUSEMOVE
  13. WM_LBUTTONUP
  14. WM_KEYDOWN

并在WM_CHAR的响应函数CHexEdit::OnPaint()中绘制了三大部分:地址栏、十六进制栏、字符显示栏。其中的细节比较多,这里就不全部描述了。

3.7.2 硬盘编辑功能

硬盘编辑有两种选择, 一种是普通的通过应用层API CreateFile()打开物理对象\\.\PhysicalDrive0,实现函数为CMySystem::ReadSector(__int64Sect, BYTE *OutBuf).另一种在核心态自己构造IRP发送至磁盘驱动ATAPI.SYS(这是Windows处理磁盘请求的最后一站,再往下就是硬盘IO指令了),直接绕过文件系统FSD.在我们的测试中,意外的发现这种极为底层的技术甚至连知名的影子系统, RVS,冰点……等还原软件被穿透。

3.8 网络防火墙

网络防火墙有两种用途,一种是建立规则,阻止或通过指定网络包。防火墙的驱动实现将在后面讲解。

3.8.1 建立防火墙规则

通过CPage8:: OnButtonAdd()打开规则对话框,对规则进行相关配置后,调用CPage8::OnButtoninStall再调用CPage8::AddFilterToFw发送规则至驱动。

3.8.2 监视网络数据包

在驱动中我们自己实现了一个类似DbgPrint的函数。应用层中申请了一个定时器,反复读取内核传来的网络数据包。并分析数据包中的源IP、目标IP和数据包协议类型。相关函数为CPage8::OnBnClickedMonitor()

3.9 PE文件分析

文件分析部分没有什么内核技术,纯粹是个辅助性功能。相应部分看函数便可知。

  1. CPage9::LoadFile();
  2. CPage9::IsPEFile()
  3. CPage9::PrintFileHeader();
  4. CPage9::PrintOptionAlHeader();
  5. CPage9::PrintSectionInfo();
  6. CPage9::printET();
  7. CPage9::printIAT();
  8. CPage9::UnLoadFile()

至此,应用层分析完毕。

4 内核层各个功能实现原理

下面结合代码详细介绍内核层各个功能及其实现。

4.1 进程管理

见应用层分析部分

4.2 自动防御

见应用层分析部分

4.3 驱动模块列举

见应用层分析部分

4.4 列举LSP

见应用层分析部分

4.5 SSDT

见应用层分析部分

4.6 文件管理

文件管理器通过自己构造IRP数据包下发至卷驱动。相应的函数位于Explorer.sys中的GetDirectory(char *lpDirName, PULONG dwRetSize)里。函数调用ZwOpenFile打开设备链接\\DosDevices\\C:\\卷驱动,调用IoAllocateIrp分配一个IRP,然后填充IRP:

  1. KeInitializeEvent(&event,SynchronizationEvent,FALSE);
  2. lpInformation = ExAllocatePool(PagedPool,655350);
  3. lpSystemBuffer = ExAllocatePool(PagedPool,655350);
  4. RtlZeroMemory(lpSystemBuffer,655350);
  5. RtlZeroMemory(lpInformation,655350);
  6. lpirp->UserEvent = &event;
  7. lpirp->UserBuffer = lpInformation;
  8. lpirp->AssociatedIrp.SystemBuffer = lpInformation;
  9. lpirp->MdlAddress = NULL;
  10. lpirp->Flags = 0;
  11. lpirp->UserIosb = &ios;
  12. lpirp->Tail.Overlay.OriginalFileObject = lpFileObject;
  13. lpirp->Tail.Overlay.Thread = PsGetCurrentThread();
  14. lpirp->RequestorMode = KernelMode;
  15. lpsp = IoGetNextIrpStackLocation(lpirp);
  16. lpsp->MajorFunction = IRP_MJ_DIRECTORY_CONTROL;
  17. lpsp->MinorFunction = IRP_MN_QUERY_DIRECTORY;
  18. lpsp->FileObject = lpFileObject;
  19. lpsp->DeviceObject = lpDeviceObject;
  20. lpsp->Flags = SL_RESTART_SCAN;
  21. lpsp->Control = 0;
  22. lpsp->Parameters.QueryDirectory.FileIndex = 0;
  23. lpsp->Parameters.QueryDirectory.FileInformationClass = FileDirectoryInformation;
  24. lpsp->Parameters.QueryDirectory.FileName = NULL;
  25. lpsp->Parameters.QueryDirectory.Length = 655350;

填这样当IRP返回时便携带了我们所需要的文件信息。

强制删除文件同样采用构造IRP下发方式。不同的是下发的IRP参数不同:

  1. FileInformation.DeleteFile = TRUE;
  2. Irp->AssociatedIrp.SystemBuffer = &FileInformation;
  3. Irp->UserEvent = &event;
  4. Irp->UserIosb = &ioStatus;
  5. Irp->Tail.Overlay.OriginalFileObject = fileObject;
  6. Irp->Tail.Overlay.Thread = (PETHREAD)KeGetCurrentThread();
  7. Irp->RequestorMode = KernelMode;
  8. irpSp = IoGetNextIrpStackLocation(Irp);
  9. irpSp->MajorFunction = IRP_MJ_SET_INFORMATION;
  10. irpSp->DeviceObject = DeviceObject;
  11. irpSp->FileObject = fileObject;
  12. irpSp->Parameters.SetFile.Length = sizeof(FILE_DISPOSITION_INFORMATION);
  13. irpSp->Parameters.SetFile.FileInformationClass = FileDispositionInformation;
  14. irpSp->Parameters.SetFile.FileObject = fileObject;

4.7 硬盘编辑

硬盘编辑功能的实现位于驱动SIoctl.sys中。为了实现底层的硬盘编辑,我们选择PhysicalDrive0设备对象。根据《深入解析windows》(第5版),这个对象其实是硬盘驱动atapi.sys的一个驱动。在这层驱动中,文件系统所需要的文件路径等已经不存在了,我们面对是直接是硬盘。下面是填充IRP和下发过程:

  1. irpSp = IoGetNextIrpStackLocation(irp);
  2. irp->UserEvent = &event;
  3. irp->IoStatus.Status = 0;
  4. irp->IoStatus.Information = 0;
  5. irp->UserBuffer = NULL;
  6. irp->Flags = (irp->Type << 16) | 5;
  7. irp->Tail.Overlay.Thread = PsGetCurrentThread();
  8. irp->Cancel = FALSE;
  9. IoSetCancelRoutine(irp,NULL);
  10. irp->RequestorMode =KernelMode;
  11. irp->AssociatedIrp.SystemBuffer = NULL;
  12. irpSp->DeviceObject = DeviceObject;
  13. irpSp->MajorFunction = (UCHAR)ReadOrWrite;
  14. irpSp->Parameters.DeviceIoControl.InputBufferLength = 0;

4.8 网络防火墙

在WINDOWS 2000 DDK中,微软包含了称为Filter-HookDriver的新型网络驱动。可以使用它来过滤所有进出接口的数据。实际上,Filter-Hook Driver并不是网络驱动,它是一种内核模式驱动(Kernel Mode Driver)。大致上是这样的:在Filter-Hook Driver中我们提供回调函数(callback),然后使用IP Filter Driver注册回调函数。这样当数据包发送和接收时,IP Filter Driver会调用回调函数。那么我们到底该如何实现这些步骤呢?总结如下:

  • 建立Filter-HookDriver.我们必须建立内核模式驱动,你可以选择名称,DOS名称和其它驱动特性,这些不是必须的,但我建议使用描述名称

  • 如果我们要安装过滤函数,首先我们必须得到指向IP Filter Driver的指针,这是第二步

  • 我们已经取得了指针,现在我们可以通过发送特殊的IRP来安装过滤函数,该”消息”传递的数据包含了过滤函数的指针

  • 过滤数据包

  • 当我们想结束过滤,我们必须撤销过滤函数。这通过传递NULL指针作为过滤函数指针来实现

下面是我们的防火墙构架:

  • 一个创建设备的驱动程序入口,为通讯创建符号连接和处理IRPs(分派,加载,卸载,创建…)的标准例程

  • 在标准例程里管理IRPs。在我们的代码中,我们实现了四个IOCTL代码:START_IP_HOOK(注册过滤函数),STOP_IP_HOOK(注销过滤函数),ADD_FILTER(安装新的过滤规则),CLEAR_FILTER(清除所有规则)

  • 对于我们的驱动,我们实现多个用于过滤的函数

我们在IP Filter Driver中执行一个函数来注册过滤函数,步骤如下:

  • 首先,得到IP Filter Driver的指针,这要求驱动已经安装并执行。为了保证IP Filter Driver已经安装并执行,在前述用户程序中,在加载本驱动前加载并启动IP Filter Driver

  • 第二步,建立用IOCTL_PF_SET_EXTENSION_POINTER作为控制代码的IRP。传递PF_SET_EXTENSION_HOOK_INFO 参数,该参数结构中包含了指向过滤函数的指针。如果要卸载该函数,必须在同样的步骤里传递NULL作为过滤函数指针

  • 向设备驱动发送:创建IRP, 这里有一个大的问题,只有一个过滤函数可以安装,因此如果另外的应用程序已经安装了一个过滤函数,你就不能再安装了

设置过滤函数的代码如下:

  1. NTSTATUS SetFilterFunction(PacketFilterExtensionPtr filterFunction)
  2. {
  3. NTSTATUS status = STATUS_SUCCESS, waitStatus=STATUS_SUCCESS;
  4. UNICODE_STRING filterName;
  5. PDEVICE_OBJECT ipDeviceObject=NULL;
  6. PFILE_OBJECT ipFileObject=NULL;
  7. PF_SET_EXTENSION_HOOK_INFO filterData;
  8. KEVENT event;
  9. IO_STATUS_BLOCK ioStatus;
  10. PIRP irp;
  11. dprintf("Getting pointer to IpFilterDriver\n");
  12. RtlInitUnicodeString(&filterName, DD_IPFLTRDRVR_DEVICE_NAME);
  13. status = IoGetDeviceObjectPointer(&filterName,STANDARD_RIGHTS_ALL, &ipFileObject, &ipDeviceObject);
  14. dprintf("OK:%x",status);
  15. if(NT_SUCCESS(status))
  16. {
  17. filterData.ExtensionPointer = filterFunction;
  18. KeInitializeEvent(&event, NotificationEvent, FALSE);
  19. irp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,
  20. ipDeviceObject,
  21. (PVOID) &filterData,
  22. sizeof(PF_SET_EXTENSION_HOOK_INFO),
  23. NULL,
  24. 0,
  25. FALSE,
  26. &event,
  27. &ioStatus);
  28. if(irp != NULL)
  29. {
  30. status = IoCallDriver(ipDeviceObject, irp);
  31. if (status == STATUS_PENDING)
  32. {
  33. waitStatus = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
  34. if (waitStatus != STATUS_SUCCESS )
  35. dprintf("Error waiting for IpFilterDriver response.");
  36. }
  37. status = ioStatus.Status;
  38. if(!NT_SUCCESS(status))
  39. dprintf("Error, IO error with ipFilterDriver\n");
  40. }
  41. else
  42. {
  43. status = STATUS_INSUFFICIENT_RESOURCES;
  44. dprintf("Error building IpFilterDriver IRP\n");
  45. }
  46. if(ipFileObject != NULL)
  47. {
  48. dprintf("ObDereferenceObject");
  49. ObDereferenceObject(ipFileObject);
  50. }
  51. ipFileObject = NULL;
  52. ipDeviceObject = NULL;
  53. }
  54. else
  55. dprintf("Error while getting the pointer\n");
  56. return status;

这时已经完成了建立过滤函数的工作,当取得设备驱动的指针后必须释放文件对象。我们使用事件来通知IpFilterDriver 已经完成了IRP处理。

下面是过滤函数代码:

  1. PF_FORWARD_ACTION cbFilterFunction(IN unsigned char *PacketHeader, IN unsigned char *Packet, IN unsigned int PacketLength, IN unsigned int RecvInterfaceIndex, IN unsigned int SendInterfaceIndex, IN unsigned long RecvLinkNextHop, IN unsigned long SendLinkNextHop)
  2. {
  3. IPPacket *ipp;
  4. TCPHeader *tcph;
  5. UDPHeader *udph;
  6. int countRule = 0;
  7. struct filterList *aux = first;
  8. WCHAR wcMessage[MAXSTR];
  9. ipp = (IPPacket*)PacketHeader;
  10. MyPrint(SEPARATOR);
  11. dprintf("PacketInfo %x, %d\r\n", PacketLength, RecvInterfaceIndex);
  12. dprintf("Source: %x Destination: %x Protocol: %d\r\n", ipp->ipSource, ipp
  13. ->ipDestination, ipp->ipProtocol);
  14. swprintf(wcMessage, L "PacketLength: %x, RecvInterfaceIndex:%d\r\n",
  15. PacketLength, RecvInterfaceIndex);
  16. MyPrint(wcMessage);
  17. swprintf(wcMessage, L "NetInfomation:@%x@@%x@@@%d\r\n", ipp->ipSource, ipp
  18. ->ipDestination, ipp->ipProtocol);
  19. MyPrint(wcMessage);
  20. if (ipp->ipProtocol == 6)
  21. {
  22. tcph = (TCPHeader*)Packet;
  23. dprintf("FLAGS: %x\r\n", tcph->flags);
  24. swprintf(wcMessage, L "FLAGS: %x\r\n", tcph->flags);
  25. if (!(tcph->flags &0x02))
  26. return PF_FORWARD;
  27. }
  28. while (aux != NULL)
  29. {
  30. dprintf("Comparing with Rule %d", countRule);
  31. if (aux->ipf.protocol == 0 || ipp->ipProtocol == aux->ipf.protocol)
  32. {
  33. if (aux->ipf.sourceIp != 0 && (ipp->ipSource &aux->ipf.sourceMask) != aux->ipf.sourceIp)
  34. {
  35. aux = aux->next;
  36. countRule++;
  37. continue;
  38. }
  39. if (aux->ipf.destinationIp != 0 && (ipp->ipDestination &aux->ipf.destinationMask) != aux->ipf.destinationIp)
  40. {
  41. aux = aux->next;
  42. countRule++;
  43. continue;
  44. }
  45. //tcp, protocol = 6
  46. if (ipp->ipProtocol == 6)
  47. {
  48. if (aux->ipf.sourcePort == 0 || tcph->sourcePort == aux->ipf.sourcePort)
  49. {
  50. if (aux->ipf.destinationPort == 0 || tcph->destinationPort == aux->ipf.destinationPort)
  51. //puerto tcp destino
  52. {
  53. if (aux->ipf.drop)
  54. return PF_DROP;
  55. else
  56. return PF_FORWARD;
  57. }
  58. }
  59. }
  60. //udp, protocol = 17
  61. else if (ipp->ipProtocol == 17)
  62. {
  63. udph = (UDPHeader*)Packet;
  64. if (aux->ipf.sourcePort == 0 || udph->sourcePort == aux->ipf.sourcePort)
  65. {
  66. if (aux->ipf.destinationPort == 0 || udph->destinationPort == aux->ipf.destinationPort)
  67. {
  68. if (aux->ipf.drop)
  69. return PF_DROP;
  70. else
  71. return PF_FORWARD;
  72. }
  73. }
  74. }
  75. else
  76. {
  77. if (aux->ipf.drop)
  78. return PF_DROP;
  79. else
  80. return PF_FORWARD;
  81. }
  82. }
  83. countRule++;
  84. aux = aux->next;
  85. }
  86. return PF_FORWARD;
  87. }

4.9 PE文件分析

见应用层分析部分

至此,整个KsBinSword分析完毕。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
资源转载自:https://download.csdn.net/download/anzyky/3329962
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

上传的附件 cloud_download Anti-Rootkit(ARK)内核级系统防护软件KsBinSword.7z ( 2.79mb, 12次下载 )
error_outline 下载需要12点积分

发送私信

人生就像坐飞机,飞多高不重要,重要的是安全到达目的地

10
文章数
19
评论数
最近文章
eject