摘要:新版Office正版增值计划通知程序组件OGAEXEC.exe在读取odataext.dat文件的安全描述符及其中的DACL内容的过程中,在调用GetSecurityDescriptorDacl后的判断中,忽略了lpbDaclPresent为TRUE而pDacl为NULL的情况,导致直接使用NULL的pDacl指针调用GetAclInformation,从而触发了一个access violation(0xC0000005),导致程序出错退出。
国庆回来,打开电脑,Windows Update更新了OGA通知(KB949810)的一个新版本。
更新完后重启电脑,登录进入桌面时OGAEXEC.exe进程出现错误提示,0x7c94cede处访问的0x00000000内存不能为Read。
网上搜索一下,发现这个问题八月底就有人在微软论坛反映了:
http://social.microsoft.com/Forums/en-US/genuineoffice/thread/3448c562-7799-45ec-ac41-a3fd4c36c45b
用户向微软反馈后,微软提供的方法是要求用户取消任务计划中的OGALogon.job,相当于取消这个验证功能来避免这个提示。这说明确实是OGA程序自己的问题。
为了看到问题在哪里,我再次重启电脑,当错误提示出现时点“取消”,激活了作为JIT调试器的WINDBG:
(bc0.7f0): Access violation - code c0000005 (!!! second chance !!!)
eax=0006fe58 ebx=0000ffff ecx=0006fe64 edx=0006fe80 esi=00000000 edi=80070000
eip=7c94cede esp=0006fe18 ebp=0006fe1c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!RtlQueryInformationAcl+0x9:
7c94cede 8a06 mov al,byte ptr [esi] ds:0023:00000000=??
0:000> u ntdll!RtlQueryInformationAcl
ntdll!RtlQueryInformationAcl:
7c94ced5 8bff mov edi,edi
7c94ced7 55 push ebp
7c94ced8 8bec mov ebp,esp
7c94ceda 56 push esi
7c94cedb 8b7508 mov esi,dword ptr [ebp+8]
7c94cede 8a06 mov al,byte ptr [esi]
7c94cee0 3c02 cmp al,2
7c94cee2 7256 jb ntdll!RtlQueryInformationAcl+0x7d (7c94cf3a)
这个Access Violation是在ntdll!RtlQueryInformationAcl中触发的,原因是这时传入的pAcl参数为NULL。
0:000> kb
ChildEBP RetAddr Args to Child
0006fe1c 77dc7e8f 00000000 0006fe58 0000000c ntdll!RtlQueryInformationAcl+0x9
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\system32\OGAEXEC.exe
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fe34 01004b5a 00000000 0006fe58 0000000c ADVAPI32!GetAclInformation+0x17
0006fe88 01006b12 00096d28 00000000 0006fec4 OGAEXEC+0x4b5a
0006febc 010090d6 00724e08 0006fed0 00000000 OGAEXEC+0x6b12
0006fed8 0100a183 01034320 00cc0004 00000000 OGAEXEC+0x90d6
0006ff10 0100a49e 00000001 00000000 00020b3e OGAEXEC+0xa183
0006ffa8 0101f9fb 019df984 019df9a4 7ffdb000 OGAEXEC+0xa49e
0006ffc0 7c817077 019df984 019df9a4 7ffdb000 OGAEXEC+0x1f9fb
0006fff0 00000000 0101f984 00000000 78746341 kernel32!BaseProcessStart+0x23
调用是从ADVAPI32!GetAclInformation进来的,ADVAPI32!GetAclInformation只是直接把参数push一遍然后传给了ntdll!RtlQueryInformationAcl,从上面信息看到传入ADVAPI32!GetAclInformation的pAcl参数就是NULL,根据MSDN,这必然会引发Access Violation:
The GetAclInformation function retrieves information about an access control list (ACL).
BOOL GetAclInformation(
PACL pAcl,
LPVOID pAclInformation,
DWORD nAclInformationLength,
ACL_INFORMATION_CLASS dwAclInformationClass
);
Parameters
pAcl
[in] A pointer to an ACL. The function retrieves information about this ACL. If a null value is passed, the function causes an access violation.
显然,这说明OGAEXEC.exe不应该在pAcl为NULL的情况下调用ADVAPI32!GetAclInformation(或者如果知道pAcl在调用时有可能为NULL的话,就应该搞个异常处理)。
进一步对OGAEXEC.exe的分析表明,OGAEXEC.exe调用该函数是为了获得文件odataext.dat的DACL信息。
具体地来说,OGAEXEC.exe调用GetFileSecurityW获取文件C:\Documents and Settings\All Users\Application Data\Office Genuine Advantage\data\odataext.dat的SecurityDescriptor:
.text:01006AD0
.text:01006AD0 loc_1006AD0: ; CODE XREF: sub_1006A24+A0j
.text:01006AD0 lea eax, [ebp+nLengthNeeded]
.text:01006AD3 push eax ; lpnLengthNeeded
.text:01006AD4 push [ebp+nLengthNeeded] ; nLength
.text:01006AD7 mov [ebp+var_4], edi
.text:01006ADA push edi ; pSecurityDescriptor
.text:01006ADB push DACL_SECURITY_INFORMATION ; RequestedInformation
.text:01006ADD push [ebp+lpFileName] ; lpFileName
.text:01006AE0 call esi ; GetFileSecurityW
然后对得到的SecurityDescriptor,调用GetSecurityDescriptorDacl获得其中的DACL:
.text:01004AF5
.text:01004AF5 loc_1004AF5: ; CODE XREF: sub_1004AC0+27j
.text:01004AF5 push ebx
.text:01004AF6 push edi
.text:01004AF7 lea eax, [ebp+bDaclDefaulted]
.text:01004AFA push eax ; lpbDaclDefaulted
.text:01004AFB lea eax, [ebp+pAcl]
.text:01004AFE push eax ; pDacl
.text:01004AFF lea eax, [ebp+bDaclPresent]
.text:01004B02 push eax ; lpbDaclPresent
.text:01004B03 push [ebp+SecurityDescriptor] ; pSecurityDescriptor
.text:01004B06 call ds:GetSecurityDescriptorDacl
.text:01004B0C test eax, eax
.text:01004B0E mov ebx, 0FFFFh
.text:01004B13 mov edi, 80070000h
.text:01004B18 jnz short loc_1004B33
.text:01004B18
对GetSecurityDescriptorDacl调用成功后的输出,OGAEXEC.exe做了这样的判断
.text:01004B33
.text:01004B33 loc_1004B33: ; CODE XREF: sub_1004AC0+58j
.text:01004B33 cmp [ebp+bDaclPresent], esi ; esi is 0/FALSE
.text:01004B36 jnz short loc_1004B49 ; if bDaclPresent is TRUE, pAcl can also be NULL
.text:01004B36
.text:01004B38 cmp [ebp+pAcl], esi
.text:01004B3B jnz short loc_1004B49
.text:01004B3B
.text:01004B3D mov [ebp+var_4], 8000FFFFh
.text:01004B44 jmp loc_1004C6E
.text:01004B44
OGAEXEC.exe的以上判断逻辑认为,只要返回的bDaclPresent为TRUE,那么返回的pAcl值就一定非NULL,只有bDaclPresent为FALSE才需要对pAcl是否为NULL进行判断,这样在bDaclPresent为TRUE的情况下,直接就对该pAcl值调用了GetAclInformation:
.text:01004B49 ; ---------------------------------------------------------------------------
.text:01004B49
.text:01004B49 loc_1004B49: ; CODE XREF: sub_1004AC0+76j
.text:01004B49 ; sub_1004AC0+7Bj
.text:01004B49 push 2 ; dwAclInformationClass
.text:01004B4B push 0Ch ; nAclInformationLength
.text:01004B4D lea eax, [ebp+pAclInformation]
.text:01004B50 push eax ; pAclInformation
.text:01004B51 push [ebp+pAcl] ; pAcl
.text:01004B54 call ds:GetAclInformation ; cause access violation
.text:01004B5A test eax, eax
.text:01004B5C jnz short loc_1004B77
然而,根据MSDN中对GetSecurityDescriptorDacl函数的解释:
A value of TRUE for lpbDaclPresent does not mean that pDacl is not NULL. That is, lpbDaclPresent can be TRUE while pDacl is NULL, meaning that a NULL DACL is in effect. A NULL DACL implicitly allows all access to an object and is not the same as an empty DACL. An empty DACL permits no access to an object. For information about creating a proper DACL, see Creating a DACL.
也就是说,在此处当bDaclPresent为TRUE时,pAcl可能为NULL,这时的NULL DACL表示对该对象允许所有权限。而OGAEXEC.exe没有对这种情况进行判断,导致在bDaclPresent为TRUE的情况下直接把一个为NULL的pAcl指针传给了ADVAPI32!GetAclInformation,导致后者在ntdll!RtlQueryInformationAcl访问这个NULL指针,从而触发了Access Violation异常。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)