00 윈도우 DLL 분석

원문 :  http://kkamagui.springnote.com/pages/388634

 

들어가기 전에...

 

개요

 보안 프로그램들이 실행될 때, 실행 중인 프로세스에 DLL을 밀어 넣는 것은 알만한 사람들은 다 아는 비밀(??)이다. DLL을 밀어넣어서 각종 보안에 필요한 API를 후킹한다던지 하는 작업을 하게되는데... 순간 의문점이 생겼다. DLL이 삽입(Injection)되어서 Kernel32.dll에 포함된 코드들을 변경할 경우, 코드의 영향력은 어디까지 일까? 해당 프로세스에만 영향을 미치는 걸까? 아니면 Kernel32.dll을 사용하는 프로그램 전체에 영향을 미치는 것일까?

 지금까지 여기저기 작업하면서 어렴풋이 해당 프로세스에만 영향을 미칠 것이라고 생각하고 있었는데, 오늘 제대로 한번 분석해 보았다. 일단 대상은 LoadLibrary로 정하였고 내가 Python으로 만든 디스어셈블러를 이용하여 분석하였다.

 

Kernel32.dll 정보 분석

 일단 Kernel32.dll에 있는 LoadLibrary() 함수를 찾아서 그 함수가 존재하는 영역의 메모리 정보를 얻어야 했다. 그래서 아래와 같은 테스트 프로그램을 작성하였다. 아래 코드는 VirtualQuery()와 VirtualProtect() 함수를 이용해서 간단히 메모리 영역에 대한 정보를 보고 변경하는 코드이다.

  1. #include "stdafx.h"
    #include <windows.h>
  2. /**
        Protect String을 표시한다.
    */
    void PrintProtectString( DWORD dwProtect )
    {
        if( dwProtect & PAGE_READONLY )
        {
            printf( "PAGE_READONLY " );
        }
       
        if( dwProtect & PAGE_READWRITE )
        {
            printf( "PAGE_READWRITE " );
        }
       
        if( dwProtect & PAGE_WRITECOPY )
        {
            printf( "PAGE_WRITECOPY " );
        }
       
        if( dwProtect & PAGE_EXECUTE )
        {
            printf( "PAGE_EXECUTE " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READ )
        {
            printf( "PAGE_EXECUTE_READ " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READWRITE )
        {
            printf( "PAGE_EXECUTE_READWRITE " );
        }
  3.     if( dwProtect & PAGE_EXECUTE_WRITECOPY )
        {
            printf( "PAGE_EXECUTE_WRITECOPY " );
        }
  4.     if( dwProtect & PAGE_GUARD )
        {
            printf( "PAGE_GUARD " );
        }
  5.     if( dwProtect & PAGE_NOACCESS )
        {
            printf( "PAGE_NOACCESS " );
        }
  6.     if( dwProtect & PAGE_NOCACHE )
        {
            printf( "PAGE_NOCACHE " );
        }
    }
  7. /**
        State를 출력한다.
    */
    void PrintState( DWORD dwState )
    {
        if( dwState & MEM_COMMIT )
        {
            printf( "MEM_COMMIT " );
        }
  8.     if( dwState & MEM_FREE )
        {
            printf( "MEM_FREE " );
        }
  9.     if( dwState & MEM_RESERVE )
        {
            printf( "MEM_RESERVE " );
        }
    }

  10. /**
        Type을 표시한다.
    */
    void PrintType( DWORD dwType )
    {
        if( dwType & MEM_IMAGE )
        {
            printf( "MEM_IMAGE " );
        }
  11.     if( dwType & MEM_MAPPED )
        {
            printf( "MEM_MAPPED " );
        }
  12.     if( dwType & MEM_PRIVATE )
        {
            printf( "MEM_PRIVATE " );
        }
    }
  13. /**
        Memory Information을 출력한다.
    */
    void DumpMemoryInformation( MEMORY_BASIC_INFORMATION* pstMbi )
    {
        printf( "Type : " );
        PrintType( pstMbi->Type );
        printf( "\n" );
  14.     printf( "State : " );
        PrintState( pstMbi->State );
        printf( "\n" );
  15.     printf( "Base Address : 0x%X\n", pstMbi->BaseAddress );
  16.     printf( "Allocation Base : 0x%X\n", pstMbi->AllocationBase );
  17.     printf( "Region Size : %d\n", pstMbi->RegionSize );
  18.     printf( "Allocation Protect : " );
        PrintProtectString( pstMbi->AllocationProtect );
        printf( "\n" );
  19.     printf( "Protect : " );
        PrintProtectString( pstMbi->Protect );
        printf( "\n\n" );
    }
  20. int main(int argc, char* argv[])
    {
        HMODULE hModule;
        BYTE* pbOpenProcess;
        MEMORY_BASIC_INFORMATION stMbi;
        DWORD dwWriteEnable;
        DWORD dwOldProtect;
  21.     hModule = LoadLibrary( "Kernel32.dll" );
        pbOpenProcess = ( BYTE* ) GetProcAddress( hModule, "LoadLibraryA" );
  22.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  23.     // 일단 막무가네 쓰기 테스트
        // 여기서 이렇게 하면 에러난다.
        //pbOpenProcess[ 0 ] = 0xFF;
  24.     // 정보 출력
        printf( "Befere Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  25.     // Write Enable을 하고나면
        dwWriteEnable = stMbi.Protect;
        dwWriteEnable &= ~( PAGE_READONLY | PAGE_EXECUTE_READ );
        dwWriteEnable |= ( PAGE_READWRITE );
        if( VirtualProtect( pbOpenProcess, stMbi.RegionSize, dwWriteEnable,
            &dwOldProtect ) == FALSE )
        {
            printf( "Write Protect Error\n" );
            return 0;
        }
  26.     // 막무가네 쓰기
        // 이렇게 하면 성공한다.
        pbOpenProcess[ 0 ] = 0xFF;
  27.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  28.     // 정보 출력
        printf( "After Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  29.     return 0;
    }

 

 위의 코드를 Console로 만들어서 실행하면 아래와 같이 LoadLibrary() 함수 영역에 대한 정보를 뽑을 수 있다.

MemoryInfo.PNG

 일단 메모리의 STATE가 MEM_COMMIT인 것을 보면 물리 메모리가 맵핑된 상태의 가상메모리 주소임을 알 수 있고, Allocation Protect가 PAGE_EXECUTE_WIRTECOPY 인 것을 보아 쓰기가 되면 메모리가 새로 복사되어 맵핑될 것이라는 것을 알 수 있다.

 실제 위의 코드에서 보면 Before Protect Change와 After Protect Chage 사이에서 메모리에 보호 설정을 변경하여 제일 첫바이트를 0xFF로 변경한 것을 볼 수 있는데, 이때 쓰기와 동시에 Page 한개의 크기(4096Byte)만큼 새로 할당되어 맵핑된 것으로 추측된다. 이것은 위에 Region Size가 4096으로 변경된 것을 보고 추측한 것이다.

 

변경 영향력 검사

 위의 포로그램을 실행한 상태에서 디스어셈블리어를 실행시켜 LoadLibrary()를 디스어셈블리시켜 보았다. 만약 저 변경이 글로벌한 것이라면 디스어셈블리의 결과 첫바이트가 0xFF로 변경되어 나와야 한다. 일단 아래 결과를 보자.

0x7c801d77 (02) 8BFF                 MOV EDI, EDI
0x7c801d79 (01) 55                   PUSH EBP
0x7c801d7a (02) 8BEC                 MOV EBP, ESP

 위 결과에서 보는 것과 같이 0x8B로 변하지 않았다는 것을 알 수 있다. 이것은 변화가 해당 프로세스에 국한 된다는 것을 말한다.

 

결론

 위의 결과로 보아 VirtualProtect를 이용함 메모리 수정은 해당 프로세스에 종속되므로 결국 모든 프로세스에 DLL을 삽입해야 할 것 같다. 어렴풋이 알고 있는 것을 제대로 알게 되어 다행이다. 역시 직접 해보는 것이 최고 @0@)/~!!

 

ps)

만약 kernel32.dll에 실제 물리주소를 얻어서 직접 수정을 가한다면 모든 프로세스에 DLL을 삽입하지 않아도 될 것 같은데... 상당히 위험한 방법일 것 같기도 하고 ㅡ_ㅡ;;;;(많이 위험할래나...)

 

이 글은 스프링노트에서 작성되었습니다.

FAT 파일 시스템(File System)

원문 :  http://kkamagui.springnote.com/pages/345737


들어가기 전에...

1.FAT 파일 시스템 개요

 FAT 파일 시스템은 전통적으로 DOS 시절부터 사용되어 왔다. FAT 파일 시스템이 다른 파일 시스템과 구별되는 점은 파일 내용을 클러스터 단위로 구성하고 이것을 링크드 리스트(Linked List)의 형태로 보관하는 것이다.

 말로 하면 어려우므로, 일단 아래 그림을 보도록 하자.

FAT1.PNG

<FAT의 간략한 구조>



 위 그림과 같이 파일은 고정된 크기의 클러스터(Cluster) 단위로 나누어져 있으며, 그 클러스터들은 클러스터 풀(Cluster Pool)에 존재하는 클러스터를 연결한 클러스터 채인(Cluster Chain)으로 되어있다. 실제 FAT 파일 시스템에서 클러스터 풀은 FAT(File Allocation Table)이라는 용어를 사용한다.

 클러스터(Cluster)란 별개 아니고 여러개의 섹터를 모아서 하나의 블럭 단위로 보는 것이다. 윈도우 포맷 프로그램을 이용해서 포맷을 하면 일반적으로 클러스터의 크기를 4Kbyte로 잡는데, 이 말은 한 섹터(Sector)의 크기가 512byte 이므로 8개의 섹터를 하나의 블럭으로 설정한다고 보면 된다.

 그럼 왜 클러스터라는 말을 쓰는 것일까? 그것은 섹터를 블럭으로 관리하면 얻는 이득이 있기때문이다. 아까 잠시 클러스터 풀에 대한 이야기를 했는데, 고용량일수록 클러스터 풀(FAT 영역)이 커지게 된다. 이것이 커질수록 파일을 읽고 쓸때 관리해야 하는 양이 늘어나게되고, 또한 디스크의 비어있는 공간이 줄어들게 된다. 이것을 블럭으로 관리하게 되면 이런 문제가 해결되며, 또한 블럭 단위로 읽기 쓰기를 수행함으로 얻는 효율로 인해 성능이 좋아지게 되는 것이다.

 특히 요즘처럼 파일의 크기가 큰 상황에서는 클러스터의 크기가 큰 것이 성능 향상에 도움이 된다. 하지만 클러스터의 크기가 무조건 크다고 좋은 것은 아니다. 아까 이야기 했듯이 클러스터의 단위로 작업을 하기 때문에, 작은 파일 같은 경우는 클러스터 하나를 할당받아서 대부분의 영역을 낭비하는 경우도 있으니 적당히 조절해야 한다.


 자 그럼 FAT 파일 시스템의 큰 특징을 알아봤으니 세부적인 내용을 알아보자

 FAT 파일 시스템은 총 3가지의 타입이 있다.

  • FAT12 : 클러스터의 개수가 1 ~ 0xFFF 까지를 가지는 타입. 플로피 디스크에서 주로 사용됨
  • FAT16 : 클러스터의 개수가 0xFFF ~  0xFFFF 까지를 가지는 타입. 소용량에 주로 사용됨
  • FAT32 : 클러스터의 개수가 0xFFFF ~ 0xFFFFFFFF 까지를 가지는 타입. 대용량에 주로 사용됨

 세가지 타입 모두 내부 포맷은 비슷하며, 차이점은 클러스터 개수라고 볼 수 있다.(물론 그것만 차이나는 것은 아니다. ㅡ,.ㅡ;;; 오해하지 말자.). 클러스터의 개수에 따라 FAT 타입을 분류한다고 했는데, 이 클러스터 개수는 어떻게 구하는 것일까? 실제 데이터 영역을 구성하는 크기를 가지고 계산하는 것일까?


 실제 타입 구분에 사용되는 클러스터의 크기는 디스크 전체 크기를 범위로 한다. 즉 아래의 공식으로 클러스터의 크기를 구하고 그것으로 타입을 유추하는 것이다.

클러스터의 개수 = 파티션 또는 디스크의 크기 / 클러스터의 크기

 이렇게 구한 클러스터의 개수는 실제로 사용가능한 데이터 영역의 클러스터의 개수와 비교하면 당연히 큰값을 가진다. 왜냐하면 디스크 또는 파티션 영역에 데이터만 들어가는 것이 아니고 FAT 파일 시스템을 관리하기위한 파일 시스템 메타 데이터(Meta Data)를 포함하기 때문이다.

 이제 파티션과 메타 데이터에 대해서 알아보자.


2.파티션(Partition)

 파티션에 대해서 익히 알고있고 들어봤을 것이다. 디스크 전체를 하나로 사용하는 것이 아니라 그 안에 C: D:와 같이 영역을 분할하여 사용하는 것이 파티션이다. 파티션을 나누면 디스크를 효율적으로 관리할 수 있는 장점 때문에 약간의 디스크 공간을 낭비하더라도 파티션을 해서 많이 사용한다(아래와 같이 나눌 수도 있다).

Partition.PNG


 그럼 어떻게해서 파티션이 나누어지는 것일까? 파티션을 나누게 되면 그 파티션에 대한 정보가 MBR(Master Boot Record)란 영역에 삽입되게 된다. MBR이란 디스크의 첫번째 섹터로 부트 코드와 여러가지를 정보를 포함하고 있다. MBR의 존재는 맨 마지막 510째부터 0x55 0xAA가 있는지 확인하여 이것이 있으면 MBR이 존재한다고 보면 된다(사실 다 있다. ㅡ_ㅡ;;; OS를 깔아 쓰면 저것 말고도 여러 정보가 MBR에 들어가 있다.)

 MBR의 세부구조를 한번 알아보자.

MBR.PNG

 헥사 에디터 프로그램으로 4개의 파티션으로 나누어진 USB 메모리의 MBR을  캡쳐한 화면이다. 좌측 상단에 0xEB 0x3C 0x90의 3Byte가 있는데, 이부분은 부트 코드의 시작으로 jmp 하라는 어셈블리어 명령이다. 굳이 필요한 것은 아니나 윈도우에서 MBR 인식을 위해서는 이 부분이 꼭 필요하다( 이부분이 0x00으로 비어져있는 경우에 윈도우가 MBR로 인식하지 못하는 것을 발견했다).

 우측 하단에 0x55 0xAA의 매직넘버가 보이고 그위에 붉은 색 줄이 쳐져있는 16byte의 라인이 보인다. 이부분이 파티션 데이터의 시작으로 그 앞부분은 모두 Boot Code의 용도로 사용되거나 아니면 사용되지 않는다. 파티션 정보는 16Byte씩 4개로 총 64Byte로 되어있으며 각 항목은 아래와 같은 구조체로 되어있다.

  1. /**
        Partition에 대한 정보를 포함하는 구조체
    */
    typedef struct partitionStruct
    {
        BYTE bBootIndicator;
        BYTE bStartingHeader;
        WORD wStartingSectorAndCylinder;
        BYTE bSystemID;
        BYTE bEndingHeader;
        WORD wEndingSectorAndCylinder;
        DWORD dwRelativeSector;
        DWORD dwTotalSector;
    } PARTITION, * PPARTITION;

  여기서 중요하게 봐야할 부분은 위에서 파란색으로 표시된 부분인데, 그 외 부분들은 옛날 도스 시절에나 사용된 필드기 때문에 무시해도 된다. 각 필드는 아래와 같은 의미를 가진다.

  • Boot Indicator : 부팅 가능한 파티션이면 0x80을 가짐. 그렇지 않으면 0x00
  • System ID : 파일 시스템의 ID. 일반적으로 윈도우에서 사용하는 값은 0x0B. 그 외의 값도 많으나 잘 쓰이지 않음
  • Relative Sector : 파티션이 시작되는 섹터.
  • Total Sector : 총 파티션의 크기

  위 정도값만 제대로 설정하면 윈도우에서 정상적으로 파티션 된 것으로 인식한다. MBR에 파티션 필드는 총 4개가 있으므로 파티션은 최대 4개까지 만들 수 있다. 그럼 도스 시절에는 파티션이 4개 이상이 가능했는데, 이것은 어떻게 한 것일까? 옛날 도스에서는 확장 파티션(Extension Partition)이라는 기능을 사용했다. 즉 MBR 파티션에는 확장 파티션 영역을 표시해 놓고, 확장 파티션 영역의 첫번째 섹터로 이동하면 그 첫번째 섹터에 다시 파티션 정보 + 확장 파티션 정보를 기입하는 식으로 체인으로 연결했던 것이다(이렇게 하면 디스크 용량이 허락하는 한 거의 무한대의 파티션을 생성할 수 있다. ㅡ,.ㅡ;;;;). 도스 이후에는 별로 사용하지 않으므로 일단 넘어가자. 궁금한 사람은 http://www.nondot.org/sabre/os/articles에 가서 Partition 쪽을 보면 된다.



3.파일 시스템 메타 데이터(File System Meta Data)

 FAT_메타_데이터_구조.PNG

 FAT 파일 시스템을 구성하는 메타 데이터는 위와 같은 순서로 구성된다. 각 항목에 대한 설명은 아래와 같다.

  • PBR : 파티션 부트 레코드(Partition Boot Record)의 약자로서 파티션에 대한 전반적인 정보를 저장하고 있음. 핵심적인 부분
  • Reserved : 특수한 용도(Root Directory의 시작위치를 조절하거나 특수한 데이터를 저장할 용도)로 사용되는 공간. 존재하지 않을 수도 있음
  • FAT1/2 : 클러스터 풀로써 클러스터 체인을 구성하는 부분. 뒤에오는 Data Cluster의 개수에 따라 가변적인 크기를 가짐. FAT1은 FAT2은 동일한 데이터를 가지며 FAT는 1만 존재할 수도 있음
  • Root Directory : 파일시스템의 최상위 디렉토리. FAT12/16의 경우 섹터단위로 설정할 수 있고, FAT32의 경우 클러스터 체인으로 구성함.
  • FSInfo : FAT32에만 존제하는 영역으로 FAT32에 속하는 기타 정보를 포함하는 영역
  • Backup Area : FAT32에만 존재하는 영역으로 PBR부터 FSInfo까지를 백업하는 영역. 완전히 복사하여 파일 시스템에 문제가 생기면 Backup 영역에서 데이터를 다시 복사하여 복원하는 역할

3.1 PBR(Partition Boot Record), BPB(BIOS Parmeter Block)

 PBR은 여러 이름으로 불리는데 다른 이름으로는 BPB라고도 한다. 옛날 도스 시절에 BIOS 콜을 통해 파티션에 대한 정보를 얻어오고 처리하고 했는데, 그 기본 정보가 되는 블럭이라서 저런 이름이 붙은 것 같다. 일단 여기서는 PBR이라고 하겠다.

 PBR과 MBR의 차이는 무엇일까? MBR은 하나만 존재하며 섹터 0에만 있고, PBR은 각 파티션의 시작에 존재하며 FAT에 대한 정보를 포함하고 있다는 것이 다르다. PBR에는 어떤 정보가 포함되어있을까? FAT Type에 따라 다르므로 각각 살펴보도록 하자.


 FAT16/32의 PBR에 포함된 데이터는 아래와 같다.

  1. /**
        FAT16에 대한 구조체
    */
    typedef struct fat16Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  2.     // 여기 위까지는 FAT16/32 공통
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 448 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT16, * PFAT16;
  3. /**
        FAT32에 대한 구조체
    */
    typedef struct fat32Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  4.     // 여기 위까지는 FAT16/32 공통
        DWORD dwSectorsPerFAT32;
        WORD wExtensionFlag;
        WORD wFSVersion;
        DWORD dwRootCluster;
        WORD wFSInfo;
        WORD wBackupBootSector;
        BYTE vbReserved[ 12 ];
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 420 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT32, * PFAT32;

 상당히 많은 데이터를 포함하고 있는데, 일단 FAT16/32에 공통인 부분부터 살펴보자.

  • BYTE vbJumpCode[ 3 ] : Jump Code. 0xEB 0x?? 0x90 또는 0xE9 0x?? 0x??으로 구성되어야 함(이 부분을 제대로 넣지 않을 경우 윈도우에서 파티션 인식이 제대로 안됨)
  • char vcCreatingSystemIdentifier[ 8 ] : Format을 수행한 OS의 이름. 대충 집어넣으면 됨. "MSWIN4.1"와 같은 값.
  • WORD wSectorSize : 섹터 하나의 크기. 보통 512byte
  • BYTE bSectorsPerCluster : 클러스터를 구성하는 섹터의 수. 1Byte이므로 최대 255개까지 클러스터로 설정가능하나 1, 2, 4, 8, 16, 32, 64, 128이 일반적으로 유효한 값.
  • WORD wReservedSectorCount : 파티션 시작 영역부터 FAT 1영역이 시작되기 전에 존재하는 섹터의 수(위 그림 참조). FAT12/16에서는 1, FAT32에서는 32로 권장. 하지만 0xFFFF까지 값을 가질 수 있음. 이 값을 이용하면 Root Cluster의 시작을 적당한 위치로 설정할 수 있음
  • BYTE bNumberOfFATs : File Allocation Table(FAT)의 개수. 1개 또는 2개가 설정 가능하나 일반적으로 2의 값을 가짐.
  • WORD wNumberOfRootDirectoryEntries : Root Directory의 Entry의 개수. FAT12/16에서 사용하며 FAT16에서는 512 권장. FAT32에서는 0.
  • WORD wTotalSectors : 파티션의 크기가 0xFFFF 섹터 이하이면 이 필드 사용. FAT32에서는 0.
  • BYTE bMediumIdentifer : Media의 Type. 일반적으로 0xF8 사용.
  • WORD wSectorsPerFAT : FAT12/16에서 사용되는 값. FAT32에서는 0.
  • WORD wSectorsPerTrack : Track에 포함된 섹터의 수.
  • WORD wNumberOfSides : Size의 개수. 일반적으로 Cylinder의 수.
  • DWORD dwNumberOfHiddenSectors : 파티션 시작 이전에 존재하는 섹터의 수.
  • DWORD dwTotalSectors : 파티션의 크기가 0xFFFF 초과일때 사용되는 필드. FAT32에서는 반드시 이 필드 사용.
  •  BYTE bExtendedBootRecordSignature :  0x29 값으로 설정.
  • char vcVolumeIDNumber[ 4 ] : Volume ID. 일반적으로 일자/시간을 섞어서 만듬. 대충 만들어도 됨.
  • char vcVolumeLabel[ 11 ] : Volume Label. 볼륨 이름.
  • char vcFileSystemType[ 8 ] : "FAT12", "FAT16", "FAT", "FAT32" 같은 문자열.
  • BYTE vbSignatureWord[ 2 ] : 0x55 0xAA

 공통인 부분들에 대해 살펴봤으니, 이제 FAT32에 특화된 부분을 보자

  • DWORD dwSectorsPerFAT32 : FAT32용 FAT 영역 크기를 설정하는 부분
  • WORD wExtensionFlag : FAT1/2 영역이 존재할때, 하나만 사용할 것인지 아니면 둘다 동기화 해서 사용할 것인지 설정하는 플래그. 일반적으로 0으로 설정하여 둘다 동기화 하여 사용하는 것으로 설정.
  • WORD wFSVersion : File System Version. High Byte는 Major, Low Byte는 Minor를 나타냄. 일반적으로 0x00 0x00으로 설정.
  • DWORD dwRootCluster : Root Cluster의 번호. 일반적으로 2로 설정(클러스터 0과 1번은 Signature 같은 용도로 사용).
  • WORD wFSInfo : FSInfo가 위치하는 Sector Offset. 일반적으로 PBR 바로 뒤에 위치하므로 1의 값을 가짐.
  • WORD wBackupBootSector : Backup 영역이 존재하는 Sector Offset. 일반적으로 6의 값을 가짐.
  • BYTE vbReserved[ 12 ] : 예약된 영역. 무조건 0으로 설정.
  • BYTE bPhysicalDiskNumber : Drive Number. 일반적으로 0x80을 가짐.
  • BYTE bReserved : 예약된 영역. 무조건 0으로 설정.

  위의 필드 값을 FAT Type에 따라 PBR에 설정해 주면 반은 끝난 것이다. 약간 주의할 점은 Reserved Sector에 값을 설정했다면 파티션의 시작부터 Reserved Sector에 설정된 값만큼을 0으로 초기화해야한다(적극 권장). 위의 설명에도 나와있듯이 Reserved Sector의 값은 파티션의 시작부터 FAT1/2 이전까지의 섹터 수이므로, 절대 0이 될 수 없다. 왜? PBR이 있기 때문에 @0@)/~!!! 아무리 작아도 1 이상의 값을 가진다.

 이제 다음 메타 데이터를 살펴보자.


3.2File Allocation Table(FAT) 1/2 영역

 FAT 1/2 영역은 클러스터의 개수에 따라서 영역의 크기가 달라진다고 이야기 했다. 여기에서 말하는 클러스터의 개수는 실제 사용가능한 클러스터의 개수를 이야기하는데, 이 크기 이상만 된다면 얼마든지 좀 크게 잡아도 상관없다.

 FAT12의 경우 FAT를 구성하는 클러스터 링크의 비트수가 12bit가 이고, FAT16의 경우  16bit, FAT32의 경우 32bit이다. 따라서 한 섹터를 기준으로 FAT Type에 따라서 포함할 수 있는 클러스터 링크의 개수는 아래와 같이 된다.

  • FAT12 : 512Byte * 8 / 12 = 341.3333 개
  • FAT16 : 512Byte * 8 / 16 = 256 개
  • FAT32 : 512Byte * 8 / 32 = 128 개

 위에서 보는 것과 같이 FAT32로 갈수록 한 섹터에 담을 수 있는 클러스터 링크의 개수가 작아진다. 이것은 동일한 클러스터의 크기를 사용한다면 대용량일수록 FAT의 크기가 커진다는 것을 의미하고 FAT가 커지면 커질수록 실제로 사용가능한 클러스터의 크기가 줄어들게된다. 클러스터의 크기를 적당히 조절하여 FAT 크기를 너무 크지않게 하는 것이 좋다.

 FAT의 0번째 클러스터 링크와 1번째 클러스터 링크는 Signature의 용도 비슷하게 사용되는데, FAT Type에 따라 아래와 같이 설정해 준다.

  • FAT12 : 0xFF8, 0xFFF
  • FAT16 : 0xFFF8, 0xFFFF
  • FAT32 : 0xFFFFFFF8, 0xFFFFFFFF

 자 그럼 이제 FAT의 실제 크기를 한번 계산해 보자. 가장 간단하게 계산할 수 있는 방법은 파티션 전체의 크기를 클러스터의 크기로 나누어 구하는 방법이다. 실제로 이렇게 구하면 PBR + Rerserved Area + FAT1/2 영역의 크기도 사용가능한 것으로 인식하여 계산하기 때문에 낭비가 좀 있지만 계산은 편리하다. 특히 Root Directory의 시작 위치를 맞추거나 할때는 이렇게 계산하여 FAT의 크기를 구하고 여기에 Reserved Area의 값을 조절하여 맞출 수 있어 편리하다.

 또다른 방법은 전체 크기에서 PBR과 Reserved 영역을 제외하고 구하는 방법이다. 낭비가 좀 줄어들긴 한데, 식이 복잡해 진다. 아래는 그 계산 식이다(511은 올림을 위해 넣었다)

FAT의 크기(섹터) = [ ( 전체 파티션 크기(섹터) - PBR(1섹터) - Reserved Sector(?섹터)  )  / 클러스터의 크기(섹터) * 클러스터 링크의 크기 + 511 ] / 512

 이렇게 계산된 크기를 PBR에 wSectorsPerFAT나 dwSectorsPerFAT32 영역에 넣어주면 된다. 만약 wNumberOfFAT의 값을 2로 설정했으면 FAT1/2 영역을 연속해서 만들어줘야 하며 위의 FAT의 크기 * 2 만큼의 영역을 할당해 줘야 한다.



3.3 Root Directory Sector or Cluster 영역

  FAT16/32의 경우 위의 순서대로 PBR, FAT1/2 를 만들고 Root Directory Sector만 생성해 주면 정상적으로 윈도우에서 인식이 가능하다. 만약 Volume Label을 "NO NAME"으로 입력한 경우 Root Directory는 0으로 초기화 해주는 것으로 충분하다. 하지만 Volume Label을 사용한다면 Directory Entry Structure를 생성해서 Volume Label을 삽입해야 한다.


 Directory Entry Structure는 아래와 같이 구성되어있다.

  1. typedef struct directoryStruct
    {
        char vcNameAndExtension[ 11 ];
        BYTE bAttribute;
        BYTE bReservedForNT;
        BYTE bCreatedTimeTenth;
        WORD wCreatedTime;
        WORD wCreatedDate;
        WORD wLastAccessDate;
        WORD wStartingClusterNumberHigh;
        WORD wTimeRecorded;
        WORD wDateRecorded;
        WORD wStartingClusterNumberLow;
        DWORD dwFileLength;
    } DIRECTORY, * PDIRECTORY;

 각 항목은 아래와 같은 의미를 가진다.

  • char vcNameAndExtension[ 11 ] : 파일 이름과 확장자 또는 Volume Label 저장. 0번째 값이 0x00 or 0xE5이면 Free Entry.
  • BYTE bAttribute : Entry의 속성을 표시. 여러가지가 있음(타입은 아래 참조)
  • BYTE bReservedForNT : NT를 위해 사용되는 필드. 0의 값으로 설정.
  • BYTE bCreatedTimeTenth : 생성된 초. 1/10초 단위까지 저장.
  • WORD wCreatedTime : 생성된 시간(포맷은 아래 참조)
  • WORD wCreatedDate : 생성된 날짜(포맷은 아래 참조)
  • WORD wLastAccessDate : 마지막으로 접근한 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberHigh : 클러스터의 상위 16bit 번호
  • WORD wTimeRecorded : 마지막으로 쓴 시간(포맷은 아래 참조)
  • WORD wDateRecorded : 마지막으로 쓴 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberLow : 클러스터의 하위 16bit 번호
  • DWORD dwFileLength : 파일의 크기

 조금 복잡한데, 일단 Attribute 부터 보면 아래와 같이 나와있다(첨부 항목에 White Paper 참조).

DIR_ATTRIBUTE.PNG

 일단 포맷 후에 Volume Label을 생성해야 하므로 ATTR_VOLUME_ID를 생성하면 된다. 이때 주의할 것은 Volume Label을 설정하는 Directory Entry의 경우 Name과 Attribute를 제외한 나머지는 모두 0으로 설정해야한다.

 위를 보면 낯 익은 값들도 있을 것이다. 만약 윈도우가 FAT Filesystem으로 포맷되어있다면 위의 항목들이 설정된 Directory Entry 구조체가 여기저기 널려있을 것이다. Hex Editor와 같은 프로그램을 이용해서 하드디스크를 열어서 확인해 보자 @0@)/~~!!!!. 각 항목에 대한 자세한 설명은 White Paper를 참조하자.


 Time과 Date 부분은 공통적인 포맷을 사용하는데, White Paper에 아래와 같이 나와있다(간단한 비트 연산으로 만들 수 있다).

