ZIPs tricks

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Command-line tools์€ zip files ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ํ•„์ˆ˜์ ์ด๋ฉฐ zip files์˜ ์ง„๋‹จ, ๋ณต๊ตฌ, ํฌ๋ž˜ํ‚น์— ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ฃผ์š” ์œ ํ‹ธ๋ฆฌํ‹ฐ์ž…๋‹ˆ๋‹ค:

  • unzip: zip files๊ฐ€ ์••์ถ• ํ•ด์ œ๋˜์ง€ ์•Š๋Š” ์ด์œ ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  • zipdetails -v: zip file format ํ•„๋“œ์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ถ„์„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • zipinfo: ์ถ”์ถœํ•˜์ง€ ์•Š๊ณ  zip files์˜ ๋‚ด์šฉ์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.
  • zip -F input.zip --out output.zip ๋ฐ zip -FF input.zip --out output.zip: ์†์ƒ๋œ zip files ๋ณต๊ตฌ๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
  • fcrackzip: zip ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ธŒ๋ฃจํŠธํฌ์Šค๋กœ ํฌ๋ž™ํ•˜๋Š” ๋„๊ตฌ๋กœ, ๋Œ€๋žต 7์ž ๋‚ด์™ธ์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ์— ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

The Zip file format specification์€ zip files์˜ ๊ตฌ์กฐ์™€ ํ‘œ์ค€์— ๋Œ€ํ•œ ์ข…ํ•ฉ์ ์ธ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์•”ํ˜ธ๋กœ ๋ณดํ˜ธ๋œ zip files๋Š” ๋‚ด๋ถ€์˜ ํŒŒ์ผ ์ด๋ฆ„์ด๋‚˜ ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์ด ์ ์€ RAR๋‚˜ 7z ํŒŒ์ผ๊ณผ ๋‹ฌ๋ฆฌ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์•”ํ˜ธํ™”ํ•˜์ง€ ์•Š๋Š” ๋ณด์•ˆ ๊ฒฐํ•จ์ž…๋‹ˆ๋‹ค). ๋˜ํ•œ, ์˜ค๋ž˜๋œ ZipCrypto ๋ฐฉ์‹์œผ๋กœ ์•”ํ˜ธํ™”๋œ zip files๋Š” ์••์ถ•๋œ ํŒŒ์ผ์˜ ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ ๋ณต์‚ฌ๋ณธ์ด ์กด์žฌํ•  ๊ฒฝ์šฐ plaintext attack์— ์ทจ์•ฝํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณต๊ฒฉ์€ ์•Œ๋ ค์ง„ ๋‚ด์šฉ์„ ์ด์šฉํ•ด zip์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํฌ๋ž™ํ•˜๋Š” ๋ฐฉ์‹์ด๋ฉฐ, ์ด ์ทจ์•ฝ์ ์€ HackThisโ€™s article์™€ this academic paper์—์„œ ์ž์„ธํžˆ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, AES-256๋กœ ๋ณดํ˜ธ๋œ zip files๋Š” ์ด plaintext attack์— ๋ฉด์—ญ์ด๋ฏ€๋กœ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ์—๋Š” ์•ˆ์ „ํ•œ ์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.


APKs์—์„œ ์กฐ์ž‘๋œ ZIP headers๋ฅผ ์‚ฌ์šฉํ•œ ์•ˆํ‹ฐ๋ฆฌ๋ฒ„์‹ฑ ํŠธ๋ฆญ

ํ˜„๋Œ€์˜ Android malware droppers๋Š” ์ž˜๋ชป๋œ ZIP metadata๋ฅผ ์‚ฌ์šฉํ•ด static tools (jadx/apktool/unzip)์„ ๊นจ๋œจ๋ฆฌ๋ฉด์„œ๋„ APK๋ฅผ ๊ธฐ๊ธฐ์—์„œ ์„ค์น˜ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ํ”ํ•œ ํŠธ๋ฆญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • ZIP General Purpose Bit Flag (GPBF) ๋น„ํŠธ 0์„ ์„ค์ •ํ•ด ๊ฐ€์งœ ์•”ํ˜ธํ™” ํ‘œ์‹œ
  • ํŒŒ์„œ๋ฅผ ํ˜ผ๋™์‹œํ‚ค๊ธฐ ์œ„ํ•œ ํฐ/์ปค์Šคํ…€ Extra ํ•„๋“œ ๋‚จ์šฉ
  • ์‹ค์ œ ์•„ํ‹ฐํŒฉํŠธ๋ฅผ ์ˆจ๊ธฐ๊ธฐ ์œ„ํ•œ ํŒŒ์ผ/๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„ ์ถฉ๋Œ(์˜ˆ: ์‹ค์ œ classes.dex ์˜†์— classes.dex/๋ผ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ)

1) Fake encryption (GPBF bit 0 set) without real crypto

์ฆ์ƒ:

  • jadx-gui๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๋กœ ์‹คํŒจํ•จ:
