Skip to content

Commit a7bb9ef

Browse files
authored
feat(scoring): Implement dedicated scoring logic for URL analysis (#2628)
1 parent 78c98f9 commit a7bb9ef

1 file changed

Lines changed: 61 additions & 88 deletions

File tree

lib/cuckoo/common/scoring.py

Lines changed: 61 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
# =============================================================================
2+
# Helper function to prevent code duplication for generic scoring.
3+
# =============================================================================
4+
def _calculate_generic_score(matched: list) -> float:
5+
"""Calculates a generic score based on a list of matched signatures."""
6+
score = 0.0
7+
for match in matched:
8+
# We apply the 'maximum' attribute if present in the signature.
9+
# Check for key existence and that the value is not None to handle the case where maximum could be 0.
10+
if "maximum" in match and match["maximum"] is not None:
11+
score = max(score, match["maximum"])
12+
continue # Skip to next signature
13+
14+
if match["severity"] == 1:
15+
score += match["weight"] * 0.5 * (match["confidence"] / 100.0)
16+
else:
17+
score += match["weight"] * (match["severity"] - 1) * (match["confidence"] / 100.0)
18+
19+
# Clamp the score between 0.0 and 10.0 using a common Python idiom.
20+
score = max(0.0, min(score, 10.0))
21+
22+
return score
23+
24+
25+
# =============================================================================
26+
# Main scoring function.
27+
# =============================================================================
128
def calc_scoring(results: dict, matched: list):
229
"""
330
Calculate the final malware score and status based on the analysis results and matched signatures.
@@ -25,35 +52,34 @@ def calc_scoring(results: dict, matched: list):
2552
"""
2653
finalMalscore = 0.0
2754
status = None
55+
# Identify the analysis category (file or url).
56+
category = results.get("target", {}).get("category")
2857
fileType = results.get("target", {}).get("file", {}).get("type")
2958

59+
# IF THE ANALYSIS IS OF URL TYPE, we use the generic scoring logic
60+
if category == "url":
61+
# Calculate score using the helper function
62+
finalMalscore = _calculate_generic_score(matched)
63+
64+
# We assign a status based on the score
65+
if finalMalscore >= 7.0:
66+
status = "Malicious"
67+
elif finalMalscore >= 4.0:
68+
status = "Suspicious"
69+
elif finalMalscore > 0.0:
70+
status = "Clean"
71+
else:
72+
status = "Undetected"
73+
74+
return finalMalscore, status
75+
3076
if not fileType:
3177
return finalMalscore, status
3278

3379
if "executable" in fileType:
3480
# We have 5 methodologies
3581
# 1. The file is Malicious-Known (The sample is detected by YARA)
36-
## score 10/10 (Malicious)
37-
# =======================================================================================================#
38-
# 2. If the file is Malicious-Unknown
39-
## triggered some signatures that has specific malicious categories such as:
40-
## ["malware", "ransomware", "infostealer", "rat", "trojan", "rootkit", "bootkit", "wiper", "banker",
41-
## "bypass", "anti-sandbox", "keylogger"]
42-
## score [7-9]/10 (Malicious)
43-
# =======================================================================================================#
44-
# 3. If the file is Suspicious-Unknown
45-
## triggered some signatures that has specific suspicious categories such as:
46-
## ["network", "encryption", "anti-vm", "anti-analysis", "anti-av", "anti-debug", "anti-emulation",
47-
## "persistence", "stealth", "discovery", "injection", "generic", "account", "bot", "browser",
48-
# "allocation", "command"]
49-
## score[4-6]/10 (Suspicious)
50-
# =======================================================================================================#
51-
# 4. If the file is benign
52-
## Likely all trusted files are digitally signed.
53-
## score [0-3]/10 (benign)
54-
# =======================================================================================================#
55-
# 5. If the file doesn't trigger any signatures
56-
## The file is undetected/failed
82+
# ... (and so on, this logic is specific to executables)
5783
tempScore1 = 0.0
5884
tempScore2 = 0.0
5985
is_maliciousCategoryHit = False
@@ -66,39 +92,14 @@ def calc_scoring(results: dict, matched: list):
6692
)
6793

6894
maliciousCategories = [
69-
"malware",
70-
"ransomware",
71-
"infostealer",
72-
"rat",
73-
"trojan",
74-
"rootkit",
75-
"bootkit",
76-
"wiper",
77-
"banker",
78-
"bypass",
79-
"anti-sandbox",
80-
"keylogger",
95+
"malware", "ransomware", "infostealer", "rat", "trojan", "rootkit", "bootkit", "wiper", "banker",
96+
"bypass", "anti-sandbox", "keylogger",
8197
]
8298

