Programming2008. 12. 3. 11:39
파일복구 프로그램이란 디스크에 저장한 파일이 삭제됐을 때 이를 원형 그대로 복원하는 작업을 자동화한 애플리케이션이다. 복원률은 파일시스템의 특성에 따라 100%에서 0%까지 천차만별인데, 일반적으로 FAT(File Allocation Table) 파일시스템의 경우 덮어쓰기(overwrite)되지 않았고 데이터가 연속적으로 저장돼 있는 경우 100% 복원 가능하며, 두 가지 조건에서 어긋날 수록 확률이 낮아진다. NTFS(New Technology File System) 파일시스템 역시 복원률을 결정하는 조건은 FAT와 같다(물론 파일시스템의 설계 상 기본적으로 FAT보다 복원률이 높다).
복원이나 복구나 서로 유사한 말이긴 하나, 복원이란 말은 원형 그대로 다시 살릴 수 있는 경우이고 복구란 원형 그대로는 다시 살릴 수 없으나 부분적으로 회복하는 경우다. 이번 연재를 통해 개발해 볼 프로그램은 역시 하드디스크에 기록된 파일이 삭제됐을 때 이를 복구해 주는 프로그램이다.
현재 국내에 출시된 파일복구 프로그램에는 여러 가지가 있다. 파이널데이터의 파이널데이터와 바이로봇이라는 백신 프로그램으로 유명한 하우리의 데이터메딕, 라이브데이터의 라이브데이터, 그리고 필자가 무료배포하고 있는 데이터매직 등이 있다. 프리웨어로 배포하는 해외 프로그램에는 일본인인 Brian Kato가 만든 Restoration 2.5.14 버전이 있으나 2002년 5월 14일 이후로 버전업이 중단된 상태다(클러스터 검색기능은 데이터복구 프로그램과 파일복구 프로그램을 구분하는 기준이기도 하다).

파일복구 프로그램의 원리
파일복구 프로그램은 어떻게 삭제된 파일을 복구하는 것일까? 해답은 파일시스템의 설계에 있다. 현재 가장 널리 사용되는 FAT과 NTFS를 기준으로 살펴보면, 먼저 FAT에는 디렉토리 엔트리(Directory Entry)가 존재한다. 이곳에 파일과 디렉토리에 대한 정보와 파일명이 기록되는데 파일이나 폴더가 삭제되면 첫 번째 바이트에 E5h가 마킹돼 탐색기 상에서는 마치 지워진 것으로 간주돼 표시되지 않는다. 파일복구 프로그램은 이렇게 E5h로 된 엔트리의 시작 클러스터 값을 참조해 파일할당 테이블에서 데이터 클러스터와 맵핑된 위치로 가서 파일 크기만큼 읽어오는 방식을 사용한다. 이렇게 찾은 것이 곧 복구하고자 하는 파일의 내용이므로 이를 다른 드라이브에 저장해 파일을 복구한다. 이 때 덮어쓰기가 이루어지지 않았고, 데이터가 즉 클러스터가 연속적으로 저장돼 있다면 거의 100%까지 복구할 수 있다.
NTFS는 섹터의 몇 개 묶음을 최대 4KB 이내에서 하나의 클러스터로 보고, 이러한 단위 클러스터에 파일 레코드를 하나씩 배치한다. 섹터를 512바이트로 보면 8개 섹터가 하나의 클러스터가 되고 이것이 최대치이므로 클러스터가 곧 파일레코드이자 NTFS의 저장단위인 셈이다.
NTFS는 메타데이터와 정상파일(normal files)로 구분된다. 메타데이터에는 $MFT, $MFTMirr, $LogFile, $Volume, $AttrDef, .(루트), $Bitmap, $Boot, $BadClus, $Secure, $UpCase, $Extend 등이며, 정상파일은 파일과 폴더를 의미한다. 파일레코드에는 메타데이터들이 각각 존재하며 이러한 파일레코드를 읽어 지워진 파일레코드로 표시된 경우를 찾아 복구하는 방식이다. 실제 복구과정은 삭제된 것으로 표시된 파일레코드의 LCN (Logical Cluster Number)를 찾아 VCN (Virtual Cluster Number)만큼 런리스트(Run List)를 참조해 읽어 들인 후 저장한다.

