离职以来是想通了很多东西,有些事情,真的没必要在意,也不值得自己在意。。太久没有在看雪发帖啦,工作的那段时间在看雪的唯一活动就是给同事发邀请码,
自己注册马甲灌水。。。真没出息啊!趁过年即将来临之际发一帖,提前给大家拜个早年。。
PS:本文章适合菜鸟,老鸟飞过。。
好,下面入正题。LPC是什么,不多说了,没意义。LPC可以做什么,也不多说,大家更清楚。LPC的原理是什么,没精力全讲,网上资料也太多(造成误解也多)。。。
这里主要简单谈一下LPC在内核编程中的设计问题。。。
之前有个同学问我关于LPC的多向通讯问题,问我有没有Demo,其实自己也没怎么用过LPC。于是在看雪随便下个Demo给他
(http://bbs.pediy.com/showthread.php?p=1038030)。
后来自己看了一下代码才发现这样的设计其实是没有做到多向通讯的,并且造成对多向通讯设计的误区,就是:一个线程调用NtConnectPort负责
监听客户端的连接------>连接后新建一个线程负责提供服务。每有一个客户端连接都创建一个线程提供服务。对于这个问题,毛德操老师在他的文章
《漫谈兼容内核之十八:Windows的LPC机制》中讲的也是模凌两可,比较含糊
(http://www.longene.org/techdoc/0078130001224576866.html)。
事实上,这是LPC多向通讯设计的误区。
为什么说是误区,其实关键在于LPC中出现的两种端口:NtCreatePort所创建的连接端口、客户端连接上后服务器和客户端分别得到的数据通讯端口。
从NtReplyWaitReceivePortEx和NtRequestWaitReplyPort中可以看到这两种LPC端口对象的逻辑关系。
先来看看端口对象:
kd> dt e1320030 _LPCP_PORT_OBJECT
nt!_LPCP_PORT_OBJECT
+0x000 ConnectionPort : 0xe12ca458 _LPCP_PORT_OBJECT ------> 由服务器调用NtCreatePort所创建的连接端口
+0x004 ConnectedPort : 0xe10f3388 _LPCP_PORT_OBJECT ------> 已经建立连接后,对方通讯端口的对象
+0x008 MsgQueue : _LPCP_PORT_QUEUE ------> 消息队列
+0x018 Creator : _CLIENT_ID
+0x020 ClientSectionBase : (null)
+0x024 ServerSectionBase : (null)
+0x028 PortContext : (null)
+0x02c ClientThread : (null)
+0x030 SecurityQos : _SECURITY_QUALITY_OF_SERVICE
+0x03c StaticSecurity : _SECURITY_CLIENT_CONTEXT
+0x078 LpcReplyChainHead : _LIST_ENTRY [ 0xe13200a8 - 0xe13200a8 ]
+0x080 LpcDataInfoChainHead : _LIST_ENTRY [ 0xe13200b0 - 0xe13200b0 ]
+0x088 ServerProcess : (null)
+0x088 MappingProcess : (null)
+0x08c MaxMessageLength : 0x148
+0x08e MaxConnectionInfoLength : 0
+0x090 Flags : 0x80000004
+0x094 WaitEvent : _KEVENT
NtReplyWaitReceivePortEx的关键地方是:
//
// 对于创建得到的连接端口和客户端连接后得到的数据通讯端口,服务器调用
// NtReplyWaitReceivePortEx始终在监听NtCreatePort得到的连接端口
//
if ((PortObject->Flags & PORT_TYPE) != CLIENT_COMMUNICATION_PORT) {
if (PortObject->ConnectionPort == PortObject) {
ConnectionPort = ReceivePort = PortObject;
ObReferenceObject (ConnectionPort);
} else {
LpcpAcquireLpcpLockByThread(CurrentThread);
ConnectionPort = ReceivePort = PortObject->ConnectionPort;
if (ConnectionPort == NULL) {
LpcpReleaseLpcpLock();
ObDereferenceObject( PortObject );
return STATUS_PORT_DISCONNECTED;
}
ObReferenceObject( ConnectionPort );
LpcpReleaseLpcpLock();
}
//
// 对于客户端来说,调用NtReplyWaitReceivePortEx监听的总是自己连接上服务器
// 得到的通讯端口。所以在设计中,监听自身通讯端口,可以将发起权交给服务器
//
} else {
ReceivePort = PortObject;
}
// ...............
Status = KeWaitForSingleObject( ReceivePort->MsgQueue.Semaphore,
WrLpcReceive,
WaitMode,
FALSE,
Timeout );
NtRequestWaitReplyPort的关键地方是:
//
// 对于客户端来说,调用NtRequestWaitReplyPort总是把请求消息插入到
// NtCreatePort所创建的连接端口对象的消息队列中。。。
//
LpcpAcquireLpcpLockByThread(CurrentThread);
Msg->PortContext = NULL;
if ((PortObject->Flags & PORT_TYPE) != SERVER_CONNECTION_PORT) {
QueuePort = PortObject->ConnectedPort;
if (QueuePort == NULL) {
LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN );
ObDereferenceObject( PortObject );
return STATUS_PORT_DISCONNECTED;
}
RundownPort = QueuePort;
if ((PortObject->Flags & PORT_TYPE) == CLIENT_COMMUNICATION_PORT) {
Msg->PortContext = QueuePort->PortContext;
ConnectionPort = QueuePort = PortObject->ConnectionPort;
if (ConnectionPort == NULL) {
LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN );
ObDereferenceObject( PortObject );
return STATUS_PORT_DISCONNECTED;
}
} else if ((PortObject->Flags & PORT_TYPE) != SERVER_COMMUNICATION_PORT) {
ConnectionPort = QueuePort = PortObject->ConnectionPort;
if (ConnectionPort == NULL) {
LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN );
ObDereferenceObject( PortObject );
return STATUS_PORT_DISCONNECTED;
}
}
if (ConnectionPort) {
ObReferenceObject( ConnectionPort );
}
//
// 对于服务器来说,调用NtRequestWaitReplyPort就是将消息插入调用者指定的端口对象的
// 消息队列。所以在设计中可以利用这个特点,自己给自己发请求来退出监控线程的。
//
} else {
QueuePort = PortObject;
RundownPort = PortObject;
}
// ............
InsertTailList( &QueuePort->MsgQueue.ReceiveHead, &Msg->Entry );
InsertTailList( &RundownPort->LpcReplyChainHead, &CurrentThread->LpcReplyChain );
加上NtListenPort的本质是调用NtReplyWaitReceivePortEx,由此可以得到,真正的多向通讯的设计模型应该是:
细心的你会发现,这个模型跟Device IO的通信模型非常像的:设备对象==客户端通信端口,驱动对象==服务器连接端口,多个设备,都是由同一个驱动的
分发处理函数进行处理。到这里会不会有狂然大悟的感觉呢?所以针对这种模型,就设计出下面的框架结构:
//
// 作為服務器和客戶端的統一管理結構,表示该驱动可以建立
// 多个服务器和连接多个服务器
//
typedef struct _SD_CONTROL_SYSTEM
{
LIST_ENTRY leServerPortList;
KSPIN_LOCK ksServerPortList;
LIST_ENTRY leClientPortList;
KSPIN_LOCK ksClientPortList;
}SD_CONTROL_SYSTEM,*PSD_CONTROL_SYSTEM;
//
// 服務器通訊端口控制對象結構,模仿_DRIVER_OBJECT
//
typedef struct _SD_CONTROL_OBJECT
{
LIST_ENTRY leEntry; // 创建的所有控制对象组织成链表
LIST_ENTRY leConnectedList; // 该端口下的所有连接组织成链表(这样效率不高,以后应该用hash表代替)
KSPIN_LOCK ksLock; // 链表锁
KEVENT kEvent; // 准备一个同步事件,备用
HANDLE hPort; // LPC端口句柄
ULONG ulFlags; // 标记
PVOID pSelf; // 指向自身对象
PVOID pCache; // 這個域指向一個緩存區,方便調用者建立自定義結構,類似DriverObject的擴展
PVOID pThread; // 每个端口需要一个线程来维护,退出时可以进行等待
PVOID pCallback[LPC_MAXIMUM_FUNCTION];// LPC Message handler !
}SD_CONTROL_OBJECT,*PSD_CONTROL_OBJECT;
// ulFlags的屬性,暫時只有兩個屬性
#define CONTROL_OBJECT_DISABLING 0x00000001
#define CONTROL_OBJECT_DISABLED 0x00000002
//
// 連接管理結構,模仿_DEVICE_OBJECT
//
typedef struct _SD_CONTROL_CONNECTION
{
LIST_ENTRY leEntry;
HANDLE hConnection; // 客户端连接端口
ULONG ulFlags; // 标记
PSD_CONTROL_OBJECT pstCtlObject; // 指向SD_CONTROL_OBJECT
PPORT_MESSAGE pstLastMessage; // 最后一个消息
CLIENT_ID stClientId;
PORT_VIEW stPortView;
REMOTE_PORT_VIEW stRemotePortView;
PVOID pSelf; // 指向自身
PVOID pCache; // 這個域指向一個緩存區,方便調用者建立自定義結構,類似DeviceObject的擴展
PROCESS_INFO stProcInfo; // 连接进程的详细信息
}SD_CONTROL_CONNECTION,*PSD_CONTROL_CONNECTION;
// ulFlags的屬性,暫時只有兩個屬性
#define CONTROL_CONNECTION_DISCONNECTING 0x00000001
#define CONTROL_CONNECTION_DISCONNECTED 0x00000002
//
// 对于回调处理,建立两个模式,一种是预处理,一种的后处理
//
typedef enum _CONNECTION_MESSAGE_CALLBACK_MODE
{
PreviousMode,
FartherMode
}CONNECTION_MESSAGE_CALLBACK_MODE;
//
// 通訊消息的分發處理函數
//
typedef BOOLEAN CONNECTION_MESSAGE_CALLBACK(
__in PPORT_MESSAGE pstPortMsg,
__in PSD_CONTROL_CONNECTION pstConnection,
__in NTSTATUS ntStatus,
__in CONNECTION_MESSAGE_CALLBACK_MODE Mode
);
typedef CONNECTION_MESSAGE_CALLBACK *PCONNECTION_MESSAGE_CALLBACK;
正所谓,有通讯就应该有对应的协议,在这里私人也送上一个简单的协议吧!该协议涉及的出发点是从安全的角度出发,数据交互全部经过加密处理。
//
// 協議會話的會話ID,ulSessionId的取值
//
typedef enum _SD_CONTROL_PROTOCOL_SESSION
{
SessionConnecting,
SessionAuthenticating,
SessionReAuthenticating,
SessionCommunicating,
SessionDisconnecting
}SD_CONTROL_PROTOCOL_SESSION;
//
// 會話的操作碼,可以理解為會話詳細過程,在認證過程中ulCtlCode的值
// 在認證成功后ulCtlCode的值為相應的控制碼
//
typedef enum _SD_CONTROL_PROTOCOL_SESSION_CODE
{
SessionClientConnectionRequest,
SessionServerConnectionReply,
SessionClientAuthenRequest,
SessionServerAuthenReply,
SessionServerAuthenRequest,
SessionClientAuthenReply,
SessionAuthenSuccessed,
SessionAuthenFailed,
SessionClientDisconnectRequest,
SessionServerDisconnectRequest
}SD_CONTROL_PROTOCOL_SESSION_CODE;
//
// 應用層跟內核通訊採用的簡單協議
//
typedef struct _SD_CONTROL_PROTOCOL
{
ULONG ulTag; // 标签,特征
ULONG ulVersion; // 协议版本号
ULONG ulSessionId; // 当前通讯处于的会话
ULONG ulCtlCode; // 通讯控制码
ULONG ulExtesionSize; // 扩展大小,就是LPC中利用Section进行大数据传输的大小
ULONG ulKey; // btVerifyInfo和btControlInfo的解密KEY
// 下面是校驗數據,需要動態分配的
UCHAR btVerifyInfo[1]; // 长度|数据。指定校验数据,假如你需要进行认证的话
// 下面是傳輸數據,需要動態分配的
UCHAR btControlInfo[1]; // 长度|数据。真正传输的数据
}SD_CONTROL_PROTOCOL,*PSD_CONTROL_PROTOCOL;
因为之前在复习日文,所以所有注释都用了繁体字,也懒得改了,反正也不难看懂吧!!
过年后又要奔波找工作了。。。不知道哪个同学可以介绍一下呢。。。。
顺便贴一下个人信息啦。这里需要说一下,鉴于小弟比较喜欢cosplay,
如果你有这方面的洁癖,勿加。。
QQ:997872586
Blog:http://zzydog.tk
[课程]Android-CTF解题方法汇总!