파일 · 권한 · 사용자 관리 18 / 50 · Part 2. Linux 파일 종류와 속성
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정analyst)
기초편 연계: 「리눅스 시스템 기초」 13. Linux 파일 종류
17편에서 Linux는 확장자가 아니라 내용으로 파일 형식을 판단하고, 그 판정 도구가 file이라는 것을 확인했다. 이번 글에서는 file이 어떻게 형식을 알아내는지를 파고든다.
핵심은 Magic Number(매직 넘버) — 파일 앞부분에 있는 형식별 고유 시그니처다. ELF, PNG, gzip, ZIP, PE(Windows 실행 파일)의 실제 매직 바이트를 xxd로 직접 확인하고, file이 틀릴 수 있는 경우까지 정리한다.
대부분의 이진 파일 형식은 시작 부분에 정해진 바이트 열을 둔다. 프로그램은 확장자 대신 이 값을 읽어 "이 파일이 정말 PNG인가"를 확인한다.
| 형식 | 매직 (hex) | ASCII |
|---|---|---|
| ELF | 7F 45 4C 46 | .ELF |
| 셸 스크립트 | 23 21 (#!) | #! |
| PNG | 89 50 4E 47 0D 0A 1A 0A | .PNG␍␊␚␊ |
| gzip | 1F 8B 08 | |
| ZIP (jar/docx/xlsx/apk 포함) | 50 4B 03 04 | PK␃␄ |
25 50 44 46 | %PDF | |
| PE (Windows exe/dll) | 4D 5A | MZ |
file은 다음 순서로 형식을 정한다.
/usr/share/misc/magic.mgc)와 대조| 옵션 | 의미 |
|---|---|
-i / --mime-type | MIME 타입 출력 |
-b | 파일명 없이 판정 결과만 |
-z | 압축 파일 내부까지 판정 |
-k | 첫 판정에서 멈추지 않고 여러 후보 표시 |
-s | 블록/문자 장치도 읽어서 판정 |

file은 파일 앞부분(기본 수 KB)만 읽는다. 전체를 읽지 않으므로 큰 파일도 빠르다. 매직 데이터베이스에는 수천 개의 규칙이 있고, 각 규칙은 "몇 번째 바이트가 어떤 값이면 어떤 형식"이라는 형태다.
주의할 점은 file이 앞부분만 본다 는 사실 자체가 한계라는 것이다. 공격자가 PHP 코드 앞에 GIF89a 같은 이미지 매직을 붙이면 file은 GIF로 판정할 수 있다. 그래서 file은 1차 분류 도구 이고, 확정은 구조 검증(파일 전체가 규격에 맞는지)과 해시로 한다.
mkdir ~/lab18 && cd ~/lab18
# 1) 실제 파일의 매직을 xxd 로 확인
head -c 16 /usr/bin/ls | xxd # ELF
# 2) 각 형식의 매직 바이트 만들어 보기
printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR' > a.png && xxd a.png | head -1
echo hi | gzip > a.gz && xxd a.gz | head -1
printf '#!/bin/bash\necho hi\n' > run && xxd run | head -1
printf 'PK\003\004' > b.zip && xxd b.zip
# 3) file 로 판정
file /usr/bin/ls a.png a.gz run b.zip
# 4) MIME 타입
file -i /usr/bin/ls run
# 5) 매직만 있고 나머지가 없는 위조 파일
printf 'MZ\x90\x00' > update.bin && file update.bin

텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ mkdir ~/lab18 && cd ~/lab18
[analyst@rocky9-lab lab18]$ head -c 16 /usr/bin/ls | xxd
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
[analyst@rocky9-lab lab18]$ printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR' > a.png && xxd a.png | head -1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
[analyst@rocky9-lab lab18]$ echo hi | gzip > a.gz && xxd a.gz | head -1
00000000: 1f8b 0800 0000 0000 0003 cbc8 e402 007a ...............z
[analyst@rocky9-lab lab18]$ printf '#!/bin/bash\necho hi\n' > run && xxd run | head -1
00000000: 2321 2f62 696e 2f62 6173 680a 6563 686f #!/bin/bash.echo
[analyst@rocky9-lab lab18]$ printf 'PK\003\004' > b.zip && xxd b.zip
00000000: 504b 0304 PK..
[analyst@rocky9-lab lab18]$ file /usr/bin/ls a.png a.gz run b.zip
/usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f628b6b16fbee260b947fe90fd66b286210dacf7, for GNU/Linux 3.2.0, stripped
a.png: PNG image data, 0 x 0, 0-bit grayscale, non-interlaced
a.gz: gzip compressed data, from Unix, truncated
run: Bourne-Again shell script, ASCII text executable
b.zip: data
[analyst@rocky9-lab lab18]$ file -i /usr/bin/ls run
/usr/bin/ls: application/x-pie-executable; charset=binary
run: text/x-shellscript; charset=us-ascii
[analyst@rocky9-lab lab18]$ printf 'MZ\x90\x00' > update.bin && file update.bin
update.bin: data
| 출력 | 해석 |
|---|---|
head -c 16 /usr/bin/ls \| xxd → 7f45 4c46 | ELF 매직 7F 45 4C 46. 이 실습 컨테이너의 ls는 정상 ELF 바이너리다 |
a.png → 8950 4e47 0d0a 1a0a | PNG 8바이트 시그니처 |
a.gz → 1f8b 0800 | gzip 매직 1F 8B + 압축 방식 08(deflate) |
run → 2321 2f62 696e | #!/bin — shebang |
b.zip → 504b 0304 | ZIP 매직 PK |
file ... a.png → PNG image data, 0 x 0 | 매직 뒤 IHDR을 읽었지만 크기가 0이라 그렇게 표시 |
a.gz → gzip compressed data ... truncated | 매직은 맞지만 뒤가 불완전 |
run → Bourne-Again shell script | 매직(#!) + 인터프리터 경로로 판정 |
b.zip → data | PK 4바이트만으로는 ZIP 구조를 확인할 수 없어 data |
file -i run → text/x-shellscript | MIME도 내용 기준 |
update.bin (MZ 4바이트) → data | 매직만으로는 PE로 확정하지 못한다. PE는 MZ 뒤 오프셋의 PE 헤더까지 있어야 판정된다 |
b.zip과 update.bin 결과가 file의 성격을 잘 보여 준다. 매직 바이트 몇 개만 있다고 무조건 그 형식으로 판정하지 않는다. 뒤따르는 구조까지 최소한 확인 한다. 반대로 말하면, 구조를 충분히 갖춰 위조하면 속을 수도 있다.
| 활용 | 방법 |
|---|---|
| 위장 파일 탐지 | 확장자와 file 판정이 불일치하는 파일 검색 |
| 다운로드 검사 | 첨부·다운로드 파일의 실제 형식 확인 (.pdf가 ELF/PE인지) |
| polyglot 파일 | 이미지+스크립트처럼 두 형식을 동시에 만족하는 파일 주의 → file -k로 여러 후보 확인 |
| 압축 폭탄 대비 | -z로 내부를 볼 때도 실제 압축 해제는 신중히 |
file의 한계도 명확히 안다.
| 한계 | 설명 |
|---|---|
| 앞부분만 검사 | 매직을 위조해 속일 수 있음 |
| 매직 없는 형식 | 순수 텍스트, 일부 스크립트는 내용 추정이라 부정확 |
| 잘린 파일 | truncated, data로 표시 |
| 새 형식 | 매직 DB에 없으면 data |
[Hunt] 업로드/임시 경로 전수 검사
find /var/www/html/uploads /tmp /dev/shm -type f -exec file {} + \
| grep -iE 'ELF|executable|PHP|shell script|PE32'
↓
[교차] 확장자와 실제 형식 불일치 목록 작성 (예: .jpg 인데 PHP script)
↓
[확정] 의심 파일: sha256sum → 위협 인텔 조회, 필요 시 샌드박스 분석
↓
[Response] 격리·차단, 유입 경로(웹 업로드·메일·다운로드) 추적
| 관점 | 내용 |
|---|---|
| 분류 vs 확정 | file은 1차 분류. IOC 확정은 해시와 구조 검증 |
| 자동화 | EDR/샌드박스는 매직뿐 아니라 전체 구조·행위를 본다. file은 빠른 1차 필터로 사용 |
| 오탐 | 매직이 우연히 겹치는 텍스트, EXIF 안 문자열 등. 판정과 실제 코드를 함께 확인 |
| 실수 | 결과 | 예방 |
|---|---|---|
file 결과를 100% 신뢰 | 위조 파일에 속음 | 해시·구조 검증 병행 |
| 매직 몇 바이트만 보고 형식 확정 | polyglot·잘린 파일 오판 | file의 종합 판정 사용 |
| MIME를 클라이언트 값으로 신뢰 | 위조 가능 | 서버에서 file로 재확인 |
| 큰 파일 전체를 읽어 형식 확인 | 느림 | file은 앞부분만 읽음 |
data = 안전으로 해석 | 새 형식·암호화·잘린 악성일 수 있음 | 맥락과 함께 판단 |
[ ] xxd 로 ELF·PNG·gzip·ZIP 매직을 직접 확인했다
[ ] file 이 shebang 스크립트를 판정하는 것을 확인했다
[ ] PK 4바이트만으로는 ZIP 으로 확정되지 않음을 확인했다
[ ] MZ 4바이트만으로는 PE 로 확정되지 않음을 확인했다
[ ] file -i 로 MIME 를 확인했다
[ ] file 의 한계 3가지를 설명할 수 있다
file은 이를 매직 DB와 대조한다.7F454C46, #!, PNG 89504E47…, gzip 1F8B, ZIP 504B0304, PE MZ.file은 파일 시스템 → 매직 → 언어 순으로 판정하며, 앞부분만 읽는다.PK, MZ만으로는 data).file은 위조·잘린 파일·새 형식에 약하다. 1차 분류 로 쓰고 확정은 해시·구조 검증으로 한다.다음 글 「19. 숨김 파일과 숨겨진 파일 탐지」 에서는 .으로 시작하는 숨김 파일, 공백·특수문자로 위장한 이름, ... 같은 디렉터리를 직접 만들어 보고, ls가 놓치는 것들을 find와 ls -b로 드러내는 방법을 정리한다.