Windows.Carving.CobaltStrike

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.


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