2013. 6. 3. 15:35

ntfs parser (ntfs parsing, analysis, c, c++) (ntfs 파싱, 분석, VC++ 9.0)

앞선
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)

ntfsparser_r8.zip

License : GPL v3.0 (본 소스를 사용하시려면 GPL 라이선스를 따르시기 바랍니다.)

위 ntfsparser.zip의 ntfsparser.cpp는 다음을 설명하고 있습니다.
즉, 아래의 설명은 ntfsparser.cpp에 sample code 형태로 포함되어 있습니다.

LPST_NTFS OpenNtfs(IN LPCSTR lpszPath)
VOID CloseNtfs(IN LPST_NTFS pstNtfs)
NTFS 개체를 열때 사용됩니다. (참고, 모든 것은 CNTFSHelper class를 사용하고 있습니다.
OpenNtfs는 CNTFSHelper::OpenNtfs로, ST_NTFS는 CNTFSHelper::ST_NTFS로 사용하십시요.)
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;
OpenNtfs를 실행하면 위와 같은 정보를 전달합니다. BPB는 NTFS 전반적인 Meta-data를 가지고 있습니다.
BPB는 BIOS Parameter Block을 의미하며, Sector의 크기나 MFT의 위치와 같은 정보가 포함됩니다. 그리고, ST_NTFS_INFO에는 CNTFSHelper에 의해 계산되어진 값이 전달됩니다. 전반적인 NTFS 환경이 전달됩니다. NTFS의 기본 단위인 Cluster의 크기, 전체 Cluster의 개수등이 포함됩니다. 그리고 Bitmap 정보가 들어가는데, 이는 내부에서 사용되므로, 호출자가 사용할 경우는 없습니다.
LPST_BITMAP_INFO BitmapAlloc(IN LPST_NTFS pstNtfs)
BOOL IsClusterUsed(IN UBYTE8 nLCN, IN LPST_BITMAP_INFO pstBitmapInfo)
VOID BitmapFree(IN LPST_BITMAP_INFO pstBitmapInfo)
Disk상 NTFS에서 Cluster 사용 여부는 BITMAP 영역에 저장되어 있습니다. 예를 들어, 010010.... 인 경우 1번째, 4번째 Cluster가 사용된다는 뜻입니다. 이렇게 bit로 표현된 mapping data가 BITMAP에 저장되어 있습니다. BitmapAlloc과 BitmapFree를 이용하여 bitmap 정보를 가져올 수 있습니다. 그리고, IsClusterUsed를 통해 해당 Cluster(LCN 번호)가 사용중인지를 판별할 수 있습니다.
LPST_FIND_ATTRIB FindAttribAlloc(IN LPST_NTFS pstNtfs, IN TYPE_INODE nTypeInode, IN TYPE_ATTRIB nTypeAttrib)
VOID FindAttribFree(IN LPST_FIND_ATTRIB pstFindAttrib)
NTFS의 모든 정보를 File Record로 기록되어 있습니다. 즉, NTFS에서 중요한 정보인 MFT도 $MFT라는 파일로 저장되어 있습니다. 이와 같은 TYPE_INODE는 다음과 같이 구분됩니다. (퍼옴. 원본. 원본2의 zip/doc/ntfsprogs/1.9.0/ntfsprogs-1.9.0/ntfsdoc/index.html)
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
... ...   ...
즉, 23번 이전으로는 NTFS의 기본 정보가 들어가며, 24번 이후부터는 일반적인 파일이 들어가게 됩니다.
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
즉, 각각의 inode는 위와 같은 Attribute를 포함합니다. 위의 모든 것이 포함되는 것이 아니라, inode의 특성에 맞게 있거나 없을수 있습니다. 물론, 모든 inode는 File 이름을 가지기 때문에, $FILE_NAME은 모두 포함됩니다. 그리고 실제 Data는 $DATA에 포함되는데, 앞선 설명대로 Run list에 저장되는 경우가 많습니다.
UBYTE1* GetRunListByte(IN LPST_ATTRIB_RECORD pstAttrib)
LPST_RUNLIST GetRunListAlloc(IN UBYTE1* pRunList, OUT PINT pnCount)
VOID GetRunListFree(IN LPST_RUNLIST pRunList)
Run list에 관련된 함수는 위와 같습니다. 우선 Run list는 링크와 같이 byte stream으로 표현됩니다. 그렇기 때문에, GetRunListAlloc 함수에는 byte가 들어가게 됩니다. 이러한 byte stream는 File record 정보에 포함되는데, 앞선 FindAttribAlloc으로 얻어진 LPST_FIND_ATTRIB의 LPST_ATTRIB_RECORD를 가지고 GetRunListByte에 전달할 수 있습니다. 만일, 해당 Attribute가 non-resident 형태이라면 NULL을 전달하게 됩니다. 이를 통해 구해진 LPST_RUNLIST는 다음과 같은 정보가 포함됩니다.
typedef struct tagST_RUNLIST
{
	TYPE_RUNTYPE	cType;
	UBYTE8		llLCN;
	UBYTE8		llLength;
} ST_RUNLIST, *LPST_RUNLIST;
즉, 해당 runlist의 type과 시작 Cluster 위치, 그리고 Cluster 개수의 정보가 포함됩니다. Run list가 3개의 node로 이뤄져 있다면, 각각의 시작 위치와 길이를 통해 값을 구할 수 있습니다.
NTFS_HANDLE FindFirstFile(IN LPCWSTR lpFileName, OUT LPWIN32_FIND_DATAW lpFindFileData)
BOOL FindNextFile(IN NTFS_HANDLE hFindFile, OUT LPWIN32_FIND_DATA lpFindFileData)
 BOOL FindClose(IN NTFS_HANDLE hFindFile)
