Code Monkey home page Code Monkey logo

cs-situational-awareness-bof's People

Contributors

5il avatar ajpc500 avatar attl4s avatar billm avatar brimstone avatar cfa-tigerteam avatar deviousbanana avatar evanmcbroom avatar firat-nviso avatar freefirex avatar frenetic00 avatar funnybananas avatar j0wns avatar johnlatwc avatar kapn-kaos avatar kev169 avatar leebaird avatar m0t avatar martindube avatar mmmmcoffee avatar paulwhitings2 avatar physics-sec avatar remiescourrou avatar s4ntiagop avatar skellyb0n3s avatar timgates42 avatar trainr3kt avatar tw1sm avatar tycx2ry avatar wotwot563 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cs-situational-awareness-bof's Issues

Remote wmi receives access denied after using make_token

I have experienced an issue using the wmi_query as well as the tasklist BOF in which trying to use either BOF on a remote machine with a token created using make_token returns Access Denied.

In a beacon running as the user DA ( a Domain Admin in the network) I am successfully able to use the wmi_query and tasklist BOFs remotely.

image

In a beacon running as SYSTEM, I use make_token with DA's creds. I am successfully able to use the created token, as demonstrated by doing a ls \dev-dc\c$ as well as using shell wmic ...

I am unable however to use wmi_query or tasklist remotely, receiving an Access Denied error. I'm running CobaltStrike version 4.7.2 and have confirmed this using the latest branch of CS-Situational-Awareness-BOF

image

Issue with running BOF modules

Somewhere between commit c46f814 (May 7, 2021) and commit 63b4e7d (Sept 1, 2021), an error was introduced that persists to the most recent commit. Most, if not all, of the commands will trigger the following error on x64 systems (x86 was not tested):
Error: [-] no slot for function (reduce number of Win32 APIs called)

Examples:
`beacon> tasklist
Connecting to \. and retrieving list of currently running processes

[*] Running tasklist
[+] host called home, sent: 10272 bytes
[-] no slot for function (reduce number of Win32 APIs called)
`

beacon> driversigs [*] Running driversigs [+] host called home, sent: 7726 bytes [-] no slot for function (reduce number of Win32 APIs called)

Reproduction:

  1. git clone https://github.com/trustedsec/CS-Situational-Awareness-BOF.git
  2. cd CS-Situational-Awareness-BOF
  3. make_all
  4. Load CS-Situational-Awareness-BOF/SA.cna
  5. from an active x64 beacon session 'tasklist' or 'driversigs' (these are not the only ones)

I've written some internal/private ones that have not changed, but inherit the same libraries/common code, and they issue the same error. It may have been introduced as soon as commit fffdf75 with its numerous additions to bofdefs.h; however, I have not attempted testing to confirm.

Search other domains with `ldapsearch`

Not necessarily a coding issue per se, but wondering if you had ideas on how to query other AD domains (vs the current one) and listing all the domains in the forest with the ldapsearch BOF ?

Would this necessarily require some code change ?

Correctness issues

Issue 1

In go() a call is made to task_list() with a variable server read from a Beacon with BeaconDataExtract.

server = (wchar_t *)BeaconDataExtract(&parser, NULL);

This is passed in to Wmi_Connect as pwszServer. Besides being null terminated, I don't see a length constraint on pwszServer

hr = Wmi_Connect(&m_WMI, pwszServer, NULL );

The possibility for a heap overrun exists because of the insecure use of wcscpy to a buffer that is a maximum size of MAX_PATH*sizeof(wchar_t)

MSVCRT$wcscpy(pwszServer, pwszServerArg);

Issue 2

There is a correctness issue on this line due to a copy/paste error. pwszNameSpace is the allocated string, but the variable pwszServer is checked.

	pwszNameSpace = (LPWSTR)KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH*sizeof(wchar_t));
-	if (NULL == pwszServer)
+	if (NULL == pwszNameSpace)

if (NULL == pwszServer)

Issue 3

The possibility for a wild free exists in validate_driver():

Take the case where the for loop completes successfully once. At this point, the variables certificate_context, certificate_header, and certificate have been freed but not set back to NULL. On an nth pass through loop, take this case:
if the allocation for certificate_header fails on line 98 and it jumps to the clear label on line 102, both certificate and certificate_context are non-NULL from a previous time through the loop but will have free routines called on them resulting in a wild free. There are similar issues in the loop on the other goto clear jumps. After freeing the memory, it should be set to NULL.

		clear:
		if (certificate_context)
