windows PPTP协议通道分析
la0gke 发表于 北京 二进制安全 600浏览 · 2024-08-12 04:26

描述

Windows VPN中的PPTP协议基于通过套接字实现,Windows 内核中的套接字 – Winsock 内核 (WSK):https://docs.microsoft.com/en-us/windows-hardware/drivers/network/winsock-kernel-overview。主要以API的形式提供服务,WSK API 通常的使用方式是通过一组事件驱动的回调函数。一旦建立了套接字,应用程序就可以提供一个调度表,其中包含一组要为套接字相关事件调用的函数指针。为了使应用程序能够通过这些回调维护自己的状态,驱动程序还为每个回调提供了上下文结构,以便可以在连接的整个生命周期中跟踪连接的状态。

pptp协议整个过程分为控制通道和数据通道,控制通道的创建分为套接字、控制连接、呼叫连接几个阶段。

套接字生命周期

主要通过WskOpenSocket函数处理客户端套接字。传入如下几个参数客户端上下文、客户端调度表等等。

主要有这么几个回调函数。

对于接收到的客户端,使用WskConnAcceptEvent函数进行处理。

该函数先创建一个240大小,标记为“wana”的标记内存池。

之后把新的客户端套接字写入到这个内存。a1+8是控制连接上下文结构。

通过DBG看一下分别是什么。首先写入十进制1129010007,16进制是434b5357,也就是字符串"WSKC"

接着偏移2个QWORD处的指向函数raspptp!CtlReceiveCallback

偏移3处指向raspptp!CtlDisconnectCallback

偏移4处指向raspptp!CtlConnectQueryCallback

偏移6处指向raspptp!CtlLogWskLibActivity:

偏移8处处指向raspptp!CtlpSendMessageComplete

最后是sock释放函数指向FreeSockHandle和引用计数初始化为1。

把初始化后的客户端套接字指向当前堆栈结构体中的上下文字段,供整个过程使用。

释放

主要通过WskConnDisconnectEvent函数进行释放。

客户端套接字根据引用计数,决定继续使用或者释放。

每次使用上下文结构体时引用计数会递增,如下:

_InterlockedIncrement((volatile signed __int32 *)(a1 + 512));

通过调试可以看到当前被引用了3次

使用结束会对引用计数递减,当引用次数为0时,说明没有引用了,进行释放。

if ( !_InterlockedDecrement((volatile signed __int32 *)(a1 + 512)) )
    (*(void (__fastcall **)(__int64))(a1 + 520))(a1);

控制连接生命周期

相关回调函数

控制连接对象在套接字对象中包含,主要包含以下几个回调函数

CtlConnectQueryCallback:控制连接查询,新的控制连接由这个回调函数处理

CtlDisconnectCallback:控制断开,结束掉当前控制连接。

CtlReceiveCallback:控制接收,处理控制接收的数据。

CtlpSendMessageComplete:控制发送消息完成,负责控制连接发送消息。

结构体定义

控制连接上下文主要通过CtlAlloc函数定义。

函数开始先分配0x290的大小,标记为"PTPT"

接下来进行结构体构造,看看都分配了什么。

偏移0h:存放自身指针地址

偏移8h:存放自身指针地址

偏移20h处存放字符PTPT

偏移28h处存放NDIS句柄

接着创建一个NDIS定时器,把构造好的Ctl结构体指向NDIS定时器上下文函数字段,同时配置NDIS定时器头设置为(NDIS_OBJECT_HEADER)0x180197,是一个NDIS对象标准头。标签设置为"PTMT",定时器函数为(PNDIS_TIMER_FUNCTION)CtlpEchoTimeout。

偏移358h处存放创建后的NDIS定时器指针。

新控制连接处理

新建控制连接由CtlConnectQueryCallback处理。

参数a1,未知

参数a2,未知

参数a3,猜测应该是一个wsk类型的套接字结构体对象,因为数据结构一样。

该函数先通过CtlAlloc创建一个控制连接对象,创建的对象并不完整,该函数会进一步赋值。

先设置状态为3,在对象的偏移30h处。

偏移48h处设置为1。

偏移58h处指向自旋锁

偏移80h处指向套接字结构体对象

偏移60h处存放IRQL,自旋锁释放用到。

偏移248h处指向NDIS类型的计时器句柄,设置时间为30秒

偏移238h处指向Echo计时器,如果有设置,按照设置的进行。

释放

跟套接字类似,也是使用引用计数的方式,调用的时候引用计数递增,调用结束引用计数递减。处理逻辑主要在函数CtlCleanup中。

递增。这里的a1 + 104,就是前边的控制连接结构体偏移48。因为这里的控制连接结构体是从a1偏移20h处开始的。

_InterlockedIncrement((volatile signed __int32 *)(a1 + 16));
*(_BYTE *)(a1 + 104) = 1;

递减,如果递减后等于0,调用自身指向的释放函数CtlFree进行释放。

*(_BYTE *)(a1 + 104) = 0;
if ( !_InterlockedDecrement((volatile signed __int32 *)(a1 + 16)) )
    (*(void (__fastcall **)(__int64))(a1 + 24))(a1);

呼叫连接(CallAlloc)

Call结构体定义

首先定义该结构体对象占内存大小为0x2B8ui,标志为"PTPC"

偏移30h处写入标志“PTPC”

偏移38h处指向一个NDIS_HANDLE类型的对象

偏移c2h处设置为0

偏移248h处设置为0

偏移60h、88h、a8h处分别是自旋锁指针,并进行初始化。

偏移1E0h处指向由NdisAllocateNetBufferList函数返回的网络缓冲列表。

偏移258h处指向由NdisAllocateIoWorkItem函数获取的IO工作项句柄。

如果获取的Io工作项为真,就再获取一次。

偏移260h处指向第二次获取的Io工作项

如果第二次获取的Io工作项仍然为真。

偏移78h、80h、A0h、98h处都设置为0。

然后设置NDIS定时器对象TimerCharacteristics属性。函数上下文指向初始化的Call结构体对象。

头指向NDIS_OBJECT_HEADER结构体,值为16进制180197

分配标签为"PTMT",跟控制连接的一样。

定时器回调函数指向CallpCloseTimeout。

使用NDIS库中的NdisAllocateTimerObject函数进行定时器分配,

偏移1a0h处指向分配后的定时器对象

接着分配Call Ack定时器

偏移1c0h处指向Call Ack定时器。

偏移200h处指向CallpDialTimeout计时器

释放

Call对象使用完之后,一般会调用CallCleanup进行释放。同样的,也是使用引用计数的方式进行调用和释放。不同的是,在初始化Call对象时,如果出错,会直接调用 CallFree函数进行释放。

递减,如果等于1,调用偏移40处的函数进行释放。

0 条评论
某人
表情
可输入 255