java.util.zip.ZipException: invalid CEN header (encrypted entry)
  • unzip์ด ํ•ต์‹ฌ APK ํŒŒ์ผ๋“ค์— ๋Œ€ํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์š”์ฒญํ•˜์ง€๋งŒ, ์œ ํšจํ•œ APK๋Š” classes*.dex, resources.arsc, ๋˜๋Š” AndroidManifest.xml์„ ์•”ํ˜ธํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:
unzip sample.apk
[sample.apk] classes3.dex password:
skipping: classes3.dex                          incorrect password
skipping: AndroidManifest.xml/res/vhpng-xhdpi/mxirm.png  incorrect password
skipping: resources.arsc/res/domeo/eqmvo.xml            incorrect password
skipping: classes2.dex                          incorrect password

zipdetails๋กœ ํƒ์ง€:

zipdetails -v sample.apk | less

local ๋ฐ central ํ—ค๋”์˜ General Purpose Bit Flag๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ํ•ต์‹ฌ ํ•ญ๋ชฉ(core entries)์—์„œ๋„ ํŠน์ง•์ ์ธ ๊ฐ’์€ ๋น„ํŠธ 0(Encryption)์ด ์„ค์ •๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

Extract Zip Spec      2D '4.5'
General Purpose Flag  0A09
[Bit 0]   1 'Encryption'
[Bits 1-2] 1 'Maximum Compression'
[Bit 3]   1 'Streamed'
[Bit 11]  1 'Language Encoding'

ํœด๋ฆฌ์Šคํ‹ฑ: APK๊ฐ€ ๊ธฐ๊ธฐ์— ์„ค์น˜๋˜์–ด ์‹คํ–‰๋˜์ง€๋งŒ ํ•ต์‹ฌ ํ•ญ๋ชฉ๋“ค์ด ๋„๊ตฌ์— ์˜ํ•ด โ€œencryptedโ€œ๋กœ ๋ณด์ธ๋‹ค๋ฉด GPBF๊ฐ€ ๋ณ€์กฐ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ: Local File Headers (LFH)์™€ Central Directory (CD) ํ•ญ๋ชฉ ์–‘์ชฝ์—์„œ GPBF์˜ bit 0์„ ํด๋ฆฌ์–ดํ•˜์„ธ์š”. Minimal byte-patcher:

# gpbf_clear.py โ€“ clear encryption bit (bit 0) in ZIP local+central headers
import struct, sys

SIG_LFH = b"\x50\x4b\x03\x04"  # Local File Header
SIG_CDH = b"\x50\x4b\x01\x02"  # Central Directory Header

def patch_flags(buf: bytes, sig: bytes, flag_off: int):
out = bytearray(buf)
i = 0
patched = 0
while True:
i = out.find(sig, i)
if i == -1:
break
flags, = struct.unpack_from('<H', out, i + flag_off)
if flags & 1:  # encryption bit set
struct.pack_into('<H', out, i + flag_off, flags & 0xFFFE)
patched += 1
i += 4  # move past signature to continue search
return bytes(out), patched

if __name__ == '__main__':
inp, outp = sys.argv[1], sys.argv[2]
data = open(inp, 'rb').read()
data, p_lfh = patch_flags(data, SIG_LFH, 6)  # LFH flag at +6
data, p_cdh = patch_flags(data, SIG_CDH, 8)  # CDH flag at +8
open(outp, 'wb').write(data)
print(f'Patched: LFH={p_lfh}, CDH={p_cdh}')

์‚ฌ์šฉ๋ฒ•:

python3 gpbf_clear.py obfuscated.apk normalized.apk
zipdetails -v normalized.apk | grep -A2 "General Purpose Flag"

์ด์ œ ํ•ต์‹ฌ ์—”ํŠธ๋ฆฌ์—์„œ General Purpose Flag 0000์ด ํ‘œ์‹œ๋˜๊ณ  ๋„๊ตฌ๋“ค์ด APK๋ฅผ ๋‹ค์‹œ ํŒŒ์‹ฑํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

2) ํŒŒ์„œ๋ฅผ ๋ฌด๋ ฅํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€ํ˜•/์ปค์Šคํ…€ Extra ํ•„๋“œ

๊ณต๊ฒฉ์ž๋“ค์€ ๋””์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ํ˜ผ๋ž€์‹œํ‚ค๊ธฐ ์œ„ํ•ด ํ—ค๋”์— ๊ณผ๋„ํ•œ ํฌ๊ธฐ์˜ Extra ํ•„๋“œ์™€ ์ด์ƒํ•œ ID๋“ค์„ ๋„ฃ์Šต๋‹ˆ๋‹ค. ์‹ค์ „์—์„œ๋Š” (์˜ˆ: JADXBLOCK๊ณผ ๊ฐ™์€ ๋ฌธ์ž์—ด) ๊ทธ๋Ÿฐ ์ปค์Šคํ…€ ๋งˆ์ปค๊ฐ€ ํฌํ•จ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒ€์‚ฌ:

zipdetails -v sample.apk | sed -n '/Extra ID/,+4p' | head -n 50

