Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve detection of machO backdoors & stealers #631

Merged
merged 9 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pkg/action/testdata/scan_archive
Original file line number Diff line number Diff line change
Expand Up @@ -1100,8 +1100,8 @@
"MatchStrings": [
"/home/sha2561.32.11.33.01.33.11.33.21.34.01.34.11.35.01.36.0ID"
],
"RiskScore": 2,
"RiskLevel": "MEDIUM",
"RiskScore": 1,
"RiskLevel": "LOW",
"RuleURL": "https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/path/home.yara#home_path",
"ID": "fs/path/home",
"RuleName": "home_path"
Expand Down Expand Up @@ -1911,8 +1911,8 @@
],
"RiskScore": 2,
"RiskLevel": "MEDIUM",
"RuleURL": "https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/content-length-0.yara#content_length_0",
"ID": "net/http/content_length_0",
"RuleURL": "https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/content-length.yara#content_length_0",
"ID": "net/http/content_length",
"RuleName": "content_length_0"
},
{
Expand Down
6 changes: 5 additions & 1 deletion pkg/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,16 @@ var rulesWithWarnings = map[string]bool{
"osascript_quitter": true,
"exfil_libcurl_elf": true,
"small_opaque_archaic_gcc": true,
"elf_hardcoded_ip": true,
"bin_hardcoded_ip": true,
"python_hex_decimal": true,
"python_long_hex": true,
"python_long_hex_multiple": true,
"pam_passwords": true,
"decompress_base64_entropy": true,
"macho_opaque_binary": true,
"macho_opaque_binary_long_str": true,
"long_str": true,
"macho_backdoor_libc_signature": true,
}

func Recursive(ctx context.Context, fss []fs.FS) (*yara.Rules, error) {
Expand Down
44 changes: 0 additions & 44 deletions rules/anti-static/base64/eval.yara
Original file line number Diff line number Diff line change
Expand Up @@ -57,47 +57,3 @@ rule python_exec_near_enough: high {
condition:
all of them and math.abs(@base64 - @exec) < 200
}

rule echo_decode_bash_probable: high {
meta:
description = "likely pipes base64 into a shell"

strings:
$echo = "echo" fullword
$base64_decode = "base64 --decode"
$base64_d = "base64 -d"
$bash = "bash" fullword
$sh = "sh" fullword
$not_uucp = "UUCP" fullword
$not_git = "git-core"
$not_copyright = "Copyright (c)"
$not_syntax = "syntax file"

condition:
filesize < 15KB and $echo and ($bash or $sh) and ($base64_decode or $base64_d) and none of ($not*)
}

rule acme_sh: override {
meta:
description = "acme.sh"
echo_decode_bash_probable = "medium"
iplookup_website = "medium"

strings:
$ref = "https://github.com/acmesh-official"

condition:
$ref
}

rule echo_decode_bash: critical {
meta:
description = "executes base64 encoded shell commands"

strings:
$bash = /[\w=\$]{0,8} ?\| ?base64 -d[ecod]{0,5} ?\| ?bash/
$sh = /[\w=\$]{0,8} ?\| ?base64 -d[ecod]{0,5} ?\| ?z?sh/

condition:
filesize < 64KB and any of them
}
50 changes: 49 additions & 1 deletion rules/anti-static/base64/exec.yara
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ rule base64_commands: high {
$b_tar_c = "tar -c" base64
$b_tar_x = "tar -x" base64
$b_bash_c = "bash -c" base64
$b_type_nul = "type nul" base64
$not_kandji = "kandji-parameter-agent"
$not_mdmprofile = "mdmprofile"
$not_example = "commands are encoded"
Expand All @@ -46,3 +45,52 @@ rule base64_suspicious_commands: critical {
condition:
filesize < 64KB and any of them
}

rule base64_exec: critical {
meta:
description = "executes base64 encoded commands"

strings:
$os_system = /os\.system\(b64[\"\'\(\)\w\=]{3,96}/ fullword

condition:
any of them
}

rule echo_decode_bash: critical {
meta:
description = "executes base64 encoded shell commands"

strings:
$ref = /base64 {0,2}(-d|--decode) {0,2}\| {0,2}(bash|zsh|sh)/ fullword

condition:
filesize < 256KB and any of them
}

import "math"

rule echo_decode_bash_probable: high {
meta:
description = "likely pipes base64 into a shell"

strings:
$decode = /base64 {0,2}(-d|--decode)/ fullword
$shell = /(bash|zsh|sh)/ fullword

condition:
filesize < 256KB and any of them and (@shell[#shell] - @decode[#decode]) < 32 and (@shell[#shell] - @decode[#decode]) > 0
}

rule acme_sh: override {
meta:
description = "acme.sh"
echo_decode_bash_probable = "medium"
iplookup_website = "medium"

strings:
$ref = "https://github.com/acmesh-official"

condition:
$ref
}
27 changes: 26 additions & 1 deletion rules/anti-static/binary/opaque.yara
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import "math"

rule opaque_binary: medium {
meta:
description = "binary contains little text content"

strings:
$word_with_spaces = /[a-z]{2,} [a-z]{2,}/
$word_with_spaces = /[a-z]{2,16} [a-uxyz]{2,16}/ fullword
$not_gmon_start = "__gmon_start__"
$not_usage = "usage:" fullword
$not_usage2 = "Usage:" fullword
Expand All @@ -13,3 +15,26 @@ rule opaque_binary: medium {
condition:
filesize < 52428800 and (uint32(0) == 1179403647 or uint32(0) == 4277009102 or uint32(0) == 3472551422 or uint32(0) == 4277009103 or uint32(0) == 3489328638 or uint32(0) == 3405691582 or uint32(0) == 3199925962) and #word_with_spaces < 4 and none of ($not*)
}

rule mystery_regex_binary: high {
meta:
description = "opaque binary with suspicious libc calls and regex usage"

strings:
$f_environ = "environ" fullword
$f_chdir = "chdir" fullword
$f_fork = "fork" fullword
$f_fopen = "fopen" fullword
$f_fwrite = "fwrite" fullword
$f_mkdir = "mkdir" fullword
$f_opendir = "opendir" fullword
$f_rand = "rand" fullword
$f_popen = "popen" fullword
$f_readdir = "readdir" fullword
$f_srand = "srand" fullword
$f_regexec = "regexec" fullword
$f_umask = "umask" fullword

condition:
filesize < 512KB and opaque_binary and math.entropy(1, filesize) >= 3.6 and all of them
}
6 changes: 3 additions & 3 deletions rules/anti-static/elf/entropy.yara
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ private rule small_elf {
filesize < 400KB and uint32(0) == 1179403647
}

rule normal_elf_high_entropy_7: medium {
rule higher_elf_entropy_68: medium {
meta:
description = "higher entropy ELF binary (>7.1)"
description = "higher entropy ELF binary (>6.95)"

condition:
normal_elf and math.entropy(1, filesize) >= 7.1
normal_elf and math.entropy(1, filesize) >= 6.95
}

rule normal_elf_high_entropy_7_4: high {
Expand Down
12 changes: 6 additions & 6 deletions rules/anti-static/macho/entropy.yara
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ private rule smaller_macho {
filesize < 64MB and (uint32(0) == 4277009102 or uint32(0) == 3472551422 or uint32(0) == 4277009103 or uint32(0) == 3489328638 or uint32(0) == 3405691582 or uint32(0) == 3199925962)
}

rule high_entropy_7_2: medium {
rule higher_entropy_6_9: medium {
meta:
description = "higher entropy binary (>7.2)"
description = "higher entropy binary (>6.9)"

condition:
smaller_macho and math.entropy(1, filesize) >= 7.2
smaller_macho and math.entropy(1, filesize) >= 6.9
}

rule high_entropy_7_9: high {
rule high_entropy_7_2: high {
meta:
description = "high entropy binary (>7.9)"
description = "high entropy binary (>7.2)"

strings:
// prevent bazel false positive
$bin_java = "bin/java"

condition:
smaller_macho and math.entropy(1, filesize) >= 7.9 and not $bin_java
smaller_macho and math.entropy(1, filesize) >= 7.2 and not $bin_java
}
4 changes: 2 additions & 2 deletions rules/anti-static/obfuscation/js.yara
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ rule js_hex_obfuscation: critical {

rule high_entropy: medium {
meta:
description = "high entropy javascript (>5.37)"
description = "high entropy javascript (>6)"

condition:
probably_js and math.entropy(1, filesize) >= 5.37
probably_js and math.entropy(1, filesize) >= 6
}

rule very_high_entropy: critical {
Expand Down
10 changes: 10 additions & 0 deletions rules/anti-static/obfuscation/sh.yara
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rule echo_base64_decode: high {
meta:
description = "echo and decode base64 text"

strings:
$ref = /echo [\w=\$]{2,256} {0,2}\| {0,2}base64 {0,2}(-d|--decode)/ fullword

condition:
filesize < 256KB and any of them
}
11 changes: 9 additions & 2 deletions rules/c2/addr/ip.yara
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ rule hardcoded_ip: medium {
$not_1_2_3_4 = "1.2.3.4"
$not_root_servers_h = "128.63.2.53"
$not_root_servers_i = "192.36.148.17"
$not_send_att = "3.2.5.7"

condition:
filesize < 200MB and 1 of ($sus_ip*) and none of ($not*)
}

rule elf_hardcoded_ip: high {
private rule elf_or_macho {
condition:
uint32(0) == 1179403647 or (uint32(0) == 4277009102 or uint32(0) == 3472551422 or uint32(0) == 4277009103 or uint32(0) == 3489328638 or uint32(0) == 3405691582 or uint32(0) == 3199925962 or uint32(0) == 3405691583 or uint32(0) == 3216703178)
}

rule bin_hardcoded_ip: high {
meta:
description = "ELF with hardcoded IP address"

Expand All @@ -36,9 +42,10 @@ rule elf_hardcoded_ip: high {
$not_123456789 = "123.45.67.89"
$not_10_11_12_13 = "10.11.12.13"
$not_libebt_among_init = "libebt_among_init"
$not_send_att = "3.2.5.7"

condition:
filesize < 12MB and uint32(0) == 1179403647 and 1 of ($sus_ip*) and none of ($not*)
filesize < 12MB and elf_or_macho and 1 of ($sus_ip*) and none of ($not*)
}

rule http_hardcoded_ip: high exfil {
Expand Down
11 changes: 11 additions & 0 deletions rules/c2/connect/curl_easy.yara
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
rule curl_easy: medium {
meta:
description = "uses curl_easy for HTTP transfers, possibly to a C2"

strings:
$curl = "curl_easy_init" fullword

condition:
filesize < 1MB and all of them
}

10 changes: 10 additions & 0 deletions rules/collect/databases/sql.yara
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rule sql: low {
meta:
description = "accesses SQL databases"

strings:
$ref = "SQL" fullword

condition:
any of them
}
2 changes: 2 additions & 0 deletions rules/crypto/tls.yara
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ rule tls {
$tlsversion = "TLSVersion"
$TLS123 = "TLS13"
$tls123 = "tls123"
$require = "require(\"tls\")"
$require2 = "require('tls')"

condition:
any of them
Expand Down
2 changes: 1 addition & 1 deletion rules/data/random/bytes.yara
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ rule rand {
$ref = ".randomBytes(" fullword

condition:
$ref
any of them
}
1 change: 0 additions & 1 deletion rules/discover/system/hardware.yara
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ rule macos_hardware_profiler: medium {

strings:
$p_system_profiler = "system_profiler SPHardwareDataType"
$p_uuid = "IOPlatformUUID"
$p_ioreg = "ioreg -"
$p_hw_model = "hw.model"
$p_machineid = "machineid.ID"
Expand Down
1 change: 1 addition & 0 deletions rules/discover/system/hostname.yara
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rule gethostname {
$proc = "/proc/sys/kernel/hostname"
$python = "socket.gethostname"
$nodejs = "os.hostname()"
$js = "os.default.hostname"

condition:
any of them
Expand Down
10 changes: 10 additions & 0 deletions rules/evasion/indicator_blocking/close_window.yara
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rule tell_terminal_to_close: high {
meta:
description = "closes Terminal window"

strings:
$close = "tell application \"Terminal\" to close first window"

condition:
filesize < 10MB and all of them
}
Loading