+		{
			CRYPT32$CertFreeCertificateContext(certificate_context);
+			certificate_context = NULL;
+		}
		if(certificate_header)
+		{
			intFree(certificate_header);
+			certificate_header = NULL;
+		}
		if(certificate)
+		{
			intFree(certificate);
+			certificate = NULL;
+		}

Issue 4

Memory leak in getIPInfo(). There are early function exits which do not release the memory belonging to info.

    IP_ADAPTER_INFO * info = intAlloc(sizeof(IP_ADAPTER_INFO) * 32); // have to keep stack < 4K
...
    if(IPHLPAPI$GetNetworkParams(pFixedInfo, &netOutBufLen) == ERROR_BUFFER_OVERFLOW){
        pFixedInfo = (FIXED_INFO *)intAlloc(netOutBufLen);
        if (pFixedInfo == NULL)
        {

			BeaconPrintf(CALLBACK_ERROR, "could not get network adapter info");
            return;   
! Leaks memory belonging to info 
        }
        if (IPHLPAPI$GetNetworkParams(pFixedInfo, &netOutBufLen) != NO_ERROR)
        {
            if (pFixedInfo){
                intFree(pFixedInfo);
            }
			BeaconPrintf(CALLBACK_ERROR, "could not get network adapter info");
            return;  
! Leaks memory belonging to info 
        }

    }

The early returns are on these lines:

Issue 5

Handle leak in PrintModules() on early function return.

    hProcess = KERNEL32$OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
...
    // Get size needed before requesting hMods
    if(!PSAPI$EnumProcessModulesEx(hProcess, 0, 0, &cbNeeded, LIST_MODULES_ALL)){
        internal_printf("Failed to enumerate modules\n");
        return 1;   
! Fails to call CloseHandle on hProcess
    }

The early return is here:

Issue 6

Registry handle leak in Reg_GetValue() on early function return.

		dwRet = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey);
...
		dwRet = ADVAPI32$RegOpenKeyExA(RemoteKey, keystring, 0, KEY_READ, &key);
...
    ValueData = intAlloc(size);
    if (ValueData == NULL){
        BeaconPrintf(CALLBACK_ERROR, "Failed to allocate memory\n");
        return E_OUTOFMEMORY;   
! fails to free registry key handles done at the END: label
    }

This is where the keys are normally freed:

    END:
...
! early function exits fail to release allocated resources as done here
    if(key)
     	ADVAPI32$RegCloseKey(key);
	if(RemoteKey)
		ADVAPI32$RegCloseKey(RemoteKey);

The early return is here:

Issue 7

Invalid permissions specified in call to ADVAPI32$OpenServiceA (SC_MANAGER_CONNECT is a permission for ADVAPI32$OpenSCManagerA, not ADVAPI32$OpenServiceA

		if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)   
! SC_MANAGER_CONNECT is not a service permission

Even though it is incorrect, it doesn't cause any harm because SC_MANAGER_CONNECT maps to 0x0001 which is SERVICE_QUERY_CONFIG

SC_MANAGER_CONNECT should be removed because GENERIC_READ is sufficient to cover the calls to get_service_config and get_service_status.


GENERIC_READ --->  
    STANDARD_RIGHTS_READ
    SERVICE_QUERY_CONFIG
    SERVICE_QUERY_STATUS
    SERVICE_INTERROGATE
    SERVICE_ENUMERATE_DEPENDENTS

See these places for instances of this issue::

if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

