This artifact extracts cobalt strike configuration from a byte stream, process or file on disk such as a process dump. Best used as a triage step against a detection of a cobalt strike beacon via a yara process scan.
The User can define bytes, file glob, process name or pid regex as a target. The content will search for a configuration pattern, extract a defined byte size, xor with discovered key, then attempt configuration extraction.
This content simply carves the configuration and does not unpack files on disk. That means pointing this artifact as a packed or obfuscated file may not obtain the expected results.
Unpacking later version.
name: Windows.Carving.CobaltStrike
author: Matt Green - @mgreen27
description: |
This artifact extracts cobalt strike configuration from a byte stream, process
or file on disk such as a process dump. Best used as a triage step against a
detection of a cobalt strike beacon via a yara process scan.
The User can define bytes, file glob, process name or pid regex as a target. The
content will search for a configuration pattern, extract a defined byte size,
xor with discovered key, then attempt configuration extraction.
- Cobalt Strike beacon configuration is typically XORed with 0x69 or 0x2e
(depending on version) but trivial to change.
- Configuration is built in a typical index / type / length / value structure
with either big endian values or zero terminated strings.
- If no beacon is found, parser will fallback to CobaltStrike Shellcode analysis.
This content simply carves the configuration and does not unpack files on
disk. That means pointing this artifact as a packed or obfuscated file may not
obtain the expected results.
Unpacking later version.
reference:
- https://attack.mitre.org/software/S0154/
- https://blog.didierstevens.com/2020/11/07/1768-k/
parameters:
- name: TargetBytes
default:
- name: TargetFileGlob
default:
- name: PidRegex
default: .
type: regex
- name: ProcessRegex
default: .
type: regex
- name: ExtractBytes
type: int
default: 10000
- name: BruteXor
type: bool
description: Select to attempt brute forcing Xor byte in config. Default is 0x2e or 0x69.
- name: IncludeDecodedData
type: bool
description: Select to include decoded data in output.
- name: FindConfigTemplate
type: hidden
default: |
rule cobalt_strike_beacon {
strings:
$REPLACEME
condition:
any of them
}
- name: FindShellcode
type: hidden
default: |
rule cobalt_strike_shellcode {
strings:
$header = { FC }
$s1 = "hwini"
$s2 = "hws2_"
$s3 = "wininet"
condition:
( $header at 0 and filesize < 4096 )
or any of ($s*) // we enact offset limits in VQL ( 0..4096 )
}
- name: FindSleepFunction
type: hidden
default: |
rule cobalt_strike_sleepfunction {
strings:
$x64 = { 4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03 }
$x86 = { 8B 46 04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2 }
condition:
any of them
}
export: |
LET PROFILE = '''[
[CobaltConfig, 0, [
# 0x0001:BeaconType, 0x0001:Type, 0x0002:Length
["BeaconType", 6, "Enumeration", {
"type": "uint16b",
"choices": {
"0": "windows-beacon_http-reverse_http",
"1": "windows-beacon_dns-reverse_http",
"2": "windows-beacon_smb-bind_pipe",
"8": "windows-beacon_https-reverse_https",
"16": "windows-beacon_tcp-bind_tcp"
}
}],
# 0x0002:Port, 0x0001:Type, 0x0002:Length
["__port_prefix", 0, "String",{"term_hex": "000200010002", length: 10000, max_length: 10000}],
["Port", "x=>len(list=x.__port_prefix) + 6", "uint16b"],
# 0x0003:Sleeptime,0x0002:Type, 0x0004:Length
["__sleeptime_prefix", 0, "String", {"term_hex": "000300020004", length: 10000, max_length: 10000}],
["Sleeptime", "x=>len(list=x.__sleeptime_prefix) + 6", "uint32b"],
# 0x0004:Maxgetsize, 0x0002:Type, 0x0004:Length
["__maxgetsize_prefix", 0, "String",{"term_hex": "000400020004", length: 10000, max_length: 10000}],
["Maxgetsize", "x=>len(list=x.__maxgetsize_prefix) + 6", "uint32b"],
# 0x0005:Jitter, 0x0001:Type, 0x0002:Length
["__jitter_prefix", 0, "String",{"term_hex": "000500010002", length: 10000, max_length: 10000}],
["Jitter", "x=>len(list=x.__jitter_prefix) + 6", "uint16b"],
# 0x0006:MaxDns, 0x0001:Type, 0x0002:Length
["__maxdns_prefix", 0, "String",{"term_hex": "000600010002", length: 10000, max_length: 10000}],
["MaxDns", "x=>len(list=x.__maxdns_prefix) + 6", "uint16b"],
# 0x0007:Publickey,0x0003:Type,
["__publickey_prefix", 0, "String",{"term_hex": "000700030100", length: 10000, max_length: 10000}],
["__publickey_raw", "x=>len(list=x.__publickey_prefix) + 6", "String",{"term_hex":"00000008"}],
["PublicKey", "x=>len(list=x.__publickey_prefix) + 6", "Value",{"value":"x=>format(format='% x',args=x.__publickey_raw)"}],
# 0x0008:server/get-uri,0x0003:Type,
["__c2server_prefix", 0, "String",{"term_hex": "00080003", length: 10000, max_length: 10000}],
["C2Server", "x=>len(list=x.__c2server_prefix) + 6", "String"],
# 0x0009:useragent,0x0003:Type,
["__useragent_prefix", 0, "String",{"term_hex": "00090003", length: 10000, max_length: 10000}],
["UserAgent", "x=>len(list=x.__useragent_prefix) + 6", "String"],
# 0x000a:PostUri,0x0003:Type,
["__PostUri_prefix", 0, "String", {"term_hex": "000a0003", length: 10000, max_length: 10000}],
["PostURI", "x=>len(list=x.__PostUri_prefix) + 6", "String"],
# 0x000b:Malleable_C2_Instructions,0x0003:Type, adding length check as not sure if we can rely on termination
["__Malleable_C2_Instructions_prefix", 0, "String",{"term_hex": "000b0003", length: 10000, max_length: 10000}],
["__Malleable_C2_Instructions_length","x=>len(list=x.__Malleable_C2_Instructions_prefix) + 4","uint16b"],
["__Malleable_C2_Instructions", "x=>len(list=x.__Malleable_C2_Instructions_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__Malleable_C2_Instructions_length"}],
["MalleableC2Instructions",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__Malleable_C2_Instructions, re='[^ -~\\r\\n]', replace='')])" }],
#["Malleable_C2_Instructions",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__Malleable_C2_Instructions)" }], #uncomment to return base64 encoded raw Malleable_C2_Instructions
# 0x000c:HttpGetHeader,0x0003:Type, adding length check as we can not rely on termination
["__HttpGetHeader_prefix", 0, "String",{"term_hex": "000c0003", length: 10000, max_length: 10000}],
["__HttpGetHeader_length","x=>len(list=x.__HttpGetHeader_prefix) + 4","uint16b"],
["__HttpGetHeader","x=>len(list=x.__HttpGetHeader_prefix) + 6","String",{"term":"***NOTERM***", "length": "x=> x.__HttpGetHeader_length"}],
["HttpGetHeader",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__HttpGetHeader, re='[^ -~\\r\\n]', replace='')])" }],
#["HttpGetHeader",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__HttpGetHeader)" }], #uncomment to return base64 encoded raw HttpGetHeader
# 0x000d:HttpPostHeader,0x0003:Type, adding length check as we can not rely on termination
["__http_post_header_prefix", 0, "String",{"term_hex": "000d0003", length: 10000, max_length: 10000}],
["__HttpPostHeader_length","x=>len(list=x.__http_post_header_prefix) + 4","uint16b"],
["__HttpPostHeader","x=>len(list=x.__http_post_header_prefix) + 6","String",{"term":"***NOTERM***", "length": "x=> x.__HttpPostHeader_length"}],
["HttpPostHeader",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__HttpPostHeader, re='[^ -~\\r\\n]', replace='')])" }],
#["HttpPostHeader",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__HttpPostHeader)" }], #uncomment to return base64 encoded raw HttpPostHeader
# 0x000e:SpawnTo,0x0003:Type # Adding length check as we can not rely on termination
["__SpawnTo_header_prefix", 0, "String",{"term_hex": "000e0003", length: 10000, max_length: 10000}],
["__SpawnTo_header_length","x=>len(list=x.__SpawnTo_header_prefix) + 4","uint16b"],
["__SpawnTo", "x=>len(list=x.__SpawnTo_header_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__SpawnTo_header_length"}],
["SpawnTo",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__SpawnTo, re='[^ -~\\r\\n]', replace='')])" }],
#["SpawnTo",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__SpawnTo)" }], #uncomment to return base64 encoded raw SpawnTo
# 0x000f:PipeName,0x0003:Type
["__pipename_prefix", 0, "String",{"term_hex": "000f0003", length: 10000, max_length: 10000}],
["Pipename", "x=>len(list=x.__pipename_prefix) + 6", "String",{"term_hex":"0000"}],
# 0x0010:KillDateYear, 0x0001:Type, 0x0002:Length
["__KillDateYear_prefix", 0, "String",{"term_hex": "001000010002", length: 10000, max_length: 10000}],
["KillDateYear", "x=>len(list=x.__KillDateYear_prefix) + 6", "uint16b"],
# 0x0011:KillDateMonth, 0x0001:Type, 0x0002:Length
["__KillDateMonth_prefix", 0, "String",{"term_hex": "001200010002", length: 10000, max_length: 10000}],
["KillDateMonth", "x=>len(list=x.__KillDateMonth_prefix) + 6", "uint16b"],
# 0x0012:KillDateDay, 0x0001:Type, 0x0002:Length
["__KillDateDay_prefix", 0, "String",{"term_hex": "001200010002", length: 10000, max_length: 10000}],
["KillDateDay", "x=>len(list=x.__KillDateDay_prefix) + 6", "uint16b"],
# 0x0013:DNSIdle, 0x0002:Type, 0x0004:Length
["__DNSIdle_prefix", 0, "String",{"term_hex": "001300020004", length: 10000, max_length: 10000}],
["__DNSIdle1", "x=>len(list=x.__DNSIdle_prefix) + 6", "uint8"],
["__DNSIdle2", "x=>len(list=x.__DNSIdle_prefix) + 7", "uint8"],
["__DNSIdle3", "x=>len(list=x.__DNSIdle_prefix) + 8", "uint8"],
["__DNSIdle4", "x=>len(list=x.__DNSIdle_prefix) + 9", "uint8"],
["DNSIdle", 0, "Value", {
"value": "x=> str(str=x.__DNSIdle1) + '.' + str(str=x.__DNSIdle2) + '.' + str(str=x.__DNSIdle3) + '.' + str(str=x.__DNSIdle4)"
}],
# 0x0014:DNSSleep', 0x0002:Type, 0x0004:Length
["__DNSSleep_prefix", 0, "String",{"term_hex": "001400020004", length: 10000, max_length: 10000}],
["DNSSleep", "x=>len(list=x.__DNSSleep_prefix) + 6", "uint32b"],
# 0x0015:SSH_1, to complete - didnt find any examples assuming zero terminated
["__SSH_1_prefix", 0, "String",{"term_hex": "00150003", length: 10000, max_length: 10000}],
["SSH_1", "x=>len(list=x.__SSH_1_prefix) + 6", "String"],
# 0x0016:SSH_2, to complete - didnt find any examples assuming zero terminated
["__SSH_2_prefix", 0, "String",{"term_hex": "00160003", length: 10000, max_length: 10000}],
["SSH_2", "x=>len(list=x.__SSH_2_prefix) + 6", "String"],
# 0x0017:SSH_3, to complete - didnt find any examples assuming zero terminated
["__SSH_3_prefix", 0, "String",{"term_hex": "00170003", length: 10000, max_length: 10000}],
["SSH_3", "x=>len(list=x.__SSH_3_prefix) + 6", "String"],
# 0x0018:SSH_4, to complete - didnt find any examples assuming zero terminated
["__SSH_4_prefix", 0, "String",{"term_hex": "00180003", length: 10000, max_length: 10000}],
["SSH_4", "x=>len(list=x.__SSH_4_prefix) + 6", "String"],
# 0x0019:SSH_5, to complete - didnt find any examples assuming zero terminated
["__SSH_5_prefix", 0, "String",{"term_hex": "00190003", length: 10000, max_length: 10000}],
["SSH_5", "x=>len(list=x.__SSH_5_prefix) + 6", "String"],
# 0x001a:GetVerb,0x0003:Type
["__GetVerb_prefix", 0, "String",{"term_hex": "001a0003"}],
["GetVerb", "x=>len(list=x.__GetVerb_prefix) + 6", "String",{"term_hex":"0000"}],
# 0x001b: PostVerb, 0x0003:Type
["__PostVerb_prefix", 0, "String",{"term_hex": "001b0003"}],
["PostVerb", "x=>len(list=x.__PostVerb_prefix) + 6", "String",{"term_hex":"0000"}],
# 0x001c:HttpPostChunk,0x0002:Type, 0x0004:Length
["__HttpPostChunk_prefix", 0, "String", {"term_hex": "001c00020004"}],
["HttpPostChunk", "x=>len(list=x.__HttpPostChunk_prefix) + 6", "uint32b"],
# 0x001d:spawnto_x86,0x0003:Type
["__spawnx86_prefix", 0, "String",{"term_hex": "001d0003", length: 10000, max_length: 10000}],
["SpawnTox86", "x=>len(list=x.__spawnx86_prefix) + 6", "String",{"term_hex":"0000"}],
# 0x001e:spawn_to_x64,0x0003:Type
["__spawnx64_prefix", 0, "String",{"term_hex": "001e0003", length: 10000, max_length: 10000}],
["SpawnTox64", "x=>len(list=x.__spawnx64_prefix) + 6", "String",{"term_hex":"0000"}],
# 0x001f:CryptoScheme, 0x0001:Type, 0x0002:Length
["__CryptoScheme_prefix", 0, "String",{"term_hex": "001f00010002", length: 10000, max_length: 10000}],
["CryptoScheme", "x=>len(list=x.__CryptoScheme_prefix) + 6", "uint16b"],
# 0x0020:Proxy, 0x0003:Type
["__Proxy_prefix", 0, "String",{"term_hex": "000e0003", length: 10000, max_length: 10000}],
#["__Proxy_length","x=>len(list=x.__Proxy_prefix) + 4","uint16b"],
["Proxy", "x=>len(list=x.__Proxy_prefix) + 6", "String"],
# 0x0021:ProxyUsername, 0x0003:Type
["__ProxyUsername_prefix", 0, "String",{"term_hex": "000e0003", length: 10000, max_length: 10000}],
["__ProxyUsername_length","x=>len(list=x.__ProxyUsername_prefix) + 4","uint16b"],
["ProxyUsername", "x=>len(list=x.__ProxyUsername_prefix) + 6", "String"],
# 0x0022:ProxyPassword, 0x0003:Type
["__ProxyPassword_prefix", 0, "String",{"term_hex": "000e0003", length: 10000, max_length: 10000}],
["__ProxyPassword_length","x=>len(list=x.__ProxyPassword_prefix) + 4","uint16b"],
["ProxyPassword", "x=>len(list=x.__ProxyPassword_prefix) + 6", "String"],
# 0x0023:ProxyType, 0x0001:Type, 0x0002:Length
["__ProxyType", 0, "String",{"term_hex": "002300010002", length: 10000, max_length: 10000}],
["ProxyType", "x=>len(list=x.__ProxyType) + 6", "Enumeration", {
"type": "uint16b",
"choices": {
"1": "No proxy",
"2": "IE settings",
"4": "Hardcoded proxy"}
}],
# 0x0024:Deprecated, 0x0001:Type, 0x0002:Length
["__Deprecated_prefix", 0, "String",{"term_hex": "002400010002", length: 10000, max_length: 10000}],
["Deprecated", "x=>len(list=x.__Deprecated_prefix) + 6", "uint16b"],
# 0x0025:LicenseId,0x0002:Type, 0x0004:Length
["__LicenseId_prefix", 0, "String", {"term_hex": "002500020004", length: 10000, max_length: 10000}],
["LicenseId", "x=>len(list=x.__LicenseId_prefix) + 6", "uint32b"],
# 0x0026:bStageCleanup, 0x0001:Type, 0x0002:Length
["__bStageCleanup_prefix", 0, "String",{"term_hex": "002600010002", length: 10000, max_length: 10000}],
["bStageCleanup", "x=>len(list=x.__bStageCleanup_prefix) + 6", "uint16b"],
# 0x0027:bCFGCaution, 0x0001:Type, 0x0002:Length
["__bCFGCaution_prefix", 0, "String",{"term_hex": "002700010002", length: 10000, max_length: 10000}],
["bCFGCaution", "x=>len(list=x.__bCFGCaution_prefix) + 6", "uint16b"],
# 0x0028:KillDate,0x0002:Type, 0x0004:Length
["__KillDate_prefix", 0, "String", {"term_hex": "002800020004", length: 10000, max_length: 10000}],
["KillDate", "x=>len(list=x.__KillDate_prefix) + 6", "uint32b"],
# 0x0029:TextSectionEnd,0x0002:Type, 0x0004:Length
["__TextSectionEnd_prefix", 0, "String", {"term_hex": "002900020004", length: 10000, max_length: 10000}],
["TextSectionEnd", "x=>len(list=x.__TextSectionEnd_prefix) + 6", "uint32b"],
# 0x002a:ObfuscateSectionsInfo,0x0003:Type # Adding length check as we can not rely on termination
["__ObfuscateSectionsInfo_prefix", 0, "String",{"term_hex": "002a0003", length: 10000, max_length: 10000}],
["__ObfuscateSectionsInfo_length","x=>len(list=x.__ObfuscateSectionsInfo_prefix) + 4","uint16b"],
["__ObfuscateSectionsInfo", "x=>len(list=x.__ObfuscateSectionsInfo_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__ObfuscateSectionsInfo_length"}],
["ObfuscateSectionsInfo",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__ObfuscateSectionsInfo, re='[^ -~\\r\\n]', replace='')])" }],
#["ObfuscateSectionsInfo",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__ObfuscateSectionsInfo)" }], #uncomment to return base64 encoded raw ObfuscateSectionsInfo
#0x002b:ProcessInjectStartRWX, 0x0001:Type, 0x0002:Length
["__ProcessInjectStartRWX", 0, "String",{"term_hex": "002b00010002", length: 10000, max_length: 10000}],
["ProcessInjectStartRWX", "x=>len(list=x.__ProcessInjectStartRWX) + 6", "Enumeration", {
"type": "uint16b",
"choices": {
"0x1": "PAGE_NOACCESS",
"0x2": "PAGE_READONLY",
"0x4": "PAGE_READWRITE",
"0x8": "PAGE_WRITECOPY",
"0x10": "PAGE_EXECUTE",
"0x20": "PAGE_EXECUTE_READ",
"0x40": "PAGE_EXECUTE_READWRITE",
"0x80": "PAGE_EXECUTE_WRITECOPY"}
}],
#0x002c:ProcessInjectUseRWX, 0x0001:Type, 0x0002:Length
["__ProcessInjectUseRWX", 0, "String",{"term_hex": "002c00010002", length: 10000, max_length: 10000}],
["ProcessInjectUseRWX", "x=>len(list=x.__ProcessInjectUseRWX) + 6", "Enumeration", {
"type": "uint16b",
"choices": {
"0x1": "PAGE_NOACCESS",
"0x2": "PAGE_READONLY",
"0x4": "PAGE_READWRITE",
"0x8": "PAGE_WRITECOPY",
"0x10": "PAGE_EXECUTE",
"0x20": "PAGE_EXECUTE_READ",
"0x40": "PAGE_EXECUTE_READWRITE",
"0x80": "PAGE_EXECUTE_WRITECOPY"}
}],
# 0x002d:ProcessInjectMinAlloc,0x0002:Type, 0x0004:Length
["__ProcessInjectMinAlloc_prefix", 0, "String", {"term_hex": "002d00020004", length: 10000, max_length: 10000}],
["ProcessInjectMinAlloc", "x=>len(list=x.__ProcessInjectMinAlloc_prefix) + 6", "uint32b"],
# 0x002e:ProcessInjectTransformx86, 0x0003:Type, # Adding length check as we can not rely on termination
["__ProcessInjectTransformx86_prefix", 0, "String",{"term_hex": "002e0003", length: 10000, max_length: 10000}],
["__ProcessInjectTransformx86_length","x=>len(list=x.__ProcessInjectTransformx86_prefix) + 4","uint16b"],
["__ProcessInjectTransformx86", "x=>len(list=x.__ProcessInjectTransformx86_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__ProcessInjectTransformx86_length"}],
["ProcessInjectTransformx86",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__ProcessInjectTransformx86, re='[^ -~\\r\\n]', replace='')])" }],
#["ProcessInjectTransformx86",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__ProcessInjectTransformx86)" }],#uncomment to return base64 encoded raw ProcessInjectTransformx86
# 0x002f:ProcessInjectTransformx64, 0x0003:Type, # Adding length check as we can not rely on termination
["__ProcessInjectTransformx64_prefix", 0, "String",{"term_hex": "002f0003", length: 10000, max_length: 10000}],
["__ProcessInjectTransformx64_length","x=>len(list=x.__ProcessInjectTransformx64_prefix) + 4","uint16b"],
["__ProcessInjectTransformx64", "x=>len(list=x.__ProcessInjectTransformx64_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__ProcessInjectTransformx64_length"}],
["ProcessInjectTransformx64",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__ProcessInjectTransformx64, re='[^ -~\\r\\n]', replace='')])" }],
#["ProcessInjectTransformx64",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__ProcessInjectTransformx64)" }],#uncomment to return base64 encoded raw ProcessInjectTransformx64
# 0x0032:UsesCookies, 0x0001:Type, 0x0002:Length
["__UsesCookies_prefix", 0, "String",{"term_hex": "003200010002", length: 10000, max_length: 10000}],
["UsesCookies", "x=>len(list=x.__UsesCookies_prefix) + 6", "uint16b"],
# 0x0033:ProcessInjectExecute, 0x0003:Type # Adding length check as we can not rely on termination
["__ProcessInjectExecute_prefix", 0, "String",{"term_hex": "00330003", length: 10000, max_length: 10000}],
["__ProcessInjectExecute_length","x=>len(list=x.__ProcessInjectExecute_prefix) + 4","uint16b"],
["__ProcessInjectExecute", "x=>len(list=x.__ProcessInjectExecute_prefix) + 6", "String",{"term":"***NOTERM***", "length": "x=> x.__ProcessInjectExecute_length"}],
["ProcessInjectExecute",0,"Value",{ "value": "x=>format(format='%s', args=[regex_replace(source=x.__ProcessInjectExecute, re='[^ -~\\r\\n]', replace='')])" }],
#["ProcessInjectExecute",0,"Value",{ "value": "x=>'base64:' + base64encode(string=x.__ProcessInjectExecute)" }], #uncomment to return base64 encoded raw ProcessInjectExecute
# 0x0034:ProcessInjectAllocationMethod, 0x0001:Type, 0x0002:Length
["__ProcessInjectAllocationMethod_prefix", 0, "String",{"term_hex": "003400010002", length: 10000, max_length: 10000}],
["ProcessInjectAllocationMethod", "x=>len(list=x.__ProcessInjectAllocationMethod_prefix) + 6", "uint16b"],
# 0x0035:ProcessInjectStub, 0x0003:Type # Adding length check as we can not rely on termination
["__ProcessInjectStub_prefix", 0, "String",{"term_hex": "00350003", length: 10000, max_length: 10000}],
["__ProcessInjectStub_length","x=>len(list=x.__ProcessInjectStub_prefix) + 4","uint16b"],
["__ProcessInjectStub", "x=>len(list=x.__ProcessInjectStub_prefix) + 6", "String",{"term_hex":"00000000", "length": "x=> x.__ProcessInjectStub_length"}],
["ProcessInjectStub",0,"Value",{ "value": "x=>format(format='% x', args=x.__ProcessInjectStub)" }],
# 0x0036:HostHeader, 0x0003:Type # Adding length check as we can not rely on termination
["__HostHeader_prefix", 0, "String",{"term_hex": "00360003", length: 10000, max_length: 10000}],
["__HostHeader_length","x=>len(list=x.__HostHeader_prefix) + 4","uint16b"],
["HostHeader", "x=>len(list=x.__HostHeader_prefix) + 6", "String",{"term_hex":"00000000", "length": "x=> x.__HostHeader_length"}],
]],
[Shellcode, 0, [
["__Position", 0, "Value",{"value":"x=>unhex(string=position(data=_Data))"}],
["Server", 0, "Value",{"value":"x=>regex_replace(source=regex_replace(source=x.__Position,re='\\x{00}.{4}[^$]*$',replace=''),re='\u0000',replace='')"}],
["TargetUri", 0, "Value",{"value":"x=>find_strings(data=_Data,length=5,filter='^/').Strings[0]"}],
["__LicenseBytes", 0, "Value",{"value":"x=>read_file(accessor='data',filename=x.__Position || '', offset=len(list=x.Server) + 1 ,length=4)"}],
["License", 0, "Value",{"value":"x=>parse_binary(accessor='data', filename=x.__LicenseBytes,struct='uint32b')"}],
["Strings", 0, "Value",{"value":"x=>find_strings(data=_Data,length=5,filter='.').Strings"}],
]],
["EmbeddedPE", 0, [
["__PayloadType", 0, "uint32"],
["PayloadType", 0, "Value",{"value":"x=>format(format='0x%08x',args=x.__PayloadType)"}],
["__PayloadSize", 4, "uint32"],
#["PayloadSize", 4, "Value",{"value":"x=>format(format='0x%08x',args=x.__PayloadSize)"}],
["__XorKey", 8, "uint32b"],
["XorKey", 8, "Value",{"value":"x=>format(format='0x%08x',args=x.__XorKey)"}],
["__Id2", 12, "uint32"],
["Id2", 12, "Value",{"value":"x=>format(format='0x%08x',args=x.__Id2)"}],
["__Payload", 16, "Value",{"value":"x=>read_file(accessor='data',filename=embedded_section(path=TargetBytes || OSPath,
type=if(condition=TargetBytes,then='data',else='auto'))[0].Data || '', offset=16,length=x.__PayloadSize)"}],
#["__Payload", 16, "String",{"term_hex":"",length=x.__PayloadSize)"}],
["DecodedPayload", 16, "Value",{"value":"x=>xor(string=x.__Payload,key=unhex(string=x.XorKey))"}],
["PayloadHash", 16, "Value",{"value":"x=>hash(path=xor(string=x.__Payload,key=unhex(string=x.XorKey)),accessor='data')"}],
["OriginalFileHash", 16, "Value",{"value":"x=>hash(path=OSPath)"}],
]]]'''
sources:
- query: |
-- unique function to groupby value for enumerate
LET unique(values) = SELECT _value as value FROM foreach(row=values) GROUP BY _value
-- section to dynamically generate Xor configuration yara hunt strings
LET a <= unhex(string='01')
LET b <= unhex(string='02')
LET c <= unhex(string='03')
LET XorChars <=
SELECT format(format="%#02x", args=_value) AS H,
unhex(string=format(format="%02x", args=_value)) as X
FROM range(start=0, end=256, step=1)
WHERE if(condition=BruteXor,
then=True,
else= H=~ '0x2e|0x69')
Let XorCharsStep2 =
SELECT H, X,
xor(string=a, key=X) as aXor,
xor(string=b, key=X) as bXor,
xor(string=c, key=X) as cXor,
len(list=X)
FROM XorChars
LET YaraStrings =
SELECT -- { 00 01 00 01 00 02 ?? ?? 00 02 00 01 00 02 ?? ?? 00 03 }
X,H,
H + ' = { ' + format(format='% x', args=X + aXor + X + aXor + X + bXor) +
' ?? ?? ' + format(format='% x', args=X + bXor + X + aXor + X + bXor) +
' ?? ?? ' + format(format='% x', args=X + cXor) + ' }' as Line
FROM XorCharsStep2
LET FindConfig =
regex_replace(
source=FindConfigTemplate,
re='REPLACEME',
replace=join(array=YaraStrings.Line, sep=" $$"))
-- function to extract potential additional encoded PE in data section
LET embedded_section(path,type) = SELECT
path as OriginalFileName,
_value.Name as Name,
_value.Size as Size,
_value.FileOffset as FileOffset,
_value.VMA as VMA,
_value.RVA as RVA,
_value.Perm as Perm,
read_file(filename=path,
accessor=type,
offset=_value.FileOffset,
length=_value.Size) as Data
FROM foreach(row= parse_pe(file=path,accessor=type).Sections)
WHERE Name = '.data' AND Size > 15
-- scan DataBytes for CobaltStrike config
LET ByteConfiguration = SELECT Rule,
len(list=TargetBytes) as Size,
hash(path=TargetBytes,accessor='data') as Hash,
format(format="%v_%v.bin", args=[Rule,String.Offset]) as _DecodedDataName,
Xor,_Data,
Rule as _Group
FROM switch( -- switchcase will find beacon as priority, then search for shellcode
beacon = {
SELECT *,
substr(start=0, end=1, str=String.Data) as Xor,
read_file(accessor='data',
filename=TargetBytes,
offset= String.Offset,
length=ExtractBytes) as _Data
FROM yara(accessor='data',files=TargetBytes || "",
rules=FindConfig, number=99)
},
shellcode = {
SELECT *, '' as Xor,
read_file(accessor='data',
filename=TargetBytes,
offset=String.Offset,length=4096) as _Data
FROM yara(accessor='data',
files=TargetBytes,
rules=FindShellcode, number=99)
},
section_encoded_pe = {
SELECT *,
'Embedded data section: ' + Rule as Rule,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(accessor='data',
filename=File.OSPath,
offset=String.Offset,
length=ExtractBytes) as _Data
FROM yara(files=parse_binary(
accessor='data',
filename= embedded_section(
path=TargetBytes, type='data')[0].Data || "",
profile=PROFILE,
struct="EmbeddedPE").DecodedPayload,
accessor='data', rules=FindConfig, number=99)
},
section_encoded_stager = {
SELECT *,
'' as Xor,
'Embedded data section: ' + Rule as Rule,
read_file(accessor='data',
filename=File.OSPath) as _Data
FROM yara(files=parse_binary(
accessor='data',
filename= embedded_section(
path=TargetBytes,type='data')[0].Data || "",
profile=PROFILE,
struct="EmbeddedPE").DecodedPayload,
accessor='data', rules=FindShellcode, number=99)
},
sleepfunction = {
SELECT *, '' as Xor,
if(condition= String.Name= '$x86',
then= 'Sleep mask 32-bit 4.2 deobfuscation routine found.',
else= 'Sleep mask 64-bit 4.2 deobfuscation routine found.') as _Data
FROM yara(accessor='data',files=TargetBytes, rules=FindSleepFunction, number=1)
})
-- find target files
LET TargetFiles = SELECT OSPath AS OSPath,Size
FROM glob(globs=TargetFileGlob) WHERE NOT IsDir
-- scan files in scope with our rule
LET FileConfiguration = SELECT * FROM foreach(row=TargetFiles,
query={
SELECT
Rule,
OSPath, Size,
hash(path=OSPath) as Hash,
Xor,_Data,
Rule + '|' + OSPath.String as _Group,
format(format="%v_%v_%v.bin", args=[Rule,OSPath,String.Offset]) as _DecodedDataName
FROM switch( -- switchcase will find beacon as priority, then search for shellcode
beacon = {
SELECT *,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(
filename=OSPath,
offset= String.Offset,
length=ExtractBytes) as _Data
FROM yara(files=OSPath, rules=FindConfig, number=99)
},
shellcode = {
SELECT *, '' as Xor,
read_file(filename=OSPath,length=4096) as _Data
FROM yara(files=OSPath, rules=FindShellcode, number=99)
},
section_encoded_pe = {
SELECT *,
'Embedded data section: ' + Rule as Rule,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(accessor='data',filename=File.OSPath,
offset=String.Offset,length=ExtractBytes) as _Data
FROM yara(files=parse_binary(
accessor='data',
filename= embedded_section(path=OSPath,type='auto')[0].Data || "",
profile=PROFILE,
struct="EmbeddedPE").DecodedPayload,
accessor='data', rules=FindConfig, number=99)
},
section_encoded_stager = {
SELECT *,
'' as Xor,
'Embedded data section: ' + Rule as Rule,
read_file(accessor='data',
filename=File.OSPath,
length=ExtractBytes) as _Data
FROM yara(files=parse_binary(
accessor='data',
filename= embedded_section(path=OSPath,type='auto')[0].Data || "",
profile=PROFILE,
struct="EmbeddedPE").DecodedPayload,
accessor='data', rules=FindShellcode, number=99)
},
sleepfunction = {
SELECT *, '' as Xor,
if(condition= String.Name= '$x86',
then= 'Sleep mask 32-bit 4.2 deobfuscation routine found.',
else= 'Sleep mask 64-bit 4.2 deobfuscation routine found.') as _Data
FROM yara(files=OSPath, rules=FindSleepFunction, number=1)
})
})
-- find velociraptor process
LET me <= SELECT * FROM if(condition= NOT ( TargetFileGlob OR TargetBytes ),
then = { SELECT Pid FROM pslist(pid=getpid()) })
-- find all processes and add filters
LET processes = SELECT Name as ProcessName, CommandLine, Pid
FROM pslist()
WHERE
Name =~ ProcessRegex
AND format(format="%d", args=Pid) =~ PidRegex
AND NOT Pid in me.Pid
-- scan processes in scope with our rule
LET ProcessConfiguration = SELECT * FROM foreach(
row=processes,
query={
SELECT Rule,
Pid, ProcessName, CommandLine,
format(format="%v_%v_%v_%v.bin", args=[Rule,ProcessName,Pid,String.Offset]) as _DecodedDataName,
Xor,_Data,_Group
FROM switch( -- switchcase will find beacon as priority, then search for shellcode
beacon = {
SELECT *,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(accessor='process',
filename=str(str=Pid),
offset= String.Offset,
length=ExtractBytes) as _Data,
Rule +'|'+ str(str=Pid) +'|'+ ProcessName +'|'+ CommandLine as _Group
FROM yara(accessor='process',files=str(str=Pid), rules=FindConfig, number=99)
},
shellcode = {
SELECT *, '' as Xor,
read_file(accessor='process',
filename=str(str=Pid),
offset=String.Offset,length=4096) as _Data,
Rule +'|'+ str(str=Pid) +'|'+ ProcessName +'|'+ CommandLine as _Group
FROM yara(accessor='process',files=str(str=Pid), rules=FindShellcode, number=99)
},
sleepfunction = {
SELECT *, '' as Xor,
if(condition= String.Name= '$x86',
then= 'Sleep mask 32-bit 4.2 deobfuscation routine found.',
else= 'Sleep mask 64-bit 4.2 deobfuscation routine found.') as _Data,
'' as _Group
FROM yara(accessor='process',files=str(str=Pid), rules=FindSleepFunction, number=1)
})
})
-- Add dynamic functions for shellcode parsing
LET position(data) = if(condition= len(list=split(string=format(format='%x',args=data),sep='ffff')) > 1,
then= split(string=format(format='%x',args=data),sep='ffff')[-1],
else= False )
LET find_strings(data,length,filter) = SELECT Strings
FROM parse_records_with_regex(file=data,accessor='data',regex='(?P<Strings>[ -~]+)')
WHERE len(list=Strings) > length - 1
AND Strings =~ filter
AND NOT Strings =~ '^\\s+$'
LIMIT 150
-- generate results remove any FPs
LET results <= SELECT *,
if(condition= Rule=~'cobalt_strike_beacon$',
then= format(format='0x%x',args=Xor),else='0x00') as Xor,
if(condition= Rule=~'cobalt_strike_beacon',
then= parse_binary(accessor='data',
filename= xor(string=_Data || "" ,key=Xor),
profile = PROFILE,struct = "CobaltConfig"),
else= if(condition= Rule=~'cobalt_strike_shellcode',
then= parse_binary(accessor='data',
filename= _Data || "",
profile = PROFILE,struct="Shellcode"),
else= _Data )) AS DecodedConfig
FROM if(condition=TargetBytes,
then=ByteConfiguration,
else= if(condition=TargetFileGlob,
then= FileConfiguration,
else= ProcessConfiguration))
WHERE _Data
AND
(( DecodedConfig.C2Server =~ '^[ -~]+$' AND DecodedConfig.BeaconType )
OR ( DecodedConfig.Pipename =~ '^[ -~]+$' AND DecodedConfig.BeaconType )
OR DecodedConfig.Server =~ '^[ -~]+' -- AND DecodedConfig.TargetUri )
OR Rule='cobalt_strike_sleepfunction' )
-- add decoded data seperate to keep pretty output
LET output_decoded_data = SELECT *,
upload(accessor = 'data',
file = if(condition = Rule='cobalt_strike_beacon',
then = xor(string=_Data,key=unhex(string=Xor)),
else = _Data),
name = _DecodedDataName) as DecodedData
FROM results
LET cleanup(config) = to_dict(item=
{
SELECT _key, _value
FROM items(item=config)
WHERE NOT _key =~ '^__' AND ( _value OR _key =~ '^license' )
})
-- output rows, standard config priority, exclude _Data
SELECT *,
if(condition= format(format='%T',args=DecodedConfig)='string',
then= DecodedConfig,
else= cleanup(config=DecodedConfig)) as DecodedConfig
FROM column_filter(
query={
SELECT * ,
-- NOTE: some junk strings for shellcode _Group are removed in GROUP BY
if(condition= Rule='cobalt_strike_beacon',
then= _Group +'|'+ str(str=DecodedConfig),
else= _Group +'|'+ str(str=DecodedConfig.Server) +'|'+ str(str=DecodedConfig.TargetUri) +'|'+ str(str=DecodedConfig.Licence) ) as _Group
FROM if(condition=IncludeDecodedData,
then= output_decoded_data,
else= results)
GROUP BY _Group
}, exclude=["_Data","_Group"])
column_types:
- name: DecodedData
type: preview_upload