Win32 API에 있는 FindFirstFile 함수군을 ntfsparser를 통해 구현한 함수들입니다.
자세한 내용은 msdn을 참고하시기 바라며,
현재 지원되거나 지원되지 않는 이슈사항들은 다음과 같습니다.
- Win32 API에서 lpFileName에 사용가능한 wild-card는 msdn에 알려진대로 구현되지 않았음.
  (*.* 혹은 *만 지원. 그 이외의 값이 전달되면 ERROR_NOT_SUPPORTED를 SetLastError한다)
- Win32 API에서 lpFileName에 "C:"를 전달하면 IDE 값을 전달하는데, 지원하지 않음
- lpFindFileData의 다음 값들은 지원되지 않음
  cAlternateFileName, dwReserrved0, dwReserved1

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)

 NTFS_HANDLE FindFirstFileWithInodeExt(IN LPCWSTR lpFileName, OUT LPWIN32_FIND_DATAW lpFindFileData, OUT LPST_INODEDATA pstInodeData, OPTIONAL IN LPST_IO_FUNCTION pstIoFunction, OPTIONAL IN LPST_INODE_CACHE pstInodeCache)
 BOOL FindNextFileWithInodeExt(IN NTFS_HANDLE hFindFile, OUT LPWIN32_FIND_DATA lpFindFileData, OUT LPST_INODEDATA pstInodeData)
앞선 FindFirstFile Emulated API를 이용하면, 내부에서 inode를 다시 처음 부터 찾는 과정이 발생합니다. Windows file system(ntfs.sys)에서는 아마 해당 inode들을 cache 혹은 indexing하여 속도를 향상시키는듯 하나, 본 함수군들은 미약하기는 하나 호출자가 정의한 cache 함수를 사용할 수 있도록 방법을 알려주고 있습니다. 또한, inode정보를 직접 이용할 수 있도록 기능이 제공됩니다. 그래서, 속도 향상을 위해 해당 함수를 약간 수정하여 만든 확장 API를 지원하였습니다.

