Skip to content

Commit

Permalink
Add CobaltStrike parser built in detection (Velocidex#1211)
Browse files Browse the repository at this point in the history
* Add CobaltStrike parser built in detection
* Add detection function tests
  • Loading branch information
mgreen27 authored Aug 20, 2021
1 parent c3b21bb commit 02a4afc
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 22 deletions.
134 changes: 115 additions & 19 deletions artifacts/definitions/Windows/Carving/CobaltConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ description: |
a cobalt strike beacon via a yara process scan.
The User can define a file glob, process name or pid regex as a target. The
content will firstly search for a configuration pattern, extract a defined
byte size, xor with discovered key, then attempt configuration extraction.
content will firstly confirm detection. Next it 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.
Expand All @@ -21,8 +22,9 @@ description: |
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 / shellcode analysis may come in a later
version.
Unpacking / shellcode analysis may come in a later version.
Detection is optional - simply remove DetectionYara.
Please submit PR/improvements on additional testing.
reference:
Expand All @@ -46,13 +48,60 @@ parameters:
type: hidden
default: |
rule find_cobalt_strike_config {
strings:
$REPLACEME
strings:
$REPLACEME
condition:
any of them
}
- name: DetectionYara
default: |
rule win_cobalt_strike_auto {
meta:
author = "Felix Bilstein - yara-signator at cocacoding dot com"
date = "2019-11-26"
version = "1"
description = "autogenerated rule brought to you by yara-signator"
tool = "yara-signator 0.2a"
malpedia_reference = "https://malpedia.caad.fkie.fraunhofer.de/details/win.cobalt_strike"
malpedia_license = "CC BY-SA 4.0"
malpedia_sharing = "TLP:WHITE"
strings:
$sequence_0 = { 3bc7 750d ff15???????? 3d33270000 }
$sequence_1 = { e9???????? eb0a b801000000 e9???????? }
$sequence_2 = { 8bd0 e8???????? 85c0 7e0e }
$sequence_3 = { ffb5f8f9ffff ff15???????? 8b4dfc 33cd e8???????? c9 c3 }
$sequence_4 = { e8???????? e9???????? 833d?????????? 7505 e8???????? }
$sequence_5 = { 250000ff00 33d0 8b4db0 c1e908 }
$sequence_6 = { ff75f4 ff7610 ff761c ff75fc }
$sequence_7 = { 8903 6a06 eb39 33ff 85c0 762b 03f1 }
$sequence_8 = { 894dd4 8b458c d1f8 894580 8b45f8 c1e818 0fb6c8 }
$sequence_9 = { 890a 8b4508 0fb64804 81e1ff000000 c1e118 8b5508 0fb64205 }
$sequence_10 = { 33d2 e8???????? 48b873797374656d3332 4c8bc7 488903 49ffc0 }
$sequence_11 = { 488bd1 498d4bd8 498943e0 498943e8 }
$sequence_12 = { b904000000 486bc90e 488b542430 4c8b442430 418b0c08 8b0402 }
$sequence_13 = { ba80000000 e8???????? 488d4c2438 e8???????? 488d4c2420 8bd0 e8???????? }
$sequence_14 = { 488b4c2430 8b0401 89442428 b804000000 486bc004 }
$sequence_15 = { 4883c708 4883c304 49ffc3 48ffcd 0f854fffffff 488d4c2420 }
condition:
7 of them
}
rule cobaltstrike_beacon_4_2_decrypt
{
meta:
author = "Elastic"
description = "Identifies deobfuscation routine used in Cobalt Strike Beacon DLL version 4.2."
strings:
$a_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}
$a_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
any of them
}
export: |
LET Profile = '''[
[CobaltConfig, 0, [
Expand Down Expand Up @@ -331,6 +380,9 @@ export: |
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')
Expand All @@ -352,7 +404,7 @@ sources:
len(list=X)
FROM XorChars
LET XorCharsStep3 =
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) +
Expand All @@ -364,18 +416,38 @@ sources:
regex_replace(
source=FindConfigTemplate,
re='REPLACEME',
replace=join(array=XorCharsStep3.Line, sep=" $$"))
replace=join(array=YaraStrings.Line, sep=" $$"))
-- find target files
LET TargetFiles = SELECT FullPath FROM glob(globs=TargetFileGlob)
-- scan files in scope with our rule
LET file_hits = SELECT * FROM foreach(row=TargetFiles,
LET FileDetections = SELECT * FROM foreach(row=TargetFiles,
query={
SELECT * FROM if(condition=DetectionYara,
then={
SELECT * FROM switch(
a={ -- yara detection
SELECT FullPath, unique(values=enumerate(items=Rule)).value as Detections
FROM yara(files=FullPath, rules=DetectionYara)
GROUP BY FullPath
},
b={ -- yara miss
SELECT FullPath, Null as Detections FROM TargetFiles
})
},
else={ -- no yara detection run
SELECT FullPath, 'N/A' as Detections FROM TargetFiles
})
})
-- scan files in scope with our rule
LET FileConfiguration = SELECT * FROM foreach(row=FileDetections,
query={
SELECT
FullPath as ConfigSource,
dict(FullPath=FullPath,Detection=Detections) as ConfigSource,
String.Offset as Offset,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(filename=FullPath,
Expand All @@ -384,6 +456,9 @@ sources:
FROM yara(files=FullPath, rules=FindConfig, number=99)
})
-- find velociraptor process
LET me <= SELECT Pid FROM pslist(pid=getpid())
Expand All @@ -395,20 +470,41 @@ sources:
AND format(format="%d", args=Pid) =~ PidRegex
AND NOT Pid in me.Pid
-- scan processes in scope with our Detection
LET ProcessDetections = SELECT * FROM foreach(row=processes,
query={
SELECT * FROM if(condition=DetectionYara,
then={
SELECT * FROM switch(
a={ -- yara detection
SELECT ProcessName, CommandLine, Pid, unique(values=enumerate(items=Rule)).value as Detections
FROM yara(files=Pid,accessor='process', rules=DetectionYara)
GROUP BY Pid
},
b={ -- yara miss
SELECT ProcessName, CommandLine, Pid, Null as Detections FROM scope()
})
},
else={ -- no yara detection run
SELECT ProcessName, CommandLine, Pid, 'N/A' as Detections FROM scope()
})
})
-- scan processes in scope with our rule
LET process_hits = SELECT * FROM foreach(
row=processes,
LET ProcessConfiguration = SELECT * FROM foreach(
row=ProcessDetections,
query={
SELECT *,
dict(Pid=Pid, ProcessName=ProcessName,CommandLine=CommandLine) as ConfigSource,
dict(Pid=Pid,ProcessName=ProcessName,CommandLine=CommandLine,
Detection=Detections) as ConfigSource,
String.Offset as Offset,
substr(start=0,end=1,str=String.Data) as Xor,
read_file(filename=str(str=Pid), accessor='process',
offset=String.Offset,
length=int(int=ExtractBytes)
) as Data
FROM yara(files=Pid, accessor='process',
rules=FindConfig, number=999)
rules=FindConfig, number=99)
})
-- output rows
Expand All @@ -422,5 +518,5 @@ sources:
struct="CobaltConfig") AS DecodedConfig,
format(format="% x", args=xor(string=Data,key=Xor)) as DecodedData
FROM if(condition=TargetFileGlob,
then= file_hits,
else= process_hits)
then= FileConfiguration,
else= ProcessConfiguration)
23 changes: 21 additions & 2 deletions artifacts/testdata/server/testcases/cobalt.in.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
Parameters:
TestYara: |
rule CobaltStrike_v3 {
strings:
strings:
$a = { 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 2e 2e 2e }
$b = { 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e 2e }
condition: $a
}
rule CobaltStrike_v3_2 {
strings: $a = { 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 }
condition: $a
}
Queries:
- SELECT relpath(path=ConfigSource, base=srcDir, sep="/") as TestPath,
- SELECT relpath(path=ConfigSource.FullPath, base=srcDir, sep="/") as TestPath,ConfigSource.Detection,
Offset, Xor, DecodedConfig,DecodedData
FROM Artifact.Windows.Carving.CobaltStrike(TargetFileGlob=srcDir + "/artifacts/testdata/files/CSDump.bin")
FROM Artifact.Windows.Carving.CobaltStrike(TargetFileGlob=srcDir + "/artifacts/testdata/files/CSDump.bin",DetectionYara=TestYara)
WHERE Xor = "0x69"
- SELECT relpath(path=ConfigSource.FullPath, base=srcDir, sep="/") as TestPath,ConfigSource.Detection,
Offset, Xor, DecodedConfig,DecodedData
FROM Artifact.Windows.Carving.CobaltStrike(TargetFileGlob=srcDir + "/artifacts/testdata/files/CSDump.bin",DetectionYara='')
Loading

0 comments on commit 02a4afc

Please sign in to comment.