diff --git a/README.md b/README.md index a23d4cb9..a56538de 100644 --- a/README.md +++ b/README.md @@ -61,33 +61,26 @@ bincapz /bin/ping There are flags for controlling output (see the Usage section) and filtering out rules. Here's the `--format=markdown` output: - -| RISK | KEY | DESCRIPTION | -|-------|-----------------------|--------------------------------------------------------| -| meta | entitlements | com.apple.private.network.management.data.development | -| | | com.apple.security.network.client | -| | | com.apple.security.network.server | -| meta | format | macho | -| | | | -| 1/LOW | net/hostname/resolve | resolves network hosts via name | -| 1/LOW | net/icmp | iCMP (Internet Control Message Protocol), aka ping | -| 1/LOW | net/interface/get | get network interfaces by name or index | -| 1/LOW | net/interface/list | list network interfaces and their associated addresses | -| 1/LOW | net/ip | access the internet | -| 1/LOW | net/ip/multicast/send | send data to multiple nodes simultaneously | -| 1/LOW | net/ip/resolve | resolves network hosts via IP address | -| 1/LOW | net/ip/send/unicast | send data to the internet | -| 1/LOW | net/socket/connect | initiate a connection on a socket | -| 1/LOW | net/socket/receive | receive a message from a socket | -| 1/LOW | net/socket/send | send a message to a socket | -| 1/LOW | process/userid/set | set real and effective user ID of current process | -| 2/MED | combo/net/scan_tool | may scan networks: "connect | -| | | gethostbyname | -| | | port | -| | | scan | -| | | socket" | -| 2/MED | net/ip/string | converts IP address from byte to string | - +{Overall risk: ⚠️ MEDIUM + +| RISK | KEY | DESCRIPTION | EVIDENCE | +|----------|----------------------------|---------------------------------------------------------------------------|------------------------------| +| 2/MEDIUM | combo/net/raw_flooder | raw sockets with multiple targets, possible DoS or security scanning tool | flood
raw socket
srand | +| 2/MEDIUM | combo/recon/system_network | invasive recon val | ipv4=addr
ipv6=addr | +| 2/MEDIUM | net/interface/list | list network interfaces | freeifaddrs
getifaddrs | +| 2/MEDIUM | net/ip/parse | parses IP address (IPv4 or IPv6) | inet_pton | +| 2/MEDIUM | net/ip/string | converts IP address from byte to string | inet_ntoa
inet_ntop | +| 2/MEDIUM | net/raw_sockets | able to send raw malformed IP packets | SOCK_RAW
raw socket | +| 1/LOW | net/hostport/parse | network address and service translation | freeaddrinfo
getaddrinfo | +| 1/LOW | net/icmp | iCMP (Internet Control Message Protocol), aka ping | ICMP | +| 1/LOW | net/interface/get | get network interfaces by name or index | if_nametoindex | +| 1/LOW | net/ip/multicast/send | send data to multiple nodes simultaneously | multicast | +| 1/LOW | net/ip/send/unicast | send data to the internet | unicast | +| 1/LOW | net/socket/local/address | get local address of connected socket | getsockname | +| 1/LOW | net/socket/receive | receive a message from a socket | recvmsg | +| 1/LOW | net/socket/send | send a message to a socket | sendmsg
sendto | +| 1/LOW | process/userid/set | set real and effective user ID of current process | setuid | +| 1/LOW | random/insecure | generate random numbers insecurely | srand | Behaviors are sorted by lowest to highest risk: this binary doesn't have anything particularly exciting about it. If you want to only show output for the most suspicious behaviors, use `--min-level=3`, which shows only "HIGH" or "CRITICAL" risk behaviors. @@ -103,27 +96,32 @@ Here is a result using the 3CX compromise as a test case. Each of the lines that ## 🐙 changed behaviors: testdata/macOS/libffmpeg.dirty.dylib -| RISK | KEY | DESCRIPTION | -|---------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| -| +1/LOW | **compression/gzip** | works with gzip files | -| +1/LOW | **env/HOME** | looks up the HOME directory for the current user | -| +1/LOW | **fs/lock/update** | apply or remove an advisory lock on a file | -| +1/LOW | **kernel/dispatch/semaphore** | uses Dispatch Semaphores | -| +1/LOW | **kernel/hostname/get** | gets the hostname of the machine | -| +1/LOW | **net/http/accept/encoding** | able to decode multiple forms of HTTP responses (example: gzip) | -| +1/LOW | **random/insecure** | generate random numbers insecurely | -| +1/LOW | **sync/semaphore/user** | uses semaphores to synchronize data between processes or threads | -| +2/MED | **exec/pipe** | uses popen to launch a program and pipe output to/from it | -| +2/MED | **fs/permission/modify** | modifies file permissions | -| +2/MED | **net/http/cookies** | able to access HTTP resources using cookies | -| +2/MED | **net/url/request** | requests resources via URL | -| +2/MED | **ref/path/hidden** | references a hidden file that can be generated dynamically: "%s/.main_storage" | -| +2/MED | **shell/arbitrary_command/dev_null** | runs arbitrary commands redirecting output to /dev/null | -| +4/CRIT | **3P/godmoderules/iddqd/god/mode** | detects a wide array of cyber threats, from malware and ransomware to advanced persistent threats (APTs), by Florian Roth | -| +4/CRIT | **3P/signature_base/3cxdesktopapp/backdoor** | detects 3CXDesktopApp MacOS Backdoor component, by X__Junior (Nextron Systems) | -| +4/CRIT | **3P/signature_base/nk/3cx** | detects malicious DYLIB files related to 3CX compromise, by Florian Roth (Nextron Systems) | -| +4/CRIT | **3P/signature_base/susp/xored** | detects suspicious single byte XORed keyword 'Mozilla/5.0' - it uses yara's XOR modifier and therefore cannot print the XOR key, by Florian Roth | -| +4/CRIT | **3P/volexity/iconic** | detects the MACOS version of the ICONIC loader., by threatintel@volexity.com +Previous Risk: ⚠️ MEDIUM +New Risk: 🚨 CRITICAL + +| RISK | KEY | DESCRIPTION | EVIDENCE | +|-------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| +4/CRITICAL | **3P/signature_base/3cxdesktopapp/backdoor** | detects 3CXDesktopApp MacOS Backdoor component, by X__Junior (Nextron Systems) | $op1
$op2
%s/.main_storage
%s/UpdateAgent | +| +4/CRITICAL | **3P/signature_base/nk/3cx** | detects malicious DYLIB files related to 3CX compromise, by Florian Roth (Nextron Systems) | $xc1
$xc2
$xc3 | +| +4/CRITICAL | **3P/signature_base/susp/xored** | detects suspicious single byte XORed keyword 'Mozilla/5.0' - it uses yara's XOR modifier and therefore cannot print the XOR key, by Florian Roth | $xo1 | +| +4/CRITICAL | **3P/volexity/iconic** | detects the MACOS version of the ICONIC loader., by threatintel@volexity.com | $str1
$str2
$str3 | +| +4/CRITICAL | **evasion/xor/user_agent** | xOR'ed user agent, often found in backdoors, by Florian Roth | $Mozilla_5_0 | +| +2/MEDIUM | **exec/pipe** | launches program and reads its output | _pclose
_popen | +| +2/MEDIUM | **fs/permission/modify** | modifies file permissions | chmod | +| +2/MEDIUM | **net/http/cookies** | able to access HTTP resources using cookies | Cookie
HTTP | +| +2/MEDIUM | **net/url/request** | requests resources via URL | NSMutableURLRequest | +| +2/MEDIUM | **ref/path/hidden** | hidden path generated dynamically | %s/.main_storage | +| +2/MEDIUM | **shell/arbitrary_command/dev_null** | runs commands, discards output | "%s" >/dev/null | +| +1/LOW | **compression/gzip** | works with gzip files | gzip | +| +1/LOW | **env/HOME** | looks up the HOME directory for the current user | HOME
getenv | +| +1/LOW | **fs/lock/update** | apply or remove an advisory lock on a file | flock | +| +1/LOW | **kernel/dispatch/semaphore** | uses Dispatch Semaphores | dispatch_semaphore_signal | +| +1/LOW | **kernel/hostname/get** | gets the hostname of the machine | gethostname | +| +1/LOW | **net/http/accept/encoding** | able to decode multiple forms of HTTP responses (example: gzip) | Accept-Encoding | +| +1/LOW | **random/insecure** | generate random numbers insecurely | _rand
srand | +| +1/LOW | **ref/path/home_library** | path reference within ~/Library | /System/Library/Frameworks/CoreFoundation
/System/Library/Frameworks/Foundation | +| +1/LOW | **sync/semaphore/user** | uses semaphores to synchronize data between processes or threads | semaphore_create
semaphore_signal
semaphore_wait + If you like to do things the hard way, you can also store the JSON output and diff the keys by hand: @@ -150,7 +148,7 @@ bincapz --format=json | jq '.Files.[].Behaviors | keys' bincapz behaves similarly to the initial triage step most security analysts use when faced with an unknown binary: a cursory `strings` inspection. bincapz has several advantages over human analysis: the ability to match raw byte sequences, decrypt data, and a library of 12,000+ YARA rules that combines the experience of security engineers worldwide. -This strategy works, as every program leaves traces of its capabilities in its contents, particularly on UNIX platforms. These fragments are typically `libc` or `syscall` references or error codes. Scripting languages are easier to analyze due to their cleartext nature and are also supported. +This strategy works, as every program leaves traces of its capabilities in its contents, particularly on UNIX platforms. These fragments are typically `libc` or `syscall` references or error codes. Scripting languages are easier to analyze due to their cleartext nature and are also supported. ### Why not properly reverse-engineer binaries? diff --git a/pkg/bincapz/bincapz.go b/pkg/bincapz/bincapz.go index bb0cda03..de6ded72 100644 --- a/pkg/bincapz/bincapz.go +++ b/pkg/bincapz/bincapz.go @@ -5,8 +5,6 @@ package bincapz type Behavior struct { Description string `json:",omitempty" yaml:",omitempty"` - // Values are critical values to be surfaced in the UI - Values []string `json:",omitempty" yaml:",omitempty"` // MatchStrings are all strings found relating to this behavior MatchStrings []string `json:",omitempty" yaml:",omitempty"` RiskScore int diff --git a/pkg/render/markdown.go b/pkg/render/markdown.go index bb3f20bf..fd859ceb 100644 --- a/pkg/render/markdown.go +++ b/pkg/render/markdown.go @@ -94,14 +94,6 @@ func markdownTable(_ context.Context, fr *bincapz.FileReport, w io.Writer, rc ta data := [][]string{} - for k, v := range fr.Meta { - data = append(data, []string{"meta", k, v}) - } - if len(data) > 0 { - data = append(data, []string{"", "", ""}) - } - - maxDescWidth := 180 for _, k := range kbs { desc := k.Behavior.Description before, _, found := strings.Cut(desc, ". ") @@ -116,17 +108,6 @@ func markdownTable(_ context.Context, fr *bincapz.FileReport, w io.Writer, rc ta } } - if len(k.Behavior.Values) > 0 { - values := strings.Join(k.Behavior.Values, "\n") - before := " \"" - after := "\"" - if (len(desc) + len(values) + 3) > maxDescWidth { - before = "\n" - after = "" - } - desc = fmt.Sprintf("%s:%s%s%s", desc, before, strings.Join(k.Behavior.Values, "\n"), after) - } - // lowercase first character for consistency desc = strings.ToLower(string(desc[0])) + desc[1:] risk := fmt.Sprintf("%d/%s", k.Behavior.RiskScore, k.Behavior.RiskLevel) @@ -141,11 +122,13 @@ func markdownTable(_ context.Context, fr *bincapz.FileReport, w io.Writer, rc ta if strings.HasPrefix(risk, "+") { key = fmt.Sprintf("**%s**", key) } - data = append(data, []string{risk, key, desc}) + + evidence := strings.Join(k.Behavior.MatchStrings, "
") + data = append(data, []string{risk, key, desc, evidence}) } table := tablewriter.NewWriter(w) table.SetAutoWrapText(false) - table.SetHeader([]string{"Risk", "Key", "Description"}) + table.SetHeader([]string{"Risk", "Key", "Description", "Evidence"}) table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) table.SetCenterSeparator("|") table.AppendBulk(data) // Add Bulk Data diff --git a/pkg/report/report.go b/pkg/report/report.go index 9597c73c..72319c8c 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -202,47 +202,6 @@ func matchToString(ruleName string, m yara.MatchString) string { return strings.TrimSpace(s) } -// extract important values. -func matchValues(key string, ruleName string, ms []yara.MatchString) []string { - raw := []string{} - - keyHasCombo := strings.Contains(key, "combo/") - keyHasRef := strings.Contains(key, "ref/") - keyHasXor := strings.Contains(key, "xor/") - keyHasBase64 := strings.Contains(key, "base64/") - ruleHasValue := strings.Contains(ruleName, "value") - ruleHasVal := strings.HasSuffix(ruleName, "val") - - for _, m := range ms { - keep := false - - switch { - case strings.HasSuffix(m.Name, "val"): - keep = true - case keyHasCombo: - keep = true - case keyHasRef: - keep = true - case keyHasXor: - keep = true - case keyHasBase64: - keep = true - case ruleHasValue: - keep = true - case ruleHasVal: - keep = true - } - if !keep { - continue - } - - raw = append(raw, matchToString(ruleName, m)) - } - - slices.Sort(raw) - return longestUnique(raw) -} - // extract match strings. func matchStrings(ruleName string, ms []yara.MatchString) []string { raw := []string{} @@ -317,7 +276,6 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, ignoreTags b := bincapz.Behavior{ RiskScore: risk, RiskLevel: RiskLevels[risk], - Values: matchValues(key, m.Rule, m.Strings), MatchStrings: matchStrings(m.Rule, m.Strings), } @@ -352,10 +310,6 @@ func Generate(ctx context.Context, path string, mrs yara.MatchRules, ignoreTags if strings.HasPrefix(key, "meta/") { k := strings.ReplaceAll(filepath.Dir(key), "meta/", "") v := filepath.Base(key) - if len(b.Values) > 0 { - k = strings.ReplaceAll(key, "meta/", "") - v = strings.Join(b.Values, "\n") - } fr.Meta[k] = v continue diff --git a/samples/macOS/2023.3CX/libffmpeg.dirty.mdiff b/samples/macOS/2023.3CX/libffmpeg.dirty.mdiff index 66ce1cd5..8050c6a2 100644 --- a/samples/macOS/2023.3CX/libffmpeg.dirty.mdiff +++ b/samples/macOS/2023.3CX/libffmpeg.dirty.mdiff @@ -3,27 +3,26 @@ Previous Risk: ⚠️ MEDIUM New Risk: 🚨 CRITICAL -| RISK | KEY | DESCRIPTION | -|-------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| -| +4/CRITICAL | **3P/signature_base/3cxdesktopapp/backdoor** | detects 3CXDesktopApp MacOS Backdoor component, by X__Junior (Nextron Systems) | -| +4/CRITICAL | **3P/signature_base/nk/3cx** | detects malicious DYLIB files related to 3CX compromise, by Florian Roth (Nextron Systems) | -| +4/CRITICAL | **3P/signature_base/susp/xored** | detects suspicious single byte XORed keyword 'Mozilla/5.0' - it uses yara's XOR modifier and therefore cannot print the XOR key, by Florian Roth | -| +4/CRITICAL | **3P/volexity/iconic** | detects the MACOS version of the ICONIC loader., by threatintel@volexity.com | -| +4/CRITICAL | **evasion/xor/user_agent** | xOR'ed user agent, often found in backdoors, by Florian Roth: "$Mozilla_5_0" | -| +2/MEDIUM | **exec/pipe** | launches program and reads its output | -| +2/MEDIUM | **fs/permission/modify** | modifies file permissions | -| +2/MEDIUM | **net/http/cookies** | able to access HTTP resources using cookies | -| +2/MEDIUM | **net/url/request** | requests resources via URL | -| +2/MEDIUM | **ref/path/hidden** | hidden path generated dynamically: "%s/.main_storage" | -| +2/MEDIUM | **shell/arbitrary_command/dev_null** | runs commands, discards output | -| +1/LOW | **compression/gzip** | works with gzip files | -| +1/LOW | **env/HOME** | looks up the HOME directory for the current user | -| +1/LOW | **fs/lock/update** | apply or remove an advisory lock on a file | -| +1/LOW | **kernel/dispatch/semaphore** | uses Dispatch Semaphores | -| +1/LOW | **kernel/hostname/get** | gets the hostname of the machine | -| +1/LOW | **net/http/accept/encoding** | able to decode multiple forms of HTTP responses (example: gzip) | -| +1/LOW | **random/insecure** | generate random numbers insecurely | -| +1/LOW | **ref/path/home_library** | path reference within ~/Library: "/System/Library/Frameworks/CoreFoundation | -| | | /System/Library/Frameworks/Foundation" | -| +1/LOW | **sync/semaphore/user** | uses semaphores to synchronize data between processes or threads | +| RISK | KEY | DESCRIPTION | EVIDENCE | +|-------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| +4/CRITICAL | **3P/signature_base/3cxdesktopapp/backdoor** | detects 3CXDesktopApp MacOS Backdoor component, by X__Junior (Nextron Systems) | $op1
$op2
%s/.main_storage
%s/UpdateAgent | +| +4/CRITICAL | **3P/signature_base/nk/3cx** | detects malicious DYLIB files related to 3CX compromise, by Florian Roth (Nextron Systems) | $xc1
$xc2
$xc3 | +| +4/CRITICAL | **3P/signature_base/susp/xored** | detects suspicious single byte XORed keyword 'Mozilla/5.0' - it uses yara's XOR modifier and therefore cannot print the XOR key, by Florian Roth | $xo1 | +| +4/CRITICAL | **3P/volexity/iconic** | detects the MACOS version of the ICONIC loader., by threatintel@volexity.com | $str1
$str2
$str3 | +| +4/CRITICAL | **evasion/xor/user_agent** | xOR'ed user agent, often found in backdoors, by Florian Roth | $Mozilla_5_0 | +| +2/MEDIUM | **exec/pipe** | launches program and reads its output | _pclose
_popen | +| +2/MEDIUM | **fs/permission/modify** | modifies file permissions | chmod | +| +2/MEDIUM | **net/http/cookies** | able to access HTTP resources using cookies | Cookie
HTTP | +| +2/MEDIUM | **net/url/request** | requests resources via URL | NSMutableURLRequest | +| +2/MEDIUM | **ref/path/hidden** | hidden path generated dynamically | %s/.main_storage | +| +2/MEDIUM | **shell/arbitrary_command/dev_null** | runs commands, discards output | "%s" >/dev/null | +| +1/LOW | **compression/gzip** | works with gzip files | gzip | +| +1/LOW | **env/HOME** | looks up the HOME directory for the current user | HOME
getenv | +| +1/LOW | **fs/lock/update** | apply or remove an advisory lock on a file | flock | +| +1/LOW | **kernel/dispatch/semaphore** | uses Dispatch Semaphores | dispatch_semaphore_signal | +| +1/LOW | **kernel/hostname/get** | gets the hostname of the machine | gethostname | +| +1/LOW | **net/http/accept/encoding** | able to decode multiple forms of HTTP responses (example: gzip) | Accept-Encoding | +| +1/LOW | **random/insecure** | generate random numbers insecurely | _rand
srand | +| +1/LOW | **ref/path/home_library** | path reference within ~/Library | /System/Library/Frameworks/CoreFoundation
/System/Library/Frameworks/Foundation | +| +1/LOW | **sync/semaphore/user** | uses semaphores to synchronize data between processes or threads | semaphore_create
semaphore_signal
semaphore_wait | diff --git a/samples/macOS/2024.SpectralBlur.DPRK/SpectralBlur-macshare.md b/samples/macOS/2024.SpectralBlur.DPRK/SpectralBlur-macshare.md index d4568658..23d710ee 100644 --- a/samples/macOS/2024.SpectralBlur.DPRK/SpectralBlur-macshare.md +++ b/samples/macOS/2024.SpectralBlur.DPRK/SpectralBlur-macshare.md @@ -2,35 +2,27 @@ Overall risk: 🔥 HIGH -| RISK | KEY | DESCRIPTION | -|----------|-------------------------|-----------------------------------------------------| -| meta | format | macho | -| | | | -| 3/HIGH | combo/backdoor/net_term | uploads, provides a terminal, runs program: "_uname | -| | | _unlink | -| | | _waitpid | -| | | execve | -| | | shell | -| | | tcsetattr | -| | | upload" | -| 2/MEDIUM | device/pseudo_terminal | pseudo-terminal access functions | -| 2/MEDIUM | exec/program | executes external programs | -| 2/MEDIUM | kernel/uname/get | get system identification | -| 2/MEDIUM | net/download | download files | -| 2/MEDIUM | net/ip/parse | parses IP address | -| 2/MEDIUM | net/ip/string | converts IP address from byte to string | -| 2/MEDIUM | net/socket/connect | initiate a connection on a socket | -| 2/MEDIUM | net/upload | uploads files | -| 2/MEDIUM | shell/exec | executes shell | -| 1/LOW | env/SHELL | users preferred SHELL path | -| 1/LOW | exec/program/background | wait for process to exit | -| 1/LOW | fs/file/delete | deletes files | -| 1/LOW | fs/symlink/resolve | resolves symbolic links | -| 1/LOW | net/hostname/resolve | resolves network hosts via name | -| 1/LOW | net/socket/receive | receive a message from a socket | -| 1/LOW | net/socket/send | send a message to a socket | -| 1/LOW | process/create | create a new child process using fork | -| 1/LOW | process/multithreaded | uses pthreads | -| 1/LOW | process/username/get | get login name | -| 1/LOW | random/insecure | generate random numbers insecurely | +| RISK | KEY | DESCRIPTION | EVIDENCE | +|----------|-------------------------|--------------------------------------------|-------------------------------------------------------------------------| +| 3/HIGH | combo/backdoor/net_term | uploads, provides a terminal, runs program | _uname
_unlink
_waitpid
execve
shell
tcsetattr
upload | +| 2/MEDIUM | device/pseudo_terminal | pseudo-terminal access functions | grantpt
posix_openpt
ptsname
unlockpt | +| 2/MEDIUM | exec/program | executes external programs | execve | +| 2/MEDIUM | kernel/uname/get | get system identification | uname | +| 2/MEDIUM | net/download | download files | _proc_download_content | +| 2/MEDIUM | net/ip/parse | parses IP address | inet_addr | +| 2/MEDIUM | net/ip/string | converts IP address from byte to string | inet_ntoa | +| 2/MEDIUM | net/socket/connect | initiate a connection on a socket | _connect | +| 2/MEDIUM | net/upload | uploads files | upload | +| 2/MEDIUM | shell/exec | executes shell | /bin/sh | +| 1/LOW | env/SHELL | users preferred SHELL path | SHELL | +| 1/LOW | exec/program/background | wait for process to exit | waitpid | +| 1/LOW | fs/file/delete | deletes files | unlink | +| 1/LOW | fs/symlink/resolve | resolves symbolic links | realpath | +| 1/LOW | net/hostname/resolve | resolves network hosts via name | gethostbyname | +| 1/LOW | net/socket/receive | receive a message from a socket | _recv | +| 1/LOW | net/socket/send | send a message to a socket | _send | +| 1/LOW | process/create | create a new child process using fork | _fork | +| 1/LOW | process/multithreaded | uses pthreads | pthread_create | +| 1/LOW | process/username/get | get login name | getlogin | +| 1/LOW | random/insecure | generate random numbers insecurely | _rand
srand |