分类

课内:
不限
类型:
不限 游戏 项目 竞赛 个人研究 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019 2020 2021

资源列表

  • 基于HTML5实现的贪吃蛇小游戏

    1 游戏介绍贪吃蛇的经典玩法有两种:

    积分闯关
    一吃到底

    第一种是笔者小时候在掌上游戏机最先体验到的(不小心暴露了年龄),具体玩法是蛇吃完一定数量的食物后就通关,通关后速度会加快;第二种是诺基亚在1997年在其自家手机上安装的游戏,它的玩法是吃到没食物为止。笔者要实现的就是第二种玩法。
    2 MVC设计模式基于贪吃蛇的经典,笔者在实现它时也使用一种经典的设计模型:MVC(即:Model - View - Control)。游戏的各种状态与数据结构由 Model 来管理;View 用于显示 Model 的变化;用户与游戏的交互由 Control 完成(Control 提供各种游戏API接口)。
    Model 是游戏的核心也是本文的主要内容;View 会涉及到部分性能问题;Control 负责业务逻辑。 这样设计的好处是: Model完全独立,View 是 Model 的状态机,Model 与 View 都由 Control 来驱动。
    2.1 Model看一张贪吃蛇的经典图片。

    贪吃蛇有四个关键的参与对象:

    蛇(snake)
    食物(food)
    墙(bounds)
    舞台(zone)

    舞台是一个 m * n 的矩阵(二维数组),矩阵的索引边界是舞台的墙,矩阵上的成员用于标记食物和蛇的位置。
    空舞台如下:
    [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0],]
    食物(F)和蛇(S)出现在舞台上:
    [ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,F,0,0,0,0,0,0,0], [0,0,0,S,S,S,S,0,0,0], [0,0,0,0,0,0,S,0,0,0], [0,0,0,0,S,S,S,0,0,0], [0,0,0,0,S,0,0,0,0,0], [0,0,0,0,S,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0],]
    由于操作二维数组不如一维数组方便,所以笔者使用的是一维数组, 如下:
    [ 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,F,0,0,0,0,0,0,0, 0,0,0,S,S,S,S,0,0,0, 0,0,0,0,0,0,S,0,0,0, 0,0,0,0,S,S,S,0,0,0, 0,0,0,0,S,0,0,0,0,0, 0,0,0,0,S,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,]
    舞台矩阵上蛇与食物只是舞台对二者的映射,它们彼此都有独立的数据结构:

    蛇是一串坐标索引链表
    食物是一个指向舞台坐标的索引值

    2.1.1 蛇的活动蛇的活动有三种,如下:

    移动(move)
    吃食(eat)
    碰撞(collision)

    2.1.1.1 移动蛇在移动时,内部发生了什么变化?

    蛇链表在一次移动过程中做了两件事:向表头插入一个新节点,同时剔除表尾一个旧节点。用一个数组来代表蛇链表,那么蛇的移动就是以下的伪代码:
    function move(next) { snake.pop() & snake.unshift(next); }
    数组作为蛇链表合适吗?
    这是笔者最开始思考的问题,毕竟数组的 unshift & pop 可以无缝表示蛇的移动。不过,方便不代表性能好,unshift 向数组插入元素的时间复杂度是 O(n),pop 剔除数组尾元素的时间复杂度是 O(1)。
    蛇的移动是一个高频率的动作,如果一次动作的算法复杂度为 O(n) 并且蛇的长度比较大,那么游戏的性能会有问题。笔者想实现的贪吃蛇理论上讲是一条长蛇,所以笔者在本文章的回复是 ——— 数组不适合作为蛇链表。
    蛇链表必须是真正的链表结构。
    链表删除或插入一个节点的时间复杂度为O(1),用链表作为蛇链表的数据结构能提高游戏的性能。javascript 没有现成的链表结构,笔者写了一个叫 Chain 的链表类,Chain 提供了 unshfit & pop。以下伪代码是创建一条蛇链表:
    let snake = new Chain();
    2.1.1.2 吃食 & 碰撞「吃食」与「碰撞」区别在于吃食撞上了「食物」,碰撞撞上了「墙」。笔者认为「吃食」与「碰撞」属于蛇一次「移动」的三个可能结果的两个分支。蛇移动的三个可能结果是:「前进」、「吃食」和「碰撞」。
    回头看一下蛇移动的伪代码:
    function move(next) { snake.pop() & snake.unshift(next); }
    代码中的 next 表示蛇头即将进入的格子的索引值,只有当这个格子是 0 时蛇才能「前进」,当这个格子是 S 表示「碰撞」自己,当这个格子是 F 表示吃食。
    好像少了撞墙?
    笔者在设计过程中,并没有把墙设计在舞台的矩阵中,而是通过索引出界的方式来表示撞墙。简单地说就是 next === -1 时表示出界和撞墙。
    以下伪代码表示蛇的整上活动过程:
    // B 表示撞墙let cell = -1 === next ? B : zone[next]; switch(cell) { // 吃食 case F: eat(); break; // 撞到自己 case S: collision(S); break; // 撞墙 case B: collision(B): break; // 前进 default: move; }
    2.1.2 随机投食随机投食是指随机挑选舞台的一个索引值用于映射食物的位置。这似乎很简单,可以直接这样写:
    // 伪代码food = Math.random(zone.length) >> 0;
    如果考虑到投食的前提 ——— 不与蛇身重叠,你会发现上面的随机代码并不能保证投食位置不与蛇身重叠。由于这个算法的安全性带有赌博性质,且把它称作「赌博算法」。为了保证投食的安全性,笔者把算法扩展了一下:
    // 伪代码function feed() { let index = Math.random(zone.length) >> 0; // 当前位置是否被占用 return zone[index] === S ? feed() : index; }food = feed();
    上面的代码虽然在理论上可以保证投食的绝对安全,不过笔者把这个算法称作「不要命的赌徒算法」,因为上面的算法有致命的BUG ——— 超长递归 or 死循环。
    为了解决上面的致命问题,笔者设计了下面的算法来做随机投食:
    // 伪代码function feed() { // 未被占用的空格数 let len = zone.length - snake.length; // 无法投食 if(len === 0) return ; // zone的索引 let index = 0, // 空格计数器 count = 0, // 第 rnd 个空格子是最终要投食的位置 rnd = Math.random() * count >> 0 + 1; // 累计空格数 while(count !== rnd) { // 当前格子为空,count总数增一 zone[index++] === 0 && ++count; } return index - 1; }food = feed();
    这个算法的平均复杂度为 O(n/2)。由于投食是一个低频操作,所以 O(n/2)的复杂度并不会带来任何性能问题。不过,笔者觉得这个算法的复杂度还是有点高了。回头看一下最开始的「赌博算法」,虽然「赌博算法」很不靠谱,但是它有一个优势 ——— 时间复杂度为 O(1)。
    「赌博算法」的靠谱概率 = (zone.length - snake.length) / zone.length。snake.length 是一个动态值,它的变化范围是:0 ~ zone.length。推导出「赌博算法」的平均靠谱概率是:

    「赌博算法」平均靠谱概率 = 50%

    看来「赌博算法」还是可以利用一下的。于是笔者重新设计了一个算法:
    // 伪代码function bet() { let rnd = Math.random() * zone.length >> 0; return zone[rnd] === 0 ? rnd : -1; }function feed() { ...}food = bet(); if(food === -1) food = feed();
    新算法的平均复杂度可以有效地降低到 O(n/4),人生有时候需要点运气。
    2.2 View在 View 可以根据喜好选择一款游戏渲染引擎,笔者在 View 层选择了 PIXI 作为游戏游戏渲染引擎。
    View 的任务主要有两个:

    绘制游戏的界面;
    渲染 Model 里的各种数据结构

    也就是说 View 是使用渲染引擎还原设计稿的过程。本文的目的是介绍「贪吃蛇」的实现思路,如何使用一个渲染引擎不是本文讨论的范畴,笔者想介绍的是:「如何提高渲染的效率」。
    在 View 中显示 Model 的蛇可以简单地如以下伪代码:
    // 清空 View 上的蛇view.snake.clean(); model.snake.forEach( (node) => { // 创建 View 上的蛇节点 let viewNode = createViewNode(node); // 并合一条新蛇 view.snake.push(viewNode); });
    上面代码的时间复杂度是 O(n)。上面介绍过蛇的移动是一个高频的活动,我们要尽量避免高频率地运行 O(n) 的代码。来分析蛇的三种活动:「移动」,「吃食」,「碰撞」。
    首先,Model 发生了「碰撞」,View 应该是直接暂停渲染 Model 里的状态,游戏处在死亡状态,接下来的事由 Control 处理。
    Model 中的蛇(链表)在一次「移动」过程中做了两件事:向表头插入一个新节点,同时剔除表尾一个旧节点;蛇(链表)在一次「吃食」过程中只做一件事:向表头插入一个新节点。

    如果在 View 中对 Model 的蛇链表做差异化检查,View 只增量更新差异部分的话,算法的时间复杂度即可降低至 O(1) ~ O(2) 。以下是优化后的伪代码:
    let snakeA = model.snake, snakeB = view.snake; // 增量更新尾部while(snakeB.length <= snakeA.length) { headA = snakeA.next(); // 头节点匹配 if(headA.data === headB.data) break; // 不匹配 else { // 向snakeB插入头节点 if(snakeA.HEAD === headA.index) { snakeB.unshift(headA.data); } // 向snakeB插入第二个节点 else snakeB.insertAfter(0, headA.data); }}// 增量更新头部 let tailA = snakeA.last(), tailB; while(snakeB.length !== 0) { tailB = snakeB.last(); // 尾节点匹配 if(tailA.data === tailB.data) break; // 不匹配 else snakeB.pop(); }
    2.3 ControlControl 主要做 3 件事:

    游戏与用户的互动
    驱动 Model
    同步 View 与 Model

    「游戏与用户的互动」是指向外提供游戏过程需要使用到的 APIs 与 各类事件。笔者规划的 APIs 如下:



    name
    type
    detail




    init
    method
    初始化游戏


    start
    method
    开始游戏


    restart
    method
    重新开始游戏


    pause
    method
    暂停


    resume
    method
    恢复


    turn
    method
    控制蛇的转向。如:turn(“left”)


    destroy
    method
    销毁游戏


    speed
    property
    蛇的移动速度



    事件如下:



    name
    detail




    countdown
    倒时计


    eat
    吃到食物


    before-eat
    吃到食物前触发


    gameover
    游戏结束



    事件统一挂载在游戏实例下的 event 对象下。
    snake.event.on("countdown", (time) => console.log("剩余时间:", time));
    「驱动 Model 」只做一件事 ——— 将 Model 的蛇的方向更新为用户指定的方向。
    「同步 View 与 Model 」也比较简单,检查 Model 是否有更新,如果有更新通知 View 更新游戏界面。
    1 评论 43 下载 2018-10-31 18:11:26 下载需要9点积分
  • Anti-Rootkit(ARK)内核级系统防护软件KsBinSword的设计与实现

    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来检测隐藏进程的基本原理正是如此,系统内所有进程对象的对象类型是一样的,先取得任一进程对象的对象类型,然后访问所有句柄值,是进程对象则记录下来。下面是实现代码:
    VOID IsValidProcess(){ //判断是否是进程对象,是则记录,不是则放弃 ULONG PspCidTable; ULONG TableCode; ULONG table1,table2; ULONG object,objectheader; ULONG NextFreeTableEntry; ULONG processtype,type; ULONG flags; ULONG i; PspCidTable=GetCidAddr(); processtype=GetProcessType(); if(PspCidTable==0) { return ; } else { //TableCode的最后两位在XP中决定了句柄表的层数 TableCode=*(PULONG)(*(PULONG)PspCidTable); if((TableCode&0x3)==0x0) { table1=TableCode; table2=0x0; } if((TableCode&0x3)==0x1) { TableCode=TableCode&0xfffffffc; table1=*(PULONG)TableCode; table2=*(PULONG)(TableCode+0x4); } //对cid从0x0到0x4e1c进行遍历 for(i=0x0;i<0x4e1c;i++) { if(i<=0x800) { if(MmIsAddressValid((PULONG)(table1+i*2))) { object=*(PULONG)(table1+i*2); if(MmIsAddressValid((PULONG)(table1+i*2+NEXTFREETABLEENTRY))) { NextFreeTableEntry=*(PULONG)(table1+i*2+NEXTFREETABLEENTRY); if(NextFreeTableEntry==0x0)//正常的_HANDLE_TABLE_ENTRY中NextFreeTableEntry为0x0 { object=((object | 0x80000000)& 0xfffffff8);//转换为对象(体)指针 objectheader=(ULONG)OBJECT_TO_OBJECT_HEADER(object);//获取对象(头)指针 if(MmIsAddressValid((PULONG)(objectheader+TYPE))) { type=*(PULONG)(objectheader+TYPE); if(type==processtype) { flags=*(PULONG)((ULONG)object+FLAGS); if((flags&0xc)!=0xc) RecordInfo(object);//flags显示进程没有退出 } } } } } } else { if(table2!=0) { if(MmIsAddressValid((PULONG)(table2+(i-0x800)*2))) { object=*(PULONG)(table2+(i-0x800)*2); if(MmIsAddressValid((PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY))) { NextFreeTableEntry=*(PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY); if(NextFreeTableEntry==0x0) { object=((object | 0x80000000)& 0xfffffff8); objectheader=(ULONG)OBJECT_TO_OBJECT_HEADER(object); if(MmIsAddressValid((PULONG)(objectheader+TYPE))) { type=*(PULONG)(objectheader+TYPE); if(type==processtype) { flags=*(PULONG)((ULONG)object+FLAGS); if((flags&0xc)!=0xc) RecordInfo(object); } } } } } } } } }}
    上面解决了检测进程功能。但PspCidTable是未被Windows导出的,属于未文档结构。下面的代码负责查找PspCidTable:
    //通过搜索PsLookupProcessByProcessId函数,获取PspCidTable的地址ULONG GetCidAddr(){ PUCHAR addr; PUCHAR p; UNICODE_STRING pslookup; ULONG cid; RtlInitUnicodeString (&pslookup, L"PsLookupProcessByProcessId"); addr = (PUCHAR) MmGetSystemRoutineAddress(&pslookup);//MmGetSystemRoutineAddress可以通过函数名获得函数地址 for (p=addr;p<addr+PAGE_SIZE;p++) { if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8)) { cid=*(PULONG)(p+2); return cid; break; } } return 0;}
    具体细节的补充说明:

    本程序所使用的结构都是在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被更改。关键函数代码:
    //枚举SSDTBOOL CMySystem::EnumSSDT( IN HANDLE hDriver ){ HINSTANCE hNtDllInst = NULL; ULONG ulNtDllOffset; ULONG ulFuncNameCount = 0; PIMAGE_EXPORT_DIRECTORY pImgExpDir = NULL; PULONG pFuncNameArray = NULL; ULONG i; BOOL bOK = TRUE; do { RealCount = 0; //个数清 if( pList ) //还有存没有释放 { DestroyModList( pList ); //释放它 pList = NULL; } pList = CreateModList( &NTBase ); //创建模块信息链表,顺便得到NT基址 if( pList == NULL ) //创建失败 { bOK = FALSE; break; } if( !( hNtDllInst = LoadLibrary( L"ntdll" ) ) ) { bOK = FALSE; break; } ///////////////////////////////////////////////////////// //分配SSDT保存缓冲表 //得到SSDT个数 SSDT ssdt; if( !GetSSDT( hDriver, &ssdt ) ) { bOK = FALSE; break; } if( TotalSSDTCount == -1 ) //得到SSDT个数失败 { bOK = FALSE; break; } if( pSSDTST ) //pSSDTST已有值,先释放它 { free( pSSDTST ); pSSDTST = NULL; } pSSDTST = (pSSDTSaveTable)malloc( TotalSSDTCount * sizeof( SSDTSaveTable ) ); if( pSSDTST == NULL ) { bOK = FALSE; break; } for( i = 0; i < TotalSSDTCount; i ++ ) //初始化它 { ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulServiceNumber = -1; ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress = 0L; ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulOriginalFunctionAddress = 0L; memset( ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ServiceFunctionName, \ 0, \ sizeof(((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ServiceFunctionName)); memset( ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName, \ 0, \ sizeof(((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName)); } ///////////////////////////////////////////////////////// //枚举 ulNtDllOffset = (ULONG)hNtDllInst; //PE头部 ulNtDllOffset += ((PIMAGE_DOS_HEADER)hNtDllInst)->e_lfanew + sizeof( DWORD ); //数据目录 ulNtDllOffset += sizeof( IMAGE_FILE_HEADER ) + sizeof( IMAGE_OPTIONAL_HEADER ) - IMAGE_NUMBEROF_DIRECTORY_ENTRIES * sizeof( IMAGE_DATA_DIRECTORY ); //导出表 ulNtDllOffset = (DWORD)hNtDllInst + ((PIMAGE_DATA_DIRECTORY)ulNtDllOffset)->VirtualAddress; //导出目录表 pImgExpDir = (PIMAGE_EXPORT_DIRECTORY)ulNtDllOffset; //得到函数个数 ulFuncNameCount = pImgExpDir->NumberOfNames; //函数名数组指针 pFuncNameArray = (PULONG)( (ULONG)hNtDllInst + pImgExpDir->AddressOfNames ); ///////////////////// //循环找函数名 for( i = 0; i < ulFuncNameCount; i ++ ) { //函数名 PCSTR pszName = (PCSTR)( pFuncNameArray[i] + (ULONG)hNtDllInst ); if( pszName[0] == 'N' && pszName[1] == 't' ) //Nt 开头的函数 { //查找表 LPWORD pOrdNameArray = (LPWORD)( (ULONG)hNtDllInst + pImgExpDir->AddressOfNameOrdinals ); //函数地址 LPDWORD pFuncArray = (LPDWORD)( (ULONG)hNtDllInst + pImgExpDir->AddressOfFunctions ); //函数代码 LPCVOID pFuncCode = (LPCVOID)( (ULONG)hNtDllInst + pFuncArray[pOrdNameArray[i]] ); //获取服务号 SSDTEntry EntryCode; memcpy( &EntryCode, pFuncCode, sizeof( SSDTEntry ) ); if( EntryCode.byMov == 0xB8 ) // MOV EAX, XXXX { ULONG ulAddr = 0; if( !GetHook( hDriver, EntryCode.ulIndex, &ulAddr ) ) { bOK = FALSE; break; } //////////////////////// //通过地址得到模块名 char ModNameBuf[MAX_PATH+1]; memset( ModNameBuf, 0, sizeof( ModNameBuf ) ); if( GetModuleNameByAddr( ulAddr, pList, ModNameBuf, sizeof( ModNameBuf )-1 ) ) { memcpy( \ ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ModuleName, \ ModNameBuf, \ sizeof( ModNameBuf ) \ ); } //////////////////////////////////////////////////// //保存SSDT信息到缓冲表中 ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ulServiceNumber = \ EntryCode.ulIndex; //服务号 ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress = \ ulAddr; //当前函数地址 memcpy( \ ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ServiceFunctionName, \ pszName, \ sizeof( ((pSSDTSaveTable)((ULONG)pSSDTST + RealCount * sizeof(SSDTSaveTable)))->ServiceFunctionName ) ); RealCount ++; } } } } while( FALSE ); ::FreeLibrary( hNtDllInst ); if( bOK ) //成功 { //获取剩下的服务号 for( i = RealCount; i < TotalSSDTCount; i ++ ) { if( !GetHook( hDriver, i, &((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress ) ) { bOK = FALSE; break; } //////////////////////// //通过地址得到模块名 char ModNameBuf[MAX_PATH+1]; memset( ModNameBuf, 0, sizeof( ModNameBuf ) ); if( GetModuleNameByAddr( \ ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ulCurrentFunctionAddress, \ pList, ModNameBuf, sizeof( ModNameBuf )-1 ) ) { memcpy( \ ((pSSDTSaveTable)((ULONG)pSSDTST + i * sizeof(SSDTSaveTable)))->ModuleName, \ ModNameBuf, \ sizeof( ModNameBuf ) \ ); } } //按服务号进行排序 SSDTSTOrderByServiceNum( pSSDTST ); //获取原始函数地址 GetOldSSDTAddress(); } if( pList ) { DestroyModList( pList ); //释放模块链表 pList = NULL; } return bOK;}
    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。其中最重要的函数代码如下:
    UINT CPage6::AddDirectoryNodes(HTREEITEM hItem, CString &strPathName){ WCHAR wBuf[60]; DWORD bytesReturned=0; ULONG num=0; PDIRECTORY_INFO temp={0}; DIRECTORY_INFO_EX DIRECTORY_INFO_b; CString str,str1,strFileSpec = strPathName; if (strFileSpec.Right (1) != "\\") strFileSpec += "\\"; char a[MAX_PATH]; str1=strFileSpec; CMySystem::WCHARToChar(a,strFileSpec.GetBuffer(strFileSpec.GetLength())); strFileSpec += "*.*"; DeviceIoControl(hDevice,(DWORD)IOCTL_MT_GETDIRNUMINF,a,sizeof(a),&num,sizeof(ULONG),&bytesReturned,NULL); if(num==0) { AfxMessageBox(L"驱动未加载,列举出错!"); return 0; } temp=(PDIRECTORY_INFO)calloc(num,sizeof(DIRECTORY_INFO)); if(temp==NULL) { return 0; } DeviceIoControl(hDevice,(DWORD)IOCTL_MT_GETDIRINF,a,sizeof(a),temp,num*sizeof(DIRECTORY_INFO),&bytesReturned,NULL); CWaitCursor wait; WCHAR wTemp[MAX_PATH]={'\0'}; m_FileList.DeleteAllItems(); index=0; SetPath(str1,hDevice); for(ULONG i=0;i<num;i++) { TRACE("AddDirectoryNode:%d\n",i); CMySystem::CharToWCHAR(wTemp,temp[i].FileName); str.Format(L"%s",wTemp); str=str1+str; CString strFileName = (LPCTSTR) &temp[i].FileName; if(PathIsDirectory(str)) { if(strcmp(temp[i].FileName,".")) { if(strcmp(temp[i].FileName,"..")) { CMySystem::CharToWCHAR(wTemp,temp[i].FileName); HTREEITEM hChild = m_FileTree.InsertItem ((LPCTSTR) wTemp,//&fd.cFileName, ILI_CLSDFLD , ILI_OPENFLD , hItem , TVI_SORT); CString strNewPathName = strPathName; if (strNewPathName.Right (1) != "\\") {strNewPathName += "\\";} CMySystem::CharToWCHAR(wBuf,temp[i].FileName); strNewPathName += wBuf;//fd.cFileName; SetButtonState (hChild, strNewPathName); } } } else { DIRECTORY_INFO_b.DirectoryInfo=temp[i]; DIRECTORY_INFO_b.path=str1; AddToListView(&DIRECTORY_INFO_b); } } delete temp; return num;}
    3.6.2 文件删除文件删除分为普通删除与驱动删除。

    普通删除:调用Win32 API DeleteFile()删除文件。对付普通病毒这种方式有效。但某些病毒会采用文件占用式保护本体不被删除,甚至采用驱动形式保护,此时普通删除无效
    驱动删除:传递控制字IOCTL_MT_KILLFILE至驱动Explorer.sys,驱动删除病毒,此方式有一定危险性,但对病毒有奇效。极少数病毒能逃离此法删除

    3.7 磁盘编辑器3.7.1 十六进制编辑器界面处理我们的磁盘编辑器界面采用了重载后的CEdit类。新类CHexEdit响应下列消息:
    WM_CHARWM_KILLFOCUSWM_PAINTWM_SETFOCUSWM_SIZEWM_VSCROLLWM_HSCROLLWM_GETDLGCODEWM_ERASEBKGNDWM_LBUTTONDOWNWM_LBUTTONDBLCLKWM_MOUSEMOVEWM_LBUTTONUPWM_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文件分析文件分析部分没有什么内核技术,纯粹是个辅助性功能。相应部分看函数便可知。
    CPage9::LoadFile(); CPage9::IsPEFile() CPage9::PrintFileHeader(); CPage9::PrintOptionAlHeader(); CPage9::PrintSectionInfo(); CPage9::printET(); CPage9::printIAT(); 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:
    KeInitializeEvent(&event,SynchronizationEvent,FALSE); lpInformation = ExAllocatePool(PagedPool,655350); lpSystemBuffer = ExAllocatePool(PagedPool,655350); RtlZeroMemory(lpSystemBuffer,655350); RtlZeroMemory(lpInformation,655350); lpirp->UserEvent = &event; lpirp->UserBuffer = lpInformation; lpirp->AssociatedIrp.SystemBuffer = lpInformation; lpirp->MdlAddress = NULL; lpirp->Flags = 0; lpirp->UserIosb = &ios; lpirp->Tail.Overlay.OriginalFileObject = lpFileObject; lpirp->Tail.Overlay.Thread = PsGetCurrentThread(); lpirp->RequestorMode = KernelMode; lpsp = IoGetNextIrpStackLocation(lpirp); lpsp->MajorFunction = IRP_MJ_DIRECTORY_CONTROL; lpsp->MinorFunction = IRP_MN_QUERY_DIRECTORY; lpsp->FileObject = lpFileObject; lpsp->DeviceObject = lpDeviceObject; lpsp->Flags = SL_RESTART_SCAN; lpsp->Control = 0; lpsp->Parameters.QueryDirectory.FileIndex = 0; lpsp->Parameters.QueryDirectory.FileInformationClass = FileDirectoryInformation; lpsp->Parameters.QueryDirectory.FileName = NULL; lpsp->Parameters.QueryDirectory.Length = 655350;
    填这样当IRP返回时便携带了我们所需要的文件信息。
    强制删除文件同样采用构造IRP下发方式。不同的是下发的IRP参数不同:
    FileInformation.DeleteFile = TRUE; Irp->AssociatedIrp.SystemBuffer = &FileInformation; Irp->UserEvent = &event; Irp->UserIosb = &ioStatus; Irp->Tail.Overlay.OriginalFileObject = fileObject; Irp->Tail.Overlay.Thread = (PETHREAD)KeGetCurrentThread(); Irp->RequestorMode = KernelMode; irpSp = IoGetNextIrpStackLocation(Irp); irpSp->MajorFunction = IRP_MJ_SET_INFORMATION; irpSp->DeviceObject = DeviceObject; irpSp->FileObject = fileObject; irpSp->Parameters.SetFile.Length = sizeof(FILE_DISPOSITION_INFORMATION); irpSp->Parameters.SetFile.FileInformationClass = FileDispositionInformation; irpSp->Parameters.SetFile.FileObject = fileObject;
    4.7 硬盘编辑硬盘编辑功能的实现位于驱动SIoctl.sys中。为了实现底层的硬盘编辑,我们选择PhysicalDrive0设备对象。根据《深入解析windows》(第5版),这个对象其实是硬盘驱动atapi.sys的一个驱动。在这层驱动中,文件系统所需要的文件路径等已经不存在了,我们面对是直接是硬盘。下面是填充IRP和下发过程:
    irpSp = IoGetNextIrpStackLocation(irp); irp->UserEvent = &event; irp->IoStatus.Status = 0; irp->IoStatus.Information = 0; irp->UserBuffer = NULL; irp->Flags = (irp->Type << 16) | 5; irp->Tail.Overlay.Thread = PsGetCurrentThread(); irp->Cancel = FALSE; IoSetCancelRoutine(irp,NULL); irp->RequestorMode =KernelMode; irp->AssociatedIrp.SystemBuffer = NULL; irpSp->DeviceObject = DeviceObject; irpSp->MajorFunction = (UCHAR)ReadOrWrite; 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, 这里有一个大的问题,只有一个过滤函数可以安装,因此如果另外的应用程序已经安装了一个过滤函数,你就不能再安装了

    设置过滤函数的代码如下:
    NTSTATUS SetFilterFunction(PacketFilterExtensionPtr filterFunction){ NTSTATUS status = STATUS_SUCCESS, waitStatus=STATUS_SUCCESS; UNICODE_STRING filterName; PDEVICE_OBJECT ipDeviceObject=NULL; PFILE_OBJECT ipFileObject=NULL; PF_SET_EXTENSION_HOOK_INFO filterData; KEVENT event; IO_STATUS_BLOCK ioStatus; PIRP irp; dprintf("Getting pointer to IpFilterDriver\n"); RtlInitUnicodeString(&filterName, DD_IPFLTRDRVR_DEVICE_NAME); status = IoGetDeviceObjectPointer(&filterName,STANDARD_RIGHTS_ALL, &ipFileObject, &ipDeviceObject); dprintf("OK:%x",status); if(NT_SUCCESS(status)) { filterData.ExtensionPointer = filterFunction; KeInitializeEvent(&event, NotificationEvent, FALSE); irp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER, ipDeviceObject, (PVOID) &filterData, sizeof(PF_SET_EXTENSION_HOOK_INFO), NULL, 0, FALSE, &event, &ioStatus); if(irp != NULL) { status = IoCallDriver(ipDeviceObject, irp); if (status == STATUS_PENDING) { waitStatus = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); if (waitStatus != STATUS_SUCCESS ) dprintf("Error waiting for IpFilterDriver response."); } status = ioStatus.Status; if(!NT_SUCCESS(status)) dprintf("Error, IO error with ipFilterDriver\n"); } else { status = STATUS_INSUFFICIENT_RESOURCES; dprintf("Error building IpFilterDriver IRP\n"); } if(ipFileObject != NULL) { dprintf("ObDereferenceObject"); ObDereferenceObject(ipFileObject); } ipFileObject = NULL; ipDeviceObject = NULL; } else dprintf("Error while getting the pointer\n"); return status;}
    这时已经完成了建立过滤函数的工作,当取得设备驱动的指针后必须释放文件对象。我们使用事件来通知IpFilterDriver 已经完成了IRP处理。
    下面是过滤函数代码:
    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){ IPPacket *ipp; TCPHeader *tcph; UDPHeader *udph; int countRule = 0; struct filterList *aux = first; WCHAR wcMessage[MAXSTR]; ipp = (IPPacket*)PacketHeader; MyPrint(SEPARATOR); dprintf("PacketInfo %x, %d\r\n", PacketLength, RecvInterfaceIndex); dprintf("Source: %x Destination: %x Protocol: %d\r\n", ipp->ipSource, ipp ->ipDestination, ipp->ipProtocol); swprintf(wcMessage, L "PacketLength: %x, RecvInterfaceIndex:%d\r\n", PacketLength, RecvInterfaceIndex); MyPrint(wcMessage); swprintf(wcMessage, L "NetInfomation:@%x@@%x@@@%d\r\n", ipp->ipSource, ipp ->ipDestination, ipp->ipProtocol); MyPrint(wcMessage); if (ipp->ipProtocol == 6) { tcph = (TCPHeader*)Packet; dprintf("FLAGS: %x\r\n", tcph->flags); swprintf(wcMessage, L "FLAGS: %x\r\n", tcph->flags); if (!(tcph->flags &0x02)) return PF_FORWARD; } while (aux != NULL) { dprintf("Comparing with Rule %d", countRule); if (aux->ipf.protocol == 0 || ipp->ipProtocol == aux->ipf.protocol) { if (aux->ipf.sourceIp != 0 && (ipp->ipSource &aux->ipf.sourceMask) != aux->ipf.sourceIp) { aux = aux->next; countRule++; continue; }if (aux->ipf.destinationIp != 0 && (ipp->ipDestination &aux->ipf.destinationMask) != aux->ipf.destinationIp) { aux = aux->next; countRule++; continue; } //tcp, protocol = 6 if (ipp->ipProtocol == 6) { if (aux->ipf.sourcePort == 0 || tcph->sourcePort == aux->ipf.sourcePort) { if (aux->ipf.destinationPort == 0 || tcph->destinationPort == aux->ipf.destinationPort) //puerto tcp destino { if (aux->ipf.drop) return PF_DROP; else return PF_FORWARD; } } } //udp, protocol = 17 else if (ipp->ipProtocol == 17) { udph = (UDPHeader*)Packet; if (aux->ipf.sourcePort == 0 || udph->sourcePort == aux->ipf.sourcePort) { if (aux->ipf.destinationPort == 0 || udph->destinationPort == aux->ipf.destinationPort) { if (aux->ipf.drop) return PF_DROP; else return PF_FORWARD; } } } else { if (aux->ipf.drop) return PF_DROP; else return PF_FORWARD; } } countRule++; aux = aux->next; } return PF_FORWARD;}
    4.9 PE文件分析见应用层分析部分
    至此,整个KsBinSword分析完毕。
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++资源转载自:https://download.csdn.net/download/anzyky/3329962+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    1 评论 18 下载 2018-10-05 22:02:18 下载需要13点积分
显示 60 到 62 ,共 2 条
eject