앞선
2012/12/18 - [Research/Forensic & NTFS] - Partition(파티션) 정보 [MBR/Primary(주)/Extended(확장)/Logical(논리)/GPT] 구하기 (Visual C++/GCC)
에 이어 이제는 보다 실질적인 ntfs parsing에 대해 알아보도록 하겠습니다.
2013/07/14 - [Research/Forensic & NTFS] - [ntfs forensic 강좌] 1. ntfs 기본 (file, file record, inode, attribute)에서부터 본 ntfsparser를 가지고 ntfs에 대한 분석을 진행하도록 하겠습니다.
ntfs 분석만 하더라도 웬만한 두꺼운 책 한권 정도의 분량이 나옵니다. 그리고 내용 또한 어렵고, 아직 잘 정의되고 정리된 문서 또한 많지 않습니다. 그런 상황에서 ntfs paser를 만들었는데, 현재 100% 진행된 것은 아닙니다. 즉, b-tree등이 구현되지 않아, file indexing 처리(즉, traversing)가 되지 않습니다. 그러나 그 이외의 처리등은 포함되어 있으니, 향후 확장가능할 것입니다. 여하튼, 본 post는 GPL License를 따르는 ntfsparser를 공유하며, ntfs의 이론보다는, 실용적인 부분으로 설명하고자 합니다. (현재 revision 8)
License : GPL v3.0 (본 소스를 사용하시려면 GPL 라이선스를 따르시기 바랍니다.)
위 ntfsparser.zip의 ntfsparser.cpp는 다음을 설명하고 있습니다.
즉, 아래의 설명은 ntfsparser.cpp에 sample code 형태로 포함되어 있습니다.
● VOID CloseNtfs(IN LPST_NTFS pstNtfs)
lpszPath는 윈도우에서 \\.\C: 와 같은 형태의 경로가 들어와야 합니다.
물론, 실행 프로세스는 관리자권한으로 실행되어야 합니다.
typedef struct tagST_NTFS
{
INT nFd; // disk file descriptor
ST_NTFS_BPB stBpb; // NTFS의 BPB 영역의 값을 가진다.
ST_NTFS_INFO stNtfsInfo; // CNTFSHelper에서 사용되는 IN/OUT 값을 가진다.
LPST_BITMAP_INFO pstBitmapInfo; // Bitmap 정보가 들어간다.
} ST_NTFS, *LPST_NTFS; |
BPB는 BIOS Parameter Block을 의미하며, Sector의 크기나 MFT의 위치와 같은 정보가 포함됩니다. 그리고, ST_NTFS_INFO에는 CNTFSHelper에 의해 계산되어진 값이 전달됩니다. 전반적인 NTFS 환경이 전달됩니다. NTFS의 기본 단위인 Cluster의 크기, 전체 Cluster의 개수등이 포함됩니다. 그리고 Bitmap 정보가 들어가는데, 이는 내부에서 사용되므로, 호출자가 사용할 경우는 없습니다.
● BOOL IsClusterUsed(IN UBYTE8 nLCN, IN LPST_BITMAP_INFO pstBitmapInfo)
● VOID BitmapFree(IN LPST_BITMAP_INFO pstBitmapInfo)
● VOID FindAttribFree(IN LPST_FIND_ATTRIB pstFindAttrib)
Inode | Filename | OS | Description |
---|---|---|---|
0 | $MFT | Master File Table - An index of every file | |
1 | $MFTMirr | A backup copy of the first 4 records of the MFT | |
2 | $LogFile | Transactional logging file | |
3 | $Volume | Serial number, creation time, dirty flag | |
4 | $AttrDef | Attribute definitions | |
5 | . (dot) | Root directory of the disk | |
6 | $Bitmap | Contains volume's cluster map (in-use vs. free) | |
7 | $Boot | Boot record of the volume | |
8 | $BadClus | Lists bad clusters on the volume | |
9 | $Quota | NT | Quota information |
9 | $Secure | 2K | Security descriptors used by the volume |
10 | $UpCase | Table of uppercase characters used for collating | |
11 | $Extend | 2K | A directory: $ObjId, $Quota, $Reparse, $UsnJrnl |
12-15 | <Unused> | Marked as in use but empty | |
16-23 | <Unused> | Marked as unused | |
Any | $ObjId | 2K | Unique Ids given to every file |
Any | $Quota | 2K | Quota information |
Any | $Reparse | 2K | Reparse point information |
Any | $UsnJrnl | 2K | Journalling of Encryption |
> 24 | A_File | An ordinary file | |
> 24 | A_Dir | An ordinary directory | |
... | ... | ... |
inode의 Disk상 sector위치는 File Record 크기의 배수로 대략 계산되었습니다. 해당 부분의 정밀한 확인이 필요할 지 모르겠습니다. 주요한 내용은 FindAttribAlloc 함수 내부의 주석을 참고 바랍니다.
inode가 결정되었다면, 그다음으로 Attribute가 결정되어야 합니다. 각 inode는 그 내부에 여러개의 attribute를 가지게 됩니다. 그 attribute는 data를 가지는데, 해당 data는 resident / non-resident 로 다눠지게 됩니다. resident는 data가 file Record에 포함되는 경우로, 대략 1K 미만의 작은 data를 가집니다. 즉, 예를들어, 100Byte의 작은 파일은 resident영역에 저장될 수 있습니다. 그리고, non-resident는 data가 Run list에 저장된 경우를 말합니다. 즉, 10MB 크기의 파일이 2,560개의 Cluster에 저장된 경우, Run list는 해당 2,560개의 Cluster 위치가 저장되어 있습니다. 일단, Run list는 아래에서 참고하시기 바라며, NTFS에서 Attribute는 다음과 같이 분류됩니다. (퍼옴 : 원본, 원본2의 zip/doc/ntfsprogs/1.9.0/ntfsprogs-1.9.0/ntfsdoc/index.html)
Type | OS | Name |
---|---|---|
0x10 | $STANDARD_INFORMATION | |
0x20 | $ATTRIBUTE_LIST | |
0x30 | $FILE_NAME | |
0x40 | NT | $VOLUME_VERSION |
0x40 | 2K | $OBJECT_ID |
0x50 | $SECURITY_DESCRIPTOR | |
0x60 | $VOLUME_NAME | |
0x70 | $VOLUME_INFORMATION | |
0x80 | $DATA | |
0x90 | $INDEX_ROOT | |
0xA0 | $INDEX_ALLOCATION | |
0xB0 | $BITMAP | |
0xC0 | NT | $SYMBOLIC_LINK |
0xC0 | 2K | $REPARSE_POINT |
0xD0 | $EA_INFORMATION | |
0xE0 | $EA | |
0xF0 | NT | $PROPERTY_SET |
0x100 | 2K | $LOGGED_UTILITY_STREAM |
● LPST_RUNLIST GetRunListAlloc(IN UBYTE1* pRunList, OUT PINT pnCount)
● VOID GetRunListFree(IN LPST_RUNLIST pRunList)
typedef struct tagST_RUNLIST
{
TYPE_RUNTYPE cType;
UBYTE8 llLCN;
UBYTE8 llLength;
} ST_RUNLIST, *LPST_RUNLIST; |
● NTFS_HANDLE FindFirstFileExt(IN LPCWSTR lpFileName, OUT LPWIN32_FIND_DATAW lpFindFileData, OPTIONAL IN LPST_IO_FUNCTION pstIoFunction, OPTIONAL IN LPST_INODE_CACHE pstInodeCache)
●
NTFS_HANDLE FindFirstFileByInodeExt(IN LPCWSTR lpVolumePath, IN LPST_INODEDATA pstInodeData, IN BOOL bFindSubDir, OPTIONAL OUT LPST_INODEDATA pstSubDirInodeData, OUT LPWIN32_FIND_DATAW lpFindFileData, OPTIONAL IN LPST_IO_FUNCTION pstIoFunction, OPTIONAL IN LPST_INODE_CACHE pstInodeCache)우선 ~ByInode는 호출자가 해당 경로의 inode를 알고 있는 경우 직접 그 값을 사용하도록 하여 inode 찾는과정이 발생하지 않아 속도가 향상됩니다. ~WithInode는 inode 정보를 함께 전달해주는 기능을 가집니다.
만일 FindFirstFile 계열의 함수를 사용할 때 발생하는 I/O를 사용자가 정의하고자 한다면, pstIoFunction에 값을 전달하면 됩니다. 물론 불필요한 경우 가능합니다. 자세한건 "사용자 정의 외부 사용하기"를 참고하세요.
앞서 설명한 inode 정보를 cache하고자 한다면, pstInodeCache 값을 전달하시기 바랍니다. 물론 NULL 가능하며 "inode 정보 cache하기"를 확인하세요.
CNTFSHelper::NTFS_HANDLE hHandle = INVALID_HANDLE_VALUE_NTFS;
...
hHandle = CNTFSHelper::FindFirstFileExt(L"C:\\*.*", &stFD, &stIo, NULL);
...
if (FALSE == CNTFSHelper::FindNextFile(hHandle, ...)
...
typedef struct tagST_MY_IO
{
some context data
} ST_MY_IO, *LPST_MY_IO;
...
CNTFSHelper::IO_LPVOID __cdecl MyOpenIo(...)
{
LPST_MY_IO pstRtnValue = NULL;
....
pstRtnValue = (LPST_MY_IO)malloc(sizeof(*pstRtnValue));
....
return (CNTFSHelper::IO_LPVOID)pstRtnValue;
}
와 같이, 동적 할당된 메모리를 전달합니다. 그리고, 그 메모리는 사용자가 정의한 구조체를 사용하면 좋을듯 합니다. 해당 구조체에는 I/O를 위한 데이터를 초기화할 수 있으며, 각 I/O 함수들과 공유할 수 있는 정보도 가능해 보입니다. 그리고 아래에 MyCloseIo(...)에서 해당 pointer를 free 시켜 주면 됩니다.
ntfsparser에서 inode 정보를 cache 한다는 것은, cache 처리를 위한 caallback 함수를 등록하는 과정으로 진행됩니다. 즉, cache를 처리하는 code는 호출자가 정의해야 합니다. 즉, ntfsparser에서는 cache data 처리를 위한 input/output을 set/get 요청을 합니다. 호출자는 set인 경우 (input, output) pair를 메모리에 저장하고, get인 경우, input에 대해 연결된 output을 전달해주면 됩니다.
inode cache callback 함수를 등록합니다. 총 4개의 함수이며, cache 생성/항목 찾기/항목 저장/cache 삭제에 관한 함수를 등록해야 합니다. 본 함수가 호출되면 일반적으로 cache 생성 함수가 호출됩니다. 주목할 것은 Ntfs 개체당 한개의 cache를 독립적으로 사용할 수 있다는 뜻인데, 만일 두개의 Ntfs 개체가 생성된다면, 두번의 cache 생성 함수가 호출됩니다. 전역적으로 유일한 cache를 사용하신다면 cache 생성 함수를 무시하셔도 됩니다. pUserContext는 등록된 callback 함수에 전달될 context 값입니다. Win32 API Emulator의 FindFirstFile 함수군에서 사용할 경우, 내부적으로 thread-safe를 보장하므로 자체적인 CRITICAL_SECTION은 필요하지 않습니다. 다만, FindFirstInode 함수군을 사용하는 경우 thread-safe는 보장되지 않기 때문에 CRITICAL_SECTION을 이용한 Lock을 함께 구현 하시면 보다 견고해질 것입니다. 자세한건 ntfsparser.cpp의 My~Cache 함수군들을 참고하시기 바랍니다.
이제 아래에서는 cache 관련 4개의 함수에 대한 설명을 하겠습니다.
SetInodeCache 호출에 의해 cache 생성 요청을 처리해야 합니다. cache의 메모리를 HANDLE로 처리하여 전달해야 합니다. 만일 생성에 실패하였다면 NULL를 전달할 수 있습니다. 앞선 설명대로 Ntfs 개체당 개별적인 cache 처리가 가능한데, 만약 개별적이지 않고 동일한 cache를 공유하고자 한다면, 예를 들어, 전역 변수의 메모리를 리턴하셔도 무방합니다.
ntfsparser 내부에서 FindInode 호출이 필요한 경우 본 함수를 호출합니다. MyCreateInodeCache에서 리턴한 값이 handle로 들어오며, nStartInode와 lpszNameUpperCase를 가지고 pstInodeData 정보를 전달하면 됩니다. 만일 cache memory에 nStartInode와 lpszNameUpperCase에 해당되는 값이 없는 경우 *pbFound에 FALSE를 전달해야 하며, 값을 찾았다면 TRUE를 전달해야 합니다.
ntfsparser 내부에서 FindInode 호출이 성공했을 때 호출됩니다. MyGetCache와 반대로 nStartInode와 lpszNameUpperCase를 가지고 pstInodeData를 cache memory에 저장하면 됩니다.
MyCreateInodeCache와 반대로 cache를 삭제할때 호출합니다. 주로, 연관되어있던 Ntfs 개체가 close될 때 호출됩니다.
Revision 8에서는, inode cache가 추가되었습니다. 그리고, FindFirstFileExt와 같이 함수명을 변경했으며, 내부적으로 ST_INODEDATAEX가 제거되었습니다. 만일 WIN32_FIND_DATA를 내부적으로 구하고자 한다면 GetWin32FindData를 참고하시기 바랍니다.
다음의 3가지 내용으로 이뤄져 있습니다.
- Cluster의 사용 여부 체크
- Volume 이름 구하기
- C:\Windows\System32\config에 포함되어 있는 파일을 열거
(FindFirstFile과 FindFirstFile 확장 함수 사용, inode cache 사용) - CreateFile, CreateFileByAttrib, ReadFile등의 API를 테스트 합니다.
C:\Windows\System.ini 파일 내용 출력
C:\Windows\System32\keyboard.drv 파일 내용 출력
C:\$Volume의 $VOLUME_NAME 속성 내용 출력 (meta-data file의 내용 출력)
C:\Windows\System32 경로의 $BITMAP 출력
코드 자체가 어렵지 않기 때문에 자세한 내용은 코드의 주석을 참고하시기 바랍니다.
우선, 대상 디스크는 C:\입니다. 그리고 실행후 32개의 inode에 대한 파일 출력을 시도합니다.
관리자 권한으로 실행된 cmd.exe에서, ntfsparser.exe > C:\dump.txt와 같이 실행한뒤, C:\dump.txt는 다음과 같이 출력됩니다.
[INFO] Ntfs Opened, \\.\C:
[INFO] Revision : ntfs.parser.revision.0008.beta
[INFO] Ntfs - Total Sector : 409599999
[INFO] LCN(Logical Cluster Number) 10879 : Cluster Used
[INFO] Volume name is [OS]
[INFO] C:\Windows\System32\config file list (recurse)
[000001] C:\Windows\System32\config\.
[000002] C:\Windows\System32\config\..
[000003] C:\Windows\System32\config\BCD-Template
[000004] C:\Windows\System32\config\BCD-Template.LOG
[000005] C:\Windows\System32\config\COMPONENTS
[000006] C:\Windows\System32\config\COMPONENTS.LOG
...
[000243] C:\Windows\System32\config\TxR
[INFO] C:\Windows\System32\config file list Fast Mode (recurse)
[000001] C:\Windows\System32\config\.
[000002] C:\Windows\System32\config\..
[000003] C:\Windows\System32\config\BCD-Template
[000004] C:\Windows\System32\config\BCD-Template.LOG
[000005] C:\Windows\System32\config\COMPONENTS
...
[000243] C:\Windows\System32\config\TxR
[INFO] Duration(TickCount), Normal=5538, Fast=4742
[INFO] C:\Windows\System32\config file list (recurse)
...
[INFO] Duration[Cache] (TickCount), Normal=4758, Fast=4805
[INFO] ReadFile C:\Windows\system.ini
(may be resident file)
[INFO] C:\Windows\system.ini size : 219
Addr = 0x1cf84c
[00000] 3B 20 66 6F 72 20 31 36 2D 62 69 74 20 61 70 70 ; for 16-bit app
[00010] 20 73 75 70 70 6F 72 74 0D 0A 5B 33 38 36 45 6E support..[386En
[00020] 68 5D 0D 0A 77 6F 61 66 6F 6E 74 3D 64 6F 73 61 h]..woafont=dosa
[00030] 70 70 2E 66 6F 6E 0D 0A 45 47 41 38 30 57 4F 41 pp.fon..EGA80WOA
[00040] 2E 46 4F 4E 3D 45 47 41 38 30 57 4F 41 2E 46 4F .FON=EGA80WOA.FO
[00050] 4E 0D 0A 45 47 41 34 30 57 4F 41 2E 46 4F 4E 3D N..EGA40WOA.FON=
[00060] 45 47 41 34 30 57 4F 41 2E 46 4F 4E 0D 0A 43 47 EGA40WOA.FON..CG
[00070] 41 38 30 57 4F 41 2E 46 4F 4E 3D 43 47 41 38 30 A80WOA.FON=CGA80
[00080] 57 4F 41 2E 46 4F 4E 0D 0A 43 47 41 34 30 57 4F WOA.FON..CGA40WO
[00090] 41 2E 46 4F 4E 3D 43 47 41 34 30 57 4F 41 2E 46 A.FON=CGA40WOA.F
[000A0] 4F 4E 0D 0A 0D 0A 5B 64 72 69 76 65 72 73 5D 0D ON....[drivers].
[000B0] 0A 77 61 76 65 3D 6D 6D 64 72 76 2E 64 6C 6C 0D .wave=mmdrv.dll.
[000C0] 0A 74 69 6D 65 72 3D 74 69 6D 65 72 2E 64 72 76 .timer=timer.drv
[000D0] 0D 0A 0D 0A 5B 6D 63 69 5D 0D 0A ....[mci]..
[INFO] ReadFile C:\Windows\System32\keyboard.drv contents
(may be non-resident file)
[INFO] C:\Windows\System32\keyboard.drv size : 2000
Addr = 0x1cf84c
[00000] 4D 5A 7C 00 02 00 00 00 04 00 07 00 FF FF 00 00 MZ|.............
[00010] B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
[00020] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
...
[00780] 50 72 6F 64 75 63 74 56 65 72 73 69 6F 6E 00 00 ProductVersion..
[00790] 33 2E 31 30 00 00 00 00 14 00 04 00 57 4F 57 20 3.10........WOW
[007A0] 56 65 72 73 69 6F 6E 00 34 2E 30 00 24 00 00 00 Version.4.0.$...
[007B0] 56 61 72 46 69 6C 65 49 6E 66 6F 00 14 00 04 00 VarFileInfo.....
[007C0] 54 72 61 6E 73 6C 61 74 69 6F 6E 00 09 04 E4 04 Translation.....
[INFO] ReadFile C:\$Volume -> $VOLUME_NAME attribute
(may be the volume name, C:\Volume is meta-data file)
[INFO] C:\$Volume (attrib: 0x60) size : 4
Addr = 0x1cf848
[00000] 4F 00 53 00 O.S.
[INFO] ReadFile C:\Windows\System32 -> $BITMAP
[INFO] C:\Windows\System32 (attrib: 0xb0) size : 32
Addr = 0x1cf848
[00000] FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
[00010] FF FF FF FF 03 00 00 00 00 00 00 00 00 00 00 00 ................
[INFO] Test user define I/O (FindFirstFile C:\)
MySeekIo called, context=hello, world, pos=0
MyReadIo called, context=hello, world, count=512
MySeekIo called, context=hello, world, pos=3221230592
MyReadIo called, context=hello, world, count=1024
MySeekIo called, context=hello, world, pos=3221230592
MyReadIo called, context=hello, world, count=1024
MySeekIo called, context=hello, world, pos=13680640
MyReadIo called, context=hello, world, count=4096
MySeekIo called, context=hello, world, pos=3221230592
MyReadIo called, context=hello, world, count=1024
MySeekIo called, context=hello, world, pos=3221230592
MyReadIo called, context=hello, world, count=1024
MySeekIo called, context=hello, world, pos=13680640
MyReadIo called, context=hello, world, count=4096
***** FindFirstFile Finished: $Recycle.Bin
***** FindNextFile Finished: .rnd
MyCloseIo called, context=hello, world
***** FindCloseFile Finished
******************************************************************
ntfsparser Copyright (C) 2013, greenfish, greenfish77 @ gmail.com
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions.
******************************************************************
우선 \\.\C:가 성공적으로 Open되었음을 의미합니다.
그리고 ntfsparser의 버전을 표기합니다.
전체 Sector를 표시하는데, 해당 값을 일반적인 sector 크기인 512만큼 곱하면 disk 전체 크기가 됩니다. (실제로는 sector 개수 하나를 더해야 합니다)
다음으로, Cluster 하나를 랜덤으로 골라 사용 여부를 판단하고 있습니다. 위에서는 10879번이 선택되었으며, 사용되는 영역이라고 알려주고 있습니다.
그리고 C:\ 드라이브의 볼륨 이름을 출력하고 있습니다.
C:\Windows\System32\config를 Win32 API Emulation 함수를 이용하여 파일 목록을 출력하고 있습니다. 2번 실행하는데, 처음엔 FindFirstFile과 동일한 API로 만들어진 것으로 실행한 것이고, 두번째에는 FindFirstFile API를 확장(inode 정보 추가)하여 속도를 향상시키고 있습니다. Tick count 기준으로 4150 -> 3619, 대략 23% 정도 속도가 빨라졌습니다.
cache에 의한 성능 향상은 Duration[Cache]를 통해 확인됩니다.
특이한 것은, cache를 사용하지 않을 때의 Fast와 비슷한 점입니다. 즉, ~WithInode, ~ByInode를 사용하는 것과 cache를 사용 하는것과 큰 차이가 나지 않는다는 뜻입니다. 다만, dir과 같은 한번만 지나치게 되는 파일 열거형이 아니라 탐색기 같이 지속적인 inode 탐색이 많은 경우, 성능 차이는 월등히 차이가 날 것입니다.
다음부분에서, 각 파일들의 내용을 출력하고 있습니다. 이는 CreateFile 계열의 함수들의 테스트입니다.
resident, non-resident 형식의 파일에 대한 출력 테스트가 이뤄졌으며,
C:\$Volume과 같은 meta-data 파일에 대한 내용도 보여주고 있습니다.
그리고 C:\Windows\System32와 같은 경로의 $BITMAP 내용도 보여주고 있습니다.
마지막으로 외부 사용자 정의 I/O를 테스트하고 있습니다.
FindFirstFile("C:\*.*", ...)를 외부 I/O로 연결하여 실행하였는데, 사용자 정의 I/O에 요청된 함수의 Parameter를 출력하도록 하였습니다. FindFirstFile 실행시 Seek, Read, ... 등이 순차적으로 발생하였습니다. 특이한건, FindNextFile 실행시 아무런 I/O가 발생하지 않는다는 점입니다. 본 ntfsparser에서 FindFirstFile을 하게되면, Index entry등을 내부에 List로 cache하게 되는데, I/O가 발생하지 않았다면, cache hit가 이뤄졌다고 보면 됩니다. 물론, cache를 벗어나는 경우 FindNextFile에 의해서도 I/O가 발생할 수 있습니다.
이상으로, ntfsparser에 대한 설명을 마치고자 하며, 주요 reference는 다음과 같습니다.
- NTFS Documentation : 링크
; ntfs의 어떤 이론서보다도 잘 정리된 페이지로, "This is version 0.4 of the NTFS Documentation and is available as part of the Linux-NTFS Project"로 시작합니다. - file system analysis.pdf : 직접 구글링 바람
; ntfs 뿐만 아니라 다른 file system도 포함된 file system 분석이 잘 정리된 문서임 - file system forensic pdf & mov : 링크
; 다른곳에서 잘 알려주지 않았던 정보가 잘 정리되어 있음 (특히 run list 관련) - MSDN
; http://msdn.microsoft.com/en-us/library/bb470039(v=vs.85).aspx
'Research > Forensic & NTFS' 카테고리의 다른 글
[ntfs forensic 강좌] 2. ntfs layout(개요) (0) | 2013.07.18 |
---|---|
[ntfs forensic 강좌] 1. ntfs 기본 (file, file record, inode, attribute) (0) | 2013.07.14 |
Partition(파티션) 정보 [MBR/Primary(주)/Extended(확장)/Logical(논리)/GPT] 구하기 (Visual C++/GCC) (0) | 2012.12.18 |