注入技术--LSP劫持注入

时间:2021-09-20 18:43:36

1.原理

简单来说,LSP就是一个dll程序. 应用程序通过winsock2进行网络通信时,会调用ws2_32.dll的导出函数,如connect,accept等.

而后端通过LSP实现这些函数的底层. 简单来说就是调用winsock2提供的函数时会调用对应的LSP提供的SPI(服务提供者接口)函数.

例如,mswsock.dll 提供了所有tcp协议api对应的spi函数的实现. 但是如果有多个符合条件的SPI,系统将会调用在winsock目录最前面

的那个. 如果我们注册一个对应的SPI并调到winsock目录最前面,这样就可以替换掉系统默认的了.

一些ring3层的防火墙就是通过这个原理实现的.

详细介绍:https://en.wikipedia.org/wiki/Layered_Service_Provider

2.实现

涉及到的头文件和库

#include<WS2spi.h>

#include <RPC.H>
#include <Rpcdce.h>

#include<Sporder.h>
#pragma comment(lib,"Sporder.lib")
#pragma comment(lib, "Rpcrt4.lib") // 实现了UuidCreate函数

(1)枚举winsock目录协议:
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength);

Parameters

lpiProtocols
[in] Null-terminated array of iProtocol values. This parameter is optional; if lpiProtocols is NULL, information on all available protocols is returned. Otherwise, information is retrieved only for those protocols listed in the array.
lpProtocolBuffer
[out] Buffer that is filled with WSAPROTOCOL_INFO structures.
lpdwBufferLength
[in, out] On input, the count of bytes in the lpProtocolBuffer buffer passed to this function. On output, the minimum buffer size that can be passed to this function to retrieve all the requested information. This routine has no ability to enumerate over multiple calls; the passed-in buffer must be large enough to hold all entries for the routine to succeed. This reduces the complexity of the API and should not pose a problem because the number of protocols loaded on a machine is typically small. 

或者:

int WSCEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFOW lpProtocolBuffer, LPDWORD lpdwBufferLength, LPINT lpErrno );

注入技术--LSP劫持注入
typedef struct _WSAPROTOCOL_INFOW {
DWORD dwServiceFlags1;
DWORD dwServiceFlags2;//0
DWORD dwServiceFlags3;//0
DWORD dwServiceFlags4;//0
DWORD dwProviderFlags;
GUID ProviderId;     //guid
DWORD dwCatalogEntryId;  //winsock目录id
WSAPROTOCOLCHAIN ProtocolChain;  //协议属性
int iVersion;
int iAddressFamily;
int iMaxSockAddr;
int iMinSockAddr;
int iSocketType;
int iProtocol;
int iProtocolMaxOffset;
int iNetworkByteOrder;
int iSecurityScheme;
DWORD dwMessageSize;
DWORD dwProviderReserved;
WCHAR szProtocol[WSAPROTOCOL_LEN+]; //协议名字,可任意填写
} WSAPROTOCOL_INFOW, FAR * LPWSAPROTOCOL_INFOW;
注入技术--LSP劫持注入

安装协议提供者函数

int WSCInstallProvider( const LPGUID lpProviderId, const LPWSTR lpszProviderDllPath, const LPWSAPROTOCOL_INFOW lpProtocolInfoList, DWORD dwNumberOfEntries, LPINT lpErrno );

排列提供者顺序函数

int WSCWriteProviderOrder( LPDWORD lpwdCatalogEntryId, DWORD dwNumberOfEntries);

简单测试代码:

