[VB6, twinBASIC] Getting the full path of all processes when not elevated
If you've used programs like ProcessHacker or ProcessExplorer you might have noticed that you don't need to run them elevated to see the full paths of all the running processes. If you've ever tried this yourself with the standard methods of either reading the PEB or using the QueryFullProcessImageName API, you might have noticed this will fail for processes running as SYSTEM, because we're denied even the very limited PROCESS_QUERY_LIMITED_INFORMATION access right. So how can we find the full path anyway, when those are denied and the normal enum function CreateToolhelp32Snapshot only returns the file name? One way is to ask Windows itself, since it maintains a list of all process paths internally.
This is done with the NtQuerySystemInformation and the undocumented info class SystemProcessIdInformation. This allows us to specify a SYSTEM_PROCESS_ID_INFORMATION type with the ProcessId filled in, and it will fill a buffer with the full path to the image.
Code:
Private Type SYSTEM_PROCESS_ID_INFORMATION
ProcessId As LongPtr
ImageName As UNICODE_STRING
End Type
Private Type UNICODE_STRING
uLength As Integer
uMaximumLength As Integer
pBuffer As LongPtr
End Type
Private Function GetProcessFullPathEx(pid As Long, pPath As String) As Long
'Regular method can't get path of SYSTEM process
'Note: API Returns NT path
Dim Status As Long 'NTSTATUS
Dim lpBuffer As LongPtr
Dim spii As SYSTEM_PROCESS_ID_INFORMATION
Dim sTemp As String
Dim cbMax As Long: cbMax = MAX_PATH * 2 'LenB(Of Integer) ' * sizeof(WCHAR)
Dim cbRet As Long
lpBuffer = LocalAlloc(LMEM_FIXED, cbMax)
spii.ProcessId = pid
spii.ImageName.uMaximumLength = cbMax
spii.ImageName.pBuffer = lpBuffer
Status = NtQuerySystemInformation(SystemProcessIdInformation, spii, LenB(spii), cbRet)
If NT_SUCCESS(Status) Then
sTemp = LPWSTRtoStr(lpBuffer, False)
If bSetVM = False Then
MapVolumes
End If
pPath = ConvertNtPathToDosPath(sTemp)
Else
Debug.Print "GetProcessFullPathEx error, 0x" & Hex$(Status)
End If
LocalFree lpBuffer
GetProcessFullPathEx = Status
End Function
The final piece of the puzzle is the path post-processing: ConvertNtPathToDosPath. This is needed because the paths we receive here are in the format e.g. \Device\HarddiskVolume1\Windows\System32\crss.exe rather than the drive letters we're accustomed to. The way we translate these is by first creating a map with the MapVolumes function of every drive letter to the device path:
Code:
Private Sub MapVolumes()
'Map out \Device\Harddiskblahblah
Dim sDrive As String
Dim i As Long, j As Long
Dim sBuffer As String
ReDim VolMap(0)
Dim tmpMap() As VolData
Dim nMap As Long, nfMap As Long
Dim lIdx As Long
Dim lnMax As Long
Dim cb As Long
For lIdx = 0 To 25
sDrive = Chr$(65 + lIdx) & ":"
sBuffer = String$(1000, vbNullChar)
cb = QueryDosDeviceW(StrPtr(sDrive), StrPtr(sBuffer), Len(sBuffer))
If cb Then
ReDim Preserve tmpMap(nMap)
tmpMap(nMap).sLetter = sDrive
tmpMap(nMap).sName = TrimNullW(sBuffer)
nMap = nMap + 1
End If
Next
'Next we need to sort the array so e.g. 10 will always come before 1
'We'll find the longest ones, add any of that length, then add any
'of 1 char shorter, until we've added all items
For i = 0 To (nMap - 1)
If Len(tmpMap(i).sName) > lnMax Then lnMax = Len(tmpMap(i).sName)
Next i
ReDim VolMap(nMap - 1)
For i = lnMax To 1 Step -1
For j = 0 To UBound(tmpMap)
If Len(tmpMap(j).sName) = i Then
VolMap(nfMap).sName = tmpMap(j).sName
VolMap(nfMap).sLetter = tmpMap(j).sLetter
nfMap = nfMap + 1
End If
Next j
If nfMap = nMap Then Exit For
Next i
bSetVM = True
End Sub
Then we just run each path through a find/replace of each map name to the corresponding letter.
Code:
Private Function ConvertNtPathToDosPath(sPath As String) As String
If sPath = "" Then Exit Function
Dim i As Long
ConvertNtPathToDosPath = sPath
For i = 0 To UBound(VolMap)
ConvertNtPathToDosPath = Replace$(ConvertNtPathToDosPath, VolMap(i).sName, VolMap(i).sLetter, 1, 1)
Next
End Function
All in all, this is more complicated than the traditional way, but there's plenty of situations where you're not able to run elevated but still want to display a list of processes and their paths--- or just their name, but need their paths to look up their icon.
-----
This code is compatible with both VB6 and twinBASIC, including 64bit compilation in the latter. The .twinproj is included, but you can reimport yourself to see the only change made is to use the smoother built in Anchor resizing rather than Form_Resize, as noted in the code.
There are no dependencies and this should work on all Windows versions XP and later.
UPDATE (2023 Nov 19) - Fix for garbage in system modules with no path.
Last edited by fafalone; Nov 19th, 2023 at 01:49 PM.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Very good, and also very nice to have the same code/project running in VB6 and in Twin.
If it should be always like that, this will force on mid term the use of TwinBasic (not ready on my part, I need to have my main big projects running 100% in Twin)
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I noticed that with registry too... I didn't think uninitialized memory would be a concern, but I guess so, I'll update the post.
This is the fix applied:
Code:
'For VBA7 block
Private Declare PtrSafe Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As LongPtr)
'For VB6 block
Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As LongPtr)
'For GetProcessFullPathEx, add the ZeroMemory call after the line below:
lpBuffer = LocalAlloc(LMEM_FIXED, cbMax)
ZeroMemory ByVal lpBuffer, cbMax
You can make those changes or just re-download, it's been updated. Let me know if any further issues persist or arise.
Last edited by fafalone; Nov 19th, 2023 at 01:51 PM.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I've known about this wonderful function NtQuerySystemInformation for a long time, it's my favorite function. But did you know that it doesn't always return the truth in Windows 7? If you start the process, then close the program, then rename this folder, and then run the program again, then the path to the process will already be old and incorrect. I described this topic in detail here: https://www.cyberforum.ru/visual-bas...ad3099779.html
But this bug has most likely already been fixed in Windows 10. However, I strongly recommend that you heed my advice and use other functions to correctly get the full path to the executable file. For example PSAPI will show the correct result or reading the PEB structure
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
While I agree those are better first line options, the thread exists because those are not possible, on Windows 10/11 at least, on a SYSTEM process when your app isn't elevated. For both of those, you need to open a handle to the process, and even PROCESS_QUERY_LIMITED_INFORMATION errors with 'access denied' for SYSTEM processes when you're not elevated.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Originally Posted by fafalone
While I agree those are better first line options, the thread exists because those are not possible, on Windows 10/11 at least, on a SYSTEM process when your app isn't elevated. For both of those, you need to open a handle to the process, and even PROCESS_QUERY_LIMITED_INFORMATION errors with 'access denied' for SYSTEM processes when you're not elevated.
I was wondering if using SetPrivilege SE_DEBUG_NAME, True call as implemented in this post might grant enough priviledges on your SYSTEM owned process.
I was hoisting some of the linked trick's implementation for my embeded console project when reimplementing tab-completion I needed cmd.exe current directory and it turned out pretty short procedure:
Code:
Private Function pvGetCurrentDir(ByVal hProcess As Long) As String
Const ProcessBasicInformation As Long = 0
Const sizeof_PBI As Long = 6 * 4
Const offsetof_ProcessParameters As Long = &H10
Const offsetof_CurrentDirectory As Long = &H24
Const sizeof_UNICODESTRING As Long = 2 * 4
Dim lPtr As Long
Dim aTemp(0 To 5) As Long
Dim sBuffer As String
If NtQueryInformationProcess(hProcess, ProcessBasicInformation, aTemp(0), sizeof_PBI, 0) < 0 Then
GoTo QH
End If
If ReadProcessMemory(hProcess, aTemp(1) + offsetof_ProcessParameters, lPtr, 4, 0) = 0 Then
GoTo QH
End If
If ReadProcessMemory(hProcess, lPtr + offsetof_CurrentDirectory, aTemp(0), sizeof_UNICODESTRING, 0) = 0 Then
GoTo QH
End If
sBuffer = String$((aTemp(0) And &HFFFF&) \ 2, 0)
If ReadProcessMemory(hProcess, aTemp(1), ByVal StrPtr(sBuffer), LenB(sBuffer), 0) = 0 Then
GoTo QH
End If
pvGetCurrentDir = sBuffer
QH:
End Function
Thankfully in my case child cmd.exe process is always spawned w/ same parent VB6 process bitness so no x64 checks needed here.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Indeed it would. But SeDebugPrivilege is essentially the keys to the kingdom. It's how my RunAsTrustedInstaller project works-- if you have that privilege, you can immediately escalate to SYSTEM and have full run of the computer. So you need to be running elevated to get it. Or, possibly, a member of the Administrators group with UAC disabled, though I believe that's been blocked for a while now.
Last edited by fafalone; Nov 20th, 2023 at 03:47 AM.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Originally Posted by wqweto
I was wondering if using SetPrivilege SE_DEBUG_NAME, True call as implemented in this post might grant enough priviledges on your SYSTEM owned process.
I was hoisting some of the linked trick's implementation for my embeded console project when reimplementing tab-completion I needed cmd.exe current directory and it turned out pretty short procedure:
Code:
Private Function pvGetCurrentDir(ByVal hProcess As Long) As String
Const ProcessBasicInformation As Long = 0
Const sizeof_PBI As Long = 6 * 4
Const offsetof_ProcessParameters As Long = &H10
Const offsetof_CurrentDirectory As Long = &H24
Const sizeof_UNICODESTRING As Long = 2 * 4
Dim lPtr As Long
Dim aTemp(0 To 5) As Long
Dim sBuffer As String
If NtQueryInformationProcess(hProcess, ProcessBasicInformation, aTemp(0), sizeof_PBI, 0) < 0 Then
GoTo QH
End If
If ReadProcessMemory(hProcess, aTemp(1) + offsetof_ProcessParameters, lPtr, 4, 0) = 0 Then
GoTo QH
End If
If ReadProcessMemory(hProcess, lPtr + offsetof_CurrentDirectory, aTemp(0), sizeof_UNICODESTRING, 0) = 0 Then
GoTo QH
End If
sBuffer = String$((aTemp(0) And &HFFFF&) \ 2, 0)
If ReadProcessMemory(hProcess, aTemp(1), ByVal StrPtr(sBuffer), LenB(sBuffer), 0) = 0 Then
GoTo QH
End If
pvGetCurrentDir = sBuffer
QH:
End Function
Thankfully in my case child cmd.exe process is always spawned w/ same parent VB6 process bitness so no x64 checks needed here.
cheers,
</wqw>
If you all were curious about the full structures so you can access members normally instead of via offset... they grow with each version of Windows, but here's the base XP versions you can use on that and higher:
Code:
Public Type QLARGE_INTEGER
#If (TWINBASIC = 1) Or (Win64 = 1) Then
QuadPart As LongLong
#Else
lowpart As Long
highpart As Long
#End If
End Type
Public Enum ImageSubsystemType
IMAGE_SUBSYSTEM_UNKNOWN = 0 ' Unknown subsystem.
IMAGE_SUBSYSTEM_NATIVE = 1 ' Image doesn't require a subsystem.
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 ' Image runs in the Windows GUI subsystem.
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 ' Image runs in the Windows character subsystem.
IMAGE_SUBSYSTEM_OS2_CUI = 5 ' image runs in the OS/2 character subsystem.
IMAGE_SUBSYSTEM_POSIX_CUI = 7 ' image runs in the Posix character subsystem.
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 ' image is a native Win9x driver.
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 ' Image runs in the Windows CE subsystem.
IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 '
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11 '
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12 '
IMAGE_SUBSYSTEM_EFI_ROM = 13
IMAGE_SUBSYSTEM_XBOX = 14
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG = 17
End Enum
Public Enum NTGLB_Flags
FLG_STOP_ON_EXCEPTION = &H00000001
FLG_SHOW_LDR_SNAPS = &H00000002
FLG_DEBUG_INITIAL_COMMAND = &H00000004
FLG_STOP_ON_HUNG_GUI = &H00000008
FLG_HEAP_ENABLE_TAIL_CHECK = &H00000010
FLG_HEAP_ENABLE_FREE_CHECK = &H00000020
FLG_HEAP_VALIDATE_PARAMETERS = &H00000040
FLG_HEAP_VALIDATE_ALL = &H00000080
FLG_POOL_ENABLE_TAIL_CHECK = &H00000100 '3.51 to 5.0
FLG_APPLICATION_VERIFIER = &H00000100 '5.1+
FLG_MONITOR_SILENT_PROCESS_EXIT = &H00000200 '6.1+ only
FLG_POOL_ENABLE_TAGGING = &H00000400
FLG_HEAP_ENABLE_TAGGING = &H00000800
FLG_USER_STACK_TRACE_DB = &H00001000
FLG_KERNEL_STACK_TRACE_DB = &H00002000
FLG_MAINTAIN_OBJECT_TYPELIST = &H00004000
FLG_HEAP_ENABLE_TAG_BY_DLL = &H00008000&
FLG_IGNORE_DEBUG_PRIV = &H00010000 '3.51 to 4.0
FLG_DISABLE_STACK_EXTENSION = &H00010000 '5.1+(5.0 is undef)
FLG_ENABLE_CSRDEBUG = &H00020000
FLG_ENABLE_KDEBUG_SYMBOL_LOAD = &H00040000
FLG_DISABLE_PAGE_KERNEL_STACKS = &H00080000
FLG_HEAP_ENABLE_CALL_TRACING = &H00100000 '3.51 to 4.0
FLG_ENABLE_SYSTEM_CRIT_BREAKS = &H00100000 '5.1+ (5.0 is undef)
FLG_HEAP_DISABLE_COALESCING = &H00200000
FLG_ENABLE_CLOSE_EXCEPTIONS = &H00400000 '4.0+
FLG_ENABLE_EXCEPTION_LOGGING = &H00800000 '4.0+
FLG_ENABLE_HANDLE_TYPE_TAGGING = &H01000000 '4.0+
FLG_HEAP_PAGE_ALLOCS = &H02000000 '4.0+
FLG_DEBUG_INITIAL_COMMAND_EX = &H04000000 '4.0+
FLG_DISABLE_DBGPRINT = &H08000000 '5.0+
FLG_CRITSEC_EVENT_CREATION = &H10000000 '5.0+
FLG_LDR_TOP_DOWN = &H20000000 '5.1-6.2
FLG_STOP_ON_UNHANDLED_EXCEPTION = &H20000000 '6.3+
FLG_ENABLE_HANDLE_EXCEPTIONS = &H40000000 '5.1+
FLG_DISABLE_PROTDLLS = &H80000000& '5.0+
End Enum
Public Enum APP_COMPAT_FLAGS
KACF_OLDGETSHORTPATHNAME = &H00000001
KACF_VERSIONLIE_NOT_USED = &H00000002
KACF_GETDISKFREESPACE = &H00000008
KACF_FTMFROMCURRENTAPT = &H00000020
KACF_DISALLOWORBINDINGCHANGES = &H00000040
KACF_OLE32VALIDATEPTRS = &H00000080
KACF_DISABLECICERO = &H00000100
KACF_OLE32ENABLEASYNCDOCFILE = &H00000200
KACF_OLE32ENABLELEGACYEXCEPTIONHANDLING = &H00000400
KACF_RPCDISABLENDRCLIENTHARDENING = &H00000800
KACF_RPCDISABLENDRMAYBENULL_SIZEIS = &H00001000
KACF_DISABLEALLDDEHACK_NOT_USED = &H00002000
KACF_RPCDISABLENDR61_RANGE = &H00004000
KACF_RPC32ENABLELEGACYEXCEPTIONHANDLING = &H00008000&
KACF_OLE32DOCFILEUSELEGACYNTFSFLAGS = &H00010000
KACF_RPCDISABLENDRCONSTIIDCHECK = &H00020000
KACF_USERDISABLEFORWARDERPATCH = &H00040000
KACF_OLE32DISABLENEW_WMPAINT_DISPATCH = &H00100000
KACF_ADDRESTRICTEDSIDINCOINITIALIZESECURITY = &H00200000
KACF_ALLOCDEBUGINFOFORCRITSECTIONS = &H00400000
KACF_OLEAUT32ENABLEUNSAFELOADTYPELIBRELATIVE = &H00800000
KACF_ALLOWMAXIMIZEDWINDOWGAMMA = &H01000000
KACF_DONOTADDTOCACHE = &H80000000
End Enum
'Generally, Win XP - 8
'https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/bitfield.htm
Public Enum PEB_BITFIELD_OLD
PebImageUsedLargePages = &H01
PebIsProtectedProcess = &H02 'V+
PebIsLegacyProcess = &H04 'V-8
PebIsImageDynamicallyRelocated = &H08 'V+
PebSkipPatchingUser32Forwarders = &H10
PebIsPackagedProcess = &H20
PebIsAppContainer = &H40
PebIsProtectedProcessLight = &H80
End Enum
'Generally, Windows 10-11
Public Enum PEB_BITFIELD_NEW
PebNImageUsedLargePages = &H01
PebNIsProtectedProcess = &H02 'V+
PebNIsImageDynamicallyRelocated = &H04 'V+
PebNSkipPatchingUser32Forwarders = &H08
PebNIsPackagedProcess = &H10
PebNIsAppContainer = &H20
PebNIsProtectedProcessLight = &H40
PebNIsLongPathAwareProcess = &H80
End Enum
'[ Description("This is the base compatibility PEB, usuable from Windows XP through 11+. For additional members, see additional PEBs, e.g. PEB_VISTA.")]
Public Type PEB
InheritedAddressSpace As Byte
ReadImageFileExecOptions As Byte
BeingDebugged As Byte
BitField As Byte 'PEB_BITFIELD_OLD on XP, https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/bitfield.htm
Mutant As LongPtr
ImageBaseAddress As LongPtr
Ldr As LongPtr
ProcessParameters As LongPtr 'RTL_USER_PROCESS_PARAMETERS
SubSystemData As LongPtr
ProcessHeap As LongPtr
FastPebLock As LongPtr
AtlThunkSListPtr As LongPtr
SparePtr2 As LongPtr
EnvironmentUpdateCount As Long
KernelCallbackTable As LongPtr
SystemReserved(0) As Long
SpareUlong As Long
FreeList As LongPtr
TlsExpansionCounter As Long
TlsBitmap As LongPtr
TlsBitmapBits(1) As Long
ReadOnlySharedMemoryBase As LongPtr
ReadOnlySharedMemoryHeap As LongPtr
ReadOnlyStaticServerData As LongPtr
AnsiCodePageData As LongPtr
OemCodePageData As LongPtr
UnicodeCaseTableData As LongPtr
NumberOfProcessors As Long
NtGlobalFlag As NTGLB_Flags
#If (TWINBASIC = 0) And (Win64 = 0) Then
pad(3) As Byte
#End If
CriticalSectionTimeout As QLARGE_INTEGER
HeapSegmentReserve As LongPtr
HeapSegmentCommit As LongPtr
HeapDeCommitTotalFreeThreshold As LongPtr
HeapDeCommitFreeBlockThreshold As LongPtr
NumberOfHeaps As Long
MaximumNumberOfHeaps As Long
ProcessHeaps As LongPtr
GdiSharedHandleTable As LongPtr
ProcessStarterHelper As LongPtr
GdiDCAttributeList As Long
LoaderLock As LongPtr
OSMajorVersion As Long
OSMinorVersion As Long
OSBuildNumber As Integer
OSCSDVersion As Integer
OSPlatformId As Long
ImageSubsystem As ImageSubsystemType
ImageSubsystemMajorVersion As Long
ImageSubsystemMinorVersion As Long
ImageProcessAffinityMask As LongPtr
#If Win64 Then
GdiHandleBuffer(59) As Long
#Else
GdiHandleBuffer(33) As Long
#End If
PostProcessInitRoutine As LongPtr
TlsExpansionBitmap As LongPtr
TlsExpansionBitmapBits(31) As Long
SessionId As Long
AppCompatFlagsHi As Long
AppCompatFlags As APP_COMPAT_FLAGS 'ULARGE_INTEGER
AppCompatFlagUser As QLARGE_INTEGER
pShimData As LongPtr
AppCompatInfo As LongPtr
CSDVersion As UNICODE_STRING
ActivationContextData As LongPtr
ProcessAssemblyStorageMap As LongPtr
SystemDefaultActivationContextData As LongPtr
SystemAssemblyStorageMap As LongPtr
MinimumStackCommit As LongPtr
#If (TWINBASIC = 0) And (Win64 = 0) Then
pad2(3) As Byte
#End If
End Type
There's a few other useful members in there. The OS version info is the *real* version, no version lies from compatibility shims.
Here's the base XP version of RTL_USER_PROCESS_PARAMETERS:
Code:
Public Enum UPP_CONSOLE_FLAGS
CONSOLE_IGNORE_CTRL_C = &H1 'Only defined flag in XP; later ones unknown
End Enum
Public Enum RTL_UPP_FLAGS
RTL_USER_PROCESS_PARAMETERS_NORMALIZED = &H01
RTL_USER_PROCESS_PARAMETERS_PROFILE_USER = &H02
RTL_USER_PROCESS_PARAMETERS_PROFILE_KERNEL = &H04
RTL_USER_PROCESS_PARAMETERS_PROFILE_SERVER = &H08
RTL_USER_PROCESS_PARAMETERS_UNKNOWN = &H10
RTL_USER_PROCESS_PARAMETERS_RESERVE_1MB = &H20
RTL_USER_PROCESS_PARAMETERS_RESERVE_16MB = &H40
RTL_USER_PROCESS_PARAMETERS_CASE_SENSITIVE = &H80
RTL_USER_PROCESS_PARAMETERS_DISABLE_HEAP_CHECKS = &H100
RTL_USER_PROCESS_PARAMETERS_PROCESS_OR_1 = &H200
RTL_USER_PROCESS_PARAMETERS_PROCESS_OR_2 = &H400
RTL_USER_PROCESS_PARAMETERS_PRIVATE_DLL_PATH = &H1000
RTL_USER_PROCESS_PARAMETERS_LOCAL_DLL_PATH = &H2000
RTL_USER_PROCESS_PARAMETERS_IMAGE_KEY_MISSING = &H4000
RTL_USER_PROCESS_PARAMETERS_NX = &H20000
End Enum
Public Type RTL_DRIVE_LETTER_CURDIR
Flags As Integer
Length As Integer
TimeStamp As Long
DosPath As UNICODE_STRING
End Type
Public Type RTL_USER_PROCESS_PARAMETERS
MaximumLength As Long
Length As Long
Flags As RTL_UPP_FLAGS
DebugFlags As Long 'This is essentially a boolean that if non-zero triggers DbgBreakPoint() at certain points
ConsoleHandle As LongPtr
ConsoleFlags As UPP_CONSOLE_FLAGS
StandardInput As LongPtr
StandardOutput As LongPtr
StandardError As LongPtr
CurrentDirectory As CURDIR
DllPath As UNICODE_STRING
ImagePathName As UNICODE_STRING
CommandLine As UNICODE_STRING
Environment As LongPtr
StartingX As Long
StartingY As Long
CountX As Long
CountY As Long
CountCharsX As Long
CountCharsY As Long
FillAttribute As CONSOLE_CHAR_ATTRIB
WindowFlags As STARTUP_FLAGS
ShowWindowFlags As SHOWWINDOW
WindowTitle As UNICODE_STRING
DesktopInfo As UNICODE_STRING
ShellInfo As UNICODE_STRING
RuntimeData As UNICODE_STRING
CurrentDirectores(31) As RTL_DRIVE_LETTER_CURDIR 'Unused in all versions
End Type
The undefined enums are publicly documented common ones; SW_* for SHOWWINDOW, FORE/BACKGROUND_*/ and COMMON_LVB_* for FillAttrib, STARTF_* for startup flags.
For all variations for Vista-11, see my tbShellLib API library in twinBASIC.
Last edited by fafalone; Nov 26th, 2023 at 04:51 PM.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I did have an error though. I updated the post after Dragokas' comment made me look at it again
Before he commented, it had: /* [ TypeHint(PEB_BITFIELD_OLD) ] */ BitField As Byte
VB6 and VBA do not support inline comments with /* */ syntax like twinBASIC does, so if you pasted my definition in those, a syntax error would pop up.
So the first line was a joke about it. *Now* there's no mistakes... now.
Last edited by fafalone; Nov 26th, 2023 at 03:56 PM.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
fafalone, heh, the case when we both won from language barrier:
I meant other people's deсlarations, mostly in C++, all over in the articles in Internet with random errors.
However, I just realized, that I accidentally used outdated PEB you posted on my OSInfo topic, so you helped to fix me
Please, also include "ImageSubsystemType" declaration, missed from code above.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I know you meant that, I was making a joke because my post had an error when you thought it didn't, and then I edited it to remove the error before posting my reply.
Added ImageSubsystemType:
Code:
Public Enum ImageSubsystemType
IMAGE_SUBSYSTEM_UNKNOWN = 0 ' Unknown subsystem.
IMAGE_SUBSYSTEM_NATIVE = 1 ' Image doesn't require a subsystem (e.g. kernel mode drivers).
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 ' Image runs in the Windows GUI subsystem.
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 ' Image runs in the Windows character subsystem.
IMAGE_SUBSYSTEM_OS2_CUI = 5 ' image runs in the OS/2 character subsystem.
IMAGE_SUBSYSTEM_POSIX_CUI = 7 ' image runs in the Posix character subsystem.
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 ' image is a native Win9x driver.
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 ' Image runs in the Windows CE subsystem.
IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 '
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11 '
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12 '
IMAGE_SUBSYSTEM_EFI_ROM = 13
IMAGE_SUBSYSTEM_XBOX = 14
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG = 17
End Enum
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
It's curious observation that *for some* processes:
- SystemProcessIdInformation returns incorrect letter case for folder & correct for file names.
- GetModuleFileNameEx vice versa returns incorrect letter case for file names & correct for folders.
* "correct" in meaning that we're comparing with path how it looks in file system in explorer.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I found a bug in your project. As soon as I minimize the window, the runtime error 380 immediately pops up. Invalid property value.
In the Form_Resize event, I added the line On Error Resume Next at the very beginning and this bug disappeared.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I made exactly the same project, only I have half as many lines of code! There are 271 lines of code in the code from fafalone, there are only 119 lines of code in my code. Exactly the same project!!! I just don't know why fafalone wrote so much extra! I'm sorry, of course, that I didn't add 64-bit compatibility, but I don't think it will take up many more lines of code... I think to add 64-bit compatibility, you only need to add 10 lines somewhere, no more...
Code:
Option Explicit
Private Declare Function NtQuerySystemInformation Lib "ntdll.dll" (ByVal infoClass As Long, Buffer As Any, ByVal BufferSize As Long, ret As Long) As Long
Private Declare Function GetMem4 Lib "msvbvm60" (src As Any, dst As Any) As Long
Private Declare Function GetMem8 Lib "msvbvm60" (src As Any, dst As Any) As Long
Private Declare Function GetLogicalDriveStrings Lib "kernel32" Alias "GetLogicalDriveStringsW" (ByVal nBufferLength As Long, ByVal lpBuffer As Long) As Long
Private Declare Function QueryDosDevice Lib "kernel32" Alias "QueryDosDeviceA" (ByVal lpDeviceName As String, ByVal lpTargetPath As String, ByVal ucchMax As Long) As Long
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As Long) As Long
Private Declare Sub memcpy Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Const SystemProcessInformation As Long = &H5&
Private Const SystemProcessIdInformation = 88
Private Const STATUS_INFO_LENGTH_MISMATCH As Long = &HC0000004
Private Const STATUS_SUCCESS As Long = 0&
Private Const MAX_PATH = 260
Private Type UNICODE_STRING
Length As Integer
MaxLength As Integer
lpBuffer As Long
End Type
Private Type SYSTEM_PROCESS_ID_INFORMATION
ProcessId As Long
ImageName As UNICODE_STRING
End Type
Private Function GetProcessFullPathEx(ByVal pid As Long) As String
Dim spii As SYSTEM_PROCESS_ID_INFORMATION
Dim ProcName As String
Dim cbRet As Long
Dim cbMax As Long
Dim sDrives As String
Dim strBuff As String * MAX_PATH
Dim DosDeviceName As String
Dim cnt As Long
Dim aDrive() As String
Dim i As Long
cbMax = MAX_PATH * 2
ProcName = Space$(cbMax)
spii.ProcessId = pid
spii.ImageName.MaxLength = cbMax
spii.ImageName.lpBuffer = StrPtr(ProcName)
If NtQuerySystemInformation(SystemProcessIdInformation, spii, LenB(spii), cbRet) >= 0 Then
ProcName = Left$(ProcName, spii.ImageName.Length / 2)
cnt = GetLogicalDriveStrings(0&, StrPtr(sDrives))
sDrives = Space$(cnt * 2)
cnt = GetLogicalDriveStrings(Len(sDrives), StrPtr(sDrives))
If Err.LastDllError = 0 Then
aDrive = Split(Left$(sDrives, cnt - 1), vbNullChar)
For i = 0 To UBound(aDrive)
If QueryDosDevice(Left$(aDrive(i), 2), strBuff, MAX_PATH) Then
DosDeviceName = Left$(strBuff, lstrlen(StrPtr(strBuff)))
If InStr(1, ProcName, DosDeviceName, vbTextCompare) > 0 Then
GetProcessFullPathEx = Replace(ProcName, DosDeviceName, Left$(aDrive(i), 2), , 1, vbTextCompare)
Exit Function
End If
End If
Next
End If
End If
End Function
Private Sub Command1_Click()
Dim ret As Long
Dim buf() As Byte
Dim Offset As Long
Dim deltaOffset As Long
Dim pid As Long
Dim ImgName As UNICODE_STRING
Dim ProcName As String
Dim nProc As Long
Text1.Text = vbNullString
If NtQuerySystemInformation(SystemProcessInformation, ByVal 0&, 0&, ret) = STATUS_INFO_LENGTH_MISMATCH Then
ReDim buf(ret - 1)
If NtQuerySystemInformation(SystemProcessInformation, buf(0), ret, ret) = STATUS_SUCCESS Then
Do
nProc = nProc + 1
GetMem4 buf(Offset + &H44), pid
GetMem8 buf(Offset + &H38), ImgName
ProcName = Space$(ImgName.Length \ 2)
memcpy ByVal StrPtr(ProcName), ByVal ImgName.lpBuffer, ImgName.Length
If pid = 0 Then
PostLog "ProcId 0: [System idle process]"
ElseIf pid = 4 Then
PostLog "ProcId 4: [System]"
Else
PostLog "ProcId " & pid & " (" & ProcName & "): " & GetProcessFullPathEx(pid)
End If
GetMem4 buf(Offset), deltaOffset
Offset = Offset + deltaOffset
Loop While deltaOffset
Text1.Text = Text1.Text & "Done. Enumerated " & nProc & " processes."
End If
End If
End Sub
Private Sub PostLog(sMsg As String)
Text1.Text = Text1.Text & sMsg & vbCrLf
End Sub
Private Sub Form_Resize()
On Error Resume Next
Text1.Width = Me.Width - 380
Text1.Height = Me.Height - 1270
End Sub
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
You'd actually need more than 10 lines for x64 because your magic numbers trick to play the lines of code game hardcodes offsets that differ in x64, and the size of UniqueProcessId differs so you'd need to set up GetMemPtr conditionally calling GetMem8.
Also part of the difference is creating a cache to avoid the performance penalty of remapping drives every time; I reused code from an app where that matters.
I don't know why the attitude, you want me to go next and write an even smaller version that removes more features , performance, and readability?
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Just don't be mad at me, please. Well, yes, you're right, there will be more than 10 additional lines of code... And the offset addresses for 64-bit will also need to be redone, that's right. To be honest, I was very surprised why you didn't use the GetLogicalDriveStrings function in your code, it just shocked me.
And in terms of execution speed, I don't think I'll be much slower, although I haven't checked. There may be a slight difference.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Shocked? Lol. Don't worry I'm not mad.
Just for a one off list it doesn't matter performance wise; but I reused code from my ETW app where performance very much matters; if your code takes too long the system will start dropping events, and it needs to convert dozens or even hundreds of paths per second. Wasn't trying to make it as compact as possible no matter the sacrifices.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Originally Posted by fafalone
There's a few other useful members in there. The OS version info is the *real* version, no version lies from compatibility shims.
I would like to know more about this. Do you have a really-example in order to find out the Windows version using the NtQuerySystemInformation function?
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
fafalone, don't take HackerVlad seriously. He always running for very questionable solutions causing saving few bytes of code sacrificing features, performance, and readability.
Now, I don't even understand which offset of PE he read without deeping into reverse-engineering.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Originally Posted by Dragokas
fafalone, don't take HackerVlad seriously. He always running for very questionable solutions causing saving few bytes of code sacrificing features, performance, and readability.
Now, I don't even understand which offset of PE he read without deeping into reverse-engineering.
I would like to know more about this. Do you have a really-example in order to find out the Windows version using the NtQuerySystemInformation function?
You're already reading the PEB in your code... its these:
OSMajorVersion As Long
OSMinorVersion As Long
OSBuildNumber As Integer
OSCSDVersion As Integer
If you used full UDT defs instead of reading magic number offsets you'd be able to easily expand your code.
But if you really want the most fun way to get the real Windows version...
Code:
Dim dwMajor As Long, dwMinor As Long, dwBuild As Long
CopyMemory dwMajor, ByVal &H7FFE026C, 4
CopyMemory dwMinor, ByVal &H7FFE0270, 4
CopyMemory dwBuild, ByVal &H7FFE0260, 4
Debug.Print dwMajor & "." & dwMinor & "." & dwBuild
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
Originally Posted by fafalone
Also part of the difference is creating a cache to avoid the performance penalty of remapping drives every time; I reused code from an app where that matters.
However, I don't think caching disks is the right strategy. Since a process in memory may suddenly appear unexpectedly with a new drive letter, a new process may be started from removable media. I have already tested this and thus your algorithm gets a path detection error.
I took it, launched your program, clicked for the first time to get a list of processes. Then I connected a removable USB drive to the computer, after that a new drive letter appeared in the system, started a new process from this removable drive, then I clicked on the button to get a list of processes in your program again and of course it did not determine. It turned out to be a bug.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
fafalone, Thank you for your efforts! I'm really grateful to you. I am currently working on my project to read the full process paths, but I took your developments as a basis, in particular the NtQuerySystemInformation call with the SystemProcessIdInformation class. Today I decided to check this code in Windows XP and found that it doesn't work there at all. Please tell me, is there a way to make SystemProcessIdInformation work in Windows XP? To make your project work the same way in XP.
Re: [VB6, twinBASIC] Getting the full path of all processes when not elevated
I have the code for XP. But in order to read system processes, you need to increase user to admin-rights. It seems that in Windows XP, without elevation, it is impossible to read system processes...