대게 사용 중인 저장 장치를 덤프 떠보게 되면 다음과 같은 이미지 파일을 획득할 수 있을 것이다.

 

그리고 이를 FTK Imager와 같은 이미지 분석 도구를 이용하여 확인할 경우 아래와 같은 광경을 보게 될 것이다.

이 중 실제 USB 내 데이터를 확인할 수 있는 위치는 Partition 1의 [root] 폴더이다. 그런데 이 이미지 파일에는 실제 저장된 데이터에 대한 정보 이외에 FAT32 파일 시스템과 관련된 것으로 추측되는 파일들도 있었고, 아래 사진과 같이 File List로는 확인되는 것이 없으나 이미지 파일 자체의 시작 오프셋 위치에서 별도의 hex값도 확인할 수 있었다.

 

여러 차례 이미지 파일 분석을 돌려보는 과정에서 대체 이 내부의 구조가 어떻게 되어 있길래 이러한 구성을 갖게 되는 것일지 궁금했다. 그리고 이러한 궁금증을 해결하기 위한 리서칭 중 답은 저장 장치의 구조와 관련이 있을 것으로 파악하고 이를 공부하고자 이번 포스트를 작성하게 되었다.


Storage Structure

저장 장치는 기본적으로 다음과 같은 형태를 가진다.

이 구조는 파티션 관리를 위해 MBR을 사용하는 경우에 해당하는 구조이지만, 후반부에 정리된 GPT를 사용하는 구조에서는 조금 다른 형태의 구조를 볼 수 있다. 그럼에도 크게 “저장장치 구조” 라는 범주로 보았을 때, 저장장치 부트 과정에 관여하면서 파티션에 대한 정보를 저장하는 파트와 각 파티션 별로 데이터를 저장하는 파트로 구성되는 점은 공통적으로 확인되었다.

 

MBR(Master Boot Record)

MBR은 저장장치 단위로 제일 앞에 한번 위치한다. MBR에서는 저장장치가 사용되기 위해 필요한 정보들이 저장되어 있는데, 크게 Boot Code와 Partition Table로 이루어져 있다.

MBR의 파티션 테이블의 경우 최대 4개의 primary partition만 생성 가능한 구조로 이루어져 있다는 점에서 MBR이 GPT로 대체된 이유를 알 수 있다.

MBR의 세부적인 구조는 글의 중반부에서 별도로 다루어진다.

MBR Slack

MBR과 파티션의 VBR이 존재하는 위치 사이에 있는 여유 공간을 가리킨다. 이 공간은 말그대로 여유 공간이기 때문에 악성 코드에 의해 악용되기도 하는 공간이다.

VBR(Volume Boot Record)

VBR은 각 볼륨이 시작될 때 한번씩 위치하는 부분이다. VBR은 해당 볼륨에 액세스 하기 위한 정보를 가지고 있는데, 여기에는 파일 시스템 타입, 부트 코드, 에러 메세지 등이 포함된다. MBR의 파티션 테이블을 통해 부팅 가능한 파티션에 접근하게 되고, 그 후 VBR이 실행된다.

Volume Data

각 볼륨의 데이터가 저장되는 부분이다. 각 볼륨 마다 지정된 파일 시스템 형태로 포맷되어 있으며, 이에 저장된 각 파일은 메타 데이터와 파일 데이터 정보를 가진다. 이러한 구조에 따라, directory 탐색 시에는 메타 데이터 정보를 사용하고, 파일 내용에 접근하면서 파일 데이터 정보가 사용된다.


MBR(Master Boot Record)

초기의 저장 장치에서는 정상적인 부팅을 위해 매 저장 장치의 맨 앞에 위치한 MBR을 사용하였다. 이를 이용하여 장치의 부팅 뿐만 아니라 실제 데이터가 저장되어 있는 볼륨에 접근하기 위한 정보도 관리한다.

 

MBR Structure Overview

MBR은 다음과 같은 구조로 이루어져 있다.

가장 처음에 부트 코드가 위치해있고, 그 후에는 장치 내 할당된 파티션의 정보를 가진 Partition Table이 위치한다. 이러한 구조를 이용하여 BIOS의 POST(Power On Self Test) 단계 이후 Bootstrap 실행 시 MBR 내 저장된 부트 코드를 읽어올 수 있다.

부트 코드를 위해 할당된 446 바이트 이후에는 곧바로 파티션 테이블이 위치한다. MBR에서 파티션 테이블이 차지하는 크기는 총 64바이트인데, 각 테이블에 대한 정보를 16바이트 크기로 저장한다. 이에 따라 MBR을 채용한 저장 장치의 경우에는 최대 4개의 테이블만을 가질 수 있게 된다.

부트 코드와 파티션 테이블에 이어 2바이트 크기의 Signature를 확인할 수 있다. 이 값은 16진수로 55 AA 값을 가지며 MBR의 끝을 가리킨다.

 

Partition Table entry

각 파티션을 가리키기 위해 사용되는 파티션 테이블의 entry는 다음과 같은 구조로 이루어져 있다.

Offset 0x0 에 위치하는 Boot Flag는 1바이트의 크기를 가지는 필드이며, 해당 파티션에 할당된 볼륨이 부팅 가능한 상태인지 불가능한 상태인지를 나타낸다. 이 값이 0x80일 경우에는 부팅 가능 상태, 0x00일 경우에는 부팅 불가능한 상태를 가리킨다.

Offset 0x1과 0x5에 위치하며 총 3바이트의 크기로 각각 시작 CHS 주소와 끝 CHS 주소를 가지는 필드가 있다. 이들은 파티션의 위치를 CHS 주소 지정 방식을 사용하여 나타낼 경우에 파티션이 위치하는 처음과 끝 주소를 나타낸다.

Offset 0x4 위치에는 1바이트의 크기를 사용하여 파티션의 유형을 나타내는 필드가 있다. 각 파티션 유형에 따른 값은 아래 링크를 참조하여 알 수 있다.

 

Partition type - Wikipedia

From Wikipedia, the free encyclopedia Table inside a master boot record The partition type (or partition ID) in a partition's entry in the partition table inside a master boot record (MBR) is a byte value intended to specify the file system the partition c

en.wikipedia.org

Offset 0x8 위치에는 4바이트의 크기로 시작 LBA 주소를 가지는 필드가 있다. 이 필드는 CHS 주소 지정 방식과는 다른 LBA 주소 지정 방식을 사용할 경우의 파티션 시작 위치 주소를 가진다.

이 필드를 사용하여 파티션의 위치를 참조할 경우 파티션의 끝 위치는 Offset 0xC에 있는 필드값을 사용하여 알 수 있다. 이 필드는 해당 파티션에 할당된 크기를 나타내는 필드로 활용될 수 있는데, 정확히는 해당 파티션 내에서 사용 가능한 sector의 개수를 의미하는 필드이다.

이 필드는 4바이트로 표현됨에 따라 한 파티션에서 사용 가능한 sector의 최대 개수가 2^32 개가 되며, 한 sector는 512바이트의 크기를 갖는 것에 따라 계산해보면… 한 파티션 당 최대 2TB의 크기만을 가질 수 있게 된다.