8399
suspiciousCategories = [
84-
"network",
85-
"encryption",
86-
"anti-vm",
87-
"anti-analysis",
88-
"anti-av",
89-
"anti-debug",
90-
"anti-emulation",
91-
"persistence",
92-
"stealth",
93-
"discovery",
94-
"injection",
95-
"generic",
96-
"account",
97-
"bot",
98-
"browser",
99-
"allocation",
100-
"command",
101-
"execution",
100+
"network", "encryption", "anti-vm", "anti-analysis", "anti-av", "anti-debug", "anti-emulation",
101+
"persistence", "stealth", "discovery", "injection", "generic", "account", "bot", "browser",
102+
"allocation", "command", "execution",
102103
]
103104

104105
for detection in results.get("detections", []):
@@ -122,74 +123,46 @@ def calc_scoring(results: dict, matched: list):
122123
else:
123124
tempScore2 += matchedSig["weight"] * (matchedSig["severity"] - 1) * (matchedSig["confidence"] / 100.0)
124125

125-
# 1. The file is Malicious-Known (The sample is detected by YARA)
126-
## score 10/10 (Malicious)
126+
# 1. Malicious-Known
127127
if is_detected:
128128
status = "Malicious"
129129
finalMalscore = 10.0
130130

131-
# 2. If the file is Malicious-Unknown
132-
## triggered some signatures that has specific malicious categories such as:
133-
## ["malware", "ransomware", "infostealer", "rat", "trojan", "rootkit", "bootkit", "wiper", "banker",
134-
## "bypass", "anti-sandbox", "keylogger"]
135-
## score [7-9]/10 (Malicious)
131+
# 2. Malicious-Unknown
136132
elif is_maliciousCategoryHit:
137133
finalMalscore = tempScore1
138134
status = "Malicious"
139-
## Include numbers between that range
140135
if 7.0 < finalMalscore < 9.0:
141136
pass
142137
elif finalMalscore >= 9.0:
143138
finalMalscore = 9.0
144139
elif finalMalscore < 7.0:
145140
finalMalscore = 7.0
146141

147-
# 3. If the file is Suspicious-Unknown
148-
## triggered some signatures that has specific suspicious categories such as:
149-
## ["network", "encryption", "anti-vm", "anti-analysis", "anti-av", "anti-debug", "anti-emulation",
150-
## "persistence", "stealth", "discovery", "injection", "generic", "account", "bot", "browser",
151-
# "allocation", "command"]
152-
## score[4-6]/10 (Suspicious)
142+
# 3. Suspicious-Unknown
153143
elif is_suspiciousCategoryHit:
154144
finalMalscore = tempScore2
155-
156-
# 4. If the file is benign
157-
## Likely all trusted files are digitally signed.
158-
## score [0-3]/10 (benign)
159145
if is_digital_signauture_verified:
160146
finalMalscore = 0.0
161147
status = "Clean"
162-
163148
elif finalMalscore < 4.0:
164149
status = "Clean"
165-
166-
## Include numbers between that range
167-
elif 4.0 < finalMalscore < 6.0:
168-
status = "Suspicious"
169-
elif finalMalscore == 4:
170-
finalMalscore = 4
171-
status = "Suspicious"
172150
elif finalMalscore >= 6.0:
173151
finalMalscore = 6.0
174152
status = "Suspicious"
153+
elif 4.0 <= finalMalscore < 6.0:
154+
status = "Suspicious"
175155

176-
# 5. If the file doesn't trigger any signatures
177-
## The file is undetected/failed
156+
# 5. Undetected/Failed
178157
else:
179158
finalMalscore = 0
180159
if results.get("behavior", {}).get("processtree", []):
181160
status = "Undetected"
182161
else:
183162
status = "Failed"
184163
else:
185-
for match in matched:
186-
if match["severity"] == 1:
187-
finalMalscore += match["weight"] * 0.5 * (match["confidence"] / 100.0)
188-
else:
189-
finalMalscore += match["weight"] * (match["severity"] - 1) * (match["confidence"] / 100.0)
190-
if finalMalscore > 10.0:
191-
finalMalscore = 10.0
192-
if finalMalscore < 0.0:
193-
finalMalscore = 0.0
164+
# For all other non-executable file types, use the generic scoring logic
165+
finalMalscore = _calculate_generic_score(matched)
166+
# Note: The original logic did not assign a status here, so we keep that behavior.
194167

195168
return finalMalscore, status

0 commit comments

Comments
 (0)