파일복구 프로그래밍의 출발점, 파일시스템
이처럼 파일시스템에 대한 이해는 파일복구 소프트웨어를 만드는 출발점이다. 파일시스템은 운영체제의 특성을 가장 잘 표현하며 자료를 기록할 수 있는 스토리지 미디어의 근간을 이루는 것으로 미디어 내에 데이터를 저장, 관리하는데 있어 프로세스 권한에 따라 데이터를 노출, 은닉, 암호화하고 손실 시 이를 다시 원래 상태로 회복시키는 등 총체적인 데이터 운용 기술이 적용된다(파일시스템과 비슷하게 사용되는 개념이 파일링시스템이다. 파일링(filing)이란 컴퓨터에 의해 제공되는 가장 중요한 서비스 가운데 하나로 오랜 시간에 걸쳐 대량의 데이터를 조직하고 저장할 수 있는 것을 뜻한다. 파일링시스템은 더 광의의 파일시스템이다).
FAT 파일시스템의 가장 중요한 설계 중심은 파일할당 테이블이라는 연결리스트(Linked List)의 자료구조다. FAT는 파일할당 테이블의 단위 크기에 따라 구분되는데, FAT12는 파일할당 테이블의 크기가 12비트이며, FAT16과 FAT32는 각각 16비트, 32비트의 단위 크기를 가지고 있다(<표 1> 참고). 이들은 클러스터 크기도 달라, FAT12는 212, FAT16과 FAT32는 각각 216, 232이다. 이밖에도 FAT32는 유니코드를 지원하는데, 긴파일이름(Long File Name)이라는 개념은 기존 디렉토리 엔트리와의 길이 호환성을 위해 나온 것이다.
일반적으로 FAT32는 루트 디렉토리의 크기제한이 없기 때문에 루트 디렉토리가 없다고 알려져 있으나 FAT32에도 엄연히 루트 디렉토리가 존재한다. 다만 그 형태가 FAT12, FAT16처럼 루트 디렉토리 영역으로 제한되지 않고 서브 디렉토리와 같은 형태로 자유롭다는 차이가 있다.
NTFS는 설계 상 새로운 기술들이 많이 적용된 파일시스템으로 <표 2>는 그 특징을 FAT 파일시스템과 비교해 정리한 것이다. 많은 사람들이 NTFS 버전 3.0 이상의 특징 중 하나인 희소 파일(Sparse Files)에 대해서 어렵게 생각하는데 이것은 자료구조를 배운 개발자라면 쉽게 이해할 수 있다. 희소행렬을 연결리스트 또는 비트맵(Bitmap)으로 표현한다고 보면 된다. 예를 들어 다음과 같이 가로, 세로가 8×3 크기의 행렬이 있다고 가정하면 2차원 배열을 이용했을 때 24바이트가 필요하다.

3 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 5

반면 연결리스트를 이용하면 다음과 같이 선형(linear)으로 이용할 수 있다.

3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5