GPT(GUID Partition Table)

GPT는 MBR의 한계를 해결하고자 등장한 구조이다. 최근의 대부분 PC에서는 GPT를 지원하고 있고 BIOS의 개선된 펌웨어인 EFI에서도 GPT를 지원하고 있다. MBR의 한계는 MBR 파트를 통해서도 설명했지만 이를 다시 한번 정리하면 다음과 같다.

  • 최대 4개의 primary partition 생성 가능
  • 각 파티션 당 최대 2TB의 크기 할당 가능

 

이러한 한계를 GPT는 다음과 같이 개선하였다.

  • 최대 128개의 primary partition 생성 가능
  • 각 파티션 당 최대 8ZB의 크기 할당 가능

 

뿐만 아니라 GPT는 MBR과는 달리 파티션 정보 이외에 디스크에 대한 다양한 정보들도 함께 저장한다는 특징을 가진다.

 

GPT Structure Overview

GPT를 채용한 저장 장치의 경우 다음과 같은 구조를 가진다. 앞서 MBR 사용 시 구조에서 몇 가지 사항이 추가된 것을 확인할 수 있다.

https://en.wikipedia.org/wiki/GUID_Partition_Table

GPT는 각 부분을 sector(512바이트) 단위로 할당하여 사용한다.

GPT의 본격적인 구조는 Primary GPT Header 부터 이며, 그 전에 위치한 Protective MBR은 GPT를 지원하지 않는 장치에서 파티션을 접근할 수 있도록 하기 위해 사용된다. 뿐만 아니라 MBR 기반의 legacy 디스크에 의해 GPT 영역이 훼손되는 것을 막기 위한 기능으로도 활용된다. 이 Protective MBR의 경우 항상 LBA0의 위치에 존재한다.

GPT 구조의 하단에서 볼 수 있는 Secondary GPT는 Primary GPT에 문제가 생기는 등의 이슈로 Primary GPT를 사용할 수 없을 때 backup 용으로 사용하기 위한 부분이다.

 

Primary GPT Header

LBA1에 위치한 Primary GPT Header는 Signature 값을 시작으로 각 파티션의 정보만이 아닌 Disk GUID나 파티션 개수, entry 크기 등의 정보를 함께 관리한다.

Primary GPT Header의 세부 구조는 아래와 같다. Primary GPT Header의 크기는 0x5B에 불과하지만 이를 위해 할당된 크기는 0x200이기 때문에 나머지 부분은 0으로 채워져 있다.

 

Partition Table array

Primary GPT Header의 offset 0x48에 위치한 주소를 참조하면 partition table array의 시작 위치를 찾을 수 있다. 각 entry별로 일정한 크기를 가지고 있어 순차적으로 각 파티션의 정보를 조회할 수 있다.

Partition Table array의 각 entry의 세부 구조는 아래와 같다.

GPT의 이름이 가진 의미를 여기서 확인할 수 있다. GPT는 파티션 유형 별 GUID를 사전에 정해두고 각 파티션에 해당하는 GUID를 entry에 기입한다. 파티션 유형별로 구분짓기 때문에 같은 유형의 서로 다른 파티션이라고 하더라도 Partition Type GUID 필드는 같은 값을 가지게 된다.

물론 GPT에는 Partition Type GUID와는 별도로 각 Partition에 고유한 GUID도 부여한다. 이를 통해 같은 유형의 파티션이라고 하더라도 각각의 파티션을 구분할 수 있게 된다.

Partition type GUID에 기록될 수 있는 유형의 파티션과 이에 매칭되는 GUID는 아래 링크에서 확인할 수 있다.

 

GUID Partition Table - Wikipedia

From Wikipedia, the free encyclopedia Computer disk partitioning standard The layout of a disk with the GUID Partition Table. In this example, each logical block is 512 bytes in size and each entry has 128 bytes. The corresponding partition entries are ass

en.wikipedia.org

 

이외에 Partition Table의 entry 내에 존재하는 Attribute flags라는 필드의 경우, 파티션의 세부 속성을 비트 플래그 형태로 표현하고자 하는 필드이다. 이 필드에서 주로 쓰이는 비트 플래그는 다음과 같다.

Bits Name Description
0 Platform required 시스템에 의해 사용되는 파티션. 삭제 또는 수정 시 주의를 요함
1 No Block IO Protocol UEFI에서 이 파티션에 대한 파일 시스템 매핑 작업을 무시하도록 함. 
2 Legacy BIOS bootable  
3-47 Reserved 이후 UEFI specification 버전에서 사용될 수 있음을 고려하여 예약된 영역
48-63 Partition type에 따라 다르게 사용 PartitionTypeGUID의 소유자만이 사용할 수 있도록 할당해둔 영역

 

이 중 Bit 48-63이 일부 type에서 활용되는 예시는 다음과 같다.

Microsoft 사의 Basic Data Partition

Bits Name
60 Read-only
61 Shadow copy
62 Hidden
63 No drive letter (not automount)

 

ChromeOS kernel Partition

Bits Name
48-51 Priority
0: not bootable, 1~15(highest)
52-55 Tries remaining
56 Successful boot flag

 

 

Prefetch 개요

프리패치는 Windows 운영체제에서 소프트웨어 활동과 관련된 정보를 저장하기 위해 사용하는 메모리 관리 기법 중 하나이다. 이를 사용할 경우 자주 사용될 프로그램들을 미리 메모리에 로드해두기 때문에 더 빠른 실행이 가능하다.

프리패치는 파일 형태로 데이터를 저장하는데, 이렇게 저장된 파일은 다음의 용도로 활용될 수 있다.

  • Windows 및 응용 프로그램 시작 성능 향상
  • 응용 프로그램(바이러스)의 행위 연구, 포렌식 분석

 

물론 prefetch의 동작 방식 때문에 오히려 불필요하게 RAM을 사용하게 될 수 있어 이미 PC 성능이 좋거나 SSD를 사용하고 있는 경우에는 RAM 공간 확보를 위해 비활성화를 추천하기도 한다.

그렇기에 prefetch는 무조건 활성화되어 있지 않을 수 있으며 다음의 레지스트리 경로를 이용하여 prefetch 활성화 여부를 확인할 수 있다.

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters [EnablePrefetcher]

EnablePrefetcher의 값은 0~3 중 하나로 설정할 수 있으며, 각각의 값은 다음과 같은 의미를 가진다.

  • 0: Prefetch 사용 안함
  • 1: ALP만 사용
  • 2: BP만 사용
  • 3 (기본값): ALP와 BP 모두 사용

 

여기에서 확인할 수 있듯이 prefetch에는 다음의 두 가지 유형이 존재한다. 기본적으로 두 가지를 모두 사용하도록 설정되어 있으나, 필요에 따라 레지스트리의 값을 변경하여 원하는 옵션으로 사용할 수 있다.

  • ALP(Application-Launch Prefetching): 사용자가 자주 사용하는 응용프로그램의 정보를 prefetching 하는 것으로, 응용 프로그램의 실행 속도를 높일 수 있다.
  • BP(Boot Prefetching): 부팅 시 사용하는 파일이나 프로그램의 정보를 prefetching 하는 것으로, 부팅 속도를 높일 수 있다.

 

