LPC服务器拒绝客户端的表白
开场白:
项目中遇见了一个奇怪的问题。在64位系统下,LPC的服务端是wow32位进程,在调用了ZwAcceptConnectPort拒绝客户端的连接后,ZwAcceptConnectPort返回
//
// MessageId: STATUS_INVALID_MESSAGE
//
// MessageText:
//
// The ALPC message supplied is invalid.
//
#define STATUS_INVALID_MESSAGE ((NTSTATUS)0xC0000702L)
然而此时的客户端还一如既往的等待,就像一个追求者思思等待,然后那个被追求的人没有把拒绝告白的信息传递给客户端。
表白的方式很简单 ,伪代码如下
bAccept = 0;
ntStatus = ZwAcceptConnectPort(
&hRemotePort0,
(ULONG)0,
pPortMessageCorrected,
bAccept,
//pServerView,
NULL,
NULL);
对应 pPortMessageCorrected 已经做了wow转换,模拟了真正的64位程序。
1 我们需要知道STATUS_INVALID_MESSAGE 这个值是在哪里返回的,这个不难。我们仅需要知道R3-R0的服务调用的层层关系即可,
此处引用一种网上的图片

链接引用
<>
经过跟踪,我们一种跟踪到了如下堆栈
3: kd> k
Child-SP RetAddr Call Site
00 ffff8804b78227a8 fffff8037c8a1d5f nt!AlpcpLookupMessage
01 ffff8804b78227b0 fffff8037c89f3d8 nt!AlpcpAcceptConnectPort+0x2f7
02 ffff8804b7822a20 fffff8037c583c53 nt!NtAcceptConnectPort+0x58
03 ffff8804b7822a90 00007ffef282fe84 nt!KiSystemServiceCopyEnd+0x13
04 000000000998e798 0000000068df2bf1 ntdll!NtAcceptConnectPort+0x14
05 000000000998e7a0 0000000068dd6463 wow64!whNtAcceptConnectPort+0x161
06 000000000998e850 0000000068eb1923 wow64!Wow64SystemServiceEx+0x153
07 000000000998f110 0000000068deac12 wow64cpu!ServiceNoTurbo+0xb
08 000000000998f1c0 0000000068ddbcf0 wow64!RunCpuSimulation+0xee12
09 000000000998f1f0 00007ffef2809314 wow64!Wow64LdrpInitialize+0x120
0a 000000000998f4a0 00007ffef280920b ntdll!_LdrpInitialize+0xf4
0b 000000000998f520 00007ffef28091be ntdll!LdrpInitialize+0x3b
0c 000000000998f550 0000000000000000 ntdll!LdrInitializeThunk+0xe
(切换到wow的堆栈可用
3: kd> !wow64exts.sw
Switched to Guest (WoW) mode
The context is partially valid. Only x86 user-mode context is available.
3: kd:x86> k
ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 09a8fc24 00136185 0x7737e76c
01 09a8fcb8 001367e7 ccavsrv!lpcHandleConnectionRequest+0x360 [g:\work\src\comodocloudantivirus_metadata\common\lpcsrv.cpp @ 627]
02 09a8fe3c 760d8654 ccavsrv!lpcWorkingThread+0x149 [g:\work\src\comodocloudantivirus_metadata\common\lpcsrv.cpp @ 970]
03 09a8fe50 77374a47 0x760d8654
04 09a8fe98 77374a17 0x77374a47
05 09a8fea8 00000000 0x77374a17
不过只能看到wow的栈,看不到64位的栈。所以还是切换到64位的栈吧。.effmach amd64 ; k
)
其中 AlpcpLookupMessage 的伪代码如下
signed __int64 __fastcall AlpcpLookupMessage(__int64 zero1, __int64 messageID, int zero2, unsigned __int64 *pStackOut)
{
.......
v4 = messageID;
pStackOut1 = pStackOut;
zero3 = zero2;
zero4 = zero1;
if ( (signed int)messageID < 0 )
{
if ( zero1 )
.......
}
}
else
{
result = 0xC0000702i64; // not here
}
}
else
{
JUMPOUT(messageID & 0xFC000000, 0, sub_140635421);
v8 = AlpcMessageTable;
JUMPOUT(AlpcMessageTable, 0i64, &loc_1406354C3);
v9 = (unsigned __int8)KeGetCurrentThread()->gap0[10];
if ( messageID & 0x3FC )
{
pHandleEntryByMessageId = (volatile signed __int64 *)ExpLookupHandleTableEntry(
(unsigned int *)AlpcMessageTable,
messageID & 0x3FFFFFF);
if ( pHandleEntryByMessageId )
{
while ( 1 )
{
.......
}
JUMPOUT(pRealHandleEntryByMessageId, 0i64, &loc_140635441);
}
}
ExHandleLogBadReference(v8, v4 & 0x3FFFFFF);
result = 0xC0000702i64; // here2
}
return result;
}
大体可以知道,他是通过messageID找等待的客户端,其中messageid是 +0x010 MessageId : 0x10a4的值
3: kd:x86> dt pPortMessageCorrected
Local var @ 0x9a8fc94 Type _PORT_MESSAGE*
0x00000000`0db74d58
+0x000 u1 : _PORT_MESSAGE::
+0x004 u2 : _PORT_MESSAGE::
+0x008 ClientId : _CLIENT_ID
+0x008 DoNotUseThisField : 7.0195620795348263e-311
+0x010 MessageId : 0x10a4 //这里的值
+0x014 ClientViewSize : 0x59000
+0x014 CallbackId : 0x59000
通过一个接受的和一个拒绝的回应来看。拒绝的回应的messageID是错误的,AlpcpLookupMessage的第二个参数,通过rdx就可以知道,最后导致AlpcpLookupMessage返回无效的message,但是 但是通过断在NtAcceptConnectPort查看第三个参数是我们通过R3传入值0x00000000`0db74d58 (这个值在R3是调用ZwAcceptConnectPort 的第三个参数),没有错误啊,里面的内容都没有变。这是为什么那? 通过断下一个正常的回应来看,第三个参数的值,不是R3传入的值,What ? 搞错了把,正确的导致后面的错误,错误的最后是正确的,没有搞错把? 重复了几遍,一直是这样。
2 拒绝表白需要正确的姿态
看样子调用到 nt!NtAcceptConnectPort的第三个参数值就有问题了,所以我们要从R3入手了。找到R3进入内核的最后一步
ntdll!NtAcceptConnectPort:
0010:00007ffef282fe70 4c8bd1 mov r10,rcx 0010:00007ffef282fe73 b802000000 mov eax,2 //SSDT的服务号
0010:00007ffef282fe78 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (000000007ffe0308)],1
0010:00007ffef282fe80 7503 jne ntdll!NtAcceptConnectPort+0x15 (00007ffef282fe85)
0010:00007ffef282fe82 0f05 syscall //进内核 0010:00007ffef282fe84 c3 ret
0010:00007ffef282fe85 cd2e int 2Eh 0010:00007ffef282fe87 c3 ret
我们断在ntdll!NtAcceptConnectPort:发现 调用到这里的时候值就有问题了。所以要顺着堆栈继续往上找
上一层是05 000000000998e7a0 0000000068dd6463 wow64!whNtAcceptConnectPort
并且在05 000000000998e7a0 0000000068dd6463 wow64!whNtAcceptConnectPort+0x161出调用了nt!NtAcceptConnectPort
IDA看下此处的伪代码
whNtAcceptConnectPort+143 mov [rsp+0A8h+var_88], rax
whNtAcceptConnectPort+148 mov r9b, r13b
whNtAcceptConnectPort+14B mov r8, [rsp+0A8h+arg_18] ; rdx == message
whNtAcceptConnectPort+14B ; rsp+c8
whNtAcceptConnectPort+153 mov rdx, [rsp+0A8h+var_60]
whNtAcceptConnectPort+158 mov rcx, rsi
whNtAcceptConnectPort+15B call cs:__imp_NtAcceptConnectPort ; Indirect Call Near Procedure
可以看到mov r8, [rsp+0A8h+arg_18]出是第三个参数,即 rsp+c8
我们断下wow64!whNtAcceptConnectPort+0处,对比拒绝请求和接受请求的参数,完全一样。所以了参数的转变是在
wow64!whNtAcceptConnectPort中。那就好说了,我们只需要在rsp+0A8h+arg_18处设置硬件的写入断点我们就知道在哪里把原始的第三个参数做了转变
(
通过调试得到调用了wow64!whNtAcceptConnectPort后,rcx寄存器指向了程序调用ZwAcceptConnectPort时候的堆栈信息
即
3: kd> r rcx
rcx=0000000009d0fa44
3: kd> dd 0000000009d0fa44
0000000009d0fa44 09d0fa90 00000000 0d6c71e8 00000000 0000000009d0fa54 00000000 09d0fa6c
rcx 第一个参数 09d0fa90
rcx+4 第二个参数 00000000
rcx+8 第三个参数 0d6c71e8
rcx+12 第四个参数 00000000
以此类推
)
重新运行断在whNtAcceptConnectPort,并且走过00000000`68df2a9e 4883ec70 sub rsp,70h这条指令。
然后硬件断点
ba w4 rsp的值 +C8,看下那里往这个地址写了数据 。我们先来一次接受请求的过程
第一次断下来
wow64!whNtAcceptConnectPort:
0000000068df2a90 4c8bdc mov r11,rsp 0000000068df2a93 53 push rbx
0000000068df2a94 56 push rsi 0000000068df2a95 57 push rdi
0000000068df2a96 4154 push r12 0000000068df2a98 4155 push r13
0000000068df2a9a 4156 push r14 0000000068df2a9c 4157 push r15
0000000068df2a9e 4883ec70 sub rsp,70h 0000000068df2aa2 4889642438 mov qword ptr [rsp+38h],rsp
0000000068df2aa7 8b39 mov edi,dword ptr [rcx] 0000000068df2aa9 448a690c mov r13b,byte ptr [rcx+0Ch]
0000000068df2aad 448b7910 mov r15d,dword ptr [rcx+10h] 0000000068df2ab1 448b7114 mov r14d,dword ptr [rcx+14h]
0000000068df2ab5 33d2 xor edx,edx 0000000068df2ab7 41885308 mov byte ptr [r11+8],dl
0000000068df2abb 49895318 mov qword ptr [r11+18h],rdx 0000000068df2abf 8bc7 mov eax,edi
0000000068df2ac1 f7d8 neg eax 0000000068df2ac3 481bf6 sbb rsi,rsi
0000000068df2ac6 498d4318 lea rax,[r11+18h] 0000000068df2aca 4823f0 and rsi,rax
0000000068df2acd 8b4104 mov eax,dword ptr [rcx+4] 0000000068df2ad0 498943a0 mov qword ptr [r11-60h],rax
0000000068df2ad4 448b6108 mov r12d,dword ptr [rcx+8] ds:002b:0000000009d0fa4c=0d6c71e8 //取出原始的第三个参数, //然后写入,算是初始化
0000000068df2ad8 4d896320 mov qword ptr [r11+20h],r12//这个写入。 [r11+20h] == 0000000009d4e5d8,就是调用__imp_NtAcceptConnectPort时候的第三个参数的地址 0000000068df2adc 4585ff test r15d,r15d
第二次断下
wow64!Wow64pThunkLegacyLpcMsgIn:
0000000068deed4c 488bc4 mov rax,rsp 0000000068deed4f 884808 mov byte ptr [rax+8],cl
0000000068deed52 53 push rbx 0000000068deed53 56 push rsi
0000000068deed54 57 push rdi 0000000068deed55 4154 push r12
0000000068deed57 4155 push r13 0000000068deed59 4156 push r14
0000000068deed5b 4157 push r15 0000000068deed5d 4883ec30 sub rsp,30h
0000000068deed61 4d8be8 mov r13,r8 0000000068deed64 488bf2 mov rsi,rdx
0000000068deed67 33db xor ebx,ebx 0000000068deed69 8958b8 mov dword ptr [rax-48h],ebx
0000000068deed6c 498910 mov qword ptr [r8],rdx//here 0000000068deed6f 4885d2 test rdx,rdx
3: kd> r r8
r8=0000000009d4e5d8
0000000009d4e5d8,就是调用__imp_NtAcceptConnectPort时候的第三个参数的地址
写入的值为rdx,
3: kd> r rdx
rdx=000000000db747d8 依然是原始值的初始化
//第三次断下
Child-SP RetAddr Call Site
00 0000000009d4e4a0 0000000068df2ba2 wow64!Wow64pThunkLegacyLpcMsgIn+0x1a1
01 0000000009d4e510 0000000068dd6463 wow64!whNtAcceptConnectPort+0x112
02 0000000009d4e5c0 0000000068eb1923 wow64!Wow64SystemServiceEx+0x153
03 0000000009d4ee80 0000000068deac12 wow64cpu!ServiceNoTurbo+0xb
04 0000000009d4ef30 0000000068ddbcf0 wow64!RunCpuSimulation+0xee12
05 0000000009d4ef60 00007ffef2809314 wow64!Wow64LdrpInitialize+0x120
06 0000000009d4f210 00007ffef280920b ntdll!_LdrpInitialize+0xf4
07 0000000009d4f290 00007ffef28091be ntdll!LdrpInitialize+0x3b
08 0000000009d4f2c0 0000000000000000 ntdll!LdrInitializeThunk+0xe
Wow64pThunkLegacyLpcMsgIn中
0000000068deeed8 4c0fbf06 movsx r8,word ptr [rsi] 0000000068deeedc 488d5618 lea rdx,[rsi+18h]
0000000068deeee0 488d4f28 lea rcx,[rdi+28h] 0000000068deeee4 e8b781ffff call wow64!memcpy (0000000068de70a0) 0000000068deeee9 49897d00 mov qword ptr [r13],rdi //here
0000000068deeeed 8b5c2420 mov ebx,dword ptr [rsp+20h] ss:002b:0000000009d4e4c0=00000000
3: kd> r r13
r13=0000000009d4e5d8
0000000009d4e5d8,就是调用__imp_NtAcceptConnectPort时候的第三个参数的地址
rdi=0000000009d4edd0 值发生了改变
之后直接调用到__imp_NtAcceptConnectPort ,以上都是正常的逻辑,
下面看下拒绝的逻辑断点
只有上面的第一次断下的过程,没有第二三次
然后直接调用到了ntdll!NtAcceptConnectPort
可以猜测第二三次对message内存做了重新的包装
通过IDA 看下伪代码
__int64 __fastcall whNtAcceptConnectPort(unsigned int *pOriginParameterStack, __int64 a2, __int64 a3, __int64 a4)
{
_DWORD *v4; // rdi
char v5; // r13
__int64 pWritesection; // r15
__int64 pReadsection; // r14
signed __int16 *v8; // r12
__int64 v9; // r8
int *v11; // rbx
__int64 v12; // r8
unsigned int v13; // eax
signed int v14; // er8
unsigned int v15; // eax
unsigned int v16; // eax
__int64 v17; // [rsp+0h] [rbp-A8h]
unsigned int v18; // [rsp+30h] [rbp-78h]
__int64 *v19; // [rsp+38h] [rbp-70h]
int *v20; // [rsp+40h] [rbp-68h]
__int64 v21; // [rsp+48h] [rbp-60h]
int v22; // [rsp+50h] [rbp-58h]
__int64 v23; // [rsp+58h] [rbp-50h]
__int64 v24; // [rsp+60h] [rbp-48h]
int v25; // [rsp+B0h] [rbp+8h]
__int64 v26; // [rsp+B8h] [rbp+10h]
__int64 v27; // [rsp+C0h] [rbp+18h]
__int64 v28; // [rsp+C8h] [rbp+20h]
v19 = &v17;
v4 = (_DWORD *)*pOriginParameterStack;
v5 = *((_BYTE *)pOriginParameterStack + 12);
pWritesection = pOriginParameterStack[4]; // v6 为第5个参数,即writesection
pReadsection = pOriginParameterStack[5]; // r7 为第6个参数,即readsection
LOBYTE(v25) = 0;
v27 = 0i64;
v21 = pOriginParameterStack[1];
v8 = (signed __int16 *)pOriginParameterStack[2];
v28 = pOriginParameterStack[2];
if ( (_DWORD)pWritesection )
{
v18 = Wow64pThunkLegacyPortViewIn((unsigned int)pWritesection, &v26, &v25);
if ( (v18 & 0x80000000) != 0 )
{
local_unwind_0(v19, &loc_6B022B0E, v9);
return v18;
}
}
else
{
v26 = 0i64;
}
if ( (_DWORD)pReadsection )//如果pWritesection 为NULL,这里一定不能为NULL
{
v11 = (int *)pReadsection;
if ( *(_DWORD *)pReadsection == 12 ) // 这个地方做了大小校验
{
v22 = 24;
v24 = 0i64;
v23 = 0i64;
v11 = &v22;
v20 = &v22;
LOBYTE(v25) = 1;
}
else
{
v20 = (int *)pReadsection;
}
}
else
{
v11 = 0i64;
v20 = 0i64;
}
if ( (_BYTE)v25 == 1 )这里要满足
{
v18 = Wow64pThunkLegacyLpcMsgIn((__int64)pOriginParameterStack, v8, &v28);//这里要调用
if ( (v18 & 0x80000000) != 0 )
{
local_unwind_0(v19, &loc_6B022BBD, v12);
return v18;
}
}
LOBYTE(a4) = v5;
v13 = NtAcceptConnectPort((unsigned __int64)&v27 & -(signed __int64)((_DWORD)v4 != 0), v21, v28, a4, v26, v11);
v14 = v13;
v18 = v13;
if ( (_DWORD)v4 )
*v4 = *(_DWORD *)((unsigned __int64)&v27 & -(signed __int64)((_DWORD)v4 != 0));
if ( (v13 & 0x80000000) == 0 && (_BYTE)v25 == 1 )
{
v15 = Wow64pThunkLegacyPortViewOut(v26, pWritesection, v13);
v14 = v15;
v18 = v15;
if ( (v15 & 0x80000000) != 0 )
{
local_unwind_0(v19, &loc_6B022C3C, v15);
return v18;
}
}
if ( v14 >= 0 )
{
if ( (_BYTE)v25 == 1 )
{
v16 = Wow64pThunkLegacyRemoteViewOut(v11, pReadsection);
v14 = v16;
v18 = v16;
if ( (v16 & 0x80000000) != 0 )
{
local_unwind_0(v19, &loc_6B022C7A, v16);
return v18;
}
}
if ( v14 >= 0 && (_BYTE)v25 == 1 )
*v4 |= 1u;
}
return (unsigned int)v14;
}
看了ZwAcceptConnectPort的最后一个参数不能为NULL.而且大小有要求
已经明确上面的第二三次硬件断点的过程是对message的一次重新包装,对比一下处理后的和处理前的内存
3: kd> db 000000000998f060 ///这个是对第三个参数的content重新处理之后的内存。
000000000998f060 4c 00 74 00 0a 00 00 00-e4 17 00 00 00 00 00 00 L.t............. 000000000998f070 ec 0c 00 00 00 00 00 00-a4 10 00 00 32 00 5c 00 ............2..
000000000998f080 00 90 05 00 00 00 00 00-4c 00 00 00 33 00 32 00 ........L...3.2. 000000000998f090 31 00 32 00 30 00 30 00-00 00 00 00 00 00 00 00 1.2.0.0.........
000000000998f0a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 000000000998f0b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000000998f0c0 00 00 00 00 00 00 00 00-00 00 00 00 00 20 68 bf ............. h. 000000000998f0d0 04 20 68 bf 2e 00 65 00-78 00 65 00 00 00 00 00 . h...e.x.e.....
3: kd> !wow64exts.sw
Switched to Guest (WoW) mode
The context is partially valid. Only x86 user-mode context is available.
3: kd:x86> dt pPortMessageCorrected
Local var @ 0x9a8fc94 Type _PORT_MESSAGE*
0x000000000db74d58 +0x000 u1 : _PORT_MESSAGE::<unnamed-type-u1> +0x004 u2 : _PORT_MESSAGE::<unnamed-type-u2> +0x008 ClientId : _CLIENT_ID +0x008 DoNotUseThisField : 7.0195620795348263e-311 +0x010 MessageId : 0x10a4 +0x014 ClientViewSize : 0x59000 +0x014 CallbackId : 0x59000 3: kd:x86> db 0x000000000db74d58 //处理之前的
000000000db74d58 4c 00 64 00 0a 00 00 00-e4 17 00 00 ec 0c 00 00 L.d............. 000000000db74d68 a4 10 00 00 00 90 05 00-4c 00 00 00 33 00 32 00 ........L...3.2.
000000000db74d78 31 00 32 00 30 00 30 00-00 00 00 00 00 00 00 00 1.2.0.0......... 000000000db74d88 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000000000db74d98 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 000000000db74da8 00 00 00 00 00 00 00 00-00 00 00 00 00 20 68 bf ............. h.
000000000db74db8 04 20 68 bf 00 00 00 00-00 00 00 00 00 00 00 00 . h............. 000000000db74dc8 00 00 00 00 00 00 00 00-5b 66 1d de 00 27 00 80 ........[f...'..
3 正确的表白
HANDLE hRemotePort0 = INVALID_HANDLE_VALUE;
BOOL bAccept = 0;
REMOTE_PORT_VIEW ClientView = {0};
ClientView.Length = sizeof(ClientView);
ClientView.ViewBase = 0;
ClientView.ViewSize = 0;
ntStatus = ZwAcceptConnectPort(
&hRemotePort0,
(ULONG)0,
pPortMessageCorrected,
bAccept,
//pServerView,
NULL,
&ClientView);
重新编译后,发现正常的拒绝了,不再让追求者白白等待了。