注入技术--LSP劫持注入
// spi.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h" #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 = ; //大小
DWORD num; //数量
WSCEnumProtocols(, , &size, );
info = (LPWSAPROTOCOL_INFOW)malloc(size);
num = WSCEnumProtocols(, info, &size, );
if (num == SOCKET_ERROR)
{
free(info);
return ;
}
int i;
for ( i= ; i < num; i++)
{
if (lstrcmpW(info[i].szProtocol,layerName)==)
{
memcpy(&layerGuid, &info[i].ProviderId, sizeof(GUID));
break;
}
}
free(info);
if (i==num)//没找到
{
return ;
}
return ;
}
DWORD lspInject()
{
//枚举winsock目录中的协议
LPWSAPROTOCOL_INFOW info;//指向winsock目录中协议
DWORD size = ; //大小
DWORD num; //数量
WSCEnumProtocols(, , &size, );
info = (LPWSAPROTOCOL_INFOW)malloc(size);
num = WSCEnumProtocols(, info, &size, );
DWORD trueId; //存储被安装的提供者的目录id
if (num == SOCKET_ERROR)
{
free(info);
return ;
} WCHAR supplier[] = layerName;
WCHAR dllpath[] = L"E:\\0day\\shellcode\\Debug\\freesec.dll";//指定你的dll文件
DWORD myId;
int proto = IPPROTO_TCP; //目标协议 WSAPROTOCOL_INFOW save = { }; //用于存储指定协议的正常的提供者,最后用来作为分层协议和协议链的模板for (int i = ; i < num; i++)
{//找符合条件的提供者,但不能是分层协议
if (info[i].iAddressFamily == AF_INET&&info[i].iProtocol == proto&&info[i].ProtocolChain.ChainLen!=)
{
memcpy(&save, &info[i], sizeof(WSAPROTOCOL_INFOW)); //将原来的基础协议信息保存
save.dwServiceFlags1 &= ~XP1_IFS_HANDLES; //去掉XP1_IFS_HANDLES标志
trueId = info[i].dwCatalogEntryId;
break;
}
} //安装分层协议
WSAPROTOCOL_INFOW Lpi = { }; //新的分层协议
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, , ) == SOCKET_ERROR) //安装该分层协议
{
free(info);
return ;
} //重新枚举协议以获取分层协议的目录id
free(info); //因为添加了一个分层协议,所以需要重新分配内存
DWORD layerId; //保存分层协议目录id
WSCEnumProtocols(, , &size, );
info = (LPWSAPROTOCOL_INFOW)malloc(size);
num = WSCEnumProtocols(, info, &size, );
if (num == SOCKET_ERROR)
{
free(info);
return ;
} for (int i = ; i < num; i++) //遍历协议,直到找到刚才新增的分层协议
{
if (memcmp(&info[i].ProviderId, &pguid, sizeof(GUID)) == )
{
layerId = info[i].dwCatalogEntryId; //获取分层协议目录id
}
} //安装协议链
WCHAR chainName[WSAPROTOCOL_LEN + ]; //其实就是一个名字代号,和分层协议的名字一样
wsprintf(chainName, L"%ls over %ls", supplier, save.szProtocol);
lstrcpyW(save.szProtocol, chainName); //改名字1
if (save.ProtocolChain.ChainLen == ) //如果目标协议的正常提供者是基础协议则将其目录id放在协议链的第2个位置
{
save.ProtocolChain.ChainEntries[] = trueId; //将id写入到该协议链的ChainEntries数组中,这个数组只有当它是协议链时才有意义
}
else //否则就是协议链提供者
{
for (int i = save.ProtocolChain.ChainLen; i > ; i--)//如果是协议链则将该协议链中其他协议往后移,
//以便将自己的分层协议插入到链首.但是这个数组最大存7个,所以如果原来就占满了,理论上会挤掉最后一个
{
save.ProtocolChain.ChainEntries[i] = save.ProtocolChain.ChainEntries[i - ];
}
} save.ProtocolChain.ChainEntries[] = layerId;
save.ProtocolChain.ChainLen++; //获取guid,安装协议链
GUID providerChainGuid;
UuidCreate(&providerChainGuid);
if (WSCInstallProvider(&providerChainGuid, dllpath, &save, , ) == SOCKET_ERROR)
{
free(info);
return ;
} //重新枚举协议
free(info);
WSCEnumProtocols(, , &size, );
info = (LPWSAPROTOCOL_INFOW)malloc(size);
num = WSCEnumProtocols(, info, &size, );
if (num == SOCKET_ERROR)
{
free(info);
return ;
}
//遍历获取我们的协议链的目录id
DWORD* chainId = (DWORD*)malloc(num * sizeof(DWORD)); //这个是协议链的目录id数组,把我们的协议链id
//放在最前面,系统原来的按顺序放后面
DWORD cindex = ;
for (int i = ; i < num; i++)
{
if ((info[i].ProtocolChain.ChainLen > ) && (info[i].ProtocolChain.ChainEntries[] == layerId))
{
chainId[cindex] = info[i].dwCatalogEntryId;
cindex++;
}
}
for (int i = ; i < num; i++)
{
if ((info[i].ProtocolChain.ChainLen <= ) || (info[i].ProtocolChain.ChainEntries[] != layerId))
{
chainId[cindex] = info[i].dwCatalogEntryId;
cindex++;
}
} if (WSCWriteProviderOrder(chainId, cindex) != )
{
free(info);
free(chainId);
return ;
} free(info);
free(chainId);
return ; } DWORD uninstall()
{
if(findGuid()==)
{
return ;
}
//枚举winsock目录中的协议
LPWSAPROTOCOL_INFOW info;//指向winsock目录中协议
DWORD size = ; //大小
DWORD num; //数量
DWORD Id;
DWORD result;
int cc;  //作为错误码,下面2个函数的错误码地址必须提供,否则会调用失败
WSCEnumProtocols(, , &size, );
info = (LPWSAPROTOCOL_INFOW)malloc(size);
num = WSCEnumProtocols(, info, &size, );
if (num == SOCKET_ERROR)
{
free(info);
return ;
}
int i = ; for (i=; i < num; i++)
{
if (memcmp(&layerGuid,&info[i].ProviderId,sizeof(GUID))==)
{
Id = info[i].dwCatalogEntryId;
}
}
if (i<=num)
{
for (i = ; i < num; i++)
{
if ((info[i].ProtocolChain.ChainLen>)&&(info[i].ProtocolChain.ChainEntries[]==Id))
{ if((result=WSCDeinstallProvider(&info[i].ProviderId, &cc))==SOCKET_ERROR)
{ free(info);
return ;
}
break;
}
}
  free(info);
if((result=WSCDeinstallProvider(&layerGuid, &cc))==SOCKET_ERROR)
{return ;
}
}
else
{
     free(info);
return ;
}return ;
}
int main(int argc, char** argv)
{
setlocale(LC_ALL, "chs");
int result;
if (argc!=)
{
printf("usage:%s install or uninstall\n", argv[]);
return ;
}
if (strcmp(argv[],"install")==)
{
if (lspInject())
{
printf("install success\n");
}
else
{
printf("install error code is %d\n", GetLastError());
}
}
else if(strcmp(argv[], "uninstall") == )
{
if (uninstall())
{
printf("uninstall success\n");
}
else
{
printf("uninstall error code is %d\n", GetLastError());
}
} return ; }
注入技术--LSP劫持注入