우선 ~ByInode는 호출자가 해당 경로의 inode를 알고 있는 경우 직접 그 값을 사용하도록 하여 inode 찾는과정이 발생하지 않아 속도가 향상됩니다. ~WithInode는 inode 정보를 함께 전달해주는 기능을 가집니다.

lpVolumePath는 C: D:와 같이 Volume명이 들어가면 됩니다. 물론, C:\Windows\notepad.exe와 같은 값이나 C:\*와 같은 Wild-card가 사용되어도 무방합니다. 앞의 C:만 읽기 때문입니다. 그리고, ~ByInode 함수에서 pstInodeData가 실제로 FindFirstFile에 전달할 inode 정보를 넘기면 됩니다. bFindSubDir은 *.*나 *의 사용여부를 전달하면 됩니다. ~WithInode 함수는 해당 경로의 WIN32_FIND_DATA 정보 이외에 inode 정도도 함께 전달합니다. 만일 FindFirstFile에 *나 *.*가 사용되지 않았을때 혹은 bFindSubDir이 FALSE일때 FindFirstFile 계열 함수군은 단말 파일 정보만 탐색하게 됩니다. 즉, FindNextFile은 이뤄지지 않습니다. 그때 FindFirstFileByInode에서 pstSubDirInodeData는 pstInodeData 동일한 값을 전달받게 됩니다.

만일 FindFirstFile 계열의 함수를 사용할 때 발생하는 I/O를 사용자가 정의하고자 한다면, pstIoFunction에 값을 전달하면 됩니다. 물론 불필요한 경우 가능합니다. 자세한건 "사용자 정의 외부 사용하기"를 참고하세요.

앞서 설명한 inode 정보를 cache하고자 한다면, pstInodeCache 값을 전달하시기 바랍니다. 물론 NULL 가능하며 "inode 정보 cache하기"를 확인하세요.

 TYPE_INODE FindInode(IN LPST_NTFS pstNtfs, IN TYPE_INODE nStartInode, IN LPCWSTR lpszName, OUT PBOOL pbFound, OPTIONAL OUT LPST_INODEDATA pstInodeData, OPTIONAL OUT LPST_INODEDATAEX pstInodeDataEx)
 LPST_FINDFIRSTINODE FindFirstInode(IN LPST_NTFS pstNtfs, IN TYPE_INODE nInode, OUT LPST_INODEDATA pstInodeData, OPTIONAL OUT LPST_INODEDATAEX pstInodeDataEx)
 BOOL FindNextInode(IN LPST_FINDFIRSTINODE pstFind, OUT LPST_INODEDATA pstInodeData, OPTIONAL OUT LPST_INODEDATAEX pstInodeDataEx)
BOOL FindCloseInode(IN LPST_FINDFIRSTINODE pstFind)
FindFirstFile이 File에 대한 탐색이라면, 위 함수들은 inode에 대한 탐색입니다. 대략 FindFirstFile과 유사한 내용으로 구성되나, PROGRA~1이나 $VOLUME과 같은 meta-data file도 탐색되는 특징이 있습니다. 물론, FindFirstFile 함수군을 사용할 때 보다 속도가 빠를 것입니다.
NTFS_HANDLE CreateFile(IN LPCWSTR lpFileName, IN DWORD dwDesiredAccess, NOT_USED IN DWORD dwShareMode, NOT_USED IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwCreationDisposition, NOT_USED IN DWORD dwFlagsAndAttributes, NOT_USED IN HANDLE hTemplateFile)
BOOL ReadFile(IN NTFS_HANDLE hFile, OUT LPVOID lpBuffer, IN DWORD nNumberOfBytesToRead, OUT LPDWORD lpNumberOfBytesRead, NOT_USED OPTIONAL IN OUT LPOVERLAPPED lpOverlapped)
BOOL CloseHandle(IN NTFS_HANDLE hObject)
 DWORD GetFileSize(IN HANDLE hFile, OPTIONAL OUT LPDWORD lpFileSizeHigh)