Prefetching된 데이터는 파일 형태로 저장되어 있다가, 부팅 시 저장해둔 prefetch 파일을 메모리에 로드해두고, 실제 프로그램 사용 시 메모리에서 해당 데이터를 불러와 사용할 수 있게 한다. 저장된 프리패치 파일은 %SystemRoot%\Prefetch 폴더에서 확인할 수 있다.

(Windows 10 기준) Prefetch 폴더 내 저장된 파일 및 폴더 유형은 다음과 같다.

  • ReadyBoot
  • Layout.ini
  • Prefetch Files (.pf)

 

ReadyBoot 폴더는 Boot Prefetch에 필요한 파일들을 저장하는데 사용된다. 매 부팅 시 Trace#.fx 이름으로 부팅에 필요한 파일 및 데이터 정보가 포함된 파일이 생성된다. 이 파일은 ReadyBoot 폴더 내에 생성되며 가장 최근에 생성된 파일을 기준으로 최대 5개까지 저장된다. 이외에도 1개의 rblayout.xin 파일이 ReadyBoot 폴더 내에 저장되는데, 이 파일을 이용하여 ReadyBoot 시 필요한 정보 및 캐시 파일을 관리한다.

 

Layout.ini 파일에서는 프리패치 버전과 프리패치 파일의 목록을 확인할 수 있다. Layout.ini 파일은 부팅이나 응용 프로그램 시 참조되는 순서대로 파일의 경로가 기록되며, 약 3일 마다 내용이 업데이트된다. 그렇기 때문에 실제 Prefetch 폴더에 저장된 prefetch 파일의 저장 여부와 일치하지 않을 수 있으며, 오래전에 실행된 적 있는 파일에 대한 항목도 존재할 수 있다.

 

Prefetch 파일은 한번 이상 실행된 적 있는 응용 프로그램에 대한 데이터가 저장된다. 이러한 점을 이용하여 PC에서 사용자의 응용 프로그램 사용 흔적 또는 악성 프로그램 실행 기록을 추적할 수 있다.

각 프리패치 파일에 포함되는 정보는 다음과 같다.

  • 프리패치 파일의 MAC 타임스탬프
  • 프리패치 파일의 크기
  • 프리패치 파일에 해당하는 프로세스
  • 파일이 실행된 volume 또는 논리 드라이브 경로
  • 프로그램 실행 횟수
  • 프로그램의 마지막 실행 시간에 대한 타임스탬프
  • 프리패치 파일에 의해 로드된 추가 파일

 

여기에서 파일의 생성 시각과 수정 시각 정보는 각각 다음과 같은 의미로 해석될 수 있다.

  • 파일의 생성 시각: exe 프로그램 최초 실행 시각
  • 파일의 수정 시각: exe 프로그램의 마지막 실행 시각

 

프리패치 파일은 Prefetch 폴더 내에 모두 존재하며, prefetch 파일의 개수가 운영체제 별 최대 제한 개수에 도달하면 가장 오래전에 실행된 순으로 파일을 삭제한다. 운영체제별로 유지하는 최대 prefetch 파일 개수는 다음과 같다.

  • Windows XP, 7: 128개
  • Windows 8, 10: 1024개

 

Prefetch File Format

Prefetch 파일의 file format은 파일의 압축 여부에 따라 두 가지 형태를 가진다.

먼저 압축되어 있는 prefetch 파일의 구조는 다음과 같다.

  • Offset 0x00 (4bytes): File Signature (4D 41 4D 04)
  • Offset 0x04 (4bytes): Uncompressed Data Size
  • Offset 0x08 ~: Compressed Data

Prefetch 파일의 자세한 정보를 얻기 위해서는 압축된 내용을 풀어야 한다. Windows prefetch 압축에는 LZXPRESS Huffman 압축 방식이 사용되며, 원본 내용을 보기 위해 Github에서 공유되고 있는 압축 해제 코드를 이용하여 해제하였다.

압축되지 않은 prefetch 파일의 형식은 크게 File Header와 File Body로 구분할 수 있다. File Header는 운영체제에 관계없이 공통된 구조를 가지지만 Body의 경우 운영체제 버전에 따라 서로 다른 형식을 가진다.

  • File Header: offset 0x00 (84bytes)
  • File Body: offset 0x54 ~

 

먼저, 버전에 관계없이 공통되는 부분인 File Header는 다음과 같은 구조를 가진다.

  • Offset 0x00 (4bytes): Format Version (Little-Endian)
  • Offset 0x04 (4bytes): File Signature (53 43 43 41)
  • Offset 0x0C (4bytes): File Size (Little-Endian)
  • Offset 0x10 (60bytes): File Name (실행 파일 이름)
  • Offset 0x4C (4bytes): Prefetch Hash (Prefetch 파일 이름에 기재된 해시값)
    • Prefetch Hash는 실행 파일 경로에 대한 해시값을 가지며 Windows 버전에 따라 서로 다른 해시 함수를 사용한다.
      • Windows XP, 2003: SCCA XP hash function
      • Windows Vista, 10: SCCA Vista hash function
      • Windows 2008, 7, 2012, 8: SCCA 2008 hash function

 

Format Version으로 사용되는 값의 종류는 총 4가지이며, 각각은 다음과 같은 정보를 가리킨다.

  • 0x11: Windows XP, Windows 2003
  • 0x17: Windows Vista, Windows 7
  • 0x1A: Windows 8.1
  • 0x1E: Windows 10

 

Prefetch 파일은 기재된 format version에 따라 서로 다른 구조를 가진다. Windows 11도 Windows 10 비슷한 부분이 많은 운영체제임에 따라 0x1E version을 사용한다. 다음은 각 버전에 따른 전체 Format 구조이다.

 

  • 0x11: Windows XP, Windows 2003

 

  • 0x17: Windows Vista, Windows 7

 

  • 0x1A: Windows 8.1

 

  • 0x1E: Windows 10

 

위 format을 통해 알 수 있는 것과 같이, Windows 10과 8.1의 경우는 최근 실행 시간을 가장 최근 시간을 기준으로 8개까지 저장한다는 특징을 가진다.

여기까지가 각 운영체제별로 가지는 기본적인 prefetch의 파일 포맷이었다. 이후의 내용은 포맷 내 각 필드가 가리키는 값을 추적하는 과정을 정리하였다.

 

File metrics array

각 프리패치 파일별로, 해당 파일을 실행시키기 위해 필요한 다른 파일들을 함께 기록해두는데 이에 대한 내용은 File metrics array로 관리된다. File metrics array 내에 있는 각 entry를 탐색하기 위해서는 File metrics array offset과 Filename strings offset을 필요로 한다.

File metrics array는 File metrics array offset 필드에 정의된 offset 위치에서 시작한다. File metrics array의 각 entry는 일정한 형식을 갖추고 있는데, 이 형식은 운영체제에 따라 약간의 차이를 가진다.

  • 0x11: Windows XP, Windows 2003

  • 0x17, 0x1A, 0x1E: Windows Vista, Windows 7, Windows 8.1, Windows 10

