Closed JohnLaTwC closed 3 years ago
Hi John, thanks for pointing this out. I appreciate you taking the time for this! I apologise for it being it a bit of double work as I already did a cleanup of the psx code a few months ago...but simply forgot to commit.
Let me know if you still find any issues in the code. Btw the other tools (psc e.g.) wil be updated asap.
Its also a bit of my fault: as we are in heavy development of version 2 of RedELK (one of our other projects), we wanted to align the release of new versions of both tools. @Cn33liz had a new version of psx ready for some time.
But thanks again for taking the effort and keeping us sharp.
I took a quick look at the new version (thanks for pointing that out).
Can you look over these issues and if they seem valid, I'll keep looking.
LPWSTR GetProcessUser(IN HANDLE hProcess, BOOL bCloseHandle, BOOL bReturnDomainname, BOOL bReturnUsername) {
HANDLE hToken = NULL;
ULONG ReturnLength;
PTOKEN_USER Ptoken_User = NULL;
WCHAR lpName[MAX_NAME];
WCHAR lpDomain[MAX_NAME];
DWORD dwSize = MAX_NAME;
LPWSTR lpwUser = NULL;
SID_NAME_USE SidType;
...
lpwUser = (LPWSTR)calloc(MAX_NAME, sizeof(WCHAR));
! No check for failed allocation before use
if (bReturnDomainname) {
wcscat_s(lpwUser, MAX_NAME, lpDomain);
if (bReturnUsername) {
wcscat_s(lpwUser, MAX_NAME, L"\\");
}
}
if (bReturnUsername) {
wcscat_s(lpwUser, MAX_NAME, lpName);
}
RtlCopyMemory
requires a byte count for Length, but EnumSecurityProc
passes in MAX_PATH
which is a character count because the fields are WCHAR
BOOL EnumSecurityProc(IN LPWSTR lpCompany, IN LPWSTR lpDescription, IN DWORD dwPID) {
LPCWSTR pwszCompany[26];
...
const DWORD dwSize = _countof(pwszCompany);
for (DWORD i = 0; i < dwSize && dwSecProcCount < MAX_SEC_PRD; i++) {
if (StrStrIW(lpCompany, pwszCompany[i])) {
pSecProducts[dwSecProcCount]->dwPID = dwPID;
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcCompany, lpCompany, MAX_PATH);
! MAX_PATH is character count, not byte count
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcDescription, lpDescription, MAX_PATH);
dwSecProcCount++;
}
}
if (dwSecProcCount < MAX_SEC_PRD) {
//Windows Defender (ATP)
if (StrStrIW(lpDescription, L"Antimalware Service Executable") || StrStrIW(lpDescription, L"Windows Defender")) {
pSecProducts[dwSecProcCount]->dwPID = dwPID;
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcCompany, lpCompany, MAX_PATH);
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcDescription, lpDescription, MAX_PATH);
dwSecProcCount++;
}
}
if (dwSecProcCount < MAX_SEC_PRD) {
//Carbon Black
if (StrStrIW(lpDescription, L"Carbon Black")) {
pSecProducts[dwSecProcCount]->dwPID = dwPID;
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcCompany, lpCompany, MAX_PATH);
RtlCopyMemory(pSecProducts[dwSecProcCount]->wcDescription, lpDescription, MAX_PATH);
dwSecProcCount++;
}
}
In psx.h these fields are declared as wide chars:
typedef struct _SECPROD {
DWORD dwPID;
WCHAR wcCompany[MAX_PATH];
WCHAR wcDescription[MAX_PATH];
} SECPROD, *PSECPROD;
Leak of lpwProcUser in DllMain in error branch
status = NtOpenProcess(&hProcess, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, &ObjectAttributes, &uPid);
if (hProcess != NULL) {
LPWSTR lpwProcUser = GetProcessUser(hProcess, FALSE, TRUE, TRUE);
if (lpwProcUser != NULL) {
swprintf_s(chBuffer, _countof(chBuffer), L" UserName: %s\n", lpwProcUser);
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
+ free(lpwProcUser);
goto CleanUp;
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
free(lpwProcUser);
}
RtlZeroMemory
takes a byte count but EnumPeb
passes in a character count resulting in a partial clear of the memory
BOOL EnumPeb(HANDLE hProcess, IN LPSTREAM lpStream) {
WCHAR chReadBuf[MAX_BUF] = { 0 };
WCHAR chWriteBuf[MAX_BUF] = { 0 };
DWORD dwWritten = 0;
...
RtlZeroMemory(chReadBuf, _countof(chReadBuf)); <<<<< _countof is character count not byte count
RtlZeroMemory(chWriteBuf, _countof(chWriteBuf));
...
RtlZeroMemory(chReadBuf, _countof(chReadBuf));
RtlZeroMemory(chWriteBuf, _countof(chWriteBuf));
This also occurs here: https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L616 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L625 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L633 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L683 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L675 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L691
chReadBuf
is vulnerable to stack buffer overrun because of missing length check prior to copy of ImagePathName and CommandLine
ImagePathName
is defined as a UNICODE_STRING
which defines upp.CommandLine.MaximumLength
as a USHORT. However the target buffer chReadBuf
is MAX_BUF (8192) * 2 = 16,384
bytes long, which is less than the max value of an unsigned short (65,535).
#define MAX_BUF 8192
...
BOOL EnumPeb(HANDLE hProcess, IN LPSTREAM lpStream) {
...
WCHAR chReadBuf[MAX_BUF] = { 0 };
...
status = NtReadVirtualMemory(hProcess, upp.ImagePathName.Buffer, &chReadBuf, upp.ImagePathName.MaximumLength, NULL);
! No check that upp.ImagePathName.MaximumLength exceeds size of chReadBuf
if (status != STATUS_SUCCESS) {
return FALSE;
}
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
}
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
There is a similar issue when reading the command line:
status = NtReadVirtualMemory(hProcess, upp.CommandLine.Buffer, &chReadBuf, upp.CommandLine.MaximumLength, NULL);
! No check that upp.CommandLine.MaximumLength exceeds size of chReadBuf
if (status != STATUS_SUCCESS) {
return FALSE;
}
Mismatch on declaration/allocation of pInfo.ImageName
in EnumFileProperties
Its max length set to MAX_PATH
but ImageName.MaximumLength
is a byte count not a character count. The allocated buffer is MAX_PATH * 2
.
BOOL EnumFileProperties(IN HANDLE ProcessId, IN LPSTREAM lpStream) {
SYSTEM_PROCESS_ID_INFORMATION pInfo;
...
pInfo.ProcessId = ProcessId;
pInfo.ImageName.Length = 0;
- pInfo.ImageName.MaximumLength = MAX_PATH;
+ pInfo.ImageName.MaximumLength = MAX_PATH * sizeof (WCHAR);
pInfo.ImageName.Buffer = NULL;
pInfo.ImageName.Buffer = (PWSTR)calloc(pInfo.ImageName.MaximumLength, sizeof(WCHAR));
status = NtQuerySystemInformation(SystemProcessIdInformation, &pInfo, sizeof(pInfo), NULL);
if (status != STATUS_SUCCESS) {
goto CleanUp;
}
https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L578
Hi John, above issues should be fixed. Could you run this check again?
Hi, here are a few more. Can you take a look?
lpwKernelPath
is never freed leading to memory leak
lpwKernelPath = Utf8ToUtf16((LPSTR)pModuleInfo->Module[0].FullPathName);
if (lpwKernelPath != NULL) {
UNICODE_STRING uKernel;
RtlInitUnicodeString(&uKernel, lpwKernelPath);
Incorrect calls to RtlZeroMemory
specify a character count instead of a byte count leading to partial zeroing of memory.
There are numerous calls that pass a character count to RtlZeroMemory
using _countof
. RtlZeroMemory
takes a byte count.
RtlZeroMemory(chBuffer, _countof(chBuffer));
There are many lines where this occurs. Here are a couple:
https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L356 https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L407
In EnumFileProperties
ImageName
in SYSTEM_PROCESS_ID_INFORMATION
is typed as a UNICODE_STRING
where MaximumLength
is a byte count. This code initializes it using a character count.
SYSTEM_PROCESS_ID_INFORMATION pInfo;
pInfo.ProcessId = ProcessId;
pInfo.ImageName.Length = 0;
- pInfo.ImageName.MaximumLength = MAX_PATH;
+ pInfo.ImageName.MaximumLength = MAX_PATH * sizeof(WCHAR);
pInfo.ImageName.Buffer = NULL;
pInfo.ImageName.Buffer = (PWSTR)calloc(pInfo.ImageName.MaximumLength, sizeof(WCHAR));
status = NtQuerySystemInformation(SystemProcessIdInformation, &pInfo, sizeof(pInfo), NULL);
if (status != STATUS_SUCCESS) {
goto CleanUp;
}
See (https://docs.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string)
MaximumLength
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.
In DllMain
pwszParams
is allocated but never freed.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
BOOL bReturnValue = TRUE;
LPWSTR pwszParams = (LPWSTR)calloc(strlen((LPSTR)lpReserved) + 1, sizeof(WCHAR));
I had mentioned this one before (it was Issue 5 in an earlier comment)
In EnumPeb
chReadBuf
is vulnerable to stack buffer overrun because of missing length check prior to copy of ImagePathName and CommandLine
ImagePathName
is defined as a UNICODE_STRING
which defines upp.CommandLine.MaximumLength
as a USHORT. However the target buffer chReadBuf
is MAX_BUF (8192) * 2 = 16,384
bytes long, which is less than the max value of an unsigned short (65,535).
#define MAX_BUF 8192
...
BOOL EnumPeb(HANDLE hProcess, IN LPSTREAM lpStream) {
...
WCHAR chReadBuf[MAX_BUF] = { 0 };
...
status = NtReadVirtualMemory(hProcess, upp.ImagePathName.Buffer, &chReadBuf, upp.ImagePathName.MaximumLength, NULL);
! No check that upp.ImagePathName.MaximumLength exceeds size of chReadBuf
if (status != STATUS_SUCCESS) {
return FALSE;
}
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
}
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
There is a similar issue when reading the command line:
status = NtReadVirtualMemory(hProcess, upp.CommandLine.Buffer, &chReadBuf, upp.CommandLine.MaximumLength, NULL);
! No check that upp.CommandLine.MaximumLength exceeds size of chReadBuf
if (status != STATUS_SUCCESS) {
return FALSE;
}
There are several error paths in DllMain
where the handle hProcess
is not properly closed. The code soon calls ExitProcess where this handle will be automatically closed, but from a correctness point of view, it should be closed.
NOTE: You cannot close it in the CleanUp
label because it is declared in a block that goes out of scope (if (bExtended)
block) by the time the goto would be called and you would be operating on undefined memory. You should declare the handle higher in the function.
https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L1174
//Psx (Extended Process Info)
if (bExtended) {
HANDLE hProcess = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
CLIENT_ID uPid = { 0 };
uPid.UniqueProcess = pProcInfo->ProcessId;
uPid.UniqueThread = (HANDLE)0;
status = NtOpenProcess(&hProcess, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, &ObjectAttributes, &uPid);
if (hProcess != NULL) {
LPWSTR lpwProcUser = GetProcessUser(hProcess, FALSE, TRUE, TRUE);
if (lpwProcUser != NULL) {
swprintf_s(chBuffer, _countof(chBuffer), L" UserName: %s\n", lpwProcUser);
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
goto CleanUp;
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
free(lpwProcUser);
}
DWORD dwIntegrityLevel = IntegrityLevel(hProcess);
if (dwIntegrityLevel == LowIntegrity) {
swprintf_s(chBuffer, _countof(chBuffer), L" Integrity: Low\n");
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
goto CleanUp;
! Leaks hProcess handle
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
dwLowProc++;
}
else if (dwIntegrityLevel == MediumIntegrity) {
swprintf_s(chBuffer, _countof(chBuffer), L" Integrity: Medium\n");
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
goto CleanUp;
! Leaks hProcess handle
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
dwMediumProc++;
}
else if (dwIntegrityLevel == HighIntegrity) {
swprintf_s(chBuffer, _countof(chBuffer), L" Integrity: High\n");
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
goto CleanUp;
! Leaks hProcess handle
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
dwHighProc++;
}
else if (dwIntegrityLevel == SystemIntegrity) {
swprintf_s(chBuffer, _countof(chBuffer), L" Integrity: System\n");
if (FAILED(hr = lpStream->Write(chBuffer, (ULONG)wcslen(chBuffer) * sizeof(WCHAR), &dwWritten))) {
goto CleanUp;
! Leaks hProcess handle
}
RtlZeroMemory(chBuffer, _countof(chBuffer));
dwSystemProc++;
}
Several gotos like this one: https://github.com/outflanknl/Ps-Tools/blob/1ac7299fb2b83bc3154a6ba785741a3f6903f1f7/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.cpp#L1188
Are you sure you checked the latest commit? In commit a41c3b9 most of these issues have already been fixed.
Yes, I must have reviewed an earlier on. I think these still remain from my previous comment: Issue 1 Issue 4 Issue 6
These are now fixed in commit: bd2920a
After a quick look. I think these issues affect all the Ps* tools because they have copies of the same code.
Issue 1
Leak when exiting function early from error path in
IntegrityLevel()
This bug also exists in PsC: https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsC-rDLL/PsC/ReflectiveDll.c#L101
Issue 2
Leak when exiting function early from error path in
GetTokenUser()
Issue 3
free()
must be called onchUserName
to avoid leaking memoryhttps://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L785
Issue 4
Leak when exiting function early from error path in
EnumFileProperties()
. Many of these code paths fail to close the handle forhFile
as well.https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L317 https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L330 https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L359 https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L336 https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L339 https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L364
Issue 5
Memory used to get version info is never freed
https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L362
Issue 6
EnumSecurityProc
leaks memory every time it is called. It should check to see if the memory is already allocated.https://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsX-rDLL/PsX/ReflectiveDll.c#L218
Issue 7
GetTcpSessions
andGetTcp6Sessions
leak memory forpTcpTable
on early function exithttps://github.com/outflanknl/Ps-Tools/blob/81c83ebcd366eae20bc9008d735f7b5dd0117ff0/Src/Outflank-PsH-rDLL/PsH/ReflectiveDll.c#L776
GetTcp6Sessions
has a similar issue here:https://github.com/outflanknl/Ps-Tools/blob/81c83ebcd366eae20bc9008d735f7b5dd0117ff0/Src/Outflank-PsH-rDLL/PsH/ReflectiveDll.c#L875
These functions also have different return values and it isn't clear why:
GetTcp6Sessions
returnsTRUE
whereasGetTcpSessions
returns `FALSEhttps://github.com/outflanknl/Ps-Tools/blob/81c83ebcd366eae20bc9008d735f7b5dd0117ff0/Src/Outflank-PsH-rDLL/PsH/ReflectiveDll.c#L799
https://github.com/outflanknl/Ps-Tools/blob/81c83ebcd366eae20bc9008d735f7b5dd0117ff0/Src/Outflank-PsH-rDLL/PsH/ReflectiveDll.c#L906
Issue 8
PsW
EnumWindowsProc
leaks memory on error exithttps://github.com/outflanknl/Ps-Tools/blob/106a7bd75a0316da12b6b3be55398c36272c0bac/Src/Outflank-PsW-rDLL/PsW/ReflectiveDll.c#L71