Communitygithub.com

UPX 언패커를 활용한 패킹된 악성코드 분석

UPX로 패킹된 악성코드 샘플을 식별하고 언패킹하여, 수정된 UPX 매직 바이트나 헤더로 인해 자동 압축 해제가 차단된 바이너리에서도 원본 실행 파일을 복구하여 정적 분석에 활용합니다.

UPX 언패커를 활용한 패킹된 악성코드 분석란 무엇인가요?

이 스킬은 UPX와 같은 알려진 패커로 패킹된 악성코드 샘플을 분석하고 언패킹하는 포괄적인 워크플로우를 제공합니다. 높은 엔트로피, 최소한의 임포트, LoadLibrary/GetProcAddress만으로 구성된 임포트 테이블 등 패킹된 바이너리의 특징을 식별하는 진단 단계부터 시작합니다. Detect It Easy, UPX 명령어, Python 스크립트를 사용한 섹션 엔트로피 및 임포트 분석을 통해 패커를 확인합니다. 이후 표준 `upx -d` 명령어로 압축 해제를 시도하고, 실패 시 수정된 UPX 매직 바이트와 섹션 이름을 복구하는 Python 헤더 복구 스크립트를 제공하며, 자동 도구가 완전히 실패할 경우 x64dbg를 이용한 수동 언패킹 절차까지 포함합니다. 마지막으로 언패킹된 바이너리의 유효성을 검증하고 Ghidra나 IDA에서 정적 분석을 진행합니다. 이 스킬은 PE-bear, CFF Explorer, Scylla와 같은 PE 분석 및 임포트 복구 도구의 사용법을 포함하며, 실제 악성코드 분석 시나리오를 다루어 실무에 바로 적용 가능합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mukul975/Anthropic-Cybersecurity-Skills/tree/main/skills/analyzing-packed-malware-with-upx-unpacker

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

UPX 패킹 악성코드 분석 (UPX Unpacker)

사용 시점

  • 정적 분석 결과 높은 엔트로피 섹션과 최소한의 임포트가 확인되어 바이너리가 패킹되었음을 나타낼 때
  • PEiD, Detect It Easy, PEStudio에서 UPX 또는 다른 알려진 패커를 식별했을 때
  • 임포트 테이블에 LoadLibrary와 GetProcAddress만 존재할 때 (런타임 임포트 해결은 패킹된 바이너리의 일반적인 특징)
  • Ghidra나 IDA에서 올바른 디스어셈블리 및 디컴파일을 위해 원본 바이너리를 복구해야 할 때
  • 악성코드 제작자가 UPX 매직 바이트나 헤더를 수정하여 자동 UPX 압축 해제가 실패할 때

사용하지 말아야 할 경우: 커스텀 패커, VM 기반 프로텍터(Themida, VMProtect), 디버깅을 통한 동적 언패킹이 더 적합한 샘플을 다룰 때.