DIR_DATETIME.PNG

 파일 시스템 분석을 하는 경우라면 ClusterNumber와 Name, 그리고 Attribute를 유심히 봐두는 것이 도움이 될 것이다. 다른 파일 시스템이 그러하듯이 Root Directory로부터 Sub Directory가 생성되고 트리 형태로 관리되기 때문에 Root Directory를 찾은 다음 Entry 구조만 안다면 전체를 Travel 하는 것은 어렵지 않으니까...


3.4 FSInfo

  FSInfo 영역은 FAT32에만 존재하는 영역이다. 역시 한 섹터 크기정도로 되어있고 별다른 정보를 포함하지 않는다. 단지 FAT32에 참고할만한 정보만 가지고 있다.

  1. typedef struct fsInfoStruct
    {
        BYTE vbLeadSignature[ 4 ];
        BYTE vbReserved1[ 480 ];
        BYTE vbStructureSignature[ 4 ];
        DWORD dwFreeClusterCount;
        DWORD dwNextFreeCluster;
        BYTE vbReserved2[ 12 ];
        BYTE vbTrailSignature[ 4 ];
    } FSINFO, * PFSINFO;

  각 항목은 아래와 같은 의미를 가진다.

  • BYTE vbLeadSignature[ 4 ] : 0x52 0x52 0x61 0x41 설정
  • BYTE vbReserved1[ 480 ] : 예약된 영역. 0으로 설정.
  • BYTE vbStructureSignature[ 4 ] : 0x72 0x72 0x41 0x61 설정
  • DWORD dwFreeClusterCount : Free한 클러스터의 개수 저장. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • DWORD dwNextFreeCluster : Free한 클러스터의 첫번째 번호. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • BYTE vbReserved2[ 12 ] : 예약된 영역. 0으로 설정.
  • BYTE vbTrailSignature[ 4 ] : 0x00 0x00 0x55 0xAA 설정

 위에서 보는 것과 같이 FAT32의 부가적인 정보가 포함된 영역이고 특히 클러스터 관련 필드는 권장값이므로 절대 100% 믿으면 안된다.