๊ด€์ฐฐ๋œ ์˜ˆ: 0xCAFE(โ€œJava ์‹คํ–‰ ํŒŒ์ผโ€) ๋˜๋Š” 0x414A(โ€œJA:โ€) ๊ฐ™์€ ์•Œ ์ˆ˜ ์—†๋Š” ID๊ฐ€ ๋Œ€์šฉ๋Ÿ‰ ํŽ˜์ด๋กœ๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒฝ์šฐ.

DFIR ํœด๋ฆฌ์Šคํ‹ฑ:

  • core ํ•ญ๋ชฉ(classes*.dex, AndroidManifest.xml, resources.arsc)์—์„œ Extra ํ•„๋“œ๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ํด ๋•Œ ๊ฒฝ๊ณ .
  • ํ•ด๋‹น ํ•ญ๋ชฉ๋“ค์—์„œ ์•Œ ์ˆ˜ ์—†๋Š” Extra ID๋Š” ์˜์‹ฌ์Šค๋Ÿฌ์šด ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ.

์‹ค๋ฌด์  ์™„ํ™”: ์•„์นด์ด๋ธŒ๋ฅผ ์žฌ๊ตฌ์„ฑ(์˜ˆ: ์ถ”์ถœ๋œ ํŒŒ์ผ์„ ๋‹ค์‹œ zip์œผ๋กœ ์••์ถ•)ํ•˜๋ฉด ์•…์„ฑ Extra ํ•„๋“œ๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. ๋„๊ตฌ๊ฐ€ ๊ฐ€์งœ ์•”ํ˜ธํ™” ๋•Œ๋ฌธ์— ์ถ”์ถœ์„ ๊ฑฐ๋ถ€ํ•˜๋ฉด, ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ ๋จผ์ € GPBF bit 0์„ ์ง€์šด ๋‹ค์Œ ์žฌํŒจํ‚ค์ง€ํ•˜์„ธ์š”:

mkdir /tmp/apk
unzip -qq normalized.apk -d /tmp/apk
(cd /tmp/apk && zip -qr ../clean.apk .)

3) ํŒŒ์ผ/๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„ ์ถฉ๋Œ (์‹ค์ œ ์•„ํ‹ฐํŒฉํŠธ ์ˆจ๊น€)

ZIP ํŒŒ์ผ์€ ํŒŒ์ผ X์™€ ๋””๋ ‰ํ„ฐ๋ฆฌ X/๋ฅผ ๋™์‹œ์— ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค. ์ผ๋ถ€ extractors์™€ decompilers๋Š” ํ˜ผ๋™๋˜์–ด ๋””๋ ‰ํ„ฐ๋ฆฌ ํ•ญ๋ชฉ์œผ๋กœ ์‹ค์ œ ํŒŒ์ผ์„ ๋ฎ์–ด์“ฐ๊ฑฐ๋‚˜ ์ˆจ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” classes.dex์™€ ๊ฐ™์€ ํ•ต์‹ฌ APK ์ด๋ฆ„๊ณผ ์ถฉ๋Œํ•˜๋Š” ํ•ญ๋ชฉ์—์„œ ๊ด€์ฐฐ๋˜์—ˆ๋‹ค.

๋ถ„์„ ๋ฐ ์•ˆ์ „ํ•œ ์ถ”์ถœ:

# List potential collisions (names that differ only by trailing slash)
zipinfo -1 sample.apk | awk '{n=$0; sub(/\/$/,"",n); print n}' | sort | uniq -d

# Extract while preserving the real files by renaming on conflict
unzip normalized.apk -d outdir
# When prompted:
# replace outdir/classes.dex? [y]es/[n]o/[A]ll/[N]one/[r]ename: r
# new name: unk_classes.dex

ํ”„๋กœ๊ทธ๋žจ์  ํƒ์ง€ ์ ‘๋ฏธ์‚ฌ:

from zipfile import ZipFile
from collections import defaultdict

with ZipFile('normalized.apk') as z:
names = z.namelist()

collisions = defaultdict(list)
for n in names:
base = n[:-1] if n.endswith('/') else n
collisions[base].append(n)

for base, variants in collisions.items():
if len(variants) > 1:
print('COLLISION', base, '->', variants)

Blue-team ํƒ์ง€ ์•„์ด๋””์–ด:

  • APK์˜ local headers๊ฐ€ ์•”ํ˜ธํ™”๋กœ ํ‘œ์‹œ๋˜๋‚˜ (GPBF bit 0 = 1) ์„ค์น˜/์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ ํƒ์ง€.
  • ํ•ต์‹ฌ ์—”ํŠธ๋ฆฌ์˜ ํฌ๊ฑฐ๋‚˜ ์•Œ๋ ค์ง€์ง€ ์•Š์€ Extra ํ•„๋“œ(์˜ˆ: JADXBLOCK ๊ฐ™์€ ๋งˆ์ปค)๋ฅผ ํƒ์ง€.
  • ํŠนํžˆ AndroidManifest.xml, resources.arsc, classes*.dex์— ๋Œ€ํ•ด ๊ฒฝ๋กœ ์ถฉ๋Œ(X ๋ฐ X/)์„ ํƒ์ง€.

์ฐธ๊ณ ์ž๋ฃŒ

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