사전 요구 사항

  • UPX(Ultimate Packer for eXecutables) 설치 (apt install upx-ucl 또는 https://upx.github.io/에서 다운로드)
  • 패커 식별을 위한 Detect It Easy (DIE)
  • 수동 헤더 복구를 위한 Python 3.8+ 및 pefile 라이브러리
  • 자동 도구 실패 시 수동 언패킹을 위한 x64dbg 또는 x32dbg
  • PE 헤더 검사 및 복구를 위한 PE-bear 또는 CFF Explorer
  • 네트워크 연결이 없는 격리된 분석 VM

워크플로우

1단계: 패커 식별

샘플이 패킹되었는지 확인하고 패커를 식별합니다:

# Detect It Easy로 확인
diec suspect.exe

# UPX로 확인 (언패킹 없이 테스트)
upx -t suspect.exe

# Python 기반 엔트로피 및 패커 탐지
python3 << 'PYEOF'
import pefile
import math

pe = pefile.PE("suspect.exe")

print("섹션 분석:")
for section in pe.sections:
    name = section.Name.decode().rstrip('\x00')
    entropy = section.get_entropy()
    raw = section.SizeOfRawData
    virtual = section.Misc_VirtualSize
    print(f"  {name:8s} 엔트로피: {entropy:.2f}  원시 크기: {raw:>8}  가상 크기: {virtual:>8}")

# UPX 섹션 이름 확인
section_names = [s.Name.decode().rstrip('\x00') for s in pe.sections]
if 'UPX0' in section_names or 'UPX1' in section_names:
    print("\n[!] UPX 섹션 이름 감지됨")
elif '.upx' in [s.lower() for s in section_names]:
    print("\n[!] UPX 변형 섹션 이름 감지됨")

# 임포트 개수 확인 (패킹된 바이너리는 매우 적음)
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
    total_imports = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
    print(f"\n총 임포트: {total_imports}")
    if total_imports < 10:
        print("[!] 임포트가 매우 적음 - 패킹되었을 가능성 높음")
else:
    print("\n[!] 임포트 디렉터리 없음 - 심하게 패킹됨")
PYEOF

2단계: 표준 UPX 압축 해제 시도

내장된 UPX 압축 해제 기능을 시도합니다:

# 표준 UPX 압축 해제
upx -d suspect.exe -o unpacked.exe

# UPX가 "UPX로 패킹되지 않음" 오류를 반환하면 헤더가 수정된 것일 수 있음
# 디버깅을 위한 상세 출력
upx -d suspect.exe -o unpacked.exe -v 2>&1

# 언패킹된 파일 확인
file unpacked.exe
diec unpacked.exe

3단계: 수정된 UPX 헤더 복구

표준 압축 해제가 실패하면 변조된 매직 바이트를 복구합니다:

# 수정된 UPX 헤더 복구
import struct

with open("suspect.exe", "rb") as f:
    data = bytearray(f.read())

# UPX 매직 바이트: "UPX!" (0x55505821)
# 악성코드 제작자는 자동 언패킹을 방지하기 위해 이를 수정하는 경우가 많음

# 수정된 UPX 시그니처 검색
upx_magic = b"UPX!"
modified_patterns = [b"UPX0", b"UPX\x00", b"\x00PX!", b"UPx!"]

# PE 오프셋 찾기 및 섹션 이름 복원
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
num_sections = struct.unpack_from("<H", data, pe_offset + 6)[0]
section_table_offset = pe_offset + 0x18 + struct.unpack_from("<H", data, pe_offset + 0x14)[0]

print(f"PE 오프셋: 0x{pe_offset:X}")
print(f"섹션 개수: {num_sections}")
print(f"섹션 테이블 오프셋: 0x{section_table_offset:X}")

for i in range(num_sections):
    offset = section_table_offset + (i * 40)
    name = data[offset:offset+8]
    print(f"섹션 {i}: {name}")

# 바이너리 내 UPX 매직 바이트 복원
# (일반적으로 패킹된 데이터 끝 부분 근처)
for i in range(len(data) - 4):
    if data[i:i+3] == b"UPX" and data[i+3] != ord("!"):
        print(f"오프셋 0x{i:X}에서 수정된 UPX 매직 발견: {data[i:i+4]}")
        data[i:i+4] = b"UPX!"
        print(f"다음으로 복원됨: UPX!")

# 수정된 섹션 이름도 복원
for i in range(num_sections):
    offset = section_table_offset + (i * 40)
    name = data[offset:offset+8].rstrip(b'\x00')
    if name in [b"UPX0", b"UPX1", b"UPX2"]:
        continue  # 이미 올바름
    # 일반적인 변형 확인
    if name.startswith(b"UP") or name.startswith(b"ux"):
        original = f"UPX{i}".encode().ljust(8, b'\x00')
        data[offset:offset+8] = original
        print(f"오프셋 0x{offset:X}에서 섹션 이름을 {original}(으)로 복원")

with open("suspect_fixed.exe", "wb") as f:
    f.write(data)

print("\n수정된 파일이 기록되었습니다. 재시도: upx -d suspect_fixed.exe -o unpacked.exe")

4단계: 디버거를 사용한 수동 언패킹

자동 언패킹이 완전히 실패하면 동적 언패킹을 사용합니다:

x64dbg를 사용한 수동 UPX 언패킹:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 패킹된 샘플을 x64dbg에 로드합니다.
2. 진입점까지 실행합니다 (시스템 중단점 후 F9).
3. UPX 언패킹 스텁 패턴:
   a. PUSHAD (모든 레지스터 저장)
   b. 압축 해제 루프 (패킹된 섹션 처리)
   c. 임포트 해결 (LoadLibrary/GetProcAddress 호출)
   d. POPAD (레지스터 복원)
   e. JMP to OEP (원본 진입점)
4. PUSHAD 후 ESP에 하드웨어 중단점 설정:
   - PUSHAD 후, 레지스터 창에서 ESP를 마우스 오른쪽 버튼으로 클릭 -> Follow in Dump
   - [ESP] 주소에 액세스 하드웨어 중단점 설정
   - 실행 (F9) - POPAD에서 중단, 이후 JMP to OEP
5. OEP로의 JMP에 도달할 때까지 전진 (F7/F8)
6. OEP에서: Scylla 플러그인을 사용하여 덤프 및 임포트 수정:
   - Plugins -> Scylla -> OEP = 현재 EIP
   - "IAT Autosearch" 클릭 -> "Get Imports" 클릭
   - "Dump"를 클릭하여 언패킹된 바이너리 저장
   - "Fix Dump"를 클릭하여 임포트 테이블 복구

5단계: 언패킹된 바이너리 검증

언패킹된 샘플이 유효하고 완전한지 확인합니다:

# 언패킹된 PE가 유효한지 확인
python3 << 'PYEOF'
import pefile

pe = pefile.PE("unpacked.exe")

# 섹션이 정상인지 확인
print("언패킹된 섹션 분석:")
for section in pe.sections:
    name = section.Name.decode().rstrip('\x00')
    entropy = section.get_entropy()
    print(f"  {name:8s} 엔트로피: {entropy:.2f}")

# 임포트가 해결되었는지 확인
print(f"\n임포트 개수:")
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
    for entry in pe.DIRECTORY_ENTRY_IMPORT:
        dll = entry.dll.decode()
        count = len(entry.imports)
        print(f"  {dll}: {count}개 함수")
    total = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
    print(f"  총계: {total}개 임포트")

# 파일 크기 비교
import os
packed_size = os.path.getsize("suspect.exe")
unpacked_size = os.path.getsize("unpacked.exe")
print(f"\n패킹됨:   {packed_size:>10} 바이트")
print(f"언패킹됨: {unpacked_size:>10} 바이트")
print(f"비율:    {unpacked_size/packed_size:.1f}x")
PYEOF

주요 개념

용어정의
패킹실행 코드를 압축하거나 암호화하여 파일 크기를 줄이고 정적 분석을 방해하는 것; 바이너리에는 런타임에 코드를 복원하는 언패킹 스텁이 포함됨
UPXUltimate Packer for eXecutables; 악성코드 제작자들이 무료이고 효과적이기 때문에 자주 악용하는 오픈소스 실행 파일 패커
OEP (Original Entry Point)패킹되기 전 악성코드 코드의 실제 시작 주소; 언패킹 스텁이 코드를 압축 해제한 후 OEP로 점프
임포트 재구성Scylla나 ImpRec과 같은 도구를 사용하여 메모리에서 언패킹된 프로세스를 덤프한 후 임포트 주소 테이블을 재구축하는 과정
PUSHAD/POPAD모든 범용 레지스터를 저장/복원하는 x86 명령어; UPX는 언패킹 중 레지스터 상태를 보존하기 위해 이 패턴을 사용
섹션 엔트로피PE 섹션 데이터의 무작위성 측정; 패킹된 섹션은 엔트로피가 7.0을 초과하는 반면, 일반 코드 섹션은 평균 5.0-6.5
매직 바이트파일 형식을 식별하는 시그니처 바이트; UPX는 "UPX!"를 사용하며 악성코드 제작자는 자동 압축 해제를 방지하기 위해 이를 수정함

도구 및 시스템

  • UPX: 오픈소스 실행 파일 패커로, 올바르게 패킹된 파일에 대해 내장된 압축 해제 기능 제공
  • Detect It Easy (DIE): PE, ELF, Mach-O 파일의 보호를 식별하는 패커, 컴파일러, 링커 탐지 도구
  • x64dbg/x32dbg: 동적 실행과 중단점 기반 OEP 찾기를 통한 수동 언패킹에 사용되는 오픈소스 Windows 디버거
  • Scylla: x64dbg와 통합된 임포트 재구성 도구로, 메모리 덤프 후 IAT를 재구축
  • PE-bear: PE 파일 뷰어 및 편집기로, 언패킹 후 PE 헤더 검사 및 복구에 사용

일반적인 시나리오

시나리오: 수정된 UPX 헤더를 가진 악성코드 언패킹

상황: 악성코드 샘플이 섹션 이름(UPX0, UPX1)으로 UPX 패킹으로 식별되었지만, upx -d가 "CantUnpackException: header corrupted" 오류와 함께 실패합니다. 악성코드 제작자가 자동 압축 해제를 방지하기 위해 UPX 매직 바이트를 수정했습니다.

접근 방법:

  1. 바이너리를 16진수 편집기로 열고 UPX 헤더 영역(일반적으로 패킹된 데이터 끝 부분)을 검색합니다.
  2. 수정된 매직 바이트를 식별합니다 (예: "UPX!"가 "UPX\x00"으로 변경되거나 완전히 0으로 설정).
  3. Python 복구 스크립트를 사용하여 "UPX!" 매직을 복원하고 섹션 이름을 수정합니다.
  4. 수정된 바이너리에서 upx -d를 재시도합니다.
  5. 복구가 실패하면 x64dbg를 사용한 수동 언패킹으로 전환합니다 (PUSHAD -> ESP 하드웨어 BP -> POPAD -> JMP OEP).
  6. 언패킹된 바이너리가 적절한 임포트와 합리적인 엔트로피 값을 가지고 있는지 검증합니다.
  7. Ghidra 또는 IDA로 가져와 완전한 정적 분석을 수행합니다.

주의사항:

  • UPX가 유일한 패커라고 가정하지 마십시오; 바이너리가 이중 패킹(UPX + 커스텀 레이어)되어 있을 수 있습니다.
  • 원본 패킹된 샘플을 수정하지 말고 복사본을 작업에 사용하십시오.
  • 수동 메모리 덤프 후 임포트를 재구성하는 것을 잊지 마십시오 (IAT 수정 없이는 덤프된 바이너리가 충돌합니다).
  • UPX 패킹된 PE 섹션 뒤에 추가된 오버레이 데이터가 있는지 확인하는 것을 잊지 마십시오.

출력 형식

언패킹 분석 보고서
===========================
샘플:           suspect.exe
SHA-256:          e3b0c44298fc1c149afbf4c8996fb924...
패커:            UPX 3.96 (수정된 헤더)

패킹된 바이너리
섹션:            UPX0 (엔트로피: 0.00) UPX1 (엔트로피: 7.89) .rsrc (엔트로피: 3.45)
임포트:          2개 (kernel32.dll: LoadLibraryA, GetProcAddress)
파일 크기:       98,304 바이트

언패킹 방법
방법:            헤더 복구 + UPX -d
헤더 수정:      오프셋 0x1F000에서 UPX! 매직 복원
명령어:          upx -d suspect_fixed.exe -o unpacked.exe
결과:            성공

언패킹된 바이너리
섹션:            .text (엔트로피: 6.21) .rdata (엔트로피: 4.56) .data (엔트로피: 3.12) .rsrc (엔트로피: 3.45)
임포트:          147개 (kernel32, user32, advapi32, wininet, ws2_32)
파일 크기:       245,760 바이트 (2.5배 확장)
OEP:             0x00401000

검증
PE 유효:         예
임포트 해결:     예 (8개 DLL에 걸쳐 147개 함수)
실행 가능:       예 (샌드박스에서 충돌 없이 실행)

다음 단계
- unpacked.exe를 Ghidra로 가져와 전체 디스어셈블리 수행
- 언패킹된 바이너리에 대해 YARA 규칙 실행
- 언패킹된 바이너리를 VirusTotal에 제출하여 탐지율 향상

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

abusing-dpapi-for-credential-access

Extract and decrypt Windows DPAPI-protected secrets (Credential Manager, browser logins/cookies, Wi-Fi credentials, KeePass keys) online or offline using SharpDPAPI, SharpChrome, Mimikatz, or Impacket

abusing-shadow-credentials-for-privesc

Take over Active Directory accounts by writing attacker-controlled public keys to msDS-KeyCredentialLink (Shadow Credentials) with pyWhisker, Whisker, or Certipy, then authenticate via PKINIT to recover the target

achieving-cmmc-level-2-compliance

>-

acquiring-disk-image-with-dd-and-dcfldd

Create forensically sound bit-for-bit disk images with dd or dcfldd on a Linux forensic workstation, preserving evidence integrity through hash verification (MD5/SHA) during acquisition. Use when imaging a suspect drive, USB device, or memory card for investigation, preserving volatile disk evidence during incident response, or producing a verified copy for legal or law-enforcement proceedings before any destructive analysis.

analyzing-active-directory-acl-abuse

Detect dangerous ACL misconfigurations in Active Directory using ldap3

analyzing-android-malware-with-apktool

Perform static analysis of Android APK malware using apktool for resource decompilation, jadx for Java source recovery, and androguard for manifest inspection, dangerous permission-combination detection, and identification of obfuscated code, dynamic code loading, and reflection-based API calls. Use to statically triage a suspicious APK without executing it or to build mobile malware detection rules.

analyzing-api-gateway-access-logs

Parses API Gateway access logs (AWS API Gateway, Kong, Nginx) to detect

analyzing-apt-group-with-mitre-navigator

Query ATT&CK data with attackcti, mitreattack-python, and stix2, then build MITRE ATT&CK Navigator layers and multi-layer heatmap overlays mapping one or more APT groups

analyzing-azure-activity-logs-for-threats

Queries Azure Monitor activity logs and sign-in logs via azure-monitor-query

analyzing-bootkit-and-rootkit-samples

Analyzes bootkit and advanced rootkit malware infecting the Master

analyzing-browser-forensics-with-hindsight

Parse Chromium-based browser databases with Hindsight to extract and correlate browsing history, downloads, cookies, cached content, autofill data, saved passwords, and extensions from Chrome, Edge, Brave, Opera, and Vivaldi into a unified timeline (XLSX, JSON, or SQLite output). Use during incident response, insider-threat investigations, or criminal cases when you need to reconstruct a user

analyzing-campaign-attribution-evidence

Systematically evaluate cyber-campaign evidence to attribute an operation to a threat actor, using the Diamond Model and Analysis of Competing Hypotheses (ACH) to weigh infrastructure overlaps, TTP consistency, malware code similarity, and timing/language artifacts into confidence-weighted attribution assessments. Use when an incident investigation needs a defensible attribution confidence level.

analyzing-certificate-transparency-for-phishing

Monitor Certificate Transparency logs using crt.sh and Certstream to

analyzing-cloud-storage-access-patterns

Detect abnormal access in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail Data Events, GCS audit logs, and Azure Storage Analytics for after-hours bulk downloads, new-IP access, and API-call spikes (e.g. GetObject) via statistical baselines and time-series anomaly detection. Use when investigating suspected cloud data exfiltration or building related detection rules.

analyzing-cobalt-strike-beacon-configuration

Extract and analyze Cobalt Strike beacon configuration from PE files

analyzing-cobaltstrike-malleable-c2-profiles

Parse and analyze Cobalt Strike Malleable C2 profiles with dissect.cobaltstrike (profiles and beacon-payload configs) and pyMalleableC2 (AST parsing) to extract HTTP/DNS transforms, URIs, headers, sleep/jitter, and injection behavior, then generate network detection signatures. Use when reverse-engineering a captured malleable profile or building detections against Cobalt Strike Beacon traffic.

analyzing-command-and-control-communication

Analyzes malware C2 communication over HTTP, HTTPS, DNS, and custom

analyzing-cyber-kill-chain

Analyzes intrusion activity against the Lockheed Martin Cyber Kill Chain

analyzing-disk-image-with-autopsy

Perform comprehensive forensic analysis of raw (dd), E01, or AFF disk images with Autopsy and The Sleuth Kit, recovering deleted files, examining metadata and embedded artifacts, keyword searching, and building investigation timelines with visual reports. Use for structured analysis of a forensic disk image or when stakeholders need visual reports from evidence.

analyzing-dns-logs-for-exfiltration

Analyzes DNS query logs to detect data exfiltration via DNS tunneling,

관련 스킬