이렇게 하면 단지 2개의 값 3, 5에 대해서만 연결을 갖고 있고 나머지는 0으로 간주해 더 효율적으로 구현할 수 있다. 희소파일이란 이처럼 0이 많은 데이터의 경우 0에 대해 디스크에 기록하거나 보관할 필요가 없도록 하는 파일들을 말한다. 100MB 용량의 파일 가운데 0이 아닌 데이터가 1MB이고 나머지 99MB가 0이라면 실제 디스크를 차지하는 공간은 1MB가 되는 것이다.
그렇다면 구체적으로 디스크와 드라이브에는 어떻게 접근해야 할까. 접근방법에는 크게 4가지가 있다. 예를 들어 디스크 하나를 파티션 두 개로 분할해 2개의 드라이브로 사용한다면, 논리적인 접근은 해당 드라이브의 파일시스템 구조만 알면 섹터 읽기/쓰기를 할 수 있다. 반면 디스크 단위로 섹터에 접근할 때 사용하는 물리적 접근은 파일시스템에 대한 구조뿐만 아니라 MBR(Master Boot Record)과 확장 MBR에 대한 이해가 필수적이다.
CHS(Cylinder Head Sector) 방식과 LBA(Logical Blocking Address) 방식도 있다. CHS 방식은 실린더, 헤드, 그리고 섹터 값을 통해 디스크에 접근하는 것이며, LBA는 선형적으로 1번 섹터 형태로 단일 값으로 순차적으로 접근하는 방식이다. CHS 방식은 바이오스에서 실린더를 1024개까지만 지원하는데 실린더로 MBR에 10비트만 기록할 수 있다. 이는 실린더 210=1024, 헤드 28-1 = 255, 섹터 26-1 = 63과 같은 형태로 3바이트를 차지하기 때문으로, 1024×255×63×512를 계산한 뒤 1024로 3회 나누면 GB 단위가 되므로 약 7.8GB, 즉 기존 CHS 방식으로는 7.8GB 제한이 발생하는 것이다(이런 제한을 개선한 것이 확장 CHS 방식이다. LBA 값을 환산해 CHS로 표기하는 형태다).
이 때 중요한 개념이 마스터 부트섹터(MBS)와 파티션 부트섹터(PBS)다. 이들은 문자 그대로 하나의 섹터로, 512바이트를 기준으로 하면 매우 작은 공간에 불과하지만 매우 큰 역할을 맡고 있다. MBR은 썬 계열과 일반 PC 사용자들이 많이 접하는 인텔 계열이 있는데, IPL(Initial Program Loader)이라는 446바이트 크기의 프로그램과 16바이트 길이의 4개 파티션 테이블, 그리고 매직넘버(Magic Number)가 기록된다.
IPL의 일반적인 역할은 다음과 같다. PC에 전원이 들어온 후 PO ST(Power On Self Test) 과정을 거치고 나면 바이오스 롬이 MBR을 0000:7C00h에 로드한다. 하드디스크 개수를 DL 레지스터에 건네주면 그 수를 점검한 후(일부 바이오스는 0) 부트로더용 부트 플래그(0 또는 1)를 확인하고 기본 파티션에서 활성 파티션을 찾는다. 이때 적절한 파티션이 없으면 그 다음 하드디스크 드라이브로 부팅하고 어떤 파티션이 존재하지 않을 경우 첫 번째 플로피디스크 드라이브로 부팅한다. 하드디스크이든 USB-UFC 등의 메모리이든 부팅할 수 있는 파티션이라면 해당 파티션의 부트섹터 즉 파티션 부트섹터가 메모리에 올라간다. 그리고 부트코드(Boot Code)가 실행된다(부트코드는 커널을 로드하거나 운영체제에서 처음에 수행해야 할 초기 설정을 실행한다). 그러나 IPL과 부트로더는 다양한 형태가 있다.

