分类 技术分享 下的文章

文件重定向与UrlDownloadToFile的丝丝纠缠

在wow64进程中,UrlDownloadToFile的逻辑是先把文件down到缓存目录,然后再从缓存目录MoveFile到目标地址。看起来没问题。

如果system启动了一个进程,那么这个进程的temp目录一般在c:\windows\syswow64\config\appdata\local\temp下。

假如我关闭了文件重定向,那么UrlDownloadToFile会产生什么样奇怪的化学反应呢?答案是:bug。

system启动的进程,关闭了文件重定向,然后调用UrlDownloadToFile。

UrlDownloadToFile会把文件缓存到c:\windows\syswow64\config...下,然后从c:\windows\ system32\config...把缓存文件移动到目标目录。

因为文件重定向关闭了,system32不会重定向到syswow64下,MoveFile失败。UrlDownloadToFile也宣告失败。

不过,估计微软也没考虑过这种倒霉情景。

解决方案:

UrlDownloadToFile下载到某个不会重定向的目录。
开启重定向。
MoveFileEx到所需位置。

fuzz程序的第一步:打开edge的几种方法

Edge属于Universal App,这类程序不能简单的通过CreateProcess系列函数打开,有人问我打开Edge的几种方式,这里总结一下。如果要给它做Fuzz的话,可以考虑下面三种打开方式去启动Edge。

本文授权drops.wiki及本站共发

0x01 第一种方式——ShellExecute

第一种方式,利用系统自动调用默认浏览器的功能,即:
设置默认浏览器为Edge。
使用ShellExecute(0, L"OPEN", L"HTTP://YOUR-START-PAGE/", NULL, NULL, SW_SHOW);走系统的默认浏览器逻辑启动Edge。

(通过explorer 参数或者.url快捷方式等来启动Edge也属于同样的方式)

这里的流程如下:

ShellExecuteW(ShellExecuteW就是ShellExecuteExW的wrapper)
↓
ShellExecuteExW(ShellExecuteExW则是ShellExecuteNormal的wrapper)
↓
ShellExecuteNormal(可以当作是正式的启动了。在ShellExecuteNormal中,代码创建一个CShellExecute的类的实例,然后调用其ExecuteNormal方法)
↓
CShellExecute::ExecuteNormal(根据启动参数[要启动的文件后缀为.CMD时,或者调用者含有同步原语时不满足]决定是否要使用新线程来调用,传入OPEN打开URL时,满足该判断)
↓
CShellExecute::_RunThreadMaybeWait(执行线程代码简单,创建事件并在新线程中执行具体的启动代码)
↓
pfnThreadProc(调用启动进程的入口CShellExecute::_DoExecute)
↓
CShellExecute::_DoExecute
↓
CShellExecute::_InvokeCtxMenu(它会把获取到的IContextMenu[000214E4-0000-0000-C000-000000000046]接口传给InvokeInProcExec)
↓
CShellExecute::_InvokeInProcExec(根据操作获取CmdId,传给DefFolderMenu::InvokeCommand)
↓
DefFolderMenu::InvokeCommand(这是一个巨大的函数,检测到连接打开的这种情况时,使用HDXA_LetHandlerProcessCommandEx来处理)
↓
HDXA_LetHandlerProcessCommandEx
↓
SHELL32!CRegistryVerbsContextMenu::InvokeCommand(打开URL的这个动作需要读取注册表中指定的动词[open],这里开始都是根据注册表关联的动词来启动关联程序)
↓
SHELL32!CRegistryVerbsContextMenu::_Execute
↓
windows_storage!CRegDataDrivenCommand::InvokeFromContextMenu
↓
windows_storage!CRegDataDrivenCommand::_Invoke
↓
windows_storage!CRegDataDrivenCommand::_TryInvokeAssociation
↓
windows_storage!CBindAndInvokeStaticVerb::Execute
↓
windows_storage!CBindAndInvokeStaticVerb::_TryExecuteCommandHandler
↓
windows_storage!CBindAndInvokeStaticVerb::_DoCommand
↓
twinui!CAssociationLaunchExecuteCommand::Execute

