## 개요 NTFS 파일시스템에서는 각 파일들의 기록을 관리한다. 파일의 기록이라고 했을 때 어떤 생각이 먼저 드는가? 언제 어떤 파일 내용이 추가되었고, 누가 수정했고 이런 내용이 떠오르게 될 것이다. 하지만 세세한 정보를 기록하면 할수록 저장해야 하는 데이터의 양이 많아진다. 배보다 배꼽이 더 커지는 상황인 것이다. 따라서 이렇게 세세한 정보를 기록하지는 않고, 트랜잭션에 대한 로그만을 저장한다. 이때 말하는 트랜잭션이란 파일의 생성, 수정, 삭제를 비롯한 작업들을 포함한다. 각 지점에 대한 기록을 남김으로써 오류로 인해 파일 복원이 필요한 경우 데이터를 되돌리거나 복구할 수 있을 것이다. 기록을 담당하는 기능으로는 크게 [[$Logfile]] 과 [[$UsnJrnl]]이 있다. 비슷한 기능을 하지만 차이점이 분명하게 존재하며, 각각을 자세히 알아볼 필요가 있다. 오늘은 NTFS의 $MFT Entry 의 두 번째로 예약된 파일인 $Logfile 파일을 분석해 봄으로써 어떤 종류의 정보를 얻을 수 있는지 알아보고, 바이너리 형태의 데이터를 분석 가능한 형태로 변형해볼 것이다. ## 경로 ``` %System Root%/$Logfile ``` > [!Question] 파일이 보이지 않아요! > > 당연히 보이지 않는 게 정상이다. $Logfile은 시스템 파일이기 때문이다. > > 일반적인 숨김 파일과는 다르기 때문에 파일 탐색기 상에서도 숨김 옵션 해제를 해도 보이지 않을 것이다. > > 따라서 다른 도구를 통해 파일을 추출해야 한다. ## 데이터 이제 $Logfile안에 어떤 데이터가 어떻게 저장되는지 알아볼 것이다. 언제나 그랬던 것처럼 데이터가 어떤 경우에 생성되고, 얼마나 저장되며, 언제 삭제되는지 설명할 예정이다. ### 생성 및 유지 [[#개요]] 부분에서 트랜잭션에 대해서 간단히 설명했다. $Logfile 데이터는 트랜잭션이라는 행위가 발생할 때 기록된다. 더 쉽게 풀어 말하자면, 파일이 생성되거나 수정될 때, 삭제될 때 $Logfile에 기록된다. 앞서 말했듯 모든 종류의 로그는 효율적으로 데이터를 저장해야 한다. 밑도끝도 없이 데이터를 쌓아놨다가는 저장 공간의 효율이 저하될 수 있기 때문이다. 따라서 $Logfile은 제한된 데이터를 가지고 있다. 실험을 통해 확인해 본 결과, 기본 설정값은 64MB로 설정되어 있으며 볼륨 크기에 따라 상이하게 나타날 수 있으며 Windows 기본 명령어인 `chicks %Drive Letter% /I` 옵션을 이용한다면 정확한 크기를 확인할 수 있다. 당연하게도 사용자가 변경한다면 다른 값을 사용할 수도 있다. `디스크 공간 : 7167KB, 로그 파일 : 2048KB` ![[Pasted image 20231206080100.png]] `디스크 공간 : 487726079KB, 로그 파일 : 65536KB` ![[Pasted image 20231206080132.png]] 그렇다면 최대 크기를 넘어가면 어떻게 될까? 더이상 저장을 안할 수도 있겠지만 가장 과거에 있던 데이터를 삭제하고, 새로운 데이터를 입력하는 방식을 채택한다. 따라서 최신 기록만 확인할 수 있는 것이다. ### 구조 파일의 전체적인 구조는 아래 사진과 같다. ![[Pasted image 20231206080945.png]] 재시작 영역과 로깅 영역으로 구성되어 있으며, 로깅 영역은 다시 버퍼 페이지 영역과 일반 페이지 영역으로 나뉜다. 아직 하나도 무슨 소리인지 이해할 수 없다는 거 잘 안다. 하나하나 차근차근 설명해 갈 테니 잘 따라오도록 하자. #### 재시작 영역 (0x0000 - 0x1FFF) 재시작 영역은 가장 마지막까지 작업했거나 현재 작업중인 파일의 레코드를 담고 있는 영역이다. 두개의 페이지라는 단위로 구성되어 있으며 페이지는 헤더, Restart Area 레코드, Log Client 레코드로 구성되어 있다. 마지막까지 작업했거나 작업중인 파일은 하나일텐데 왜 두개의 페이지로 구성되어 있을지 궁금할 수 있다. 뻔한 이유일수도 있지만 데이터 손상시 대체할 용도로 백업본이 필요하기 때문에 2개의 페이지로 구성되어 있는 것이다. 이제 데이터를 볼 텐데 데이터를 보는 두가지 방법이 있다. 첫번째는 도구를 이용해 $Logfile을 추출한 다음 분석하는 것이고, 두번째는 이미지 형태의 파일의 Hex값을 따라 찾는 것이다. 전자의 경우라면 따로 할 필요는 없지만 후자의 경우라면 $MFT 엔트리에서 $DATA 속성을 분석하여 데이터의 위치를 찾으면 된다. 자세한 내용은 [[MFT Attribute (1)#Non-Resident Header]] 를 다시 본다면 해낼 수 있을 것이다. 데이터 영역을 찾았다면 재시작 영역의 시그니처 값인 `RSTR`문자열을 보게 될 것이다. 값이 보인다면 맞게 찾아간 것이고 아니라면 뭔가 잘못된 것이니 다시 해결해 보도록 하자. 시그니처 값 이후의 구성은 아래 사진과 같다. ![[Pasted image 20231206082308.png]] 이름만 가지고도 느낌이 오는 것들이 있는 방면 약어로 쓰여 있어서 어떤 것을 의미하는지 모르는 값들도 있을 것이다. 표에 자세한 설명을 적어놓았으니 잘 따라와 보도록 하자. 참고로 이런 내용을 볼 때는 구조체만 딱 보는 것 보다는 실제 데이터를 보면서 하나하나 매칭 시켜 보는 것이 공부하는데 도움이 된다. ![[Pasted image 20231206082839.png]] 이때 LSN 이란 Logfile Sequence Number의 약어로 $Logfile에서 레코드를 저장할 때 분류의 기준이 되는 순서이다. 인덱싱 번호 정도로 생각하면 되겠다. ![[Pasted image 20231206083206.png]] 이제 헤더 내부의 데이터가 있는 위치는 Restart Area Offset에 작성되어 있다. 상대적 위치라는 것을 항상 알아야 하며, 기준 위치는 헤더의 시작 위치인 `0x63000`이 되겠다. 그럼 절대적인 데이터의 위치는 `0x63030`이다. 즉 헤더 바로 다음에 위치해 있게 된다. 헤더 다음에 위치하는 것은 Restart Area Record로, 재시작 영역의 레코드다. 레코드 영역에서는 지금 다루는 Restart Area와 Log Client 두개로 구성되어 있다. 순차적으로 알아볼 것이다. Restart Area의 구조는 아래 사진과 같다. ![[Pasted image 20231206083840.png]] 직후에 Log Client 레코드가 위치하게 되며, 구조는 다음 사진이랑 같다. ![[Pasted image 20231206150513.png]] 이때 Previous 나 Next client의 경우 다음 값이 없을 때에는 0xFFFF 값이 오게 된다. #### 버퍼 페이지 영역 (0x2000 - 0x3FFF) 버퍼 페이지 영역은 재시작 영역 이후 첫 두 페이지를 사용하며, 버퍼 라는 말 그대로 임시 저장하고 있는 곳이다. 버퍼 페이지가 가득 차는 경우에는 일반 페이지 영역으로 데이터를 이동시킨다. 따라서 최근에 사용되었던 파일만을 보고 싶을 때에는 버퍼 페이지 영역만을 참고하면 되겠다. 버퍼 페이지 영역과 일반 페이지 영역은 사실 로깅 영역으로 공통적인 영역으로 취급할 수 있다. 따라서 차이점만 알아보고 주요 구조는 일반 페이지 영역에서 다루도록 하겠다. #### 일반 페이지 영역 (0x4000 - EOF) 일반 페이지 영역에서 페이지는 무엇인지 궁금해 하며 읽은 사람들이 많은 것이다. 페이지는 위에서도 설명했듯이 하나의 헤더와 N개의 레코드로 구성된 구조체이다. 이때 레코드도 하나의 헤더와 데이터로 이루어져 있다. 여러 구조가 나와 헷갈릴 수 있기에 밑에 정리해 놓도록 하겠다. - 페이지 - 페이지 헤더 - 레코드 - 레코드 헤더 : 레코드의 메타데이터 저장 (0x58) - 레코드 데이터 : 작업 전/후 내용이 저장되어 있음 이제 데이터가 커지는 경우가 항상 변수로 다가올 것이다. 레코드 하나의 내용이 페이지 크기를 넘어설 수 있을 텐데, 이런 경우는 어떡할까? 바로 여러 페이지를 사용하는 것이다. 이때 첫번째 페이지에는 두번째 페이지의 위치를 명시해 놓게 된다. 따라서 여러 페이지를 이용해서 큰 사이즈의 레코드를 저장할 수 있게 되는 것이다. 이제 본격적으로 구조체를 들여다 보도록 하자. 먼저 보게될 것은 페이지 헤더 구조체이다. ![[Pasted image 20231206151517.png]] Next Record Offset이 있다는 것을 통해 여러 레코드가 있음을 유추할 수 있다. 여러 페이지를 사용할 수도 있기에 Page Count를 통해 페이지 갯수를 관리한다. 이제 레코드 헤더를 보자 ![[Pasted image 20231206151827.png]] 이때 최대 용량을 넘어서는 경우에는 가장 오래된 레코드를 삭제하고 다시 쓴다는 말을 위에서 언급했었다. (덮어쓴다는 의미이다) 즉 LSN 값을 토대로 가장 오래된 레코드의 위치에 작성하게 된다. 실질적으로 데이터를 덮어씌우기 때문에 레코드 하나는 파일의 형태로 관리되지 않는다. 파일의 형태로 관리되지 않는다는 것은 디스크 공간을 별도로 할당해 주지 않는다는 의미이므로, 삭제된 레코드를 미할당 영역에서 복구할 수 없음을 의미한다. 또 이전 LSN과 다음 LSN을 담고 있는 Doubly-Linked-List의 형태를 띈다. 하나의 레코드만 가지고도 다른 레코드를 모두 찾아낼 수 있음을 의미하겠다. 또 Redo/ Undo OPcode는 어떤 작업이 수행되었는지를 담고 있는 값이다. 트랜잭션의 유형을 담고 있다. 레코드 데이터에 대해서 짤막하게 설명해 보겠다. 레코드 데이터는 트랜잭션 전/후의 데이터를 담고 있다. 실질적인 파일의 데이터를 담고 있기 때문에 딱히 구조가 없는 데이터 영역이다. ## 결론 NTFS의 주요 로그 파일 중 하나인 $Logfile에 대해서 알아 보았다. 실질적으로 파일의 Transaction 전/후 과정을 기록하고 있는 파일로, 파일의 전후 상태를 복구할 수 있는 시스템 메타데이터 파일이라는 점이 매력적이다. 구조가 많기도 하지만 그렇게 복잡한 구조로 이루어져 있지는 않으니 한번 직접 분석해 보는 것도 좋다. 직접 분석을 해보았다면 도구도 이용해 보도록 하자. 필자가 가장 많이 이용하는 $Logfile 분석 도구는 [NTFS LogTracker](https://sites.google.com/site/forensicnote/ntfs-log-tracker)이다. 한번 이용해 보도록 하자. 깔끔하고 정확하게 정보들을 확인해 볼 수 있다.