디스크 접근 방법
PBS는 도스용 백신 프로그램에서 DBS(DOS Boot Sector)라고 표기하기도 한다. PBS와 MBR는 개념적으로는 유사해, 코드 영역과 데이터 영역을 갖는 공통점이 있고 부트코드에서 부트로더로의 바통 터치도 흡사하다. 단 MBR은 코드 영역이 먼저 위치하고 데이터 영역이 뒤따르는 반면 PBR은 데이터 영역이 먼저 온다.
부트코드는 512바이트의 공간에 존재하는 코드 영역으로, 부트로더라고도 부른다. 최근의 운영체제들은 디스크 내의 모든 파티션에서 부팅할 수 있는, 이른바 멀티부트를 지원하는 확장 부트로더 개념이 도입돼 있는데 예를 들어 NT 로더는 MBR 차원이 아닌 PBR 차원에서 더 많은 섹터를 사용해 기능을 확장했다. FAT16의 부트코드를 보면 FAT 16 파일시스템은 설계상 2GB의 제한이 있어, 이를 사용했던 윈도우 95의 경우 부트 파티션의 크기가 2GB를 넘는 부트코드를 인식하지 못했다(물론 부트코드를 수정해 기술적으로 2GB 문제를 해결할 수 있지만 부트코드를 만드는 업체는 마이크로소프트(이하 MS)이므로 일반 사용자는 그냥 불편을 감수해야 했다. 물론 개발자라면 누구나 시도해 볼 수 있지만 어셈블리로 구현하는 경우가 많으므로 이 언어에 대한 지식이 필수적이다).
MBR이 PBR을 0000:7C00h에 로드하면 도스 커널인 IO.SYS 파일을 찾는 작업을 시작한다. 부트섹터에 있는 데이터 영역을 참조해 루트 디렉토리 엔트리(Root Directory Entry)의 위치를 찾고, 32바이트씩 읽어 들이되 맨 앞에서부터 11바이트를 로드한다. 처음 8바이트는 파일 이름이 IO인지 비교하고 이후 3바이트는 확장자가 SYS인지 비교한다.
<표 3>을 보면 IO.SYS 파일을 찾아 시작 클러스터의 값을 읽어들이는 것을 알 수 있다. 파일할당 테이블에서 시작 클러스터 값에 해당하는 데이터 영역의 위치를 계산해 찾은 뒤 파일 크기만큼 읽는다. 특히 첫 번째 섹터에 있는 헤더가 MZ인지 확인한 뒤 IO.SYS 즉 커널을 로드한다.
전통적인 부트코드의 역할은 이 정도다. 현재와 같은 멀티부트로더는 NT 로더와 같이 더 복잡하게 작동한다. 이 원리를 이해하는 것이 중요한 이유는 파일을 불러오는 원리가 파일복구 프로그램의 그것과 동일하기 때문이다. 즉 파일이 삭제되면 IO.SYS 파일에서의 I 값에 해당하는 값이 49h가 E5h로 마킹된다. 이것을 도스나 윈도우 운영체제는 지워진 것으로 판단하고 화면에 보여주지 않는다. <화면 2>는 데이터 영역 즉 데이터 클러스터에 있는 내용을 확인한 대화상자다. FAT 부트섹터는 도스 버전이 2.x, 3.x, 4.x, 5.x 등으로 높아지면서 디스크 용량도 증대되고 시대적 요구에 맞게 부트섹터 또한 확장됐다.
NTFS 부트섹터 역시 FAT과 유사하다. 그러나 앞서 언급한 대로 NTFS 고유의 특성과 정보를 담아야 하기 때문에 일부 FAT와 다른 부분이 있다. 예를 들어 FAT32 등으로 기록해야 할 파일시스템 ID은 NTFS로 변경되고 제조자 ID는 제거되며, 총 섹터수가 64비트로 늘어나면서 NTFS 고유의 내부구조를 알 수 있는 정보인 $MFT의 논리 클러스터 번호(Logical Cluster Number for the file $MFT), $MFTMirr의 논리 클러스터 번호(Logical Cluster Number for the file $MFTMirr), 파일레코드 세그먼트당 클러스터수(Cluster/File Record segment), 인덱스 블럭당 클러스터수(Cluster/Index Block), 체크섬(Checksum) 등은 NTFS 고유의 특성 정보들이 추가됐다.