Unknown의 경우는 확실하지 않은 값이므로 그 쓰임을 명시해두지 못했지만
Windows 7, 8, 10에서의 Unknown 간에는 다음과 같은 규칙을 가지고 있음을 확인하였다.

  • Unknown1은 초기값 0에서 시작하여 다음 entry로 넘어갈 때마다 Unknown2 만큼 더해진 값을 갖는다.
  • 2번째 필드와 3번째 필드는 매 entry 내에서 서로 동일한 값을 갖는다.

 

이러한 필드를 제외한 채, Filename string offset과 Filename string number 필드값을 이용하면 각 entry에 해당하는 파일명을 찾을 수 있다. 다음은 Windows 10 환경의 프리패치 파일에서 직접 entry 내 파일명을 확인하기까지의 과정이다.

 

Volumes information

Prefetch 파일을 통해 이 실행 파일이 어느 볼륨에서 실행된 것인지 확인할 수 있으며, 파일에 할당된 file reference를 실행된 볼륨의 데이터와 매핑시켜볼 수 있다.

Volumes information 데이터 추적도 기본 file format의 필드로부터 시작한다. 파일의 시작 지점으로부터 offset 0x6C 위치에서 4바이트 크기의 volume information offset 정보를 확인할 수 있다. 이 offset을 따라가면 volume information entry를 찾을 수 있는데, 여기서의 entry 구조 역시 운영체제 버전에 따라 다르다.

  • 0x11: Windows XP, Windows 2003

 

  • 0x17, 0x1A: Windows Vista, Windows 7, Windows 8.1

 

  • 0x1E: Windows 10

 

다음은 Windows 10 환경의 프리패치 파일에서 직접 entry 내 볼륨 정보를 확인하기까지의 과정이다. 

 

요약

  1. Windows의 Prefetch는 자주 사용될 프로그램들을 미리 메모리에 로드해두어 빠른 실행을 가능하게 하는 메모리 관리 기법 중 하나
  2. 실행된 적 있는 프로그램은 프리패치 파일로 남기 때문에 응용 프로그램(바이러스)에 대한 행위 연구, 포렌식 분석 활용 가능
  3. Prefetch는 비활성화 설정도 가능하기 때문에 무조건 남는 아티팩트가 아니며, 안티 포렌식 목적으로 실행 후 프리패치 파일을 삭제할 수도 있음
  4. 최근 운영체제의 prefetch 파일의 경우 LZXPRESS Huffman 압축이 되어있는 경우가 많아 내용 확인 시 압축 해제 또는 전용 도구 필요

 


참고 자료

 

기본적으로 Windows OS에서 등록된 자동 실행 프로그램은 Run 레지스트리를 통해 확인할 수 있는 것으로 알고 있었다. 그리고 실제로 레지스트리를 통해 자동 실행이 되도록 설정된 프로그램을 확인할 수 있었다.

그러나 시스템이 등록된 것으로 인식하는 자동 실행 프로그램 중 레지스트리 내에서 확인할 수 없는 경우도 존재했다. 그리하여 왜 일부 프로그램만이 레지스트리에서 확인되며, 그렇기 않은 경우도 존재하는지에 대해 테스트를 통해 살펴봤다.

 

 

기본적으로 레지스트리 상에서 자동 실행 프로그램 리스트를 확인할 수 있는 위치는 아래와 같다.

  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
  • HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce

 

이 위치에는 아래와 같이 실행할 프로그램의 경로가 기록되어 있다. 여기에 기록된 프로그램들은 시스템 부팅 시 자동으로 실행된다.

 

이외에도 Windows 에는 [시작 프로그램] 폴더를 통해 자동 실행 프로그램으로 등록할 수도 있다. 이 경우 아래 경로의 폴더 내에 등록하고자 하는 프로그램의 바로가기(LNK) 파일을 넣어두면 이후 부팅 시부터 적용되어 자동으로 실행된다.

  • C:\Users\{USER}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

 

이후 진행한 테스트에서는 이 두 가지 아티팩트를 중심으로 어떤 식으로 남는지 확인하였고, Windows 7과 Windows 11 운영체제를 사용하여 테스트하였다.

 


 

대게 특정 프로그램을 자동 실행 프로그램으로 등록하기 위해, 프로그램 설치 단계에서 아래와 같은 체크박스 옵션을 사용하여 설정하고는 한다. 이러한 방식으로 자동 실행을 등록한 경우에는 어떤 식으로 아티팩트가 남을까?

 

Windows 7

Windows 7에서는 시스템 구성 창의 [시작 프로그램] 탭에서 설치한 프로그램에 대해 두 가지 항목을 기록하고 있는 것을 볼 수 있다. 

 

하나는 HKLM key 하위에 있는 Run 레지스트리를 가리키는 항목이고, 다른 하나는 [시작 프로그램] 폴더를 가리키는 항목이다. 각각의 위치를 직접 확인하여 실제로 존재하는지도 확인하였다.

 

Windows 11

Windows 11에서는 자동 실행 프로그램을 확인하기 위해 시스템 구성 창이 아닌 작업 관리자로 가야한다. 이 곳에서 시작 프로그램으로 등록된 Everything을 확인할 수 있다. 

 

그러나 Windows 11에서는 Windows 7에서와는 다르게 [시작 프로그램] 폴더 내에서 등록된 프로그램의 LNK 파일을 찾을 수 없었으며, Run 레지스트리에만 등록되어 있는 것을 볼 수 있었다.

[시작 프로그램] 폴더
Run 레지스트리

 

정리

  • Windows 7: Run 레지스트리와 [시작 프로그램] 폴더 모두 남는다.
  • Windows 11: Run 레지스트리에는 남지만 [시작 프로그램] 폴더에는 남지 않는다.

 

그렇다면 설치 프로그램을 이용하지 않고 Run 레지스트리에 직접 기록할 경우, 이것이 시스템에 어떻게 인식이 되며 [시작 프로그램] 폴더와는 어떤 관계를 가질까?

 

Windows 7

Windows 7의 Run 레지스트리에 직접 등록하고자 하는 프로그램의 실행 경로를 등록하였다.

 

그러자 곧바로 시스템 구성 창에서 이를 확인할 수 있었다. 이 경우에는 레지스트리에만 등록한 것대로 레지스트리 경로에 대해서만 등록되어 있으며, [시작 프로그램] 폴더 내 LNK 파일과 이를 참조하는 시스템 구성 항목은 없었다.

 

그러나 이렇게만 등록되어 있어도 부팅 시 자동으로 프로그램이 실행되는데에는 문제가 없었다.

 

Windows 11

Windows 11에서도 Run 레지스트리에 직접 등록하고자 하는 프로그램의 실행 경로를 등록하였다.

 

이 경우에도 [시작 프로그램] 폴더는 빈 폴더인 상태 그대로였으나, 작업 관리자 내 등록된 시작 프로그램 목록 상에서는 레지스트리에 등록한 프로그램이 정상적으로 확인이 되었고, 부팅 시에도 이것이 적용되어 자동 실행되었다.

 

정리