CreateFile 계열의 Win32 API들 입니다.
CreateFile의 dwDesiredAccess에 GENERIC_WRITE 속성이 포함되어 있으면 ERROR_NOT_SUPPORTED가 GetLastError되며 FALSE가 리턴됩니다. 그리고 dwCreationDisposition에는 OPEN_EXISTING만 지원되며 그 이외 값인 경우에도 역시 실패(ERROR_NOT_SUPPORTED)가 됩니다. 그이외 NOT_USED라 tag된 parameter는 내부에서 사용되지 않는 값입니다.
자세한 내용은 msdn을 참고하시기 바랍니다.
 NTFS_HANDLE CreateFileByAttrib(IN LPCWSTR lpFileName, IN DWORD dwDesiredAccess, NOT_USED IN DWORD dwShareMode, NOT_USED IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwCreationDisposition, NOT_USED IN DWORD dwFlagsAndAttributes, NOT_USED IN HANDLE hTemplateFile, IN TYPE_ATTRIB nTypeAttrib)
CreateFile 계열의 Win32 API들중 확장 API입니다.
즉, msdn에는 없으나 ntfsparser에서 새롭게 추가한 함수입니다.
CreateFileByAttrib은 동일하게 CreateFile을 실행하되 읽을 Data의 속성을 지정할 수 있도록 합니다. 일반적인 File의 내용은 $DATA(TYPE_ATTRIB_DATA)이나, $VOLUME_NAME이나 $BITMAP등이 경우에따라 저장되기도 합니다. 해당 값을 읽어야 할때 호출하면 됩니다.

이외에도 다음과 같은 Win32 API emulation이 지원됩니다.
자세한 내용은 msnd을 참고하시기 바랍니다.
 DWORD GetFilesize(IN NTFS_HANDLE hFileOPTIONAL OUT LPDWORD lpFilesizeHigh)
 DWORD SetFilePointer(IN NTFS_HANDLE hFileIN LONG lDistanceToMove, OPTIONAL IN OUT PLONG lpDistanceToMoveHigh, IN DWORD dwMoveMethod)

사용자 정의 외부 I/O 사용하기
Revision 6부터 사용자가 정의한 외부 I/O를 사용할 수 있습니다.
자세한 것은 ntfsparser.cpp의 '5. 외부 I/O를 만들어 연결해 보자.'를 참고하시기 바랍니다.
우선 기존 함수에 ~~~Io(...)로 끝나는 함수가 있는데, 이들 함수를 사용하면 외부 I/O로 연결됩니다. 지원 함수는 아래의 Change log : revision 6을 참고하시기 바랍니다.

우선, 지원 함수군의 특징은 기존 함수에서 ST_IO_FUNCTION을 추가로 요구하고 있습니다. 해당 구조체는 다음과 같이 사용됩니다.
...
CNTFSHelper::ST_IO_FUNCTION stIo = {0,};
...

