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?
CodePudding user response:
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 DQ
s 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
CodePudding user response:
To be explicit about the reason for the difference, it's that for a WOW64 process, Process Explorer will read from the 32-bit PEB, while WMI will read from the 64-bit PEB. For completeness, here's a WOW64 program written in NASM and C that will change its command line to all 3
s as seen by Process Explorer, and to all 6
s as seen by WMI:
global _memcpy64, _wmemset64, _readgsqword64
section .text
BITS 32
_memcpy64:
push edi
push esi
call 0x33:.heavensgate
pop esi
pop edi
ret
BITS 64
.heavensgate:
mov rdi, [esp 20]
mov rsi, [esp 28]
mov rcx, [esp 36]
mov rdx, rdi
rep movsb
mov eax, edx
shr rdx, 32
retf
BITS 32
_wmemset64:
push edi
call 0x33:.heavensgate
pop edi
ret
BITS 64
.heavensgate:
mov rdi, [esp 16]
mov eax, [esp 24]
mov rcx, [esp 28]
mov rdx, rdi
rep stosw
mov eax, edx
shr rdx, 32
retf
BITS 32
_readgsqword64:
call 0x33:.heavensgate
ret
BITS 64
.heavensgate:
mov rdx, [rsp 12]
mov rdx, gs:[rdx]
mov eax, edx
shr rdx, 32
retf
#include <windows.h>
#include <winternl.h>
#include <stdint.h>
typedef struct _TEB64 {
PVOID64 Reserved1[12];
PVOID64 ProcessEnvironmentBlock;
PVOID64 Reserved2[399];
BYTE Reserved3[1952];
PVOID64 TlsSlots[64];
BYTE Reserved4[8];
PVOID64 Reserved5[26];
PVOID64 ReservedForOle; // Windows 2000 only
PVOID64 Reserved6[4];
PVOID64 TlsExpansionSlots;
} TEB64;
typedef struct _PEB64 {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[21];
PVOID64 LoaderData;
PVOID64 ProcessParameters;
BYTE Reserved3[520];
PVOID64 PostProcessInitRoutine;
BYTE Reserved4[136];
ULONG SessionId;
} PEB64;
typedef struct _UNICODE_STRING64 {
USHORT Length;
USHORT MaximumLength;
PVOID64 Buffer;
} UNICODE_STRING64;
typedef struct _RTL_USER_PROCESS_PARAMETERS64 {
BYTE Reserved1[16];
PVOID64 Reserved2[10];
UNICODE_STRING64 ImagePathName;
UNICODE_STRING64 CommandLine;
} RTL_USER_PROCESS_PARAMETERS64;
PVOID64 memcpy64(PVOID64 dest, PVOID64 src, uint64_t count);
PVOID64 wmemset64(PVOID64 dest, wchar_t c, uint64_t count);
uint64_t readgsqword64(uint64_t offset);
PVOID64 NtCurrentTeb64(void) {
return (PVOID64)readgsqword64(FIELD_OFFSET(NT_TIB64, Self));
}
int main(void) {
UNICODE_STRING *pcmdline = &NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->CommandLine;
wmemset(pcmdline->Buffer, '3', pcmdline->Length / sizeof(wchar_t));
TEB64 teb;
memcpy64(&teb, NtCurrentTeb64(), sizeof teb);
PEB64 peb;
memcpy64(&peb, teb.ProcessEnvironmentBlock, sizeof peb);
RTL_USER_PROCESS_PARAMETERS64 params;
memcpy64(¶ms, peb.ProcessParameters, sizeof params);
wmemset64(params.CommandLine.Buffer, '6', params.CommandLine.Length / sizeof(wchar_t));
getwchar();
}
(A real program doing this should probably include some error checking and sanity tests to make sure it's running on the architecture it expects.)