Windows 7과 Windows 11에서 모두 직접 추가한 레지스트리만 유지되고 [시작 프로그램] 폴더 내 항목에는 변화가 없었다. 그럼에도 시스템에서 해당 프로그램을 자동 실행 프로그램으로 인식하는데에는 문제가 없었으며, 실제 부팅 시에도 정상적으로 자동 실행이 이루어졌다.


 

이번에는 [시작 프로그램] 폴더에만 LNK 파일을 추가하고 레지스트리는 수정하지 않는 방법으로 테스트하였다.

 

Windows 7

설치 프로그램에서 자동으로 추가한 LNK 파일과 같이 [시작 프로그램] 폴더 내에 이를 넣었다.

 

그러자 시스템 구성 창에서 이를 정상적으로 인식하고 참조하고 있음을 확인할 수 있었다.

 

그러나 Run 레지스트리에는 변화가 없었다. 그럼에도 불구하고 부팅 시 해당 프로그램이 자동으로 실행되는데에는 문제없었다.

 

Windows 11

Windows 7에서와 같이, 추가하고자 하는 프로그램의 LNK 파일을 [시작 프로그램] 폴더 내에 넣었다.

 

그러자 Windows 7에서와 마찬가지로, Run 레지스터에는 변화없이 시스템의 작업 관리자에서만 이를 인식하고 등록되어 있는 것을 확인할 수 있었다.

 

정리

Windows 7과 Windows 11에서 모두 직접 추가한 [시작 프로그램] 내 LNK 파일만 유지되고, Run 레지스트리 내 항목에는 변화가 없었다. 그럼에도 시스템에서 해당 프로그램을 자동 실행 프로그램으로 인식하는데에는 문제가 없었으며, 실제 부팅 시에도 정상적으로 자동 실행이 이루어졌다.

 


 

결과

 

자동 실행 프로그램을 등록하기 위해서는 프로그램 설치 단계에서 옵션을 설정하는 방법과 Run 레지스트리에 추가하는 방법, 그리고 [시작 프로그램] 폴더 내에 LNK 파일을 추가하는 방법을 사용할 수 있다.

설치 단계에서 옵션을 사용하여 설정하는 경우에는
Windows 7의 경우 Run 레지스트리와 [시작 프로그램] 폴더 모두에 흔적이 남았다.
그러나 Windows 11에서는 Run 레지스트리에만 흔적이 남고 [시작 프로그램] 폴더 내에는 흔적이 남지 않았다.

Run 레지스트리나 [시작 프로그램] 폴더 내에 직접 참조를 등록하는 방식으로 자동 실행 프로그램을 등록할 경우에는
어떠한 경우를 사용하더라도 이를 시스템에서 정상적으로 인식하고 동작하는데 문제가 없었다.
그러나 Run 레지스트리와 [시작 프로그램] 폴더 내 항목은 각각 독립적으로 작동하여, 서로의 위치에 자동으로 추가된 항목을 동기화하지 않는다. 따라서 수동으로 등록한 위치에만 흔적이 남고 그렇지 않은 위치에는 흔적이 남지 않는다.

이러한 이유로 사고 분석 시 자동 실행 프로그램 항목을 살피기 위해서, 레지스트리만 확인할 것이 아니라 [시작 프로그램] 폴더 수동 등록과 같은 다른 방법의 아티팩트도 함께 확인하고 교차검증 하는 것이 바람직해 보인다.

 

 

침해 이미지를 분석할 때 시스템이나 사용자에 대한 정보를 조사하기 위해 항상 레지스트리를 살피고는 했다.

그리고는 그 때마다 필요한 레지스트리만을 검색하여 보는게 다였는데, 이 참에 한번 정리해야겠다 싶어 글을 작성하였다.

 

Registry 란?

Registry는 Windows 운영체제에서 응용 프로그램과 시스템이 설정 데이터를 쉽게 저장하고 검색할 수 있도록 하기 위한 계층형 데이터베이스이다.

Registry는 계층형 데이터베이스인만큼 트리 형태로 구성되어 있다.
트리의 각 노드는 key라고 하고, 각 key는 subkey를 가질 수도 있고 value라는 데이터를 가질 수도 있다.

이를 레지스트리 편집기 상에서 보면 아래 이미지와 같다.
HKEY_LOCAL_MACHINE key는 HARDWARE, SOFTWARE 등과 같은 subkey를 가질 수 있으며, 이 중 SOFTWARE key는 다시 Bandizip 이라는 subkey를 가진다. 이 subkey는 AutoReport, Edition 등과 같은 value를 가지면서 동시에 Capabilities와 같은 subkey를 가진다.

 

Predefined Keys

이렇듯 레지스트리 내에는 수많은 key들이 존재하는데, 이 중 운영체제에 의해 미리 정의된 key가 있다. Microsoft 공식 문서에 의하면 미리 정의된 key의 종류는 Windows 버전에 따라 다를 수 있다고 한다. 확인해본 버전 중 Windows XP 부터 11까지는 다음의 5개의 predefined key를 가진다.

  1. HKEY_CLASSES_ROOT (HKCR)
  2. HKEY_CURRENT_USER (HKCU)
  3. HKEY_LOCAL_MACHINE (HKLM)
  4. HKEY_USERS (HKU)
  5. HKEY_CURRENT_CONFIG (HKCC)

 

HKEY_CLASSES_ROOT

ProgID, CLSID, IID와 같은 파일 이름 확장자 정보와 COM 클래스 등록 정보를 가지는 key이다. HKLM과 HKCU에도 이 정보가 담겨져 있으나, 두 군데에 저장된 정보의 병합된 결과가 이 key의 하위에 저장한다.

  • HKLM\Software\Classes: 로컬 컴퓨터 내 모든 사용자에게 적용될 수 있는 정보
  • HKCU\Software\Classes: 로컬 컴퓨터 내 현재 사용자에게만 적용될 수 있는 정보

 

HKEY_CURRENT_USER

현재 로그인한 사용자의 기본 설정 정보를 가지는 key이다. HKU key의 현재 사용자 정보와 동일한 정보를 가진다.

  • 설정 정보: 환경 변수 설정, 프로그램 그룹에 대한 데이터, 테마, 프린터, 네트워크 연결, 응용 프로그램 기본 설정 등

 

HKEY_LOCAL_MACHINE

컴퓨터의 물리적 상태 정보를 가지는 key이다.

  • 물리적 상태 정보: 버스 유형, 시스템 메모리, 설치된 하드웨어 및 소프트웨어 정보
    • Plug&Play 정보, 네트워크 로그온 기본 설정, 네트워크 보안 정보, 소프트웨어 관련 정보, 기타 시스템 정보

 

HKEY_USERS

로컬 컴퓨터 내 기본 사용자와 현재 사용자에 대한 사용자 설정 정보를 가지는 key이다.

 

HKEY_CURRENT_CONFIG

현재 하드웨어 프로필에 대한 정보를 가지는 key이다.

  • HKLM과 달리, 현재 하드웨어 구성과 표준 하드웨어 구성 간의 차이점에 대한 정보만 가진다.

 

 

Registry Hive

앞서 보인 것과 같이 key, subkey, value의 논리적 집합을 hive(하이브)라고 부른다.
운영체제가 시작되거나 사용자가 로그인할 때 메모리에 로드되는 설정 파일 집합을 말한다. 새로운 사용자가 로그인할 때마다 해당 사용자에 대한 새 사용자 프로필 하이브가 HKU key의 하위에 생성된다.

