c++winapicommand-line-argumentswmiwow64

How can a WOW64 program overwrite its command-line arguments, as seen by WMI?


I'm trying to write a program that can mask its command line arguments after it reads them. I know this is stored in the PEB, so I tried using the answer to "How to get the Process Environment Block (PEB) address using assembler (x64 OS)?" by Sirmabus to get that and modify it there. Here's a minimal program that does that:

#include <wchar.h>
#include <windows.h>
#include <winnt.h>
#include <winternl.h>

// Thread Environment Block (TEB)
#if defined(_M_X64) // x64
PTEB tebPtr = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
#else // x86
PTEB tebPtr = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif

// Process Environment Block (PEB)
PPEB pebPtr = tebPtr->ProcessEnvironmentBlock;

int main() {
    UNICODE_STRING *s = &pebPtr->ProcessParameters->CommandLine;
    wmemset(s->Buffer, 'x', s->Length / sizeof *s->Buffer);
    getwchar();
}

I compiled this both as 32-bit and 64-bit, and tested it on both 32-bit and 64-bit versions of Windows. I looked for the command line using Process Explorer, and also by using this PowerShell command to fetch it via WMI:

Get-WmiObject Win32_Process -Filter "name = 'overwrite.exe'" | Select-Object CommandLine

I've found that this works in every combination I tested it in, except for using WMI on a WOW64 process. Summarizing my test results in a table:

Architecture Process Explorer WMI
64-bit executable on 64-bit OS (native) ✔️ xxxxxxxxxxxxx ✔️ xxxxxxxxxxxxx
32-bit executable on 64-bit OS (WOW64) ✔️ xxxxxxxxxxxxx ❌ overwrite.exe
32-bit executable on 32-bit OS (native) ✔️ xxxxxxxxxxxxx ✔️ xxxxxxxxxxxxx

How can I modify my code to make this work in the WMI WOW64 case too?


Solution

  • wow64 processes have 2 PEB (32 and 64 bit) and 2 different ProcessEnvironmentBlock (again 32 and 64). the command line exist in both. some tools take command line correct (from 32 ProcessEnvironmentBlock for 32bit processes) and some unconditional from 64bit ProcessEnvironmentBlock (on 64 bit os). so you want zero (all or first char) of command line in both blocks. for do this in "native" block we not need access TEB/PEB/ProcessEnvironmentBlock - the GetCommandLineW return the direct pointer to the command-line string in ProcessEnvironmentBlock. so next code is enough:

    PWSTR psz = GetCommandLineW();
    while (*psz) *psz++ = 0;
    

    or simply

    *GetCommandLineW() = 0;
    

    is enough

    as side note, for get TEB pointer not need write own macro - NtCurrentTeb() macro already exist in winnt.h

    access 64 bit ProcessEnvironmentBlock from 32 bit process already not trivial. one way suggested in comment. another way more simply, but not documented - call NtQueryInformationProcess with ProcessWow64Information

    When the ProcessInformationClass parameter is ProcessWow64Information, the buffer pointed to by the ProcessInformation parameter should be large enough to hold a ULONG_PTR. If this value is nonzero, the process is running in a WOW64 environment. Otherwise, the process is not running in a WOW64 environment.

    so this value receive some pointer. but msdn not say for what he point . in reality this pointer to 64 PEB of process in wow64 process.

    so code can be next:

    #ifndef _WIN64
        PEB64* peb64;
        if (0 <= NtQueryInformationProcess(NtCurrentProcess(), 
            ProcessWow64Information, &peb64, sizeof(peb64), 0) && peb64)
        {
            // ...
        }
    #endif
    

    but declare and use 64 bit structures in 32bit process very not comfortable (need all time check that pointer < 0x100000000 )

    another original way - execute small 64bit shellcode which do the task.

    the code doing approximately the following:

    #include <winternl.h>
    #include <intrin.h>
    
    void ZeroCmdLine()
    {
        PUNICODE_STRING CommandLine = 
            &NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->CommandLine;
        if (USHORT Length = CommandLine->Length)
        {
            //*CommandLine->Buffer = 0;
            __stosw((PUSHORT)CommandLine->Buffer, 0, Length / sizeof(WCHAR));
        }
    }
    

    you need create asm, file (if yet not have it in project) with the next code

    .686
    
    .MODEL FLAT
    
    .code
    
    @ZeroCmdLine@0 proc
        push ebp
        mov ebp,esp
        and esp,not 15
        push 33h
        call @@1
        ;++++++++ x64 +++++++++
        sub esp,20h
        call @@0
        add esp,20h
        retf
    @@0:
        DQ 000003025048b4865h
        DQ 0408b4860408b4800h
        DQ 00de3677048b70f20h
        DQ 033e9d178788b4857h
        DQ 0ccccc35fab66f3c0h
        ;-------- x64 ---------
    @@1:
        call fword ptr [esp]
        leave
        ret
    @ZeroCmdLine@0 endp
    
    end
    

    the code in the DQs came from this:

        mov rax,gs:30h
        mov rax,[rax+60h]
        mov rax,[rax+20h]
        movzx ecx,word ptr [rax+70h]
        jecxz @@2
        push rdi
        mov rdi,[rax+78h]
        shr ecx,1
        xor eax,eax
        rep stosw
        pop rdi
    @@2:
        ret
        int3
        int3
    

    custom build: ml /c /Cp $(InputFileName) -> $(InputName).obj

    declare in c++

    #ifdef __cplusplus
    extern "C"
    #endif
    void FASTCALL ZeroCmdLine(void);
    

    and call it.

    #ifndef _WIN64
        BOOL bWow;
        if (IsWow64Process(GetCurrentProcess(), &bWow) && bWow)
        {
            ZeroCmdLine();
        }
    #endif