FIX: Refine UPX heuristic detection to differentiate packed vs unpacked samples

This PR improves the generic heuristic detection for UPX-packed PE binaries to
correctly handle both true UPX-packed samples and manually unpacked samples
that still have UPX section naming and structure.

**Problem:**
Generic heuristics in __GenericHeuristicAnalysis_By_DosX.7.sg were flagging
manually unpacked binaries with UPX section names as "packed," producing false positives
on legitimate unpacked samples.

**Solution:**
Add FileSize checks to distinguish:
* True UPX packed: section[0].FileSize === 0 (empty section)
* Unpacked with UPX names: section[0].FileSize !== 0 (actual code present)

**Changes:**

1. **"Sections like UPX" heuristic**
   - Skip reporting if section name is "UPX" AND section[0] is non-empty

2. **"Sections collision (UPX)" heuristic**
   - Skip reporting if collision is "UPX" AND section[0] is non-empty

3. **"Section #0 has RWX" heuristic**
   - Skip reporting if section 0 is UPX[0-3] AND section[0] is non-empty
   - RWX is normal for "UPX0" section in unpacked UPX binaries

**Result:**
* Unpacked UPX samples: no false positives
* True UPX-packed samples: still detected

**Testing:**
Tested with:
* Manually unpacked UPX samples (with retained section names)
* True UPX-packed binaries
* Verified no regression on other packer/protector detection
This commit is contained in:
yosef khaled shehata 2026-05-07 18:47:18 +03:00
commit 953bbbabb2

View file

@ -2845,12 +2845,16 @@ function scanForPackersAndCryptors_NET_and_Native() { // For .NET and Native app
var versionBySectionDetected = String();
if (sectionNamesValidatingResult != null) {
versionBySectionDetected = sectionNamesValidatingResult[1];
log(logType.nothing, "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + versionBySectionDetected + ")" : String()));
isSectionNameLikePacker = true;
if (sectionNamesValidatingResult != null) {
if (!(
(sectionNamesValidatingResult[0] === "UPX" ) &&
(PE.section[0].FileSize !== 0))
)
{
versionBySectionDetected = sectionNamesValidatingResult[1];
log(logType.nothing, "Sections like " + sectionNamesValidatingResult[0] + (versionBySectionDetected ? " (v" + versionBySectionDetected + ")" : String()));
isSectionNameLikePacker = true;
}
}
// Clean up: release the dictionary
@ -2915,8 +2919,10 @@ function scanForPackersAndCryptors_NET_and_Native() { // For .NET and Native app
isCollisionInSectionsPresent = true;
}
if (isCollisionInSectionsPresent) options = addOption(options, "Sections collision (\"" + clearSectionName(sectionNameCollision) + "\")");
if (isCollisionInSectionsPresent &&
!(sectionNameCollision === "UPX" && PE.section[0].FileSize !== 0)) {
options = addOption(options, "Sections collision (\"" + clearSectionName(sectionNameCollision) + "\")");
}
@ -4652,8 +4658,10 @@ function scanForObfuscations_Native() {
}
}
if (isRwxSectionPresent) options = addOption(options, "Section #" + rwxSectionIndex + " (\"" + clearSectionName(PE.getSectionName(rwxSectionIndex)) + "\") has RWX");
if (isRwxSectionPresent &&
!(rwxSectionIndex === 0 && /^UPX[0-3]$/.test(PE.getSectionName(rwxSectionIndex)) && PE.section[0].FileSize !== 0)) {
options = addOption(options, "Section #" + rwxSectionIndex + " (\"" + clearSectionName(PE.getSectionName(rwxSectionIndex)) + "\") has RWX");
}