로컬 컴퓨터 상에서 레지스트리 하이브 파일 경로: %SystemRoot%\System32\Config directory

 

주요 Key 경로

(계속 업데이트 중)

 

시스템 정보

  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
    • RegisterOwner
      • 윈도우 운영체제를 설치하는 과정에서 최초로 등록한 사용자의 계정명
      • 이후 또 다른 계정을 생성해도 변경되지 않고 최초 등록된 계정명으로 표시된다.
    • ProductName
      • Windows 11도 Windows 10으로 표시된다..
    • InstallDate
      • 운영체제 설치한 시각
      • InstallDate값이 변경될 경우, 이전 업데이트 날짜를 아래 경로에 저장한다.
        • HKLM\SYSTEM\Setup\Source OS (Updated on dd/mm/yyyy hh:mm:ss)

 

  • HKLM\SYSTEM\ControlSet001\Services\Tcpip\Parameters
    • HostName
      • 컴퓨터 이름
      • Windows 10부터는 사용자가 직접 설정하지 않아도 윈도우 운영체제에서 자동으로 컴퓨터 이름을 설정하기도 한다.

 

  • HKLM\SYSTEM\ControlSet001\Control\Windows
    • ShutdownTime
      • 마지막 종료 일시
      • 사용자가 시스템을 정상적으로 종료한 시각으로 기록된다.

 

  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI
    • LastLoggedOnSAMUser
      • 마지막 로그아웃 일시
      • 비정상적 종료로 인해 데이터를 신뢰할 수 없을 경우가 있어 NTUSER.DAT key의 마지막 수정 시간과 일치하는지 확인해야 한다. 일치하지 않을 경우 마지막 로그인 일시로 판단해야 한다.

 

시스템 표준 시간

  • HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation
    • 시스템 표준 시간 관련 정보를 가지는 key
    • CurrentControlSet이 없는 경우 ControlSet001을 확인하면 된다.
    • 관련 포스트: https://note-ing.tistory.com/43
 

Windows Registry Timezone과 SYSTEM Control registry 백업 원리 분석

Windows Registry 내에서 시스템의 Timezone을 확인하기 위해서는 다음과 같은 키의 값을 확인하면 된다. HKLM\SYSTEM\CurrentControlSet\Control\TimezoneInformation [TimeZoneKeyName] 그러나 간혹 레지스트리 경로 내 Curren

note-ing.tistory.com

 

사용자 계정 정보

  • LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
    • S-1-5-18: System profile
    • S-1-5-19: LocalService
    • S-1-5-20: NetworkService
    • S-1-5-21-{...}-{4자리숫자}
      • 500: Administrator
      • 1000이상: 실제 사용자 계정

 

  • HKU\{SID}\Environment
    • 사용자의 환경 설정 정보

 

Autorun 자동 실행 프로그램 정보

  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
  • HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
  • HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
    • Run: 시스템이 부팅될 때마다 실행
    • RunOnce: 한번만 실행
  • 관련 포스트: https://note-ing.tistory.com/46
 

Windows AutoRun program artifact 분석 (Win7/10)

기본적으로 Windows OS에서 등록된 자동 실행 프로그램은 Run 레지스트리를 통해 확인할 수 있는 것으로 알고 있었다. 그리고 실제로 레지스트리를 통해 자동 실행이 되도록 설정된 프로그램을 확

note-ing.tistory.com

 

응용 프로그램 실행 흔적

  • HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist
    • UserAssist
      • 최근에 실행된 프로그램 정보
      • Prefetch 파일과 연계하여 분석할 
      • 조사하고자 하는 프로그램의 GUID를 알아야 하며, Count 내 정보는 ROT13으로 난독화되어 있다.
        • AXIOM과 같은 프로그램을 이용하여 난독화된 내용을 손쉽게 확인할 수 있다.

UserAssist Count subkey의 keyname과 value 구조

 

 


참고 자료

 

 

Windows Registry 내에서 시스템의 Timezone을 확인하기 위해서는 다음과 같은 키의 값을 확인하면 된다.

HKLM\SYSTEM\CurrentControlSet\Control\TimezoneInformation [TimeZoneKeyName]

 

그러나 간혹 레지스트리 경로 내 CurrentControlSet 을 찾을 수 없는 경우를 발견한 적이 있었다.
이런 경우 CurrentControlSet 대신 ControlSet001과 ControlSet002만을 가지고 있는 경우도 있었다.

이름이 비슷하게 생긴 것을 힌트 삼아 이 두 경로가 CurrentControlSet을 대신할 수 있을까 라는 생각을 하게 되었다. 그리고 실제로 각각의 경로를 탐색한 결과 두 경로 모두 현재 시스템의 시간대 정보를 가지고 있는 것을 확인할 수 있었다.

CurrentControlSet
ControlSet001
ControlSet002

이들 간에 어떠한 차이가 있는지 알아보기 위해 우선 관련 정보를 인터넷에 검색하였다.

https://stackoverflow.com/questions/291519/how-does-currentcontrolset-differ-from-controlset001-and-controlset002

stackoverflow 내 답변에 의하면 ControlSet001과 ControlSet002는 CurrentControlSet의 백업으로 사용하는 key라고 한다.여기서 궁금했던 점은 백업용이면 한개만 있으면 될텐데 왜 굳이 2개의 백업용 key를 두었을까? 하는 부분이었다.  이를 확인하기 위해 몇가지 테스트를 진행하였다.

 


 

실습을 위해 사용한 운영체제는 Windows 7 이며 VMware 를 이용한 가상환경에서 테스트하였다.

테스트한 case는 다음과 같다.

  1. 시간대 변경 이후 재부팅하지 않을 경우
  2. 시간대 변경 후 시스템을 정상적으로 종료하는 경우
  3. 시간대 변경 후 시스템을 비정상적으로 종료하는 경우

 

case 1: 시간대 변경 이후 재부팅하지 않을 경우

초기 실습용 OS의 표준시간대는 UTC+9 이다. 이를 UTC+7로 변경하였다.

그 후 차례로 CurrentControlSet과 ControlSet001, 그리고 ControlSet002의 표준시간대 반영 결과를 확인하였다.

CurrentControlSet
ControlSet001
ControlSet002

결과는, CurrentControlSet과 ControlSet001에만 변경된 시간대가 적용이 되었고 ControlSet002에는 초기 UTC+9가 그대로 남아있었다.

이 상태에서 한번 더 시간대를 변경하였다. 이번에는 UTC+5로 변경하였다.

CurrentControlSet
ControlSet001
ControlSet002

이번에도 변경된 사항은 CurrentControlSet과 ControlSet001에만 적용되었으며, ControlSet002에는 처음의 UTC+9 시간대가 그대로 남아있었다.

 

case 2: 시간대 변경 후 시스템을 정상적으로 종료하는 경우

이제 시스템을 롤백시켜 시간대 변경을 할 때마다 시스템을 정상적으로 종료 후 부팅하는 방식을 테스트해보려고 한다.

ControlSet001
ControlSet002
CurrentControlSet