0x02 第二种方式——microsoft-edge protocol

Win8开始Windows提供的Custom Protocol Activation技术让edge有了这个新启动方式,Windows允许桌面程序和runtime app注册成某个URL scheme name的默认打开程序。通过Manifest注册关联程序的方式可以参考:https://msdn.microsoft.com/en-us/library/windows/apps/hh452686.aspx

UWP Edge可以在:
打开%SYSTEMROOT%\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe,这是Edge的App目录,8wxxx就是它的APPID。
打开AppxManifest.xml, 可以看到与之关联的协议之一:

        <uap:Extension Category="windows.protocol">
            <uap:Protocol Name="microsoft-edge">
                <uap:Logo>Assets\MicrosoftEdgeFile.png</uap:Logo>
            </uap:Protocol>
        </uap:Extension>

HKEY_CURRENT_USER\SOFTWARE\Classes\Extensions\ContractId\Windows.Protocol\PackageId\Microsoft.MicrosoftEdge_25.10586.0.0_neutral__8wekyb3d8bbwe\ActivatableClassId\ 下即为支持的协议,MicrosoftEdge.AppXeb42j1vh6rk395pm0vmcx57dxqjhej5d.mca是microsoft-edge的项目。

使用microsof-edge protocol。这意味着在C++中只需要CreateProcess系列指定参数为explorer microsoft-edge:http://www.baidu.com/,或者ShellExecute中open microsoft-edge:http://xxx/即可启动。这种方式启动的Edge,不需要用户设置默认浏览器。

所以,许多更愿意把Edge当备胎的用户应该会比较喜欢使用这种方式。

0x03 第三种方式(正经的)——IApplicationActivationManager 接口

直接上代码吧。

// testCallApplicationActivationManager.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <shobjidl.h>
#include <windows.h>
#include <atlcomcli.h>

int main()
{
    CComPtr<IApplicationActivationManager> piaam;
    CComPtr<IUnknown> punk;

    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    LPCWSTR appId = L"Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge";
    IApplicationActivationManager* paam = NULL;
    HRESULT hr = E_FAIL;
    LPCWSTR url = L"www.wooyun.org";
    do
    {
        hr = CoCreateInstance(CLSID_ApplicationActivationManager, NULL, 
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&paam));
        if (FAILED(hr))
            break;
        DWORD pid = 0;
        hr = paam->ActivateApplication(appId, url, AO_NONE, &pid);
        if (FAILED(hr))
            break;
    } while (0);

    return 0;
}

参考资料
【1】UWP是什么:https://msdn.microsoft.com/en-us/windows/uwp/get-started/whats-a-uwp
【2】https://www.microsoft.com/msj/0199/com/com0199.aspx

Nvidia CoProcManager DLL诡异的问题

最近在使用电脑的时候,发现电脑打开Edge、Taskmgr,甚至Process Explorer、Process Monitor时总是莫名崩溃,实在是干扰使用啊,但是还好Windbg打开之后没有崩溃。于是,我在程序崩溃后,WerFault的框尚在的时候,使用Windbg挂上进程,~*k查看所有线程的调用信息,找到了其中一个线程触发了崩溃。

crash01.png

可以看到nvd3d9wrapx.dll这个DLL在RtlExitUserThread-LdrShutdownThread-LdrpCallInitRoutine这个过程中插进来了,并且在这个线程要退出的时候,被initialise给插了一脚,直接崩了。