파일시스템의 종류
디스크 기반의 파일시스템에는 다양한 특성과 목적을 가진 파일시스템이 있다. 특히 파일시스템은 목적지향(Purpose-oriented)적으로 설계된다. 각 파일시스템은 특정 목적에 맞게 개발되기 때문에 어떤 파일시스템이 좋다고 평가하는 것은 무리다. 단 같은 분류에 속한 파일시스템 간에는 비교할 수 있는데, 예를 들어 많은 사람들이 사용하는 FAT와 NTFS의 비교가 바로 그것이다.
그러나 여기도 한 가지 함정이 있다. 양쪽 모두 디스크 파일시스템임은 분명하지만 NTFS는 저널링 파일시스템(Journaling File System)이고 FAT는 그렇지 않다. MS는 FAT와 NTFS를 비교해 발표하지만 이는 단지 마케팅 전략의 하나로 윈도우 9x 유저에게 새로운 운영체제의 우수성을 알리기 위한 것일뿐 디스크 파일시스템(Disk File System)으로 비교한 것은 아니다.
파일시스템의 종류는 상당히 많지만 특성에 따라 분류하면 디스크 파일시스템(Disk File Systems), 로그 구조의 파일시스템(Log-structured File System), 저널링 파일시스템(Journaling File Systems), 네트워크 파일시스템(Network File Systems), 특수 목적 파일시스템(Special Purpose File Systems), 의사파일시스템(Pseudo File System), 분산 파일시스템(Distributed File System), 플래시 파일시스템(Flash File System or Memory File System) 등을 꼽을 수 있다.
혹자는 디스크 파일시스템을 디스크 기반의 파일시스템이라고도 한다. 거의 대부분의 하드디스크가 여기에 속하며 컴퓨터에 직간접적으로 연결할 수 있다. 디스크 파일시스템은 무수히 많은데 도스와 윈도우에서 사용되는 12, 16비트 테이블 깊이의 FAT와 32비트 테이블 깊이를 갖는 FAT32, 아미가(Amiga) 시스템에서 사용되는 FFS(Fast File System), BSD 시스템에서 사용되는 FFS, OpenVMS 파일시스템인 Files-11, 구 맥OS 시스템에서 사용된 HFS(Hierarchical File System), 신 맥OS 시스템에서 사용되는 HFS플러스(HFS+), HFSX(HFS+의 case-sensitive한 변종) 등이 있다.
로그기반의 파일시스템은 디스크 파일시스템이 갑작스러운 정전 등의 위험에 노출될 경우 데이터의 신뢰성과 무결성 등을 보완하기 위해 로그를 남기는 것이 특징이다. 저널링 파일시스템으로 진화하기 위한 과도기적 기술로, 파일시스템 레이아웃을 특정한 위치에 기록하는 방법으로 유사시 대비하는 방법으로 제안됐다. 순수 저널링은 아니지만 이후 WAFL(Write Anywhere File system Layout)에 영향을 주기도 했다.
저널링 파일시스템(Journaling File Systems)은 문자 그대로 저널링을 지원하는 파일시스템이다. 저널링의 개념은 트랜잭션을 지원하는 파일시스템을 떠올리면 간단하다. 마치 일기처럼 언제(앞으로 할 기록에 대한 작업이 발생 시) 어디서든(파일시스템의 공간 내에서) 무엇을 통해서(파일에) 기록을 남기는 것이다. 미리 구조화된 정보를 파일시스템의 로그파일에 남기고 실제 작업을 수행하며 이를 완료하면 별도 표시를 하고 다음 작업을 수행한다. 이러한 흐름을 반복하다가 정전이 되거나 전원이 꺼지면 다음 부팅시에 수행 중이었던 부분을 복구하는 것이다. 미리 정의한 크기만큼 백업을 진행하면서 작업하기 때문에 큰 부담없이 복구할 수 있는 것이 특징이다.
플래시 파일 시스템은 최근 들어 저널링 개념이 도입되면서 디스크와 어깨를 나란히 할 만큼 신뢰성을 높였다. 저널링 파일시스템으로는 리눅스의 ext3 파일시스템(ext2 파일시스템에 저널링이 추가됐다)과 JFS, ReiserFS, Reiser4, XFS, OS/2의 JFS, AIX의 JFS, 윈도우 NT의 NTFS(리눅스에서는 현재 NTFS에 대한 읽기를 지원한다), 맥OS의 HFS+(저널링은 맥OS X 10.2.2.에서 추가됐다), 아이릭스의 XFS, 솔라리스의 UFS Logging, VxFS(베리타스 소프트웨어의 써드파티), HP-UX의 VxFS(HP 시스템의 JFS로 알려져 있으나 IBM의 JFS와는 다르다), BFS의 BeOS 파일시스템, WAFL 파일시스템 등이 있다.
네트워크 파일시스템(Network File Systems)이란 것도 있다. 여러 대의 컴퓨터에 의해 잠재적으로 동시에 파일이 액세스되는 파일시스템이다. AFS(Andrew File System), AppleShare(AppleShare File System), CIFS(SMB, Samba File System) 등이 대표적이다.
이밖에도 더 좁은 의미의 특화된 용도로 쓰이는 특수 목적의 파일시스템(Special Purpose File Systems)이 있다. 디스크 파일시스템과 네트워크 파일시스템이 아닌 파일시스템을 총칭하는 것으로 소프트웨어에 의해 파일이 동적으로 배치되거나 컴퓨터 프로세스 또는 임시 파일공간으로 의도된 시스템을 포함한다. 특수 목적의 파일시스템에는 텍스트 윈도우의 acme(Plan 9, Plan 9는 9P 프로토콜), archfs(archive), CD 읽기/쓰기를 목적으로 하는 cdfs, 캐싱 목적의 cfs, DEVFS, FTP 액세스 목적의 ftpfs, swapfs(Swap File System), WinFS 등이 있다.
마지막으로 플래시 파일시스템은 낸드/노어타입의 플래시 메모리에 사용되는 파일시스템으로, JFFS(Journaling Flash File System), JFFS2, JFFS3과 YAFFS(Yet Another Flash File System), YAFFS2, 그리고 삼성전자에서 설계한 RFS(Robust File System)가 대표적이다. JFFS는 노어형 플래시 메모리에 사용되고, YAFFS와 RFS는 낸드형 플래시 메모리에 사용된다.