초기 상태인 UTC+9에서 UTC+7로 변경하였다.

시간대 변경 직후, 재부팅을 안한 상태에서는 앞선 결과와 마찬가지로 CurrentControlSet과 ControlSet001만 변경되고 ControlSet002는 여전히 원래 시간대인 UTC+9로 기록되어 있는 것을 볼 수 있다.

ControlSet001
ControlSet002
CurrentControlSet

위 상태에서 정상적으로 재부팅을 한 후 다시 각 결과를 확인하였을 때, ControlSet001 외에 ControlSet002 까지도 모두 같은 시간대 정보를 가지고 있는 것을 확인할 수 있었다.

ControlSet001
ControlSet002
CurrentControlSet

 

case 3: 시간대 변경 후 시스템을 비정상적으로 종료하는 경우

case 2에와 마찬가지로 시스템을 초기 환경으로 롤백 후 다시 테스트하였다.

시간대를 변경한 후 비정상적 종료를 유발하기 위해 간단한 스크립트를 작성하여 일부 시스템 프로세스를 강제종료 시켰다...ㅎ

그 결과로 탐색기가 안열리는 상황

그 후 안전 모드로 접속하여 확인했을 때, ControlSet001과 CurrentControlSet은 변경한 시각을 가지고 있었지만, ControlSet002의 경우 정상적인 상태일때 저장된 상태값인 UTC+9를 그대로 가지고 있었다.

 

ControlSet001
ControlSet002
CurrentControlSet

비정상적으로 종료된 케이스에서도 정상적으로 재부팅을 한다면, ControlSet002의 시간대도 다시 시스템의 표준 시간대와 동일한 값을 가지는 것을 확인하였다.

 

여기서 만약 안전 모드가 아닌 표준 모드로 재부팅을 한다면?

테스트를 위해 이번에는 ControlSet001과 CurrentControlSet의 시간대를 UTC+9로 설정하고, ControlSet002만 UTC+7로 설정되어 있는 상태에서 시스템의 비정상 종료를 유발했다.

 

그 결과, ControlSet001과 CurrentControlSet의 표준 시간대가 모두 ControlSet002의 표준 시간대와 동일한 상태로 부팅이 되었다.

ControlSet001
ControlSet002
CurrentControlSet

 

물론 CurrentControlSet 에 설정된 UTC+7 대로 라면 가상머신의 시간이 현재 한국 시간보다 2시간 느려야하는데 호스트 컴퓨터의 시간과 동일하게 표시되는 것으로 봐서 어딘가 문제가 생긴건 맞는 것 같다.ㅎㅎ.. 이런 현상은 재부팅을 해도 마찬가지였고, 


각 케이스의 결과를 비교하였을 때,

대체로 ControlSet001은 CurrentControlSet과 동일한 값을 가지며

ControlSet002은 가장 최근에 정상적으로 종료된 Control값을 가지는 것을 알 수 있다.

그리고 비정상적 상태로 종료하였을 경우 ControlSet002는 가장 최근에 저장된 정상 Control 상태를 저장하였다가 복구 시 시스템의 상태를 이 ControlSet002에 저장된 상태로 설정한다.

그렇다면 CurrentControlSet을 사용하지 않고 ControlSet001을 사용하면 되는것 아닌가? 라는 의문이 들 수 있다.

이러한 의문에 대한 답은 아래 링크를 통해 해결할 수 있었다.

 

What are Control Sets? What is CurrentControlSet?

A control set contains system configuration information such as device drivers and services. You may notice several instances of control sets when viewing the Registry. Some are duplicates or mirror images of others and some are unique. This article descri

web.archive.org

 

위 내용에 따르면

CurrentControlSet, ControlSet001, ControlSet002 간의 관계를 정의하는 레지스트리가 존재한다.

그것은 바로 HKLM/SYSTEM/Select subkey 내에 있는 key들이다.

여기에는 Current, Default, Failed, LastKnownGood 과 같이, 총 4개의 서로 다른 상태를 저장할 수 있는 key가 있으며 각각은 다음과 같은 의미를 가진다.

 

● Current는 현재 CurrentControlSet이 가리키고 있는 ControlSet의 번호를 나타낸다. Current key의 값이 0x01 일 경우 CurrentControlSet의 값은 ControlSet001과 같게 된다.

● Default는 기본적으로 CurrentControlSet이 가리키고 있는 ControlSet의 번호를 나타낸다. Default key의 값이 0x01 일 경우 CurrentControlSet의 값은 ControlSet001과 같게 된다.

● Failed는 정상적으로 사용하기 어려운 ControlSet의 번호를 가리키게 된다. Failed key의 값이 0x00일 경우 모든 ControlSet 을 정상적으로 사용할 수 있다는 것을 가리킨다.

● LastKnownGood는 Current key에서 가리키고 있는 번호의 ControlSet이 설정되기 이전으로부터 가장 최근에 설정된 정상 ControlSet의 번호를 나타낸다.

 

결과적으로 현재 시스템의 Control 정보를 보고자 한다면 CurrentControlSet의 내용을 보는 것이 맞으며, 이는 Current key가 가리키고 있는 ControlSet001로부터 가져온 정보를 가진다. 만약 복구가 필요할 경우 LastKnownGood key가 가리키고 있는 ControlSet002로부터 가져온 정보를 사용하게 된다.


 

시작은 Timezone 분석으로 하였지만, 이외에도 시스템 내에 있는 다른 정보들의 백업을 위해 총 3개의 Set(CurrentControlSet, ControlSet001, ControlSet002)이 사용되고 있는 것을 알게 되었다. 

또한 글의 초입에서 언급했던 상황과 같이, CurrentControlSet을 확인할 수 없는 경우에는 Select subkey 설정값을 확인하여 CurrentControlSet과 같은 값을 가지는 백업 Set이 어느것인지 확인하고 CurrentControlSet을 대신하여 참고할 수 있을 것으로 생각된다.

 

 

이 글은 SQLite File structure 분석에 이어지는 글이며,
SQLite에서 레코드를 삭제할 경우 파일 내에 생기는 일과 그렇게 삭제된 레코드가 관리되는 방식을 다룬다.

레코드는 Leaf page에 저장되어 있다. 따라서 레코드 삭제 요청이 들어올 경우 가장 먼저 변화가 일어나는 곳도 Leaf page이다.

Page의 구조가 아래와 같다는 것을 고려할 때, Leaf page에서 어떤 레코드가 삭제되면 해당 레코드를 가리키던 cell offset의 값도 삭제될 것임을 짐작해볼 수 있다. 그렇다면 기존의 cell offset이 가리키고 있던, 삭제된 cell 공간은 추후 데이터의 삽입이 일어날 때 재사용이 될까? 효율을 위해서라면 높은 확률로 재사용될 텐데 이를 위해 어떤 체계를 쓰고 있을까?

 

Page structure

 

이러한 물음에 대한 답을 찾고자 데이터베이스 파일 내부를 직접 들여다보았다.

테스트를 위해 7개의 레코드를 갖는 테이블을 만들었다.
이 레코드들은 모두 하나의 Leaf page 안에 있는 상황이다.

 

 