if ((scService = ADVAPI32$OpenServiceA(scManager, cpServiceName, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)

Issue 8

Leak because Vserver.bstrVal is not freed after usage.

	Vserver.bstrVal = OLEAUT32$SysAllocString(server);

Vserver.bstrVal = OLEAUT32$SysAllocString(server);

Same issue here:

Vserver.bstrVal = OLEAUT32$SysAllocString(server);

Release is not called on pSubfolders

		ITaskFolderCollection *pSubfolders = NULL;
...
		pCurFolder->lpVtbl->GetFolders(pCurFolder, 0, &pSubfolders);

pCurFolder->lpVtbl->GetFolders(pCurFolder, 0, &pSubfolders);

Issue 9

Handle leak on hToken because CloseHandle path is skipped on early function exits

    if (ADVAPI32$OpenProcessToken(KERNEL32$GetCurrentProcess(), TOKEN_READ, &hToken))
    {
        ADVAPI32$GetTokenInformation(hToken,
                            TokenType,
                            NULL,
                            dwLength,
                            &dwLength);

        if (KERNEL32$GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            pTokenInfo = intAlloc(dwLength);
            if (pTokenInfo == NULL)
            {
                //printf("ERROR: not enough memory to allocate the token structure.\r\n");
                return NULL;         
! doesn't call CloseHandle on hToken
            }
        }

        if (!ADVAPI32$GetTokenInformation(hToken, TokenType,
                                 (LPVOID)pTokenInfo,
                                 dwLength,
                                 &dwLength))
        {
            //printf("ERROR 0x%x: could not get token information.\r\n", GetLastError());
            intFree(pTokenInfo);
            return NULL;		 
! doesn't call CloseHandle on hToken
        }

Early exits

netuser cannot parse usernames with a dash

During an engagement I attempted to search for a user with a dash in the name e.g. - "netuser t-testuser domain.com" and the BOF returns the error Failed to get user info

I also attempted to wrap the username in quotes, and escape the dash with a backslash. All attempted returned the error above. I am able to find the user with a traditional net user lookup via beacon run net user t-testuser /domain

Code Reviews

Issue 1

wmi_query always returns S_OK even on failure cases.

I am not sure this leads to any bugs, but it may not be the developer's intent.

HRESULT wmi_query(
	LPWSTR pwszServer,
	LPWSTR pwszNameSpace,	
	LPWSTR pwszQuery
)
{
	HRESULT	hr = S_OK;

...

	hr = S_OK;

	// Initialize COM
	hr = Wmi_Initialize(&m_WMI);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Initialize failed: 0x%08lx", hr);
		goto fail;
	}

	// Connect to WMI on host
	hr = Wmi_Connect(&m_WMI, pwszServer, pwszNameSpace);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Connect failed: 0x%08lx", hr);
		goto fail;
	}


...

fail:

	hr = Wmi_Finalize(&m_WMI);
! this overrides any previous value of hr from a `goto fail` and will result in it being set to S_OK
	if (FAILED(hr))
! this code path will never be called
	{
-		BeaconPrintf(CALLBACK_ERROR, "Wmi_Destroy failed: 0x%08lx", hr);
+		BeaconPrintf(CALLBACK_ERROR, "Wmi_Finalize failed: 0x%08lx", hr);
	}

	return hr;
}

See the implementation of Wmi_Finalize always returns S_OK:

HRESULT Wmi_Finalize(
	WMI* pWmi
)
{
	HRESULT hr = S_OK;

	SAFE_RELEASE(pWmi->pWbemServices);
	SAFE_RELEASE(pWmi->pWbemLocator);

	SAFE_FREE(pWmi->bstrLanguage);
	SAFE_FREE(pWmi->bstrServer);
	SAFE_FREE(pWmi->bstrNameSpace);
	SAFE_FREE(pWmi->bstrNetworkResource);
	SAFE_FREE(pWmi->bstrQuery);

	// un-initialize the COM library
	OLE32$CoUninitialize();

! always returns S_OK
	return hr;

}

hr = Wmi_Finalize(&m_WMI);

Issue 2

OpenSCManagerA and enumerate_services return win32 errors, not HRESULT so error code should be checked against ERROR_SUCCESS

I am not sure this leads to any bugs, but it may not be the developer's intent.

VOID go( 
	IN PCHAR Buffer, 
	IN ULONG Length 
) 
{
	const char * hostname = NULL;
	const char * servicename = NULL;
-	DWORD result = 0;
+	DWORD result = ERROR_SUCCESS;
...	
if ((gscManager = ADVAPI32$OpenSCManagerA(hostname, SERVICES_ACTIVE_DATABASEA, SC_MANAGER_CONNECT | GENERIC_READ)) == NULL)
	{
		result = KERNEL32$GetLastError();
	}
-	if(result == S_OK)
+	if(result == ERROR_SUCCESS)
	{
		result = enumerate_services();
-		if(result != S_OK)
+		if(result != ERROR_SUCCESS)
		{
			BeaconPrintf(CALLBACK_ERROR, "Failed to query service: %u", result);
		}
DWORD enumerate_services()
{
	DWORD dwResult = ERROR_SUCCESS;
...
	return dwResult;
! returns ERROR_SUCCESS or GetLastError, not S_OK
}

Issue 3

enumerate_loaded_drivers() leaks memory in early return

int enumerate_loaded_drivers()
{
	// Create a handle to the service manager for calls to EnumServicesStatusExW.
	SC_HANDLE scm_handle = ADVAPI32$OpenSCManagerA(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
	if (!scm_handle)
	{
		internal_printf("[ERROR] Cannot open handle to service manager.\n");
		return 1;
	}
	
	// Determine the bytes needed for allocation.
	unsigned long bytes_needed = 0;
	unsigned long services_returned = 0;
	ADVAPI32$EnumServicesStatusExW(scm_handle, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER, SERVICE_ACTIVE, NULL, 0, &bytes_needed, &services_returned, NULL, NULL);

	// Retrieve a buffer of active driver services.
	PBYTE services = (PBYTE)intAlloc(bytes_needed);
	if (!ADVAPI32$EnumServicesStatusExW(scm_handle, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER , SERVICE_ACTIVE, services, bytes_needed, &bytes_needed, &services_returned, NULL, NULL))
	{
		internal_printf("[ERROR] Cannot enumerate services -> %ld.\n", KERNEL32$GetLastError());
! leaks memory allocated for `services`
		return 1;
	}
	LPENUM_SERVICE_STATUS_PROCESSW service = (LPENUM_SERVICE_STATUS_PROCESSW)services;
	// Get the ImagePath for each service from registry, and pass that to the validate_driver() function.
	for (unsigned long i = 0; i < services_returned; i++)
	{
		PWCHAR registry_path = (PWCHAR)intAlloc(MAX_PATH * 2);
		if (registry_path)
		{
			MSVCRT$wcsncat(registry_path,  L"SYSTEM\\CurrentControlSet\\Services\\", MAX_PATH);
			MSVCRT$wcsncat(registry_path, service->lpServiceName, MAX_PATH);
		}
		{
			BeaconPrintf(CALLBACK_ERROR, "Out of memory");
! leaks memory allocated for `services`
			return 1;
		}
		
		HKEY key_handle;
		ADVAPI32$RegOpenKeyExW(HKEY_LOCAL_MACHINE, registry_path, 0, KEY_QUERY_VALUE, &key_handle);
		
		unsigned long length = 0;
		ADVAPI32$RegQueryValueExW(key_handle, L"ImagePath", NULL, NULL, NULL, &length);
		intFree(registry_path);
		if (length)
		{
			PWCHAR driver_path = (PWCHAR)intAlloc(length);
! should check for failed allocation for `driver_path`
			ADVAPI32$RegQueryValueExW(key_handle, L"ImagePath", NULL, NULL, (LPBYTE)driver_path, &length);
			validate_driver(driver_path);
			intFree(driver_path);
		}
		
		KERNEL32$CloseHandle(key_handle);
		service++;
	}

	intFree(services);
	KERNEL32$CloseHandle(scm_handle);
	return 0;
}


Issue 4

SCM handle must be closed with CloseServiceHandle not CloseHandle

int enumerate_loaded_drivers()
{
	// Create a handle to the service manager for calls to EnumServicesStatusExW.
	SC_HANDLE scm_handle = ADVAPI32$OpenSCManagerA(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
@@ scm_handle is not a kernel handle @@ 

...
-	KERNEL32$CloseHandle(scm_handle);
+	ADVAPI32$CloseServiceHandle(scm_handle );

	return 0;

KERNEL32$CloseHandle(scm_handle);

Issue 5

Registry handle needs to be closed with RegCloseKey not CloseHandle

int enumerate_loaded_drivers()
{

...
		HKEY key_handle;
		ADVAPI32$RegOpenKeyExW(HKEY_LOCAL_MACHINE, registry_path, 0, KEY_QUERY_VALUE, &key_handle);
		
		unsigned long length = 0;
		ADVAPI32$RegQueryValueExW(key_handle, L"ImagePath", NULL, NULL, NULL, &length);
		intFree(registry_path);
		if (length)
		{
			PWCHAR driver_path = (PWCHAR)intAlloc(length);
			ADVAPI32$RegQueryValueExW(key_handle, L"ImagePath", NULL, NULL, (LPBYTE)driver_path, &length);
			validate_driver(driver_path);
			intFree(driver_path);
		}
		
-		KERNEL32$CloseHandle(key_handle);
+		ADVAPI32$RegCloseKey(key_handle);
		service++;
	}

KERNEL32$CloseHandle(key_handle);

Issue 6

I am not sure the effect here but I wanted to flag it. There are mismatches between the count of elements and the number of elements in calls to antiStringResolve

	EServiceStatus = antiStringResolve(8, "SPACER", "STOPPED", "START_PENDING", "STOP_PENDING", "RUNNING", "CONTINUE_PENDING", "PAUSE_PENDING");
! 8 items declared but only 7 in va_args
...
	EServiceStartup = antiStringResolve(6, "BOOT_DRIVER", "SYSTEM_START_DRIVER", "AUTO_START", "DEMAND_START", "DISABLED");
! 6 items declared but only 5 in va_args

EServiceStartup = antiStringResolve(6, "BOOT_DRIVER", "SYSTEM_START_DRIVER", "AUTO_START", "DEMAND_START", "DISABLED");

	EServiceStatus = antiStringResolve(8, "SPACER", "STOPPED", "START_PENDING", "STOP_PENDING", "RUNNING", "CONTINUE_PENDING", "PAUSE_PENDING");
! 8 items declared but only 7 in va_args
	EServiceStartup = antiStringResolve(6, "BOOT_DRIVER", "SYSTEM_START_DRIVER", "AUTO_START", "DEMAND_START", "DISABLED");
! 6 items declared but only 5 in va_args

EServiceStartup = antiStringResolve(6, "BOOT_DRIVER", "SYSTEM_START_DRIVER", "AUTO_START", "DEMAND_START", "DISABLED");

	EServiceStatus = antiStringResolve(8, "SPACER", "STOPPED", "START_PENDING", "STOP_PENDING", "RUNNING", "CONTINUE_PENDING", "PAUSE_PENDING");
! 8 items declared but only 7 in va_args

EServiceStatus = antiStringResolve(8, "SPACER", "STOPPED", "START_PENDING", "STOP_PENDING", "RUNNING", "CONTINUE_PENDING", "PAUSE_PENDING");

antiStringResolve will call va_arg on the extra argument and I am not sure the result.

char ** antiStringResolve(unsigned int count, ...)
{
    va_list strings;
    va_start(strings, count);
    char ** result = intAlloc(sizeof(char *) * count);
    for(int i = 0; i < count; i++)
    {
        result[i] = (char *)va_arg(strings, char *);
    }
    va_end(strings);
    return result;
}

Issue 7

Reg_EnumKey loop missing a call to intFree on item. It frees the memory of the item contents (val->keypath, val->hreg), but not the item itself.

pregkeyval init_regkey(const char * curpath, DWORD dwcurpathsz, const char * childkey, DWORD dwchildkeysz, HKEY hreg)
{
    pregkeyval item = (pregkeyval)intAlloc(sizeof(regkeyval));
@@ allocates memory for item @@
    item->dwkeypathsz = dwcurpathsz + ((dwchildkeysz) ? dwchildkeysz + 1 : 0); //str\str does not include null or just str, if we don't have a child key
    item->keypath = intAlloc(item->dwkeypathsz + 1);
    memcpy(item->keypath, curpath, dwcurpathsz);
    if(dwchildkeysz > 0)
    {
        item->keypath[dwcurpathsz] = '\\';
        memcpy(item->keypath + dwcurpathsz + 1, childkey, dwchildkeysz);
    }
    item->hreg = hreg;
    //item->keypath[item->dwkeypathsz] = 0;
    return item;
}


void free_regkey(pregkeyval val)
{
    if(val->keypath)
    {
        intFree(val->keypath);
    }
    if(val->hreg)
    {
        ADVAPI32$RegCloseKey(val->hreg);
    }
}
DWORD Reg_EnumKey(const char * hostname, HKEY hivekey, DWORD Arch, const char* keystring, BOOL recursive){
...
    keyStack->push(keyStack, init_regkey(keystring, strlen(keystring), NULL, 0, rootkey));
    while((curitem = keyStack->pop(keyStack)) != NULL)
...
        nextloop:
        if(currentkeyname)
        {intFree(currentkeyname); currentkeyname = NULL;}
        if(currentvaluename)
        {intFree(currentvaluename); currentvaluename = NULL;}
        if(currentdata)
        {intFree(currentdata); currentdata = NULL;}
        retCode = ERROR_SUCCESS;
        i = 0;
        cSubKeys = 0;
        cbMaxSubKey = 0;
        cValues = 0;
        cchMaxValue = 0;
        cchMaxData = 0;
        free_regkey(curitem);
! curitem itself not freed?
        curitem = NULL;
    } // end While

Issue 8

Copy Paste error leads to incorrect initialization. It doesn't cause a bug because the buffer size matches.

    char szGroupName[255] = {0};
    char szDomainName[255] = {0};

    DWORD cchGroupName  = _countof(szGroupName);
-    DWORD cchDomainName = _countof(szGroupName);
+    DWORD cchDomainName = _countof(szDomainName);

DWORD cchDomainName = _countof(szGroupName);

Crash in NetGroupList

NetGroupList does not properly handle domains with an very large number of groups. I'd guess more than 100 entries.

Specifically we never update our starting index as we continue to query

Example on MSDN shows what we are missing.

NetGroupListMember / Local variants should likely also be checked

adcs_enum lists Domain Users group as having WriteProperty when they don't have that access

Tested this on multiple clients side by side with Certify and came up with conflicting results.

In multiple cases, adcs_enum lists the Domain Users group as having WriteProperty rights. A secondary check with Certify of the same template shows that is not the case (see below, a lot of data was redacted for obvious reasons). Additionally, we manually checked the ACLs on the templates with the clients and did not see WriteProperty being assigned to Domain Users.

// adcs_enum output
Template Name : User
Friendly Name : User

Extended Key Usage : Encrypting File System, Secure Email, Client Authentication
Permissions :
Owner : TEST\Enterprise Admins

Access Rights :
Principal : TEST\Domain Admins
Access mask : 00000100
Flags : 00000001
Enrollment Rights

Principal : TEST\Enterprise Admins
Access mask : 00000100
Flags : 00000001
Enrollment Rights

Principal : TEST\Domain Admins
Access mask : 00000130
Flags : 00000001
Enrollment Rights
WriteProperty Rights

Principal : TEST\Domain Users
Access mask : 00000130
Flags : 00000001
Enrollment Rights
WriteProperty Rights


// certify output
[*] Available Certificates Templates :
    CA Name                         : ADCS.TEST.local\TestRootCA
    Template Name                   : User
    pkiextendedkeyusage             : Client Authentication, Encrypting File System, Secure Email
    Permissions
      Enrollment Permissions
        Enrollment Rights           : TEST\Domain Admins       
                                      TEST\Domain Users        
                                      TEST\Enterprise Admins   
      Object Control Permissions
        Owner                       : TEST\Enterprise Admins   
        WriteOwner Principals       : TEST\Domain Admins       
                                      TEST\Enterprise Admins   
        WriteDacl Principals        : TEST\Domain Admins       
                                      TEST\Enterprise Admins   
        WriteProperty Principals    : TEST\Domain Admins       
                                      TEST\Enterprise Admins   

Issue compiling sc_enum and sc_qtriggerinfo

In doing some syncs with your repo today, I noticed that these two modules were failing to build with the errors all originating with this one (using KALI with mingw):

x86_64-w64-mingw32-gcc -o sc_qtriggerinfo.x64.o -I ../../common -Os -c entry.c -DBOF 
entry.c: In function ‘get_service_triggers’:
entry.c:49:2: error: unknown type name ‘PSERVICE_TRIGGER_INFO’; did you mean ‘LPSERVICE_SID_INFO’?
   49 |  PSERVICE_TRIGGER_INFO lpServiceConfig = NULL;
x86_64-w64-mingw32-gcc -o sc_enum.x64.o -I ../../common -Os -c entry.c -DBOF 
entry.c: In function ‘get_service_triggers’:
entry.c:227:2: error: unknown type name ‘PSERVICE_TRIGGER_INFO’; did you mean ‘LPSERVICE_SID_INFO’?
  227 |  PSERVICE_TRIGGER_INFO lpServiceConfig = NULL;
      |  ^~~~~~~~~~~~~~~~~~~~~

So trying to troubleshoot (I prefer providing solutions rather than just complaining :) )...in sc_enum I wound up moving the #endif on line 35 to line 18 and it compiles fine after doing that. But in looking at the revision history of the file, I noticed that the #endif has always been there like that. So I investigated where the setting was coming from (it wasn't anywhere in your code that I could find) and changed the beginning of sc_enum to:

#define STR(x) #x
#include <windows.h>
#ifdef SERVICE_CONFIG_TRIGGER_INFO
#pragma message "SERVICE_CONFIG_TRIGGER_INFO already defined as " XSTR(SERVICE_CONFIG_TRIGGER_INFO)
#endif

#include "bofdefs.h"
#ifdef SERVICE_CONFIG_TRIGGER_INFO
#pragma message "SERVICE_CONFIG_TRIGGER_INFO already defined as " XSTR(SERVICE_CONFIG_TRIGGER_INFO)
#endif
#include "base.c"
#ifdef SERVICE_CONFIG_TRIGGER_INFO
#pragma message "SERVICE_CONFIG_TRIGGER_INFO already defined as " XSTR(SERVICE_CONFIG_TRIGGER_INFO)
#endif
#include "anticrash.c"
#ifdef SERVICE_CONFIG_TRIGGER_INFO
#pragma message "SERVICE_CONFIG_TRIGGER_INFO already defined as " XSTR(SERVICE_CONFIG_TRIGGER_INFO)
#endif

Which gave:

╰ $ make
x86_64-w64-mingw32-gcc -o sc_enum.x64.o -I ../../common -Os -c entry.c -DBOF
entry.c:5:9: note: ‘#pragma message: SERVICE_CONFIG_TRIGGER_INFO already defined as 8’
    5 | #pragma message "SERVICE_CONFIG_TRIGGER_INFO already defined as " XSTR(SERVICE_CONFIG_TRIGGER_INFO)

Which tells me its coming from the windows.h include. I don't know what changed or if that was always there in windows.h (I assume not b/c it worked just fine for a long time until just now). Regardless, SEVICE_CONFIG_TRIGGER_INFO is already defined from windows.h but the structs are not, leading to the error. I am not sure if the proper fix is to define the struct regardless (which is what moving the #endif basically did, or if there was some other checks that should be done instead and instead am chosing to report the issue to you as the author.

netuserenum Feature Request: Enabled Users Only

Looks like you can specify some type of filter in the domain user enumeration request:

image

Would be useful to have either a switch if possible or another BOF command entirely that only pulls back enabled users only. Makes for a better brute force target list.

Leak in _adcs_get_CertificateTemplateExtendedKeyUsages() of bstFriendlyName

This BSTR for bstFriendlyName is not deallocated. Needs a call to SysFreeString

HRESULT _adcs_get_CertificateTemplateExtendedKeyUsages(VARIANT* lpvarExtendedKeyUsages)
{
...
	BSTR bstFriendlyName = NULL;
...
	while(SUCCEEDED(hr) && lFetch > 0)
	{
		if (lFetch == 1)
		{
			pDisp = V_DISPATCH(&var);
			pDisp->lpVtbl->QueryInterface(pDisp, &IID_IObjectId, (void**)&pObjectId); 
			SAFE_RELEASE(pDisp);

			hr = pObjectId->lpVtbl->get_FriendlyName(
				pObjectId, 
				&bstFriendlyName
			);
			if (FAILED(hr))	{ internal_printf("      N/A\n"); }
			else { 
			    internal_printf("      %S\n", bstFriendlyName); 
+			    SAFE_FREE(bstFriendlyName);
			}

			SAFE_RELEASE(pObjectId);
		}
		OLEAUT32$VariantClear(&var);

		hr = pEnum->lpVtbl->Next(pEnum, 1, &var, &lFetch);
	} // end loop through IObjectIds via enumerator
	SAFE_RELEASE(pObjectId);

	hr = S_OK;

	//internal_printf("\n _adcs_get_CertificateTemplateExtendedKeyUsages SUCCESS.\n");

fail:

	OLEAUT32$VariantClear(&var);

	return hr;
} // end _adcs_get_CertificateTemplateExtendedKeyUsages

else { internal_printf(" %S\n", bstFriendlyName); }

fix ldapsearch binary data

items like objectSid / objectGUID need to be pulled using ldap_get_value_len it looks like
or something else needs to be done
regardless, even with current transforms objectSid at the very least is invalid

bug in 'whoami'

Hi, Your bof is very nice! But i fond a litter bug in zh-cn local windows, it's look like(DispName words Full display):
wrong:
image
True:
image

dir does not work on a remote computer

dir does not work on a remote computer.
dir \remote-server\C$
[-] File not found: \remote-server\C$

ls \remote-server\C$ is Cobalt Strike's built in command that works.

Tasklist BOF wasn't updated with latest WMI_Connect revision

A recent update to WMI.c (which fixed token impersonation, thanks for that!) changed the parameters that the WMI_Connect function requires; the Tasklist BOF uses WMI to perform it's actions and was not updated to reflect these changes, which causes it to fail when compiling.

The relevant code from src/SA/tasklist/entry.c:

HRESULT task_list(
	LPWSTR pwszServer
)
{
	HRESULT	hr = S_OK;
	WMI		m_WMI;
	size_t	ullQuerySize = 0;
	LPWSTR	lpwszQuery = NULL;
	BSTR**	ppbstrResults = NULL;
	DWORD	dwRowCount = 0;
	DWORD	dwColumnCount = 0;
	DWORD	dwCurrentRowIndex = 0;
	DWORD	dwCurrentColumnIndex = 0;

	// Initialize COM
	hr = Wmi_Initialize(&m_WMI);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Initialize failed: 0x%08lx", hr);
		goto fail;
	}

	// Connect to WMI on host
	hr = Wmi_Connect(&m_WMI, pwszServer, NULL);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Connect failed: 0x%08lx", hr);
		goto fail;
	}

should be:

HRESULT task_list(
	LPWSTR pwszResource
)
{
	HRESULT	hr = S_OK;
	WMI		m_WMI;
	size_t	ullQuerySize = 0;
	LPWSTR	lpwszQuery = NULL;
	BSTR**	ppbstrResults = NULL;
	DWORD	dwRowCount = 0;
	DWORD	dwColumnCount = 0;
	DWORD	dwCurrentRowIndex = 0;
	DWORD	dwCurrentColumnIndex = 0;

	// Initialize COM
	hr = Wmi_Initialize(&m_WMI);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Initialize failed: 0x%08lx", hr);
		goto fail;
	}

	// Connect to WMI on host
	hr = Wmi_Connect(&m_WMI, pwszResource);
	if (FAILED(hr))
	{
		BeaconPrintf(CALLBACK_ERROR, "Wmi_Connect failed: 0x%08lx", hr);
		goto fail;
	}

I also changed SA.cna to use the resource model that wmi_query now does:

alias tasklist{
	local('$args $resource')

	$resource = "";

	if ((size(@_) < 1) || (size(@_) > 2))
	{
		berror($1, beacon_command_detail("tasklist"));
		berror($1, "Invalid number of arguments");
		return;
	}
	$resource = iff(-istrue $2, "\\\\$2\\root\\cimv2", "\\\\.\\root\\cimv2");
	$args = bof_pack($1, "Z", $resource);
	beacon_inline_execute($1, readbof($1, "tasklist", "Connecting to $resource and retrieving list of currently running processes", "T1057"), "go", $args);
}

Feature Request for nslookup: Support for reverse lookups

Looking for reverse lookup support with the nslookup BOF.

If I do nslookup 10.10.10.10 it only returns the A record for the IP but no hostname. If I try the below options I get DNS errors.
nslookup 10.10.10.10.in-addr.arpa
nslookup 10.10.10.10 PTR

Typo

trustedsec/CS-Situational-Awareness-BOF/blob/master/SA/SA.cna : Line 167

Change:
If this fails with an error about paging not being supported you can try tou se nonpagedldapsearch instead (its unregistered but has the same arguments)");

To:
If this fails with an error about paging not being supported you can try to use nonpagedldapsearch instead (its unregistered but has the same arguments)");

tou se to to use

literally unusable /s

Potential wild free on WLDAP32$ldap_value_free(ppValue);?

I am not a C++ expert, but I would suggest an additional look at this free:

WLDAP32$ldap_value_free(ppValue);

All the other variables being freed are declared and initialized at function entry. However, ppValue is not declared or initialized until line 228:

There are several code paths which skip this declaration and initialization, notably on lines lines 182, 193, 201, 213, 218, which goto end on error conditions. If the initialization of ppValue was skipped by the goto, ldap_value_free may be called on an uninitialized variable and hence a wild free.

ldapsearch nTSecurityDescriptor attribute can only be read by domain admins, need to add LDAP server control for low-priv users

I made a pull request not too long ago to include the nTSecurityDescriptor attribute in base64 to include ACL resolving via bofhound. Now it turns out I didn't test it thoroughly enough, as low-privileged domain users can't read this attribute unless specified via security descriptor control (see https://github.com/the-useless-one/pywerview/blob/master/pywerview/functions/net.py - search for 0x07). I'll see if I can get it working.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.