4.마치며...

 여기까지 간단하게 FAT Filesystem에 대해서 알아보았다. 원래 훨씬 일찍 마무리 되었어야 하는데... 과제하느라 정신이 조금 없어서 찔끔 찔끔 정리하다보니 이제야 마무리를... ㅜ_ㅜ... 다소 부족한 감이 있지만 Formatter를 개발하면서 알아낸 정보를 기반으로 작성하였다.

 다음에는 위 정보를 기반으로 실제 FAT Filesystem을 분석해 보도록 하자.


5.첨부




이 글은 스프링노트에서 작성되었습니다.

00 Window I/O 관련

원문 : http://kkamagui.springnote.com/pages/340860

 

들어가기 전에...

 

1.윈도우에서 Physical Drive 직접 읽고 쓰기

  • 드라이브 열기
  1. HANDLE OpenDrive( int iPhysicalDriveNumber )
    {
        HANDLE hDevice;
        char vcDriveName[ 30 ];
  2.  

  3.  

  4.     // HDD를 실수로 지우는걸 방지하기 위함

        if( iPhysicalDriveNumber == 0 )
        {
            return INVALID_HANDLE_VALUE;
        }

  5.     // Physical Drive를 연다.
        sprintf( vcDriveName, "\\\\.\\PhysicalDrive%d", iPhysicalDriveNumber );
        hDevice = CreateFile( vcDriveName, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        
        return hDevice;
    }

 

  • 드라이브 읽기
  1. BOOL ReadSector( HANDLE hDevice, DWORD dwSectorOffset, BYTE* pbBuffer,
                     int iSectorCount )
    {
        DWORD dwLow;
        DWORD dwHigh;
        DWORD dwRet;
        DWORD dwRead;
        DWORD dwErrorCode;
  2.     // 움직일 위치는 byte 단위로 되어야 한다.
        // 결국 dwSectorOffset에 512를 곱해줘야 한다.
        dwLow = ( dwSectorOffset << 9 );
        dwHigh = ( dwSectorOffset >> 23 );
  3.     // File Pointer를 이동한다.
        dwRet = SetFilePointer( hDevice, dwLow, &dwHigh, FILE_BEGIN );
        if( dwRet == INVALID_SET_FILE_POINTER )
        {
            return FALSE;
        }
  4.     // Sector를 읽는다.
        if( ReadFile( hDevice, pbBuffer, iSectorCount * SECTORSIZE, &dwRead,
            NULL ) == FALSE )
        {
            dwErrorCode = GetLastError();
            return FALSE;
        }
  5.     return TRUE;
    }

 

  • 드라이브 쓰기
  1. BOOL WriteSector( HANDLE hDevice, DWORD dwSectorOffset, BYTE* pbBuffer,
                     int iSectorCount )
    {
        DWORD dwLow;
        DWORD dwHigh;
        DWORD dwRet;
        DWORD dwWrite;
        DWORD dwErrorCode;
  2.     // 움직일 위치는 byte 단위로 되어야 한다.
        // 결국 dwSectorOffset에 512를 곱해줘야 한다.
        dwLow = ( dwSectorOffset << 9 );
        dwHigh = ( dwSectorOffset >> 23 );
  3.     // File Pointer를 이동한다.
        dwRet = SetFilePointer( hDevice, dwLow, &dwHigh, FILE_BEGIN );
        if( dwRet == INVALID_SET_FILE_POINTER )
        {
            return FALSE;
        }
  4.     // Sector를 쓴다.
        if( WriteFile( hDevice, pbBuffer, iSectorCount * SECTORSIZE, &dwWrite,
            NULL ) == FALSE )
        {
            dwErrorCode = GetLastError();
            return FALSE;
        }
  5.     gs_dwTotalWriteSectorCount += iSectorCount;
        return TRUE;
    }

 

  • 드라이브 닫기
  1. void CloseDrive( HANDLE hDevice )
    {
        CloseHandle( hDevice );
    }

 

2.Drive의 Geometry 읽기

Geometry정보에는 CHS 값이 들어있기 때문에 유용하게 쓸 수 있다.

  1. BOOL GetDriveGeometry( int iPhysicalDriveNumber, GEOMETRY* pstGeometry )
    {
        HANDLE hDevice;
        BOOL bRet;
        DWORD dwOutBytes;
        char vcDriveName[ 30 ];
        DISK_GEOMETRY stWindowGeometry;
  2.     // Physical Drive Number를 저장한다.
        sprintf( vcDriveName, "\\\\.\\PhysicalDrive%d", iPhysicalDriveNumber );
  3.     hDevice = CreateFile( vcDriveName, 0, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
  4.     if( hDevice == INVALID_HANDLE_VALUE )
        {
            return FALSE;
        }
  5.     bRet = DeviceIoControl( hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY,
            NULL, 0, &stWindowGeometry, sizeof( DISK_GEOMETRY ), &dwOutBytes,
            NULL );
  6.     CloseHandle( hDevice );
  7.     // 지금은 윈도우 Geometry와 같은 Geometry를 사용한다.
        memcpy( pstGeometry, &stWindowGeometry, sizeof( DISK_GEOMETRY ) );
  8.     return bRet; 
    }

 

3.Drive Descriptor 얻기

Descriptor에 보면 해당 드라이브의 제품명과 리비전 번호 같은 걸 알 수 있다.

  1. BOOL GetDeviceDescriptor( char* pcDevice, PSTORAGE_DEVICE_DESCRIPTOR pstDesc )
    {
        HANDLE hDevice;
        STORAGE_PROPERTY_QUERY  stQuery;
        DWORD dwOut;
        BOOL bRet;
  2.     memset( pstDesc, 0, sizeof(STORAGE_DEVICE_DESCRIPTOR) );
  3.     // Device를 Open한다.
        hDevice = CreateFile( pcDevice, GENERIC_READ, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if( hDevice == INVALID_HANDLE_VALUE )
        {
            return FALSE;
        }
  4.     pstDesc->Size = sizeof( STORAGE_DEVICE_DESCRIPTOR );
  5.     // Device Io Control을 호출한다.
        stQuery.PropertyId = StorageDeviceProperty;
        stQuery.QueryType = PropertyStandardQuery;
  6.     bRet = DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
                &stQuery, sizeof( STORAGE_PROPERTY_QUERY ),
                pstDesc, pstDesc->Size, &dwOut, NULL);                 
        if( bRet == FALSE )
        {
            return FALSE;
        }
  7.     CloseHandle( hDevice );
        return TRUE;
    }

 

4.Drive 문자로 Physical Index 얻기

드라이브 문자로 Physical Index를 얻는 방법은 VOLUME 정보를 이용하면 된다.

  1. int GetPhysicalDriveNumber( char cDriveName )
    {
        HANDLE hDevice;
        DWORD dwOut;
        BOOL bRet;
        char vcDriveName[ 40 ];
        VOLUME_DISK_EXTENTS* pstVolumeData;
        int iDiskNumber;
  2.     // 메모리를 할당한다.
        pstVolumeData = ( VOLUME_DISK_EXTENTS* ) malloc( VOLUMEDISKSIZE );
        if( pstVolumeData == NULL )
        {
            return -1;
        }
  3.     // 해당 Drive의 정보를 얻는다.
        sprintf( vcDriveName, "\\\\?\\%c:", cDriveName );
        // Device를 Open한다.
        hDevice = CreateFile( vcDriveName, GENERIC_READ, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if( hDevice == INVALID_HANDLE_VALUE )
        {
            return -1;
        }
  4.     // Device Io Control을 호출한다.
        bRet = DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
                NULL, 0, pstVolumeData, VOLUMEDISKSIZE, &dwOut, NULL );
        if( bRet == FALSE )
        {
            free( pstVolumeData );
            return -1;
        }
        CloseHandle( hDevice );
  5.     // Disk 정보가 1보다 작으면 실패
        if( pstVolumeData->NumberOfDiskExtents < 1 )
        {
            free( pstVolumeData );
            return -1;
        }
  6.     iDiskNumber = pstVolumeData->Extents[ 0 ].DiskNumber;
        free( pstVolumeData );
  7.     return iDiskNumber;
    }


5.Logical Drive와 Physical Drive 간의 매치방법

1. 일단 Logical Drive를 검색한다.
-> GetDriveType() 함수를 이용

2. 해당 Drvie를 열어서 Volume 정보를 얻음
-> "\\?\c" 와 같은 형태로 CreateFile() 호출
-> IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS를 날려서 Physical Drive 정보를 얻음
-> 리더기에 데이터가 연결되어있지 않으면 DeviceIoControl에서 문제 발생

이 글은 스프링노트에서 작성되었습니다.

크윽... 얼마전까지 Vendor Request를 통해 데이터를 주고받는게 가장 간단하다고 생각하고 있었다.

그래서 드라이버를 뚝딱뚝딱 만들고 펌웨어 올리고 이렇게 작업해서 갔더니만... 오늘 선배가 HID Firmware하고 Report 패킷을 이용해서 데이터를 주고받는 프로그램을 만들었고.. ㅜ_ㅜ

어흑... 이런이런... 낭패...
난 왜 그생각을 못했을까....

이런 삽질.. ㅜ_ㅜ
한 4일 뚝딱뚝딱했더니 Vendor request 명령을 통해 보드와 데이터를
주고받을 수 있는 드라이버/펌웨어 세트가 완성됬다.

@0@)/~ 뭐 DDK의 Bulk 드라이버와 FX2LP의 BulkLoop 예제를 기반으로
Vendor Request를 살짝 추가했더니

별 무리없이 실행되고.. ㅜ_ㅜ...

크윽... 이거 너무 감동이잖아... ㅜ_ㅜ
크윽... 진짜 안습이다...

펌웨어 코드를 조금 이해하고 드라이버를 짤려고 봤더니...
이게 훨~~씬 더 복잡하다.... ㅡ_ㅡa...

난 그냥 Vendor Request만 주고 받으면 되는데...
뭔가 기절할듯하게 복잡한.....

bulkdriver를 수정해서 그냥 Device Io Control만 수정해 버릴까....
아님.. 그냥 다 삭제하고 Device Io Control만 넣을까....

크윽.. 참으로 고민된다... 아따 기절하긋네... ㅜ_ㅜ

아우... 천지.. 꼽으니까 DVB-T 어쩌구 장치로 인식하고... ㅠ_ㅠ

결국 !!!

Registry에서 VID_04B4에 해당하는 모든 키 삭제
DVB-T에 관련된 모든 INF 파일 삭제


다시 드라이버 설정할때 관련 드라이버있음으로 설치해서
겨우 잡았다.

아우 눈물나네.. ㅠ_ㅠ
아놔... 뭔가 했더니 천지... Endian 문제로 장치의 VID하고 PID가 잘못 인식된거였고...

결국 소스에서 descriptor를 고쳐서 다시 올렸는데...
이상하게 계속 인식된 VID와 PID는 옛날껄로 고정.... ㅡ_ㅡa..

어디엔가 기록이 남아있는거 같다...
그래서 결국 INF 파일을 수정하는걸로 대체....

해냈다.. @0@)/~
아놔.. 왜이렇게 해매는것이 많아.. ㅡ_ㅡa...
쿠옷~!! 그렇다는 말은 EndPoint 0만 가지고도 간단한 통신을 할 수 있다는 말 @0@)/~~

이거 상당히 괜찮은데... 어차피 지금 할 것이
전화번호 정보랑 약간의 데이터를 주고 받으면 되는거라서...

이렇게 되면 이정도 데이터는 그냥 EndPoint 0를 이용하면 되겠다.

@0@)/~~ 크옷~!! 이것 참 멋진데 ㅎㅎㅎ >ㅁ<)/~~~
http://muosys.egloos.com

구매한 보드에 대한 레퍼런스 사이트

+ Recent posts