0:010> k
Child-SP          RetAddr           Call Site
0000006f`0c3fd568 00007ffe`ea443b4f ntdll!NtWaitForMultipleObjects+0x14
0000006f`0c3fd570 00007ffe`ea443a4e KERNELBASE!WaitForMultipleObjectsEx+0xef
0000006f`0c3fd870 00007ffe`ec43278f KERNELBASE!WaitForMultipleObjects+0xe
0000006f`0c3fd8b0 00007ffe`ec4322a2 KERNEL32!WerpReportFaultInternal+0x4ab
0000006f`0c3fde60 00007ffe`ea4c7ee7 KERNEL32!WerpReportFault+0x52
0000006f`0c3fde90 00007ffe`ed5cd998 KERNELBASE!UnhandledExceptionFilter+0x277
0000006f`0c3fdf90 00007ffe`ed5b5b26 ntdll!RtlUserThreadStart$filt$0+0x3e
0000006f`0c3fdfd0 00007ffe`ed5c9afd ntdll!_C_specific_handler+0x96
0000006f`0c3fe040 00007ffe`ed554fe9 ntdll!RtlpExecuteHandlerForException+0xd
0000006f`0c3fe070 00007ffe`ed5c8c0a ntdll!RtlDispatchException+0x3a9
0000006f`0c3fe780 00007ffe`e7749cde ntdll!KiUserExceptionDispatch+0x3a
0000006f`0c3fee98 00007ffe`e7750ee2 nvd3d9wrapx!initialise+0x3fe
0000006f`0c3ff5a8 00007ffe`ed5352c8 nvd3d9wrapx!setDeviceHandle+0x5832
0000006f`0c3ff618 00007ffe`ed532bf1 ntdll!LdrpCallInitRoutine+0x4c
0000006f`0c3ff678 00007ffe`ed57c62e ntdll!LdrShutdownThread+0x151
0000006f`0c3ff778 0000006f`0c3ff7ef ntdll!RtlExitUserThread+0x3e
0000006f`0c3ff7b8 00000000`00001000 0x0000006f`0c3ff7ef
0000006f`0c3ff7c0 0000019f`00000000 0x1000
0000006f`0c3ff7c8 00007ffe`ec4532a0 0x0000019f`00000000
0000006f`0c3ff7d0 0000019f`1fa100cb KERNEL32!VirtualFreeStub
0000006f`0c3ff7d8 0000019f`1fa10081 0x0000019f`1fa100cb
0000006f`0c3ff7e0 00000000`00001000 0x0000019f`1fa10081
0000006f`0c3ff7e8 00d5ffcf`8b49d3ff 0x1000
0000006f`0c3ff7f0 0000006f`0c3ff7e8 0x00d5ffcf`8b49d3ff
0000006f`0c3ff7f8 0000019f`1fa10000 0x0000006f`0c3ff7e8
0000006f`0c3ff800 00000000`00000000 0x0000019f`1fa10000

查看一下位置

0:010> lm vm nvd3d9wrapx
start             end                 module name
00007ffe`e7740000 00007ffe`e777a000   nvd3d9wrapx   (export symbols)       F:\Program Files\NVIDIA Corporation\CoProcManager\nvd3d9wrapx.dll
    Loaded symbol image file: F:\Program Files\NVIDIA Corporation\CoProcManager\nvd3d9wrapx.dll
    Image path: F:\Program Files\NVIDIA Corporation\CoProcManager\nvd3d9wrapx.dll
    Image name: nvd3d9wrapx.dll
    Timestamp:        Fri Jun 03 10:51:18 2016 (5750F0A6)
    CheckSum:         00037FD6
    ImageSize:        0003A000
    File version:     10.18.13.6839
    Product version:  10.18.13.6839
    File flags:       8 (Mask 3F) Private
    File OS:          40004 NT Win32
    File type:        3.4 Driver
    File date:        00000000.00000000
    Translations:     0409.04e4
    CompanyName:      NVIDIA Corporation
    ProductName:      NVIDIA D3D shim drivers
    InternalName:     nvd3d9wrap
    OriginalFilename: nvd3d9wrap.dll
    ProductVersion:   10.18.13.6839
    FileVersion:      10.18.13.6839
    FileDescription:  NVIDIA d3d9wrap dll, Version 368.39 
    LegalCopyright:   (C) 2016 NVIDIA Corporation. All rights reserved.

进去一看更神奇的事情出现了,这个文件的数字签名居然是自签名的,而其他文件的签名则都是正常的CA发下来的。开始我还以为我中毒了,直到看了官网包释放的DLL同样是自签名的之后,服了。你这样还不如不签名,害得我以为是病毒来着。

crash02.png

WTSQueryUserToken的一点事

起因用户态进程 hToken 为Null。
HANDLE hToken = WTSQueryUserToken(WTSGetActiveConsoleSessionId());

为啥获取的是Null呢。

WTSGetActiveConsoleSessionId function

Retrieves the session identifier of the console session. The console session is the session that is currently attached to the physical console. Note that it is not necessary that Remote Desktop Services be running for this function to succeed.

kernel32!WTSGetActiveConsoleSessionId的结构十分简单:

; Exported entry 1274. WTSGetActiveConsoleSessionId



; DWORD __stdcall WTSGetActiveConsoleSessionId()
public _WTSGetActiveConsoleSessionId@0
_WTSGetActiveConsoleSessionId@0 proc near
mov     eax, ds:7FFE02D8h
retn
_WTSGetActiveConsoleSessionId@0 endp

7ffe02d8h是什么?查询属于SharedUserData+0x2d8,也即

struct KUSER_SHARED_DATA
typedef struct _KUSER_SHARED_DATA
{
     ULONG TickCountLowDeprecated;
     ULONG TickCountMultiplier;
     KSYSTEM_TIME InterruptTime;
     KSYSTEM_TIME SystemTime;
     KSYSTEM_TIME TimeZoneBias;
     WORD ImageNumberLow;
     WORD ImageNumberHigh;
     WCHAR NtSystemRoot[260];
     ULONG MaxStackTraceDepth;
     ULONG CryptoExponent;
     ULONG TimeZoneId;
     ULONG LargePageMinimum;
     ULONG Reserved2[7];
     NT_PRODUCT_TYPE NtProductType;
     UCHAR ProductTypeIsValid;
     ULONG NtMajorVersion;
     ULONG NtMinorVersion;
     UCHAR ProcessorFeatures[64];
     ULONG Reserved1;
     ULONG Reserved3;
     ULONG TimeSlip;
     ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture;
     LARGE_INTEGER SystemExpirationDate;
     ULONG SuiteMask;
     UCHAR KdDebuggerEnabled;
     UCHAR NXSupportPolicy;
     ULONG ActiveConsoleId;

这个字段。

SharedUserData是Windows为各进程提供的共享数据结构,存有许多系统信息,如时间等等,看上面的定义,名字应该都是自解释的。

#define KI_USER_SHARED_DATA        0xffdf0000
#define SharedUserData  ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)

SharedUserData在内核中的地址是0xffdf0000,Windows通过共享映射把这个结构以只读方式映射到每个进程的0x7ffe0000的地址中,可以供各内存使用。因此,使用该API取出该字段的值是不成问题的。因此问题就出在外面的WTSQueryUserToken中。

根据MSDN的说法,只有LocalSystem且包含SE_TCB_NAME权限的进程可以调用它。

0:019> x *!*WTSQueryUserToken*
768ef736          kernel32!WTSQueryUserToken (<no parameter info>)
00a75514          XXX!_imp__WTSQueryUserToken = <no type information>
74cb1f81          WTSAPI32!WTSQueryUserToken (<no parameter info>)
761f52e6          iertutil!WTSQueryUserToken (<no parameter info>)
761ec526          iertutil!ext_ms_win_session_wtsapi32_l1_1_0_WTSQueryUserToken (<no parameter info>)
0:019> dd 00a75514
00a75514  74cb1f81 00000000 75110965 75118683
0:019> u 74cb1f81 
WTSAPI32!WTSQueryUserToken:
74cb1f81 8bff            mov     edi,edi
74cb1f83 55              push    ebp
74cb1f84 8bec            mov     ebp,esp
74cb1f86 83ec0c          sub     esp,0Ch
74cb1f89 56              push    esi
74cb1f8a 8b750c          mov     esi,dword ptr [ebp+0Ch]
74cb1f8d 85f6            test    esi,esi
74cb1f8f 0f845d0a0000    je      WTSAPI32!WTSQueryUserToken+0x10 (74cb29f2)

查看服务进程XXX的引用,可以发现实现者是wtsapi32!WTSQueryUserToken。

signed int __stdcall WTSQueryUserToken(int a1, int a2)
{
  int v2; // esi@1
  DWORD v4; // [sp+4h] [bp-Ch]@3
  DWORD v5; // [sp+8h] [bp-8h]@3
  int v6; // [sp+Ch] [bp-4h]@4

  v2 = a2;
  if ( a2 )
  {
    if ( IsProcessPrivileged() )
    {
      v4 = GetCurrentProcessId();
      v5 = GetCurrentThreadId();
      if ( (unsigned __int8)WinStationQueryInformationW(0, a1, 14, &v4, 12, &a2) )
      {
        *(_DWORD *)v2 = v6;
        return 1;
      }
    }
  }
  else
  {
    SetLastError(0x57u);
  }
  return 0;
}


signed int __cdecl IsProcessPrivileged()
{
  HANDLE v0; // eax@1
  HANDLE v1; // eax@3
  BOOL v2; // edi@4
  signed int result; // eax@8
  struct _PRIVILEGE_SET RequiredPrivileges; // [sp+8h] [bp-1Ch]@4
  BOOL pfResult; // [sp+1Ch] [bp-8h]@1
  HANDLE hObject; // [sp+20h] [bp-4h]@1

  pfResult = 0;
  hObject = 0;
  v0 = GetCurrentThread();
  if ( !OpenThreadToken(v0, 0x2000000u, 0, &hObject) )
  {
    if ( GetLastError() != 1008 || (v1 = GetCurrentProcess(), !OpenProcessToken(v1, 0x2000000u, &hObject)) )
      goto LABEL_14;
  }
  RequiredPrivileges.Privilege[0].Luid.LowPart = 7;
  RequiredPrivileges.PrivilegeCount = 1;
  RequiredPrivileges.Control = 1;
  RequiredPrivileges.Privilege[0].Luid.HighPart = 0;
  RequiredPrivileges.Privilege[0].Attributes = 2;
  v2 = PrivilegeCheck(hObject, &RequiredPrivileges, &pfResult);
  if ( v2 && !pfResult )
    SetLastError(0x522u);
  CloseHandle(hObject);
  if ( v2 && pfResult )
    result = 1;
  else
LABEL_14:
    result = 0;
  return result;
}

阅读相关代码,可以发现确实如此,用户态进程在直接调用这个函数时,必然会因为权限检查而失败。而在使用x过程中可以看到另一个比较好玩的东西,WTSQueryUserToken在kernel32.dll中也有实现,是这样的:

.text:7DE0F5DE ; __stdcall WTSQueryUserToken(x, x)
.text:7DE0F5DE _WTSQueryUserToken@8 proc near          ; DATA XREF: .text:7DD726E4o
.text:7DE0F5DE                                         ; .text:7DD726ECo ...
.text:7DE0F5DE                 push    65Ah            ; dwErrCode
.text:7DE0F5E3                 call    _SetLastError@4 ; SetLastError(x)
.text:7DE0F5E8                 xor     eax, eax
.text:7DE0F5EA                 retn    8
.text:7DE0F5EA _WTSQueryUserToken@8 endp

显然不能获取正确的UserToken。为什么微软要在kernel32中也实现一个同名的呢?很谜。直接调kernel32.dll的函数,它的LastError会告诉你不应该调这个DLL里的WTS函数,可能是因为kernel32中导出了WTSGetActiveConsoleSessionId,所以微软就认为大家可能也会认为WTSQueryUserToken在kernel32中导出?完全不明白。

#define ERROR_FUNCTION_NOT_CALLED    1626L
BOOL kernel32!WTSQueryUserToken(_In_  ULONG   SessionId,  _Out_ PHANDLE phToken)
{
    NOT_IMPLEMENTED();

    SetLastError(ERROR_FUNCTION_NOT_CALLED); //无法执行函数。 
    return FALSE;
}

那如何在应用态进程获取user token呢?easy,先找到explorer.exe,OpenProcess打开之获取到Token即是。