파일 · 권한 · 사용자 관리 18 / 50 · Part 2. Linux 파일 종류와 속성
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정 analyst)
기초편 연계: 「리눅스 시스템 기초」 13. Linux 파일 종류

1. 들어가며

17편에서 Linux는 확장자가 아니라 내용으로 파일 형식을 판단하고, 그 판정 도구가 file이라는 것을 확인했다. 이번 글에서는 file이 어떻게 형식을 알아내는지를 파고든다.

핵심은 Magic Number(매직 넘버) — 파일 앞부분에 있는 형식별 고유 시그니처다. ELF, PNG, gzip, ZIP, PE(Windows 실행 파일)의 실제 매직 바이트를 xxd로 직접 확인하고, file이 틀릴 수 있는 경우까지 정리한다.


2. 핵심 개념

2-1. Magic Number란

대부분의 이진 파일 형식은 시작 부분에 정해진 바이트 열을 둔다. 프로그램은 확장자 대신 이 값을 읽어 "이 파일이 정말 PNG인가"를 확인한다.

형식매직 (hex)ASCII
ELF7F 45 4C 46.ELF
셸 스크립트23 21 (#!)#!
PNG89 50 4E 47 0D 0A 1A 0A.PNG␍␊␚␊
gzip1F 8B 08
ZIP (jar/docx/xlsx/apk 포함)50 4B 03 04PK␃␄
PDF25 50 44 46%PDF
PE (Windows exe/dll)4D 5AMZ

2-2. file의 판정 3단계

file은 다음 순서로 형식을 정한다.

  1. 파일 시스템 검사 — 디렉터리·장치·링크·소켓 등 종류를 먼저 확인 (11편)
  2. 매직 검사 — 앞부분 바이트를 매직 데이터베이스(/usr/share/misc/magic.mgc)와 대조
  3. 언어 검사 — 매직이 없으면 텍스트 인코딩과 내용을 보고 추정 (예: 셸 스크립트, C 소스)

2-3. 주요 옵션

옵션의미
-i / --mime-typeMIME 타입 출력
-b파일명 없이 판정 결과만
-z압축 파일 내부까지 판정
-k첫 판정에서 멈추지 않고 여러 후보 표시
-s블록/문자 장치도 읽어서 판정

3. 동작 원리

Magic Number — 파일 앞부분의 형식 시그니처

file은 파일 앞부분(기본 수 KB)만 읽는다. 전체를 읽지 않으므로 큰 파일도 빠르다. 매직 데이터베이스에는 수천 개의 규칙이 있고, 각 규칙은 "몇 번째 바이트가 어떤 값이면 어떤 형식"이라는 형태다.

주의할 점은 file이 앞부분만 본다 는 사실 자체가 한계라는 것이다. 공격자가 PHP 코드 앞에 GIF89a 같은 이미지 매직을 붙이면 file은 GIF로 판정할 수 있다. 그래서 file은 1차 분류 도구 이고, 확정은 구조 검증(파일 전체가 규격에 맞는지)과 해시로 한다.


4. 명령어 실습

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

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — Magic Number 확인

텍스트 원본(실제 출력):

[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

6. 결과 해석

출력해석
head -c 16 /usr/bin/ls \| xxd → 7f45 4c46ELF 매직 7F 45 4C 46. 이 실습 컨테이너의 ls는 정상 ELF 바이너리다
a.png → 8950 4e47 0d0a 1a0aPNG 8바이트 시그니처
a.gz → 1f8b 0800gzip 매직 1F 8B + 압축 방식 08(deflate)
run → 2321 2f62 696e#!/bin — shebang
b.zip → 504b 0304ZIP 매직 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 → dataPK 4바이트만으로는 ZIP 구조를 확인할 수 없어 data
file -i run → text/x-shellscriptMIME도 내용 기준
update.bin (MZ 4바이트) → data매직만으로는 PE로 확정하지 못한다. PE는 MZ 뒤 오프셋의 PE 헤더까지 있어야 판정된다

b.zip과 update.bin 결과가 file의 성격을 잘 보여 준다. 매직 바이트 몇 개만 있다고 무조건 그 형식으로 판정하지 않는다. 뒤따르는 구조까지 최소한 확인 한다. 반대로 말하면, 구조를 충분히 갖춰 위조하면 속을 수도 있다.


7. 보안 관점

활용방법
위장 파일 탐지확장자와 file 판정이 불일치하는 파일 검색
다운로드 검사첨부·다운로드 파일의 실제 형식 확인 (.pdf가 ELF/PE인지)
polyglot 파일이미지+스크립트처럼 두 형식을 동시에 만족하는 파일 주의 → file -k로 여러 후보 확인
압축 폭탄 대비-z로 내부를 볼 때도 실제 압축 해제는 신중히

file의 한계도 명확히 안다.

한계설명
앞부분만 검사매직을 위조해 속일 수 있음
매직 없는 형식순수 텍스트, 일부 스크립트는 내용 추정이라 부정확
잘린 파일truncated, data로 표시
새 형식매직 DB에 없으면 data

8. 보안관제 관점

[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 안 문자열 등. 판정과 실제 코드를 함께 확인

9. 실무에서 자주 발생하는 실수

실수결과예방
file 결과를 100% 신뢰위조 파일에 속음해시·구조 검증 병행
매직 몇 바이트만 보고 형식 확정polyglot·잘린 파일 오판file의 종합 판정 사용
MIME를 클라이언트 값으로 신뢰위조 가능서버에서 file로 재확인
큰 파일 전체를 읽어 형식 확인느림file은 앞부분만 읽음
data = 안전으로 해석새 형식·암호화·잘린 악성일 수 있음맥락과 함께 판단

10. 실습 체크리스트

[ ] xxd 로 ELF·PNG·gzip·ZIP 매직을 직접 확인했다
[ ] file 이 shebang 스크립트를 판정하는 것을 확인했다
[ ] PK 4바이트만으로는 ZIP 으로 확정되지 않음을 확인했다
[ ] MZ 4바이트만으로는 PE 로 확정되지 않음을 확인했다
[ ] file -i 로 MIME 를 확인했다
[ ] file 의 한계 3가지를 설명할 수 있다

11. 핵심 정리

  • Magic Number는 파일 앞부분의 형식 시그니처다. file은 이를 매직 DB와 대조한다.
  • 대표 매직: ELF 7F454C46, #!, PNG 89504E47…, gzip 1F8B, ZIP 504B0304, PE MZ.
  • file은 파일 시스템 → 매직 → 언어 순으로 판정하며, 앞부분만 읽는다.
  • 매직 몇 바이트만으로는 확정하지 않고 구조까지 확인한다(예: PK, MZ만으로는 data).
  • file은 위조·잘린 파일·새 형식에 약하다. 1차 분류 로 쓰고 확정은 해시·구조 검증으로 한다.
  • 헌팅 시 확장자와 실제 형식의 불일치를 우선 점검한다.

12. 다음 편 예고

다음 글 「19. 숨김 파일과 숨겨진 파일 탐지」 에서는 .으로 시작하는 숨김 파일, 공백·특수문자로 위장한 이름, ... 같은 디렉터리를 직접 만들어 보고, ls가 놓치는 것들을 find와 ls -b로 드러내는 방법을 정리한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글