1
+ // Source: https://github.com/sonicmouse/ProcCmdLine (MIT)
2
+
3
+ using System ;
4
+ using System . Collections . Generic ;
5
+ using System . ComponentModel ;
6
+ using System . Diagnostics ;
7
+ using System . Linq ;
8
+ using System . Runtime . InteropServices ;
9
+
10
+ public static class ProcessCommandLine
11
+ {
12
+ private static class Win32Native
13
+ {
14
+ public const uint PROCESS_BASIC_INFORMATION = 0 ;
15
+
16
+ [ Flags ]
17
+ public enum OpenProcessDesiredAccessFlags : uint
18
+ {
19
+ PROCESS_VM_READ = 0x0010 ,
20
+ PROCESS_QUERY_INFORMATION = 0x0400 ,
21
+ }
22
+
23
+ [ StructLayout ( LayoutKind . Sequential ) ]
24
+ public struct ProcessBasicInformation
25
+ {
26
+ public IntPtr Reserved1 ;
27
+ public IntPtr PebBaseAddress ;
28
+ [ MarshalAs ( UnmanagedType . ByValArray , SizeConst = 2 ) ]
29
+ public IntPtr [ ] Reserved2 ;
30
+ public IntPtr UniqueProcessId ;
31
+ public IntPtr Reserved3 ;
32
+ }
33
+
34
+ [ StructLayout ( LayoutKind . Sequential ) ]
35
+ public struct UnicodeString
36
+ {
37
+ public ushort Length ;
38
+ public ushort MaximumLength ;
39
+ public IntPtr Buffer ;
40
+ }
41
+
42
+ // This is not the real struct!
43
+ // I faked it to get ProcessParameters address.
44
+ // Actual struct definition:
45
+ // https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
46
+ [ StructLayout ( LayoutKind . Sequential ) ]
47
+ public struct PEB
48
+ {
49
+ [ MarshalAs ( UnmanagedType . ByValArray , SizeConst = 4 ) ]
50
+ public IntPtr [ ] Reserved ;
51
+ public IntPtr ProcessParameters ;
52
+ }
53
+
54
+ [ StructLayout ( LayoutKind . Sequential ) ]
55
+ public struct RtlUserProcessParameters
56
+ {
57
+ public uint MaximumLength ;
58
+ public uint Length ;
59
+ public uint Flags ;
60
+ public uint DebugFlags ;
61
+ public IntPtr ConsoleHandle ;
62
+ public uint ConsoleFlags ;
63
+ public IntPtr StandardInput ;
64
+ public IntPtr StandardOutput ;
65
+ public IntPtr StandardError ;
66
+ public UnicodeString CurrentDirectory ;
67
+ public IntPtr CurrentDirectoryHandle ;
68
+ public UnicodeString DllPath ;
69
+ public UnicodeString ImagePathName ;
70
+ public UnicodeString CommandLine ;
71
+ }
72
+
73
+ [ DllImport ( "ntdll.dll" ) ]
74
+ public static extern uint NtQueryInformationProcess (
75
+ IntPtr ProcessHandle ,
76
+ uint ProcessInformationClass ,
77
+ IntPtr ProcessInformation ,
78
+ uint ProcessInformationLength ,
79
+ out uint ReturnLength ) ;
80
+
81
+ [ DllImport ( "kernel32.dll" ) ]
82
+ public static extern IntPtr OpenProcess (
83
+ OpenProcessDesiredAccessFlags dwDesiredAccess ,
84
+ [ MarshalAs ( UnmanagedType . Bool ) ] bool bInheritHandle ,
85
+ uint dwProcessId ) ;
86
+
87
+ [ DllImport ( "kernel32.dll" ) ]
88
+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
89
+ public static extern bool ReadProcessMemory (
90
+ IntPtr hProcess , IntPtr lpBaseAddress , IntPtr lpBuffer ,
91
+ uint nSize , out uint lpNumberOfBytesRead ) ;
92
+
93
+ [ DllImport ( "kernel32.dll" ) ]
94
+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
95
+ public static extern bool CloseHandle ( IntPtr hObject ) ;
96
+
97
+ [ DllImport ( "shell32.dll" , SetLastError = true ,
98
+ CharSet = CharSet . Unicode , EntryPoint = "CommandLineToArgvW" ) ]
99
+ public static extern IntPtr CommandLineToArgv ( string lpCmdLine , out int pNumArgs ) ;
100
+ }
101
+
102
+ private static bool ReadStructFromProcessMemory < TStruct > (
103
+ IntPtr hProcess , IntPtr lpBaseAddress , out TStruct val )
104
+ {
105
+ val = default ;
106
+ var structSize = Marshal . SizeOf < TStruct > ( ) ;
107
+ var mem = Marshal . AllocHGlobal ( structSize ) ;
108
+ try
109
+ {
110
+ if ( Win32Native . ReadProcessMemory (
111
+ hProcess , lpBaseAddress , mem , ( uint ) structSize , out var len ) &&
112
+ ( len == structSize ) )
113
+ {
114
+ val = Marshal . PtrToStructure < TStruct > ( mem ) ;
115
+ return true ;
116
+ }
117
+ }
118
+ finally
119
+ {
120
+ Marshal . FreeHGlobal ( mem ) ;
121
+ }
122
+ return false ;
123
+ }
124
+
125
+ public static string ErrorToString ( int error ) =>
126
+ new string [ ]
127
+ {
128
+ "Success" ,
129
+ "Failed to open process for reading" ,
130
+ "Failed to query process information" ,
131
+ "PEB address was null" ,
132
+ "Failed to read PEB information" ,
133
+ "Failed to read process parameters" ,
134
+ "Failed to read parameter from process"
135
+ } [ Math . Abs ( error ) ] ;
136
+
137
+ public enum Parameter
138
+ {
139
+ CommandLine ,
140
+ WorkingDirectory ,
141
+ }
142
+
143
+ public static int Retrieve ( Process process , out string parameterValue , Parameter parameter = Parameter . CommandLine )
144
+ {
145
+ int rc = 0 ;
146
+ parameterValue = null ;
147
+ var hProcess = Win32Native . OpenProcess (
148
+ Win32Native . OpenProcessDesiredAccessFlags . PROCESS_QUERY_INFORMATION |
149
+ Win32Native . OpenProcessDesiredAccessFlags . PROCESS_VM_READ , false , ( uint ) process . Id ) ;
150
+ if ( hProcess != IntPtr . Zero )
151
+ {
152
+ try
153
+ {
154
+ var sizePBI = Marshal . SizeOf < Win32Native . ProcessBasicInformation > ( ) ;
155
+ var memPBI = Marshal . AllocHGlobal ( sizePBI ) ;
156
+ try
157
+ {
158
+ var ret = Win32Native . NtQueryInformationProcess (
159
+ hProcess , Win32Native . PROCESS_BASIC_INFORMATION , memPBI ,
160
+ ( uint ) sizePBI , out var len ) ;
161
+ if ( 0 == ret )
162
+ {
163
+ var pbiInfo = Marshal . PtrToStructure < Win32Native . ProcessBasicInformation > ( memPBI ) ;
164
+ if ( pbiInfo . PebBaseAddress != IntPtr . Zero )
165
+ {
166
+ if ( ReadStructFromProcessMemory < Win32Native . PEB > ( hProcess ,
167
+ pbiInfo . PebBaseAddress , out var pebInfo ) )
168
+ {
169
+ if ( ReadStructFromProcessMemory < Win32Native . RtlUserProcessParameters > (
170
+ hProcess , pebInfo . ProcessParameters , out var ruppInfo ) )
171
+ {
172
+ string ReadUnicodeString ( Win32Native . UnicodeString unicodeString )
173
+ {
174
+ var clLen = unicodeString . MaximumLength ;
175
+ var memCL = Marshal . AllocHGlobal ( clLen ) ;
176
+ try
177
+ {
178
+ if ( Win32Native . ReadProcessMemory ( hProcess ,
179
+ unicodeString . Buffer , memCL , clLen , out len ) )
180
+ {
181
+ rc = 0 ;
182
+ return Marshal . PtrToStringUni ( memCL ) ;
183
+ }
184
+ else
185
+ {
186
+ // couldn't read parameter line buffer
187
+ rc = - 6 ;
188
+ }
189
+ }
190
+ finally
191
+ {
192
+ Marshal . FreeHGlobal ( memCL ) ;
193
+ }
194
+ return null ;
195
+ }
196
+
197
+ switch ( parameter )
198
+ {
199
+ case Parameter . CommandLine :
200
+ parameterValue = ReadUnicodeString ( ruppInfo . CommandLine ) ;
201
+ break ;
202
+ case Parameter . WorkingDirectory :
203
+ parameterValue = ReadUnicodeString ( ruppInfo . CurrentDirectory ) ;
204
+ break ;
205
+ }
206
+ }
207
+ else
208
+ {
209
+ // couldn't read ProcessParameters
210
+ rc = - 5 ;
211
+ }
212
+ }
213
+ else
214
+ {
215
+ // couldn't read PEB information
216
+ rc = - 4 ;
217
+ }
218
+ }
219
+ else
220
+ {
221
+ // PebBaseAddress is null
222
+ rc = - 3 ;
223
+ }
224
+ }
225
+ else
226
+ {
227
+ // NtQueryInformationProcess failed
228
+ rc = - 2 ;
229
+ }
230
+ }
231
+ finally
232
+ {
233
+ Marshal . FreeHGlobal ( memPBI ) ;
234
+ }
235
+ }
236
+ finally
237
+ {
238
+ Win32Native . CloseHandle ( hProcess ) ;
239
+ }
240
+ }
241
+ else
242
+ {
243
+ // couldn't open process for VM read
244
+ rc = - 1 ;
245
+ }
246
+ return rc ;
247
+ }
248
+
249
+ public static IReadOnlyList < string > CommandLineToArgs ( string commandLine )
250
+ {
251
+ if ( string . IsNullOrEmpty ( commandLine ) ) { return Array . Empty < string > ( ) ; }
252
+
253
+ var argv = Win32Native . CommandLineToArgv ( commandLine , out var argc ) ;
254
+ if ( argv == IntPtr . Zero )
255
+ {
256
+ throw new Win32Exception ( Marshal . GetLastWin32Error ( ) ) ;
257
+ }
258
+ try
259
+ {
260
+ var args = new string [ argc ] ;
261
+ for ( var i = 0 ; i < args . Length ; ++ i )
262
+ {
263
+ var p = Marshal . ReadIntPtr ( argv , i * IntPtr . Size ) ;
264
+ args [ i ] = Marshal . PtrToStringUni ( p ) ;
265
+ }
266
+ return args . ToList ( ) . AsReadOnly ( ) ;
267
+ }
268
+ finally
269
+ {
270
+ Marshal . FreeHGlobal ( argv ) ;
271
+ }
272
+ }
273
+ }
0 commit comments