현재 page header의 offset 0x3에 위치한 number of records의 값은 7이며,
7개의 레코드는 첫번째부터 순서대로 다음의 offset을 갖고 있다.
-> 0xFEC, 0xFD7, 0xFC2, 0xFAD, 0xF98, 0xF83, 0xF6E

이 상태에서 제일 마지막 offset인 0xFEC에 위치한 순서상으로는 첫번째 레코드(aa..)를 삭제해보았다.

 

 

page의 처음으로부터 offset 0x08에 위치했던 0xFEC가 사라지고, 그 자리를 이후에 위치한 offset값들이 차지하고 있는 것을 알 수 있다. 이와 함께 number of records 값이 6으로 변했다.

여기서 cell offset 배열 내의 한 원소를 삭제할 때 그 이후의 값들을 하나씩 당겨오는 방식으로 원소를 삭제한다는 것을 알 수 있다.

삭제된 셀이 위치해있던 0xFEC에서 일어난 변화를 보면, 첫 4 바이트가 4바이트의 어떤 정수로 덮어씌워졌으며 그 이후의 데이터는 여전히 파일에 남아있는 것을 볼 수 있다.
이 덮어씌운 정수가 어떤 값을 의미하는지를 확실히 하기 위해 이번에는 중간에 있는 데이터를 삭제해보았다.

 

 

offset 0xFC2 에 위치해있던 (초기데이터 기준으로) 세번째 레코드(cc..)를 삭제했다.

page header와  cell offset 배열에 일어난 변화는 이전과 동일했는데, 레코드의 첫 4바이트가 또 다른 어떤 정수로 바뀐 것을 볼 수 있다.

가만 보면 4 바이트 중 상위 2 바이트는 이 다음에 위치한 삭제 레코드의 offset이고, 하위 2 바이트는 이 다음에 위치한 삭제되지 않은 레코드와 현재 레코드와의 offset 차이값을 나타냄을 알 수 있다.
(0x2FC2 + 0x15 = 0x2FD7이다. -> 두번째 레코드의 offset)

이러한 가정을 조금 전에 시도했던, page의 끝에 위치한 레코드를 삭제했을 경우에 적용해보면..
이후 위치한 삭제 레코드가 없으므로 상위 2 바이트의 값이 0이었고, 이후 위치한 삭제되지 않은 레코드의 값은 이 다음 페이지와의 offset 차이값을 가지고 있었다는 것으로 그 정수값의 의미를 유추해낼 수 있다.
(0x2FEC + 0x14 = 0x3000이다. -> 세번째 페이지의 offset)

여기에 SQLite에서 page간의 관계가 B+ Tree 구조로 이루어져 있다는 것까지 함께 고려한다면 위 가정에 신뢰를 더할 수 있다.
(B+ Tree는 Leaf 노드끼리 연결리스트 구조를 이루고 있어 이들간의 선형 탐색이 가능하도록 되어있다.)

 

그렇다면 이렇게 삭제된 노드의 저장공간이 어떻게 활용되는지 확인하기 위해 새로운 레코드 하나를 추가해보겠다.

 

 

그러자 cell offset 배열의 마지막 위치에 익숙한 offset 0xFC2가 추가된 것을 확인할 수 있다.
이는 바로전에 삭제했던 레코드(cc..)의 offset이었으며, 새롭게 들어온 레코드(hh..)의 저장도 이곳에 이루어진 것을 확인할 수 있다.

삭제된 레코드의 offset은 분명 cell offset 배열에서 삭제되었는데 어떻게 이 offset을 다시 찾아갈 수 있었을까?
답은 page header의 offset 1에 위치한 offset of first block of free space 값에 있다.

눈치가 빨랐다면 이미 알았겠지만 사실 지금까지 레코드를 삭제하는 동안 이 값은 그에 따라 계속 변하고 있었다.

 

초기의 page header
첫번째 레코드(aa..) 0xFEC를 삭제했을 때 page header
세번째 레코드(cc..) 0xFC2를 삭제했을 때 page header

 

이는 마치 다음과 같은 연결리스트가 있을 때 가장 끝에 있는 노드, 즉 가장 빠른 offset을 가지고 있는 노드를 pointer가 가리키고 있는 구조로 생각할 수 있다.
여기서의 pointer 방식이 page header의 offset 1에 값을 저장하는 것이라고 생각하면 되는 것이다.
이 때, offset 값을 기준으로 오름차순이 되도록 새로운 노드가 추가되며 그렇기 때문에 레코드를 삭제한다고 해서 꼭 pointer의 값이 달라지는 것은 아니다. (pointer는 가장 빠른 offset의 free block을 가리키고 있기 때문.)

 

 

그러다 새로운 레코드가 추가되면 이 pointer가 가리키고 있는 offset에 해당 레코드를 저장하는 것이다.

 


 

이렇게 데이터를 삭제하고 삭제한 위치를 관리하는 시스템을 이용한다면 삭제된 데이터를 복구하는 작업도 가능하게 된다.
앞서 봤던 것처럼 연결리스트 구조를 이용하여 삭제된 데이터가 저장된 위치만을 순회할 수 있고,
데이터가 삭제된다고 하더라도 해당 레코드의 상위 4 바이트만 변경이 되지 그 이후의 데이터는 그대로 남아있기 때문이다.

그래서 원래 이 삭제 메커니즘을 분석했던 이유도 Chrome browser History를 복구해보기 위한 선행 지식이었기 때문이었는데,
알게 된 지식을 이용하여 History 데이터베이스를 따라가보니 안티포렌식 처리가 되어 있음을 확인해버렸다..
슬픈 결과이지만 그를 따라갔던 과정이라도 적고 마무리 하려고 한다.

Chrome 96.0 기준으로 확인한 결과이며, 언제부터 이것이 적용되었는지는 모르겠지만 이전 버전에서는 다른 결과를 보일 수도 있다.

 

아래는 내 Chrome local에 저장된 History 데이터베이스 내에 있는 urls 테이블의 한 Leaf page를 가져온 것이다.

 

 

page header offset 1 값에 의해 마지막으로 삭제된 레코드의 offset이 0x50A임을 알 수 있다. 해당 위치로 따라가보았다.

 

 

예상했던대로 첫 4바이트는 (다음 free block의 offset 2바이트 : 다음 cell과의 offset 차이값 2바이트) 로 이루어진 값이 저장되어있다.
그런데 문제는 4바이트 이후에 저장되어 있어야할 데이터가 모조리 0으로 채워져있다는 점이다.

다음 free block의 offset인 0x1E000 + 0xC8E 로도 쫒아가 나머지도 모두 같은 상황인지 확인했다.

 


마찬가지였다.

이 후 다른 테이블로 넘어가 몇개의 free block을 더 확인하였는데.. 삭제된 위치의 첫 4바이트 이후 값은 모두 0으로 지워져있었다.

 

Chrome History 복구 모듈을 만들어보고자 하는 목적으로 시작한 공부였는데, 끝이 이렇게 날 줄은...

아쉬우니 다음에 Chrome이 아닌 다른 SQLite 데이터베이스 파일의 복구 모듈을 만들어보는 것도 도전해봐야할 것 같다.

+ Recent posts