파일복구 프로그래밍을 위한 준비
지금부터는 가장 널리 사용되는 윈도우 운용체제용 파일복구 프로그래밍에 대해 살펴보자. 윈도우 95/98/SE/ME 등 16비트와 32비트가 혼재된 9x 계열에서 디스크를 물리적으로 접근(읽기/쓰기)할 수 있는 모듈을 DLL로 개발하려면 32비트 응응 프로그램 프로그래밍이 가능한 VC++ 6.0 또는 VC++ .NET을 이용해 실행파일을 만들어야 한다. 이 때 중요한 것은 16비트 DLL에 접근하는 32비트 DLL도 함께 개발해야 한다는 점이다.
32비트 응용 프로그램이 16비트 DLL에 직접 접근할 수 없어 Thunking이라는 통신방법을 이용하기 때문이다. 이밖에 터보 어셈블러 4.0 또는 매크로 어셈블러 6.11이라는 툴도 필요한데 이는 16비트 DLL 버전을 3.1에서 4.0으로 바꾸는데 사용한다. 커널이 안정적으로 동작하고 DLL의 무결성을 검사하기 위한 것으로, 이렇게 하지 않으면 16비트 모듈이 정상적으로 작동하지 않는다.
윈도우 9x에서 FAT에 접근하는 것은 일부 논리적 접근법만 특별할 뿐 크게 어렵지 않다. 문제는 NTFS, ext3, HFS+ 등 윈도우 98에서 지원하지 않는 파일시스템을 구현하는 것인데, 이는 디바이스 드라이버로 구현하는 방법도 있으나 해당 파일시스템에 대한 이해나 테스트없이 바로 디바이스 차원으로 넘어가는 것은 바람직하지 않다. 대신 직접 물리적으로 디스크를 읽어들인 뒤 파티션 정보에 따라 해당 드라이브로 접근해 파일시스템 레이아웃에 따라 섹터를 읽어 처리하면 탐색기와 같은 인터페이스의 프로그램을 제작할 수 있다.
현재 널리 쓰이고 있는 윈도우 XP는 어떨까. 흔히 윈도우 NT와 2000, XP, 2003 버전을 통칭하는 것이 바로 윈도우 NT다. 윈도우 NT는 DeviceIOControl()이라는 윈도우 API를 이용해 물리디스크를 열고 해당 디스크로 접근해 원하는 섹터를 읽기/쓰기한다. 윈도우 9x와 달리 관리자 권한만 있으면 간단하게 구현할 수 있도록 설계돼 있다.
윈도우 NT에서는 파일시스템별로 전용 코드가 필요없다. 즉 물리디스크를 열어 디스크 차원에서 섹터에 액세스하거나 논리 드라이브를 열어 드라이브 차원에서 액세스하면 된다. 다만 파일시스템 레이아웃에 대한 모든 정보를 알고 있어야 실제로 FAT 파일시스템과 NTFS를 읽어들일 수 있다(이 부분은 다음 호에 자세하게 다룰 것이다). 이밖에 ext3, HFS+, iso9660, joliet, udf 등의 파일시스템 역시 디스크나 메모리에 사용할 수 있다. 물리적인 접근이 추가로 필요할 뿐이다.
지금까지 파일복구 프로그래밍의 기본이 될만한 제작원리에 대해 살펴봤다. 다음 호에서는 FAT와 NTFS 내부구조에 대해 알아보고 실제 소프트웨어를 구현해 본다.

출처 : 마이크로스프트웨어
Posted by skensita