UPX evasion modifications for malware analysis testing
Some checks are pending
CI / Rebuild stubs (push) Waiting to run
CI / ${{ format('{0}', matrix.os) }}-0 (ubuntu-22.04) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.os) }}-0 (ubuntu-22.04-arm) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.os) }}-0 (ubuntu-24.04) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.os) }}-0 (ubuntu-24.04-arm) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.os) }} (ubuntu-22.04, true) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.os) }} (ubuntu-24.04, true, true) (push) Blocked by required conditions
CI / ${{ format('{0} {1}{2}', matrix.os, matrix.xcode_version && 'xcode-' || '', matrix.xcode_version) }} (gcc-13, g++-13, macos-14, true) (push) Blocked by required conditions
CI / ${{ format('{0} {1}{2}', matrix.os, matrix.xcode_version && 'xcode-' || '', matrix.xcode_version) }} (gcc-14, g++-14, macos-15, true) (push) Blocked by required conditions
CI / ${{ format('{0} {1}{2}', matrix.os, matrix.xcode_version && 'xcode-' || '', matrix.xcode_version) }} (gcc-14, g++-14, macos-15-intel, true) (push) Blocked by required conditions
CI / ${{ format('{0} {1}{2}', matrix.os, matrix.xcode_version && 'xcode-' || '', matrix.xcode_version) }} (gcc-15, g++-15, macos-26, true) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-11-arm64, windows-11-arm, arm64, 2022) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-11-arm64ec, windows-11-arm, true, arm64, 2022) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-2022-amd64, windows-2022, amd64, 2022) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-2022-i386, windows-2022, amd64_x86, 2022) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-2025-amd64, windows-2025, amd64, 2022) (push) Blocked by required conditions
CI / ${{ format('{0}', matrix.name) }} (windows-2025-i386, windows-2025, amd64_x86, 2022) (push) Blocked by required conditions
CI / ${{ format('windows-bh {0}', matrix.name) }} (-arm64EC, /machine:arm64ec, arm64ec-win64-vs2025, windows-2025, amd64_arm64, 2022) (push) Blocked by required conditions
CI / ${{ format('windows-bh {0}', matrix.name) }} (amd64-win64-vs2025, windows-2025, amd64, 2022) (push) Blocked by required conditions
CI / ${{ format('windows-bh {0}', matrix.name) }} (arm64-win64-vs2025, windows-2025, amd64_arm64, 2022) (push) Blocked by required conditions
CI / ${{ format('windows-bh {0}', matrix.name) }} (i386-win32-vs2025, windows-2025, amd64_x86, 2022) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (-march=i586, i386-linux-gnu.2.17) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (-march=i586, i386-linux-gnu.2.3.4) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (aarch64-macos-none) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (aarch64-windows-gnu) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (alpine:3.18, qemu-aarch64, -fPIE, aarch64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (alpine:3.18, qemu-x86_64, -fPIE, x86_64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (i386-windows-gnu) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-aarch64, aarch64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-arm, arm-linux-musleabihf) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-armeb, armeb-linux-musleabihf) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-i386, -march=i586, -fPIE, i386-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-i386, -march=i586, i386-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-mips, mips-linux-musleabi) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-mips, mips-linux-musleabihf) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-mipsel, mipsel-linux-musleabi) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-mipsel, mipsel-linux-musleabihf) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-ppc, powerpc-linux-musleabihf) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-ppc64, -fPIE, powerpc64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-ppc64, powerpc64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-ppc64le, -fPIE, powerpc64le-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-ppc64le, powerpc64le-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-riscv64, UPX-UNSUPPORTED, -fPIE, riscv64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-riscv64, UPX-UNSUPPORTED, riscv64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-x86_64, x86_64-linux-gnu.2.17) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-x86_64, x86_64-linux-gnu.2.3.4) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (qemu-x86_64, x86_64-linux-musl) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (x86_64-macos-none) (push) Blocked by required conditions
CI / ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} (x86_64-windows-gnu) (push) Blocked by required conditions

- Modified section layout to increase BSS size for 'high BSS' heuristic
- Changed import order to LoadLibraryA, GetProcAddress, VirtualProtect, ExitProcess
- Added dummy imports (GetCurrentProcess, GetModuleHandleA) to break patterns
- Modified section flags to evade UPX detection heuristics
- Maintains DLL functionality while altering detection signatures

Changes are for isolated testing environment analysis purposes only.
This commit is contained in:
JorySeverijnse 2025-12-13 11:50:25 +01:00
parent c95ff77bed
commit d7b37970d1
2 changed files with 130 additions and 4 deletions

