开场白:
项目中遇见了一个奇怪的问题。在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的服务调用的层层关系即可,
此处引用一种网上的图片
链接引用
https://blog.csdn.net/whatday/article/details/52782955
经过跟踪,我们一种跟踪到了如下堆栈
3: kd> k
# Child-SP RetAddr Call Site
00 ffff8804`b78227a8 fffff803`7c8a1d5f nt!AlpcpLookupMessage
01 ffff8804`b78227b0 fffff803`7c89f3d8 nt!AlpcpAcceptConnectPort+0x2f7
02 ffff8804`b7822a20 fffff803`7c583c53 nt!NtAcceptConnectPort+0x58
03 ffff8804`b7822a90 00007ffe`f282fe84 nt!KiSystemServiceCopyEnd+0x13
04 00000000`0998e798 00000000`68df2bf1 ntdll!NtAcceptConnectPort+0x14
05 00000000`0998e7a0 00000000`68dd6463 wow64!whNtAcceptConnectPort+0x161
06 00000000`0998e850 00000000`68eb1923 wow64!Wow64SystemServiceEx+0x153
07 00000000`0998f110 00000000`68deac12 wow64cpu!ServiceNoTurbo+0xb
08 00000000`0998f1c0 00000000`68ddbcf0 wow64!RunCpuSimulation+0xee12
09 00000000`0998f1f0 00007ffe`f2809314 wow64!Wow64LdrpInitialize+0x120
0a 00000000`0998f4a0 00007ffe`f280920b ntdll!_LdrpInitialize+0xf4
0b 00000000`0998f520 00007ffe`f28091be ntdll!LdrpInitialize+0x3b
0c 00000000`0998f550 00000000`00000000 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::<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
通过一个接受的和一个拒绝的回应来看。拒绝的回应的messageID是错误的,AlpcpLookupMessage的第二个参数,通过rdx就可以知道,最后导致AlpcpLookupMessage返回无效的message,但是 但是通过断在NtAcceptConnectPort查看第三个参数是我们通过R3传入值0x00000000`0db74d58 (这个值在R3是调用ZwAcceptConnectPort 的第三个参数),没有错误啊,里面的内容都没有变。这是为什么那? 通过断下一个正常的回应来看,第三个参数的值,不是R3传入的值,What ? 搞错了把,正确的导致后面的错误,错误的最后是正确的,没有搞错把? 重复了几遍,一直是这样。
2 拒绝表白需要正确的姿态
看样子调用到 nt!NtAcceptConnectPort的第三个参数值就有问题了,所以我们要从R3入手了。找到R3进入内核的最后一步
ntdll!NtAcceptConnectPort:
0010:00007ffe`f282fe70 4c8bd1 mov r10,rcx
0010:00007ffe`f282fe73 b802000000 mov eax,2 //SSDT的服务号
0010:00007ffe`f282fe78 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
0010:00007ffe`f282fe80 7503 jne ntdll!NtAcceptConnectPort+0x15 (00007ffe`f282fe85)
0010:00007ffe`f282fe82 0f05 syscall //进内核
0010:00007ffe`f282fe84 c3 ret
0010:00007ffe`f282fe85 cd2e int 2Eh
0010:00007ffe`f282fe87 c3 ret
我们断在ntdll!NtAcceptConnectPort:发现 调用到这里的时候值就有问题了。所以要顺着堆栈继续往上找
上一层是05 00000000`0998e7a0 00000000`68dd6463 wow64!whNtAcceptConnectPort
并且在05 00000000`0998e7a0 00000000`68dd6463 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
00000000`09d0fa44 09d0fa90 00000000 0d6c71e8 00000000
00000000`09d0fa54 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:
00000000`68df2a90 4c8bdc mov r11,rsp
00000000`68df2a93 53 push rbx
00000000`68df2a94 56 push rsi
00000000`68df2a95 57 push rdi
00000000`68df2a96 4154 push r12
00000000`68df2a98 4155 push r13
00000000`68df2a9a 4156 push r14
00000000`68df2a9c 4157 push r15
00000000`68df2a9e 4883ec70 sub rsp,70h
00000000`68df2aa2 4889642438 mov qword ptr [rsp+38h],rsp
00000000`68df2aa7 8b39 mov edi,dword ptr [rcx]
00000000`68df2aa9 448a690c mov r13b,byte ptr [rcx+0Ch]
00000000`68df2aad 448b7910 mov r15d,dword ptr [rcx+10h]
00000000`68df2ab1 448b7114 mov r14d,dword ptr [rcx+14h]
00000000`68df2ab5 33d2 xor edx,edx
00000000`68df2ab7 41885308 mov byte ptr [r11+8],dl
00000000`68df2abb 49895318 mov qword ptr [r11+18h],rdx
00000000`68df2abf 8bc7 mov eax,edi
00000000`68df2ac1 f7d8 neg eax
00000000`68df2ac3 481bf6 sbb rsi,rsi
00000000`68df2ac6 498d4318 lea rax,[r11+18h]
00000000`68df2aca 4823f0 and rsi,rax
00000000`68df2acd 8b4104 mov eax,dword ptr [rcx+4]
00000000`68df2ad0 498943a0 mov qword ptr [r11-60h],rax
00000000`68df2ad4 448b6108 mov r12d,dword ptr [rcx+8] ds:002b:00000000`09d0fa4c=0d6c71e8 //取出原始的第三个参数, //然后写入,算是初始化
00000000`68df2ad8 4d896320 mov qword ptr [r11+20h],r12//这个写入。 [r11+20h] == 0000000009d4e5d8,就是调用__imp_NtAcceptConnectPort时候的第三个参数的地址
00000000`68df2adc 4585ff test r15d,r15d
第二次断下
wow64!Wow64pThunkLegacyLpcMsgIn:
00000000`68deed4c 488bc4 mov rax,rsp
00000000`68deed4f 884808 mov byte ptr [rax+8],cl
00000000`68deed52 53 push rbx
00000000`68deed53 56 push rsi
00000000`68deed54 57 push rdi
00000000`68deed55 4154 push r12
00000000`68deed57 4155 push r13
00000000`68deed59 4156 push r14
00000000`68deed5b 4157 push r15
00000000`68deed5d 4883ec30 sub rsp,30h
00000000`68deed61 4d8be8 mov r13,r8
00000000`68deed64 488bf2 mov rsi,rdx
00000000`68deed67 33db xor ebx,ebx
00000000`68deed69 8958b8 mov dword ptr [rax-48h],ebx
00000000`68deed6c 498910 mov qword ptr [r8],rdx//here
00000000`68deed6f 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 00000000`09d4e4a0 00000000`68df2ba2 wow64!Wow64pThunkLegacyLpcMsgIn+0x1a1
01 00000000`09d4e510 00000000`68dd6463 wow64!whNtAcceptConnectPort+0x112
02 00000000`09d4e5c0 00000000`68eb1923 wow64!Wow64SystemServiceEx+0x153
03 00000000`09d4ee80 00000000`68deac12 wow64cpu!ServiceNoTurbo+0xb
04 00000000`09d4ef30 00000000`68ddbcf0 wow64!RunCpuSimulation+0xee12
05 00000000`09d4ef60 00007ffe`f2809314 wow64!Wow64LdrpInitialize+0x120
06 00000000`09d4f210 00007ffe`f280920b ntdll!_LdrpInitialize+0xf4
07 00000000`09d4f290 00007ffe`f28091be ntdll!LdrpInitialize+0x3b
08 00000000`09d4f2c0 00000000`00000000 ntdll!LdrInitializeThunk+0xe
Wow64pThunkLegacyLpcMsgIn中
00000000`68deeed8 4c0fbf06 movsx r8,word ptr [rsi]
00000000`68deeedc 488d5618 lea rdx,[rsi+18h]
00000000`68deeee0 488d4f28 lea rcx,[rdi+28h]
00000000`68deeee4 e8b781ffff call wow64!memcpy (00000000`68de70a0)
00000000`68deeee9 49897d00 mov qword ptr [r13],rdi //here
00000000`68deeeed 8b5c2420 mov ebx,dword ptr [rsp+20h] ss:002b:00000000`09d4e4c0=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重新处理之后的内存。
00000000`0998f060 4c 00 74 00 0a 00 00 00-e4 17 00 00 00 00 00 00 L.t.............
00000000`0998f070 ec 0c 00 00 00 00 00 00-a4 10 00 00 32 00 5c 00 ............2.\.
00000000`0998f080 00 90 05 00 00 00 00 00-4c 00 00 00 33 00 32 00 ........L...3.2.
00000000`0998f090 31 00 32 00 30 00 30 00-00 00 00 00 00 00 00 00 1.2.0.0.........
00000000`0998f0a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`0998f0b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`0998f0c0 00 00 00 00 00 00 00 00-00 00 00 00 00 20 68 bf ............. h.
00000000`0998f0d0 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*
0x00000000`0db74d58
+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 0x00000000`0db74d58 //处理之前的
00000000`0db74d58 4c 00 64 00 0a 00 00 00-e4 17 00 00 ec 0c 00 00 L.d.............
00000000`0db74d68 a4 10 00 00 00 90 05 00-4c 00 00 00 33 00 32 00 ........L...3.2.
00000000`0db74d78 31 00 32 00 30 00 30 00-00 00 00 00 00 00 00 00 1.2.0.0.........
00000000`0db74d88 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`0db74d98 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`0db74da8 00 00 00 00 00 00 00 00-00 00 00 00 00 20 68 bf ............. h.
00000000`0db74db8 04 20 68 bf 00 00 00 00-00 00 00 00 00 00 00 00 . h.............
00000000`0db74dc8 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);
重新编译后,发现正常的拒绝了,不再让追求者白白等待了。