// 외부 I/O 함수들을 정의한다.
stIo.dwCbSize   = sizeof(stIo);
stIo.pfnOpenIo  = MyOpenIo;
stIo.pfnReadIo  = MyReadIo;
stIo.pfnSeekIo  = MySeekIo;
stIo.pfnCloseIo = MyCloseIo;
stIo.pUserIoContext = some pointer; // MyOpenIo(...)에 전달된다.
와 같이 정의합니다. 이렇게 정의된 stIo는 예를 들어, 다음과 같이 사용됩니다.
...
CNTFSHelper::NTFS_HANDLE
hHandle
= INVALID_HANDLE_VALUE_NTFS;
...
hHandle = CNTFSHelper::FindFirstFileExt(L"C:\\*.*", &stFD, &stIo, NULL);
...
if (FALSE == CNTFSHelper::FindNextFile(hHandle, ...)
...
즉, ~~~Io(...) 함수에 stIo를 전달하는 것으로 이뤄집니다. 그렇게 되면, FindFirstFile이 실행되는 동안 필요한 I/O 호출은 stIo에서 등록된 함수를 호출함으로 이뤄집니다. 그렇다면, I/O 함수는 다음과 같은 prototype을 가집니다. (My....와 같이 이름을 정했는데, 함수명은 알아서 정의하시면 됩니다.)

CNTFSHelper::IO_LPVOID __cdecl MyOpenIo(IN LPCSTR lpszPath, OPTIONAL IN LPVOID pUserIoContext)
I/O Open시 호출을 받습니다. lpszPath는 "\\.\C:"와 같은 형태로 입력되며, pUserIoContext는 NULL 가능한데, ST_IO_FUNCTION 등록시 ST_IO_FUNCTION::pUserIoContext에 전달한 값이 들어옵니다. 본 함수의 리턴값은 리턴시 임의의 pointer를 전달하면 되며, 실패시 NULL을 전달합니다. 해당 임의의 pointer는 향후의 MyReadIo, MySeekIo 그리고 MyCloseIo등에서 지속적으로 사용됩니다. 이러한 특징으로, 해당 IO_LPVOID는 임의의 구조체에 대한 동적 할당된 pointer를 전달하면 됩니다.
즉,
...
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 시켜 주면 됩니다.


BOOL __cdecl MySeekIo(IN CNTFSHelper::IO_LPVOID pIoHandle, IN CNTFSHelper::UBYTE8 nPos, IN INT nOrigin)
I/O Seek 함수를 정의합니다. pIoHandle은 MyOpenIo에 의해 생성된 값을 입력 받습니다. nPos는 Seek할 위치(byte 단위)를 전달받습니다. nOrigin은 SEEK_SET, SEEK_CUR 그리고 SEEK_END 중 하나의 값을 전달받습니다. 주의할 것은 성공시 TRUE를 리턴하고 실패시 FALSE를 리턴해야 합니다. 일반적인 lseek 함수군의 리턴값과는 다르다는점을 유의하시기 바랍니다. 

INT __cdecl MyReadIo(IN CNTFSHelper::IO_LPVOID pIoHandle, OUT LPVOID pBuffer, IN unsigned int count)
I/O Read 함수를 정의합니다. pIoHandle은 MyOpenIo에 의해 생성된 값을 전달 받습니다. count byte 만큼 읽어 pBuffer에 값을 씁니다. 실제로 읽은 byte를 리턴값으로 전달합니다. 본 함수 실패시 -1을 전달하시기 바랍니다.

BOOL __cdecl MyCloseIo(IN CNTFSHelper::IO_LPVOID pIoHandle)
I/O Close 함수를 정의합니다. MyOpenIo에 의해 동적 할당된 pIoHandle을 free 시켜 주도록 합니다.

 

inode 정보 cache하기 

ntfsparser에서 inode 정보를 cache 한다는 것은, cache 처리를 위한 caallback 함수를 등록하는 과정으로 진행됩니다. 즉, cache를 처리하는 code는 호출자가 정의해야 합니다. 즉, ntfsparser에서는 cache data 처리를 위한 input/output을 set/get 요청을 합니다. 호출자는 set인 경우 (input, output) pair를 메모리에 저장하고, get인 경우, input에 대해 연결된 output을 전달해주면 됩니다.

 

BOOL    SetInodeCache(IN LPST_NTFS pstNtfs, IN LPFN_CreateInodeCache pfnCreateInodeCache, IN LPFN_GetCache pfnGetCache, IN LPFN_SetCache pfnSetCAche, IN LPFN_DestroyCache pfnDestroyCache, OPTIONAL IN LPVOID pUserContext)

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개의 함수에 대한 설명을 하겠습니다.

 

HANDLE_INODE_CACHE MyCreateInodeCache(OPTIONAL IN LPVOID pUserContext)

SetInodeCache 호출에 의해 cache 생성 요청을 처리해야 합니다. cache의 메모리를 HANDLE로 처리하여 전달해야 합니다. 만일 생성에 실패하였다면 NULL를 전달할 수 있습니다. 앞선 설명대로 Ntfs 개체당 개별적인 cache 처리가 가능한데, 만약 개별적이지 않고 동일한 cache를 공유하고자 한다면, 예를 들어, 전역 변수의 메모리를 리턴하셔도 무방합니다.

 

BOOL MyGetCache(IN HANDLE_INODE_CACHE hHandle, IN TYPE_INODE nStartInode, IN LPCWSTR lpszNameUpperCase, OUT PBOOL pbFound, OUT LPST_INODEDATA pstInodeData, OPTIONAL IN LPVOID pUserContext)

ntfsparser 내부에서 FindInode 호출이 필요한 경우 본 함수를 호출합니다. MyCreateInodeCache에서 리턴한 값이 handle로 들어오며, nStartInode와 lpszNameUpperCase를 가지고 pstInodeData 정보를 전달하면 됩니다. 만일 cache memory에 nStartInode와 lpszNameUpperCase에 해당되는 값이 없는 경우 *pbFound에 FALSE를 전달해야 하며, 값을 찾았다면 TRUE를 전달해야 합니다.

 

BOOL MySetCache(IN HANDLE_INODE_CACHE hHandle, IN TYPE_INODE nStartInode, IN LPCWSTR lpszNameUpperCase, IN LPST_INODEDATA pstInodeData, OPTIONAL IN LPVOID pUserContext)

ntfsparser 내부에서 FindInode 호출이 성공했을 때 호출됩니다. MyGetCache와 반대로 nStartInode와 lpszNameUpperCase를 가지고 pstInodeData를 cache memory에 저장하면 됩니다.

 

VOID MyDestroyCache(IN HANDLE_INODE_CACHE hHandle, OPTIONAL IN LPVOID pUserContext) 

MyCreateInodeCache와 반대로 cache를 삭제할때 호출합니다. 주로, 연관되어있던 Ntfs 개체가 close될 때 호출됩니다.

Change log : revision 2 [2013/07/23]
Revision 2에서 많은 부분이 수정되었습니다.
물론, 가장 큰 변화는 Traverse 지원입니다.
즉, inode나 File에 대한 dir이 가능해 졌습니다.
해당 dir은 일반적인 Win32 API의 FindFirstFile과 동일한 API로 구성하였습니다.
즉, 기존의 FindFirstFile(...) ==> CNTFSHelper::FindFirstFile(...)
과 같이 앞에 CNTFSHelper::만 추가하면, 시스템의 file system을 이용하지 않고,
ntfsparser를 통해서만 열거됩니다.
또한, FindFirstFile를 통하면 inode를 처음부터 찾는 부분이 포함되는데, 이는 속도 이슈를 야기합니다. 그래서, FindFirstFile 함수군을 확장하여 미리 계산된 inode를 이용할 수 있도록 하였습니다. 해당 함수로 전환하여 사용하면 약 10%정도 속도 향상을 기대할 수 있습니다.

- Runlist 계산 과정 버그 수정 (0xF...로 시작 --> 0x8...~0xF..로 시작)
- $ATTRIBUTE_LIST 지원 (non-resident only)
- FindAttribAlloc에서 normal file에 해당되는 inode인 경우 $MFT로부터 가져옴(GetLcnFromMftLcn)
- Win32 API를 통한 Disk 크기 계산 -> BPB값 이용 (성능 향상)
- Traverse 지원 (b-tree, inode, file, Win32 API의 FindFirstFile Emulation)

Change log : revision 3 [2013/07/24]
Revision 3에서는 다음 기능이 추가되었습니다.
- 몇몇 버그 수정
- CreateFile / ReadFile / CloseHandle / GetFileSize Win32 API Emualation 지원
- CreateFileByAttrib 확장 API 지원
즉, CreateFile 관련 함수군을 지원하였습니다. 즉, Win32 API를 사용하지 않고, 직접 CreateFile을 실행하여 파일을 열고, ReadFile을 통해 읽고, GetFileSize로 파일의 크기를 구합니다. 물론, CreateFile은 Readonly로 요청할때만 지원합니다. 그렇기 때문에 WriteFile은 없습니다.
또한 CreateFileAttrib을 가지고 실제 File의 data 뿐만 아니라 다른 속성(예, $BITMAP)도 ReadFile할 수 있도록 도와줍니다.
현재 압축된 형태의 파일은 지원하지 않습니다(ERROR_NOT_SUPPORTED).
그리고 암호화된 형태의 파일은 암호화된 상태의 파일 내용으로 출력됩니다.

Change log : revision 4 [2013/07/29]
Revision 4에서 다음 기능이 추가되었습니다.
- SetFilePointer Win32 API emulation 지원

Change log : revision 5 [2013/07/31]
Revision 5에서 다음 내용이 지원되었습니다.
- non-resident 형태의 $ATTRIBUTE_LIST 검색 지원

Change log : revision 6 [2013/08/01]
Revision 6에서 다음 내용이 지원되었습니다.
- 사용자 정의 외부 I/O 등록

즉, 기본적인 sector 단위의 Open, Read, Seek, Close등의 I/O 함수를 사용자, 즉, caller가 직접 정의하여 사용할 수 있도록 하였습니다. 지원되는 함수는 다음과 같습니다.
- OpenNtfsIo
- FindFirstFileIo
- FindFirstFileByInodeIo
- FindFirstFileWithInodeIo
- CreateFileIo
 
Change log : revision 7 [2013/08/22]
Revision 7에서는, FindFirstFile 함수군에서 Win32 API와 동일하게 . 그리고 .. 가 우선 Find되도록 수정되었습니다(기존에는 바로 실제 File 탐색). .와 ..가 탐색될 때 WIN32_FIND_DATA는 시작 경로의 directory 정보로 전달됩니다. 즉, C:\Temp\*가 호출되었을 때, . 와 .. 는 C:\Temp에 해당되는 정보를 전달합니다(Win32 API에서 그렇더군요. 혹시 잘 못 되었나요? msdn에서는 .와 ..에 대한 명확한 설명은 없었습니다)

.와 ..에 대한 지원을 위해 성능을 약간 희생하게 되었는데, 만일, .와 ..에 대한 탐색이 필요없는 경우에는,
CNTFSHelper::NTFS_HANDLE CNTFSHelper::FindFirstFileMain(...)
{
...
if ((TRUE == stHandle.stFindFirstFile.bFindSubDir) &&
    (TYPE_INODE_DOT != pstInodeData->nInode))
{
// stHandle.stFindFirstFile.bUseFindDot = TRUE;
}
...
와 같이 주석 처리하시기 바랍니다. 물론, . 와 .. 탐색을 위한 희생은 포함됩니다. 단지, .와 .. 탐색을 하지 않습니다.

 

Change log : revision 8 [2013/09/05]

Revision 8에서는, inode cache가 추가되었습니다. 그리고, FindFirstFileExt와 같이 함수명을 변경했으며, 내부적으로 ST_INODEDATAEX가 제거되었습니다. 만일 WIN32_FIND_DATA를 내부적으로 구하고자 한다면 GetWin32FindData를 참고하시기 바랍니다.

 

ntfsparser 실행
ntfsparser는 CNTFSHelper class의 샘플코드로 이뤄져 있습니다.
다음의 3가지 내용으로 이뤄져 있습니다.
  1. Cluster의 사용 여부 체크
  2. Volume 이름 구하기
  3. C:\Windows\System32\config에 포함되어 있는 파일을 열거
    (FindFirstFile과 FindFirstFile 확장 함수 사용, inode cache 사용)
  4. 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