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 );

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;

安装协议提供者函数

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

排列提供者顺序函数

int WSCWriteProviderOrder( LPDWORD lpwdCatalogEntryId, DWORD dwNumberOfEntries);

简单测试代码:

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

dll文件的测试代码:

// 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劫持注入的更多相关文章

  1. Dll注入技术之ComRes注入

    DLL注入技术之ComRes注入 ComRes注入的原理是利用Windows 系统中C:\WINDOWS\system32目录下的ComRes.dll这个文件,当待注入EXE如果使用CoCreateI ...

  2. Dll注入技术之输入法注入

    DLL注入技术之输入法注入 输入法注入原理是利用Windows系统中在切换输入法需要输入字符时,系统就会把这个输入法需要的ime文件装载到当前进程中,而由于这个Ime文件本质上只是个存放在C:\WIN ...

  3. DLL注入技术之劫持进程创建注入

    劫持进程创建注入原理是利用Windows系统中CreateProcess()这个API创建一个进程,并将第6个参数设为CREATE_SUSPENDED,进而创建一个挂起状态的进程,利用这个进程状态进行 ...

  4. Dll注入技术之APC注入

    APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:     1)当EXE里某个线程执行到SleepEx( ...

  5. DLL注入技术(输入法注入)

    输入法注入原理 IME输入法实际就是一个dll文件(后缀为ime),此dll文件需要导出必要的接口供系统加载输入法时调用.我们可以在此ime文件的DllMain函数的入口通过调用LoadLibrary ...

  6. Dll注入技术之注册表注入

    DLL注入技术之REG注入 DLL注入技术指的是将一个DLL文件强行加载到EXE文件中,并成为EXE文件中的一部分,这样做的目的在于方便我们通过这个DLL读写EXE文件内存数据,(例如 HOOK EX ...

  7. SQL注入技术专题—由浅入深【精华聚合】

    作者:坏蛋链接:https://zhuanlan.zhihu.com/p/23569276来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 不管用什么语言编写的Web应用 ...

  8. SQL注入技术专题—由浅入深【精华聚合贴】

    SQL注入技术专题—由浅入深[精华聚合贴] 不管用什么语言编写的Web应用,它们都用一个共同点,具有交互性并且多数是数据库驱动.在网络中,数据库驱动的Web应用随处可见,由此而存在的SQL注入是影响企 ...

  9. Android中通过进程注入技术改动广播接收器的优先级

    前言 这个周末又没有吊事,在家研究了怎样通过进程的注入技术改动广播接收器的优先级.关于这个应用场景是非常多的.并且也非常重要.所以就非常急的去fixed了. Android中的四大组件中有一个广播:B ...

随机推荐

  1. vue开发常见命令

    1.安装脚手架 安装脚手架命令:npm install -global vue-cli 2.升级脚手架 有时候需要把整个脚手架升级一下,这个用到命令npm install --global vue-c ...

  2. CSS 简介、 选择器、组合选择器

    #CSS 装饰器引入<!DOCTYPE html> <html> <head> <meta charset="utf-8"> < ...

  3. 现在使用Nginx实现TCP反向代理

    Nginx 在1.9.0版本发布以前如果要想做到基于TCP的代理及负载均衡需要通过打名为 nginx_tcp_proxy_module 的第三方patch来实现,该模块的代码托管在github上网址: ...

  4. 单页应用 - Token 验证

    单页应用 - Token 验证 转:https://juejin.im/post/58da720b570c350058ecd40f 第一次接触单页应用,记录公司项目关于Token验证知识. Token ...

  5. 解决IE8不支持html5标签最好解决办法?

    完美解决IE(IE6/IE7/IE8)不兼容HTML5标签的方法:HTML5的语义化标签以及属性,可以让开发者非常方便地实现清晰的web页面布局,加上CSS3的效果渲染,快速建立丰富灵活的web页面显 ...

  6. A - Packets 贪心

    A factory produces products packed in square packets of the same height h and of the sizes 1*1, 2*2, ...

  7. socket.setSoTimeout(1000);

    这个用来设置与socket的inputStream相关的read操作阻塞的等待时间,超过设置的时间了,假如还是阻塞状态,会抛出异常java.net.SocketTimeoutException: Re ...

  8. UVA12569-Planning mobile robot on Tree (EASY Version)(BFS+状态压缩)

    Problem UVA12569-Planning mobile robot on Tree (EASY Version) Accept:138  Submit:686 Time Limit: 300 ...

  9. 03 python 初学(字符格式化输出)

    #_author: lily #_date: 2018/12/16 name = input("your name: ") age = input("your age: ...

  10. 关于NSA的EternalBlue(永恒之蓝) ms17-010漏洞利用

            好久没有用这个日志了,最近WannaCry横行,媒体铺天盖地的报道,我这后知后觉的才想起来研究下WannaCry利用的这个原产于美帝的国家安全局发现的漏洞,发现漏洞不说,可以,自己偷偷 ...