ONNX
security
malware-detection
File size: 5,793 Bytes
d2507b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package bundle

import (
	"bytes"
	"debug/elf"
	"debug/macho"
	"strings"
)

// BinaryAnalyzer inspects native binaries (.so/.dylib/.node, magic-sniffed
// ELF/Mach-O) and .wasm. It confirms the format via debug/elf + debug/macho,
// pulls section/symbol/import names and printable strings, and scans them for
// exfil host / env-var names / dangerous symbols. It ALWAYS emits
// 'ships-opaque-executable' (Opaque, Structural) but is PRECISION-AWARE: base
// SevMedium (native code is common in legit skills), raised to SevHigh+
// Corroborated only when suspicious strings/symbols, hidden placement, or
// padding are present, and SevCritical when the defanged exfil host appears.
// isKnownBenignNativePattern downgrades signed/known wheels. Never panics on
// truncated headers.
type BinaryAnalyzer struct{}

func (BinaryAnalyzer) Name() string { return "binary" }

func (BinaryAnalyzer) Handles(kind FileKind) bool {
	return kind == KindNativeBinary || kind == KindWasm
}

// dangerousSymbols are import/symbol names that, present in a shipped binary,
// indicate it can read env, open sockets, load code, or shell out.
var dangerousSymbols = []string{
	"dlopen", "system", "popen", "execve", "execl", "fork",
	"getenv", "secure_getenv", "socket", "connect", "sendto",
	"curl_easy", "ptrace", "mprotect", "ld_preload",
	"createprocess", "winexec", "urldownloadtofile", "winhttp",
}

func (BinaryAnalyzer) Analyze(f *File, b *Bundle) ([]Finding, error) {
	if f == nil {
		return nil, nil
	}

	strs := printableStrings(f.Sniff, 4)
	syms := extractBinarySymbols(f.Sniff, f.Kind)
	hay := strings.ToLower(strings.Join(strs, "\n") + "\n" + strings.Join(syms, "\n"))

	// Base structural finding: native code is opaque.
	base := Finding{
		Analyzer:   "binary",
		File:       f.RelPath,
		Signal:     "ships-opaque-executable",
		Severity:   SevMedium,
		Detail:     "ships a native binary / wasm module that cannot be fully analyzed",
		Opaque:     true,
		Structural: true,
	}

	var out []Finding
	var corroborated bool

	// Exfil host inside the binary => critical.
	if exfilHostRe.MatchString(hay) {
		out = append(out, Finding{
			Analyzer:     "binary",
			File:         f.RelPath,
			Signal:       "exfil-host-reference",
			Severity:     SevCritical,
			Detail:       "native binary embeds known exfiltration host",
			Corroborated: true,
		})
		base.Severity = SevCritical
		base.Corroborated = true
		corroborated = true
	}

	// Dangerous symbols/imports + an env/secret string => suspicious native code.
	hasDangerousSym := matchedAny(hay, dangerousSymbols)
	hasEnvString := matchedAny(hay, envSourceTerms)
	if hasDangerousSym && hasEnvString {
		out = append(out, Finding{
			Analyzer:     "binary",
			File:         f.RelPath,
			Signal:       "suspicious-native-symbols",
			Severity:     SevHigh,
			Detail:       "native binary imports process/network/dlopen symbols and embeds credential/env strings",
			Corroborated: true,
		})
		if base.Severity < SevHigh {
			base.Severity = SevHigh
			base.Corroborated = true
		}
		corroborated = true
	}

	// Hidden placement or padding are corroborating factors on their own.
	if f.Hidden && !corroborated {
		base.Severity = SevHigh
		base.Corroborated = true
		base.Detail += " (hidden placement)"
	}
	if f.Truncated && f.NewlineRatio >= 0.30 {
		out = append(out, Finding{
			Analyzer:     "binary",
			File:         f.RelPath,
			Signal:       "padding-evasion",
			Severity:     SevHigh,
			Detail:       "binary exceeded read cap with a high newline ratio (padding)",
			Corroborated: true,
		})
	}

	// Known-benign native pattern downgrades the structural finding (but never a
	// real exfil-host hit, which already set Critical above).
	if base.Severity == SevMedium && isKnownBenignNativePattern(f) {
		base.Severity = SevLow
		base.Detail += " (matches known-benign native pattern)"
	}

	out = append(out, base)
	return dedupeFindings(out), nil
}

// extractBinarySymbols confirms the format and pulls section/symbol/import names
// from ELF and Mach-O. Degrades to nil on truncated/invalid headers (never
// panics). WASM has no symbol table here; printable strings cover it.
func extractBinarySymbols(data []byte, kind FileKind) []string {
	var out []string
	defer func() { _ = recover() }()

	r := bytes.NewReader(data)

	if ef, err := elf.NewFile(r); err == nil {
		for _, s := range ef.Sections {
			out = append(out, s.Name)
		}
		if syms, err := ef.ImportedSymbols(); err == nil {
			for _, s := range syms {
				out = append(out, s.Name)
			}
		}
		if libs, err := ef.ImportedLibraries(); err == nil {
			out = append(out, libs...)
		}
		if dyn, err := ef.DynString(elf.DT_NEEDED); err == nil {
			out = append(out, dyn...)
		}
		return dedupeStrings(out)
	}

	r2 := bytes.NewReader(data)
	if mf, err := macho.NewFile(r2); err == nil {
		for _, s := range mf.Sections {
			out = append(out, s.Name)
		}
		if mf.Symtab != nil {
			for _, s := range mf.Symtab.Syms {
				out = append(out, s.Name)
			}
		}
		if libs, err := mf.ImportedLibraries(); err == nil {
			out = append(out, libs...)
		}
		if syms, err := mf.ImportedSymbols(); err == nil {
			out = append(out, syms...)
		}
		return dedupeStrings(out)
	}

	return out
}

// printableStrings extracts runs of >= minRun printable ASCII bytes from a blob,
// like the unix `strings` tool. Bounded by the input length and a result cap.
func printableStrings(data []byte, minRun int) []string {
	if minRun < 1 {
		minRun = 4
	}
	var out []string
	var cur []byte
	const maxResults = 20000
	flush := func() {
		if len(cur) >= minRun {
			out = append(out, string(cur))
		}
		cur = cur[:0]
	}
	for _, bb := range data {
		if bb >= 0x20 && bb < 0x7f {
			cur = append(cur, bb)
			continue
		}
		flush()
		if len(out) >= maxResults {
			break
		}
	}
	flush()
	return out
}