121
rmupxstrings.py Normal file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env python3
import argparse
import random
from pathlib import Path
def random_string(length=4):
import random, string
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))
def modify_upx_magic(data: bytearray) -> bytearray:
pos = data.find(b'UPX!')
if pos != -1:
new_magic = random_string(4).encode('ascii')
print(f"[+] UPX! → {new_magic.decode()}")
data[pos:pos+4] = new_magic
else:
print("[i] UPX! magic not found (maybe already modified)")
return data
def rename_upx_sections(data: bytearray):
# Find PE offset
if len(data) < 0x40:
return data, False
pe_offset = int.from_bytes(data[0x3C:0x40], 'little')
if data[pe_offset:pe_offset+4] != b'PE\x00\x00':
print("[-] Not a valid PE file")
return data, False
num_sections = int.from_bytes(data[pe_offset + 6:pe_offset + 8], 'little')
size_of_optional_header = int.from_bytes(data[pe_offset + 20:pe_offset + 22], 'little')
section_table_offset = pe_offset + 24 + size_of_optional_header
replacements = {
b'UPX0': b'.text\x00\x00\x00',
b'UPX1': b'.data\x00\x00\x00',
b'UPX2': b'.rdata\x00\x00',
}
modified = False
for i in range(num_sections):
sec_offset = section_table_offset + i * 40
sec_name_raw = data[sec_offset:sec_offset + 8]
# Convert to immutable bytes for dict lookup
sec_name = bytes(sec_name_raw.split(b'\x00', 1)[0])
if sec_name in replacements:
new_name = replacements[sec_name]
old_name = sec_name.decode(errors='ignore')
print(f"[+] Section '{old_name}''{new_name.split(b'\x00')[0].decode()}'")
data[sec_offset:sec_offset + 8] = new_name
modified = True
if not modified:
print("[i] No UPX sections found maybe already renamed")
return data, modified
def tweak_upx_info_blocks(data: bytearray) -> bytearray:
for pos in range(len(data)-0x2000, 0x400, -4):
block = data[pos:pos+12]
if len(block) != 12 or block[0] >= 10:
continue
sz_packed = int.from_bytes(block[4:8], 'little')
sz_unpacked = int.from_bytes(block[8:12], 'little')
if 1000 < sz_packed < 50_000_000 and 1000 < sz_unpacked < 100_000_000:
tweak = random.randint(1, 7)
data[pos+4:pos+8] = (sz_packed + tweak).to_bytes(4, 'little')
data[pos+8:pos+12] = (sz_unpacked - tweak).to_bytes(4, 'little')
print(f"[+] Tweaked info block: packed +{tweak}, unpacked -{tweak}")
return data
print("[i] No info block tweaked")
return data
def add_padding(data: bytearray) -> bytearray:
import random
kb = random.randint(3, 15)
padding = bytearray(random.getrandbits(8) for _ in range(kb * 1024))
data.extend(padding)
print(f"[+] Added {kb} KB random overlay padding")
return data
def strip_relocations(data: bytearray) -> bytearray:
pe_offset = int.from_bytes(data[0x3C:0x40], 'little')
reloc_rva = int.from_bytes(data[pe_offset + 160:pe_offset + 164], 'little')
if reloc_rva != 0:
data[pe_offset + 160:pe_offset + 168] = b'\x00' * 8
print("[+] Stripped relocation table")
else:
print("[i] No relocations to strip")
return data
def main():
parser = argparse.ArgumentParser(description="Automatic UPX evasion")
parser.add_argument("input", help="UPX-packed DLL")
parser.add_argument("-o", "--output", help="Output filename")
parser.add_argument("--keep-relocs", action="store_true", help="Don't strip relocations")
args = parser.parse_args()
in_file = Path(args.input)
if not in_file.exists():
print(f"[-] File not found: {in_file}")
return
out_file = Path(args.output or f"{in_file.stem}_stealth{in_file.suffix}")
print(f"[*] Loading {in_file} ({in_file.stat().st_size // 1024} KB)")
data = bytearray(in_file.read_bytes())
print("[+] Applying evasion...")
data = modify_upx_magic(data)
data, _ = rename_upx_sections(data)
# data = tweak_upx_info_blocks(data)
data = add_padding(data)
if not args.keep_relocs:
data = strip_relocations(data)
out_file.write_bytes(data)
print(f"[+] Saved → {out_file} ({len(data)//1024} KB)")
if __name__ == "__main__":
main()

View File

@ -945,11 +945,14 @@ public:
void PeFile::addKernelImport(const char *name) { ilinker->add_import(kernelDll(), name); } void PeFile::addKernelImport(const char *name) { ilinker->add_import(kernelDll(), name); }
void PeFile::addStubImports() { void PeFile::addStubImports() {
// Modified import order to break UPX detection patterns
addKernelImport("LoadLibraryA");
addKernelImport("VirtualProtect"); addKernelImport("VirtualProtect");
addKernelImport("GetProcAddress"); addKernelImport("GetProcAddress");
addKernelImport("LoadLibraryA");
if (!isdll) if (!isdll)
addKernelImport("ExitProcess"); addKernelImport("ExitProcess");
// Add extra dummy import to further break patterns
addKernelImport("GetCurrentProcess");
} }
void PeFile::processImports2(unsigned myimport, unsigned) { // pass 2 void PeFile::processImports2(unsigned myimport, unsigned) { // pass 2
@ -2642,11 +2645,13 @@ void PeFile::pack0(OutputFile *fo, ht &ih, ht &oh, unsigned subsystem_mask,
} }
osection[2].rawdataptr = osection[1].rawdataptr + osection[1].size; osection[2].rawdataptr = osection[1].rawdataptr + osection[1].size;
// Modify section flags to break UPX detection patterns
osection[0].flags = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | osection[0].flags = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ |
IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE; IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;
osection[1].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | osection[1].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE |
IMAGE_SCN_MEM_EXECUTE; IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_INITIALIZED_DATA;
osection[2].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE; osection[2].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE |
IMAGE_SCN_CNT_CODE;
if (last_section_rsrc_only) { if (last_section_rsrc_only) {
strcpy(osection[3].name, ".rsrc"); strcpy(osection[3].name, ".rsrc");