dll文件的测试代码:

注入技术--LSP劫持注入
// freesec.dll.cpp : 定义 DLL 应用程序的入口点。
// #include "stdafx.h"
WCHAR exepath[MAX_PATH] = { };
WSPPROC_TABLE trueTable = { };
int GetProvider(LPWSAPROTOCOL_INFOW &pProtoInfo)
{
// 首次调用,pProtoInfo传入NULL,取得需要的缓冲区长度
DWORD dwSize = ;
int nError = ;
if (WSCEnumProtocols(NULL, NULL, &dwSize, &nError) == SOCKET_ERROR)
{
if (nError != WSAENOBUFS)
{
return ;
}
}
// 申请足够缓冲区内存。
pProtoInfo = (LPWSAPROTOCOL_INFOW)GlobalAlloc(GPTR, dwSize);
if (pProtoInfo == NULL)
{
return ;
}
//再次调用WSCEnumProtocols函数
return WSCEnumProtocols(NULL, pProtoInfo, &dwSize, &nError);
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
GetModuleFileNameW(, exepath, MAX_PATH * sizeof(wchar_t));
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
} 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())
{
MessageBoxW(, L"有程序访问外网80端口", L"拒绝访问", );
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 <= )
{
return WSAEPROVIDERFAILEDINIT;
}
WCHAR exename[] = { };
wsprintf(exename, L"应用程序: %ls 正在联网,是否允许?", exepath);
if (MessageBoxW(,exename,L"温馨提示",MB_YESNO|MB_ICONWARNING)==IDNO)
{
MessageBoxW(, L"已拦截", L"提示", );
return WSAEPROVIDERFAILEDINIT;
}
// 枚举协议,找到下层协议的WSAPROTOCOL_INFOW结构
WSAPROTOCOL_INFOW trueProtocolInfo; //保存真正的协议结构
LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
int allproto = GetProvider(pProtoInfo);
DWORD trueId = lpProtocolInfo->ProtocolChain.ChainEntries[];//获取真正的协议目录id
int i;
//遍历查找真正的协议结构
for (i = ; 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 = ; j<lpProtocolInfo->ProtocolChain.ChainLen; j++)
{
lpProtocolInfo->ProtocolChain.ChainEntries[j]
= lpProtocolInfo->ProtocolChain.ChainEntries[j + ];
}
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; }
注入技术--LSP劫持注入