20131001

________RFID________________________________________________________________________

 Inventory

 

 

 

 

 

 

 

※ 기타 함수, 소스내용 생략

 

 

 

 

 

 

 

 

 

 

 

 

________________________________________________________________________________________

 

 

 

 

 

 

 

 

 

 

 

________ASM & context switching______________________________________________________________

 

실행파일의 code섹션, data 섹션 메모리에 적재하기

 

 

 

 

 

※ 기타 함수는 생략...

 

 

<CODE, DATA 섹션이 메모리에 적재된 화면>

 

 

<go()를 이용해 적재된 프로그램이 수행되고 난뒤의 메모리 상태> 

 

 

 

________________________________________________________________________________________

 

 

 

 

 

 

 

 

 

 

______실습파일 & 수업자료_______

 

asynchronous_serial.c

 

init.asm 

init.obj 

monitor2.asm 

monitor2.obj 

monitor_c.c<== 소스 

 

monitor_c.exe 

monitor_c.obj 

t1.exe 

t2.exe 

t3.exe

 

________________________________

'스마트 컨트롤러 게시물' 카테고리의 다른 글

20131014  (0) 2013.10.11
20131002  (0) 2013.10.02
20130930  (0) 2013.09.30
20130927  (0) 2013.09.27
20130926  (0) 2013.09.26

20130930

__________RFID & UNIX____________________________________________________________________________

 

 RFID 리더기에 HOST명령 전송 (Protocols for Reader Control) 

 

 

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/signal.h>


#define SPEED B38400
#define SPORT "/dev/ttyS1"  //Serial Port2

unsigned short CRC16(unsigned char *, unsigned int);
void Handle_Serial_Sig(int);

volatile int iBreak = 0;

 

int main()
{


 char cBuff[255];                       // 수신용 버퍼
 int iDev = 0;                             // 장치 데스크립션
 int iRet = 0;                             // 반환값

 int iCnt;

 struct termios stOldState;         // 기존 Serial Port 상태 정보
 struct termios stNewState;        // 새로운 Serial Port 상태 정보
 struct sigaction stSigAct;         // 시그널 액션 설정

 

 unsigned char Buff[255] = {

            
         0x05, // Get Software Version
         0x00,
         0x65,
       
         
     /* 
         0x06, // Baud Rate Detection
         0x00,
         0x52,
         0x00,
   */

         
    /*  0x0D, // Set Output
         0x00,
         0x71,
         0x00,
         0x30,
         0x00,
         0x00,
         0x00,
         0x0A,
         0x00,
         0x00,
       */

      

        /*
         0x0D, // Set Output2
         0x00,
         0x71,
         0x00,
         0x3F,
         0x00,
         0x07,
         0x00,
         0x0A,
         0x00,
         0x00,
       */


  
         0xFF,
         0xFF
 

};

 iDev = open(SPORT, O_RDWR | O_NOCTTY | O_NONBLOCK);// Serial Port Open

 

 if( 0 > iDev)                       //시리얼 포트 Open Error
 {
        perror(SPORT);
        exit(-100);
 
 }
 
 //시리얼 포트 설정 전에 시그널 핸들러 등록
 
 bzero(&stSigAct, sizeof(stSigAct));
 stSigAct.sa_handler = Handle_Serial_Sig;
 sigaction(SIGIO, &stSigAct, NULL);

 //SIGIO signal 을 수신하도록 설정
 fcntl(iDev, F_SETOWN, getpid());//iDev(시리얼 포트) 소유자를 현재 프로세스로 바꿈
 // SIGIO 발생은 소유자에게 알려지기 때문에...

 // file descriptor를 비동기로 설정
 fcntl(iDev, F_SETFL, FASYNC);

 
 tcgetattr(iDev, &stOldState);  // 현재 Serial Port 상태 저장 (백업해둠) 
 
 bzero(&stNewState, sizeof(stNewState)); // 구조체 초기화
 

 stNewState.c_cflag = SPEED | CRTSCTS | CS8 | CLOCAL | CREAD | PARENB// 짝수패리티

 

 stNewState.c_iflag = IGNPAR | ICRNL ;
 stNewState.c_oflag = 0;                                 // 수신된 데이터를 그대로 출력

 //stNewState.c_lflag = ICANON;                        // Canonical 통신 기법 사용

 

 stNewState.c_lflag = 0 ;                        // Non-Canonical 통신 기법 사용 
 
 stNewState.c_cc[VMIN] = 1;                         // read시 리턴되기 위한 최소 문자 개수 지정
 stNewState.c_cc[VTIME] = 0;

 tcflush (iDev, TCIFLUSH);                            // 시리얼 포트수신 큐 초기화
 tcsetattr(iDev, TCSANOW, &stNewState);        //시리얼 포트에 새 속성 적용


 *((unsigned short *)(&Buff[Buff[0]-2])) = CRC16(Buff, Buff[0]-2);

 //원래 데이터에 체크섬 계산된값을 넣음

 

 
 write(iDev, Buff, Buff[0]); // 패킷전송
 

 read(iDev, Buff, sizeof(Buff)); //명령전송후 리턴되는 값 수신


 Buff[0] = 13;

 
 for(iCnt = 0; iCnt < t; ++iCnt)
 {
      printf("%02X\n", Buff[iCnt]); 
 }
 

ucTemp = Buff[4];
 Buff[4] = Buff[5];
 Buff[5] = ucTemp; // Big endian이라서 스왑해줌 
 
 printf("SW-REV  : %d\n", *((unsigned short *)(&Buff[4])) );
 printf("D-REV  : %d\n", Buff[6] );
 printf("HW-TYPE : %d\n", Buff[7] );
 

printf("SW-TYPE : ");
 switch( *((unsigned short *)(&Buff[8])) )
 {
 

 case 30:
   printf("ID ISC.M01\n");
   break;

 

 case 31:
   printf("ID ISC.M02\n");
   break;

 

 case 71:
   printf("ID ISC.PRH100.U (USB-Version)\n");
   break;

 

 case 72:
   printf("ID ISC.PRH100\n");
   break;

 

 case 73:
   printf("ID ISC.MR100.U (USB-Version)\n");
   break;

 

 case 74:
   printf("ID ISC.MR100 / .PR100\n");
   break;

 

 case 75:
   printf("ID ISC.MR200-A / -E\n");
   break;

 

 case 40:
   printf("ID ISC.LR100\n");
   break;

 

 case 41:
   printf("ID ISC.LR200\n");
   break;

 

 case 91:
   printf("ID ISC.LRU1000\n");
   break;

 

 case 80:
   printf("ID CPR.M0\n");
   break;

 

 case 81: 
   printf("ID CPR.02\n");
   break;

 

 case 84:
   printf("ID CPR.M03 (586/#)\n");
   break;

 

 default:
   printf("unknown\n");
    

}
 


 ucTemp = Buff[9];
 Buff[9] = Buff[10];
 Buff[10] = ucTemp; // Big endian이라서 스왑해줌 
 
 printf("TR-TYPE : ");

 if( 0 != ((*((unsigned short *)(&Buff[9]))) & 0x0001) )
 {
      printf("[I-Code 1] ");
 }
 if( 0 != ((*((unsigned short *)(&Buff[9]))) & 0x0002) )
 {
      printf("[Tag-it HF] ");
 }
 if( 0 != ((*((unsigned short *)(&Buff[9]))) & 0x0008) )
 {
      printf("[ISO 15693] ");
 }
 if( 0 != ((*((unsigned short *)(&Buff[9]))) & 0x0040) )
 {
      printf("[I-Code EPC] ");
 }
 if( 0 != ((*((unsigned short *)(&Buff[9]))) & 0x0080) )
 {
      printf("[I-Code UID] ");
 }
  putchar('\n');

 
 tcsetattr(iDev, TCSANOW, &stOldState); // 시리얼 포트의 원래 속성 복귀
 close(iDev); // 시리얼 포트 닫음

 
 return 0;

}

 

void Handle_Serial_Sig(int Arg)
{
      printf("Receive SIGIO Signal\n");
      iBreak = 1;
}

 

#define  CRC_POLYNOM    0x8408
#define  CRC_PRESET       0xFFFF

 

unsigned short CRC16(unsigned char *DATA, unsigned int cnt)

{

 unsigned short crc = CRC_PRESET;
 unsigned int i;
 unsigned int j;
 
 /* cnt = number of protocol bytes without CRC */
 for (i = 0; i < cnt; i++)     
 {

  crc ^= DATA[i];
  for (j = 0; j < 8; j++)
  {
       if (crc & 0x0001)    
       crc = (crc >> 1) ^ CRC_POLYNOM;
       else
       crc = (crc >> 1);
  }

 }
 return crc;

}

 

                                   <Get Software Version>

SW-REV   : Revision status of the firmware.
D-REV     : Revision status of the development firmware. D-REV is set to ‘0’ in customized firmware revisions.
HW-Type  : Displays options which are supported by the Reader Hardware
SW-TYPE : Displays the type / model of the Reader

             

 

 

 

 

 

 

☞ control byte [0x71] Set Output는 리더기의 LED와 비프음의 출력을 제어함

 

 

 

 

OS는 LED와 부저의 동작모드를 설정함

    b00 : 변화없음

    b01 : ON

    b10 : OFF

    b11 : FLASH(깜빡임,켜졌다 꺼졌다를 반복)

 

 

 

 

OSF(Out State Flash)는 OS에서 FLASH로 설정된것의 동작 주파수를 설정함 

    b00 : 8Hz(1초에 8번)

    b01 : 4Hz(1초에 4번)

    b10 : 2Hz(1초에 2번)

    b11 : 1Hz(1초에 1번)

 

 

 

OS-Time는 LED와 Beeper의 동작시간을 설정함

 0x0001       =>       1 x 100ms   

    .

    .

    .

 0xFFFE      =>       65534 x 100ms  (1:48:13h) 

 0xFFFF      =>       계속 동작 

              

 

 set Output 패킷 예.

    /*  0x0D, // beeper만 Flash 형태로 동작
         0x00,
         0x71,
         0x00,
         0x30,
         0x00,
         0x00,
         0x00,
         0x0A,
         0x00,
         0x00,
       */

      

        /*
         0x0D, // 두 LED와 Beeper가 모두 Flash 형태, 다른 속도로 동작
         0x00,
         0x71,
         0x00,
         0x3F,
         0x00,
         0x07,
         0x00,
         0x0A,
         0x00,
         0x00,
       */


  
         0xFF,
         0xFF

 

_________________________________________________________________________________________________

 

 

 

 

 

 

 

 

 

 

_____ASM & context switching_____________________________________________________

 

 실행파일의 code섹션, data 섹션 메모리에 적재하기

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<CM 프로그램>

 

<MY 프로그램>

☆ 두 프로그램이 LOAD 한 섹션값이 같음을 알수 있음

 

 

 

------------------------------------------------------------------------------------------------------------

 

 

 

※ Clear_mem()는 실행파일의 섹션을 올리고 실행하기위해 할당해 놓은 메모리 전체를 0으로 초기화하는 함수

 

※ go()는 새로운 context 구조체 변수(stNewReg)를 새로 하나더 만들어 EAX 멤버에 stOldReg의 주소를

   EIP 멤버에 ucpCode(할당해 놓은 코드영역)주소값을 ESP멤버에 ucpStack(할당해 놓은 스택영역)주소를

   넣고 LDST()를 호출한다.

 

    LDST()는 stNewReg에 셋팅된 값으로 레지스터 값들을 바꾸기 때문에 return address가 ucp코드 즉,

    ucpCode에 적재되어 있는 프로그램을 수행한다.

 

   ucpCode에 적재된 프로그램은 INIT(어셈블리어 소스)에 의해 컴파일 링크된 실행파일(t1.exe)이기 때문에

   t1.c의 test()를 수행한뒤 자동으로 LDST(&stOldReg)를 호출해준다.

 

   그러면 자동으로 main에 STST(&stOldReg)를 호출한 뒤의 위치로 점프되고 레지스터 상태도 바뀌게 되기

   때문에 kernel panic이 뜨지 않고 이전상태로 되돌아감.

         

 

 

 

 init

 

 

 

 

 

 

 t1.c

char flstz[] = "1111222233334444";
int sum=0x78563412;

 

test ()
{
     int i = 0x12345678;
     int j = 0x78563412;
     int k = 0x77777777;
 
     sum = 0x12345678;
     for (i = 0; i < sizeof(flstz)-1; i++)
     {
          flstz[i] = 'a';
     }
}

 

 

 

 

 컴파일 & 링크 방법

 

 

 

 

 

 

 실행결과

 

※go()를 실행하여 test()함수가 실행된뒤의 DATA영역과 STACK 영역의 값이 출력되어있다.

   위에서 적재되었을때와 값이 바뀐것을 확인할수 있음

 

 

 

 

 

 

_____________________________________________________________________________

 

 

 

 

 

 

 

___실습파일 & 수업자료_____

 

asynchronous_serial.c

 

 

init.asm 

monitor2.asm 

monitor_c.c 

monitor_c.exe 

t1.c 

t1.exe 

 

 

 

LOAD.pdf

 

___________________________

'스마트 컨트롤러 게시물' 카테고리의 다른 글

20131002  (0) 2013.10.02
20131001  (0) 2013.10.01
20130927  (0) 2013.09.27
20130926  (0) 2013.09.26
20130925  (0) 2013.09.25

20130927

_______RFID & UNIX_____________________________________________________________________

 

 

 RFID 리더기에 HOST명령 전송 (Protocols for Reader Control)

 >> [0x52] Baud Rate Detection ) <<

 

               ※ 0x52 control byte를 써서 명령패킷을 보내면 Baud rate가 잘 맞춰져있을 경우 보낸 패킷의 값과

                   같은 값을 보내준다.

 

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/signal.h>


#define SPEED B38400
#define SPORT "/dev/ttyS1"  //Serial Port2

unsigned short CRC16(unsigned char *, unsigned int);
void Handle_Serial_Sig(int);

volatile int iBreak = 0;

 

int main()
{


 char cBuff[255];                       // 수신용 버퍼
 int iDev = 0;                             // 장치 데스크립션
 int iRet = 0;                             // 반환값

 int iCnt;

 struct termios stOldState;         // 기존 Serial Port 상태 정보
 struct termios stNewState;        // 새로운 Serial Port 상태 정보
 struct sigaction stSigAct;         // 시그널 액션 설정

 

 unsigned char Buff[] = {

                                         0x06, // Baud Rate Detection
                                         0x00,
                                         0x52,
                                         0x00,

                                         0xFF,
                                         0xFF

                                };
 
 iDev = open(SPORT, O_RDWR | O_NOCTTY | O_NONBLOCK);// Serial Port Open

 

 if( 0 > iDev)                       //시리얼 포트 Open Error
 {
        perror(SPORT);
        exit(-100);
 
 }
 
 //시리얼 포트 설정 전에 시그널 핸들러 등록
 
 bzero(&stSigAct, sizeof(stSigAct));
 stSigAct.sa_handler = Handle_Serial_Sig;
 sigaction(SIGIO, &stSigAct, NULL);

 //SIGIO signal 을 수신하도록 설정
 fcntl(iDev, F_SETOWN, getpid());//iDev(시리얼 포트) 소유자를 현재 프로세스로 바꿈
 // SIGIO 발생은 소유자에게 알려지기 때문에...

 // file descriptor를 비동기로 설정
 fcntl(iDev, F_SETFL, FASYNC);

 
 tcgetattr(iDev, &stOldState);  // 현재 Serial Port 상태 저장 (백업해둠) 
 
 bzero(&stNewState, sizeof(stNewState)); // 구조체 초기화
 

 stNewState.c_cflag = SPEED | CRTSCTS | CS8 | CLOCAL | CREAD | PARENB// 짝수패리티

 

 stNewState.c_iflag = IGNPAR | ICRNL ;
 stNewState.c_oflag = 0;                                 // 수신된 데이터를 그대로 출력

 //stNewState.c_lflag = ICANON;                        // Canonical 통신 기법 사용

 

 stNewState.c_lflag = 0 ;                        // Non-Canonical 통신 기법 사용 
 
 stNewState.c_cc[VMIN] = 1;                         // read시 리턴되기 위한 최소 문자 개수 지정
 stNewState.c_cc[VTIME] = 0;

 tcflush (iDev, TCIFLUSH);                            // 시리얼 포트수신 큐 초기화
 tcsetattr(iDev, TCSANOW, &stNewState);        //시리얼 포트에 새 속성 적용


 *((unsigned short *)(&Buff[Buff[0]-2])) = CRC16(Buff, Buff[0]-2);

 //원래 데이터에 체크섬 계산된값을 넣음

 

 
 write(iDev, Buff, Buff[0]); // 패킷전송
 

 iRet = read(iDev, ucRecvBuff, sizeof(ucRecvBuff)); //명령전송후 리턴되는 값 수신


  
 for(iCnt = 0; iCnt < iRet; ++iCnt)
 {
      printf("%02X\n", ucRecvBuff[iCnt]); 
 }
 
/*
 while(1)
 {

  if(1 == iBreak)
  { 
       iRet = read(iDev, cBuff, 255); // 시리얼 포트로부터 데이터 수신  
       cBuff[iRet] = 0;
       printf("[%s]:[%d]\n", cBuff, iRet);
       break;
  }
  else
  {
       sleep(2);
  }
  printf("Go Sleep\n");

 }
*/ 
 tcsetattr(iDev, TCSANOW, &stOldState); // 시리얼 포트의 원래 속성 복귀
 close(iDev); // 시리얼 포트 닫음

 
 return 0;

}

 

void Handle_Serial_Sig(int Arg)
{
      printf("Receive SIGIO Signal\n");
      iBreak = 1;
}

 

#define  CRC_POLYNOM    0x8408
#define  CRC_PRESET       0xFFFF

 

unsigned short CRC16(unsigned char *DATA, unsigned int cnt)

{

 unsigned short crc = CRC_PRESET;
 unsigned int i;
 unsigned int j;
 
 /* cnt = number of protocol bytes without CRC */
 for (i = 0; i < cnt; i++)     
 {

  crc ^= DATA[i];
  for (j = 0; j < 8; j++)
  {
       if (crc & 0x0001)    
       crc = (crc >> 1) ^ CRC_POLYNOM;
       else
       crc = (crc >> 1);
  }

 }
 return crc;

}

 

 

                                      ※Baud rate가 잘 맞춰져 있으므로 보낸데이타를 그대로 다시 수신했음

 

    

 

 

_________________________________________________________________________________________________

 

 

 

 

 

______________ASM_______________________________________________________________________________

 

 

 실행파일의 code섹션, data 섹션 메모리에 적재하기

 

#include <fcntl.h>
#include <windows.h>

 

void Load(void)
{

 IMAGE_DOS_HEADER         *stpDos;
 IMAGE_NT_HEADERS         *stpNT;
 IMAGE_SECTION_HEADER  *stpSection;
 
 int iFd;
 int iRet;
 int iCode_Offset;
 int iData_Offset;
 int iSection_Size;
 
 unsigned char ucBuff[32];


 printf("파일이름을 입력하시오 :  ");
 fflush(stdin);
 
 iRet = read(0, ucBuff, 32);                                   //파일 이름 입력받음
 ucBuff[iRet-1] = 0;
 
 iFd = open(ucBuff, O_RDONLY |O_BINARY);         //파일 오픈
 //iFd = open("A.exe", O_RDONLY |O_BINARY);

 if(0 > iFd)
 {
       printf("open error\n");
        return;  
 }
  
 
 iRet = read(iFd, ucpCode, MAX_PROGRAM_SIZE); // 파일로부터 64kb 읽어들임

 if(0 > iRet)
 {
        printf("파일이 존재하지만 읽을수 없습니다.\n");

        close(iFd);
        return;  
 }


 stpDos =  (IMAGE_DOS_HEADER *)ucpCode;
 //파일을 메모리에 읽어들인 첫지점이 DOS Header 시작지점임

 
 stpNT =   (IMAGE_NT_HEADERS *)(ucpCode + (stpDos->e_lfanew));
 /* DOS Header 구조체 멤버중 e_lfanew에 시작점으로부터 얼마나 떨어진곳에
      NT header가 있는지 오프셋값이 있음 */


 (unsigned char *)stpSection = (unsigned char *)stpNT + sizeof(IMAGE_NT_HEADERS);  
 /* stpNT(NT header 시작주소)에서 NT header 크기( sizeof(IMAGE_NT_HEADERS) )만큼
     떨어진 곳에 Section Header 가 있음 */


 
 //lseek(iFd, stpNT->OptionalHeader.SizeOfHeaders, SEEK_SET);
 //PE header 의 전체 크기
 //파일 시작에서 SizeOfHeader 옵셋만큼 떨어진 위치에 첫번째 섹션이 위치
 


 iSection_Size  =  stpNT->OptionalHeader.FileAlignment //섹션크기
 iCode_Offset  =  stpSection->PointerToRawData;   //code 섹션 오프셋값 iCode_Offset에 임시저장
 iData_Offset  =  iCode_Offset + iSection_Size;
 //code 섹션 오프셋값에서 섹션크기 만큼 주소를 증가시키면 data섹션 오프셋값이 있음
 //data 섹션 오프셋값을 iData_Offset에 넣음 

 //printf("%d\n", stpSection->SizeOfRawData);
 //printf("%d\n", stpSection->PointerToRawData);
 
 lseek(iFd, iCode_Offset, SEEK_SET);
 //파일 오프셋이 파일의 처음부분을 가리키도록하고 거기서 부터
 //PointerToRawData(파일에서 섹션의 시작 위치 오프셋값) 값을 읽어들여서
 // 파일 오프셋을 이동시킴 (code 섹션)
 
 iRet = read(iFd, ucpCode, iSection_Size); //code 섹션에서 값을 읽어들여 ucpCode가 가리키는 메모리에 저장

 if(0 > iRet)
 {
      printf("파일이 존재하지만 읽을수 없습니다.\n");

      close(iFd);
      return;  
 }
 
 

 
  
 lseek(iFd, iData_Offset, SEEK_SET);
 //파일 오프셋이 파일의 처음부분을 가리키도록하고 거기서 부터
 //data섹션 오프셋값 (code 섹션 오프셋값 + 섹션크기) 만큼 파일 오프셋 이동시킴

 

 iRet = read(iFd, ucpData, iSection_Size);//data 섹션에서 값을 읽어들여 ucpData가 가리키는 메모리에 저장

 if(0 > iRet)
 {
       printf("파일이 존재하지만 읽을수 없습니다.\n");

       close(iFd);
       return;  
 }
 else
 {
       printf("파일을 성공적으로 메모리에 적재하였습니다.\n");
       printf("섹션 크기 : [%d]Bytes\n", iSection_Size);
 } 

 close(iFd);
 return;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Introduction



Windows 운영체제의 PE(Portable Executable) File Format 에 대해서 아주 상세히 공부해 보도록 하겠습니다.

PE format 을 공부하면서 Windows 운영체제의 가장 핵심적인 부분인
Process, Memory, DLL 등에 대한 내용을 같이 정리할 수 있습니다.



PE(Portable Executable) File Format



PE 파일의 종류는 아래와 같습니다.

  • 실행 파일 계열 : EXE, SCR
  • 라이브러리 계열 : DLL, OCX
  • 드라이버 계열 : SYS
  • 오브젝트 파일 계열 : OBJ

엄밀히 얘기하면 OBJ(오브젝트) 파일을 제외한 모든 파일들은 실행 가능한 파일 입니다.

DLL, SYS 파일등은 쉘(Explorer.exe) 에서 직접 실행 할 수는 없지만,
다른 형태의 방법(디버거, 서비스, 기타)을 이용하여 실행이 가능한 파일들입니다.

* PE 공식 스펙 에는 컴파일 결과물인 OBJ(오브젝트) 파일도 PE 파일로 간주합니다.
  하지만 OBJ 파일 자체로는 어떠한 형태의 실행도 불가능하므로 리버싱에서 관심을 가질 필요는 없습니다.


간단한 설명을 위해서 노트패드(notepad.exe) 파일을 hex editor 를 이용해서 열어보겠습니다.


<Fig. 1>

<Fig. 1> 은 notepad.exe 파일의 시작 부분이며, PE 파일의 헤더 (PE header) 부분입니다.

바로 이 PE header 에 notepad.exe 파일이 실행되기 위해 필요한 모든 정보가 적혀있습니다.

어떻게 메모리에 적재되고, 어디서부터 실행되어야 하며, 실행에 필요한 DLL 들은 어떤것들이 있고,
필요한 stack/heap 메모리의 크기를 얼마로 할지 등등...


수 많은 정보들이 PE header 에 구조체 형식으로 저장되어 있습니다.

즉, PE File Format 을 공부한다는 것은 PE header 구조체를 공부한다는 것과 같은 말입니다.



Basic Structure


일반적인 PE 파일의 기본 구조입니다. (notepad.exe)


<Fig. 2>

<Fig. 2> 는 notepad.exe 파일이 메모리에 적재(loading 또는 mapping)될 때의 모습을 나타낸 그림입니다.
많은 내용을 함축하고 있는데요, 하나씩 살펴보겠습니다.


  • DOS header 부터 Section header 까지를 PE Header, 그 밑의 Section 들을 합쳐서 PE Body 라고 합니다.

  • 파일에서는 offset 으로, 메모리에서는 VA(Virtual Address) 로 위치를 표현합니다.

  • 파일이 메모리에 로딩되면 모양이 달라집니다. (Section 의 크기, 위치 등)

  • 파일의 내용은 보통 코드(".text" 섹션), 데이타(".data" 섹션), 리소스(".rsrc") 섹션에 나뉘어서 저장됩니다.
    반드시 그런것은 아니며 개발도구(VB/VC++/Delphi/etc)와 빌드 옵션에 따라서
    섹션의 이름, 크기, 개수, 저장내용 등은 틀려집니다. 중요한 것은 섹션이 나뉘어서 저장 된다는 것입니다.

  • Section Header 에 각 Section 에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의 되어 있습니다.

  • PE Header 의 끝부분과 각 Section 들의 끝에는 NULL padding 이라고 불리우는 영역이 존재합니다.
    컴퓨터에서 파일, 메모리, 네트워크 패킷 등을 처리할 때 효율을 높이기 위해 최소 기본 단위 개념을 사용하는데,
    PE 파일에도 같은 개념이 적용된 것입니다.

  • 파일/메모리에서 섹션의 시작위치는 각 파일/메모리의 최소 기본 단위의 배수에 해당하는 위치여야 하고,
    빈 공간은 NULL 로 채워버립니다. (<Fig. 2> 를 보면 각 섹션의 시작이 이쁘게 딱딱 끊어지는 걸 볼 수 있습니다.)



VA & RVA


 


VA (Virtual Address) 는 프로세스 가상 메모리의 절대 주소를 말하며,
RVA (Relative Virtual Address) 는 어느 기준위치(ImageBase) 에서부터의 상대 주소를 말합니다.

VA 와 RVA 의 관계는 아래 식과 같습니다.

RVA + ImageBase = VA

PE header 내의 많은 정보는 RVA 형태로 된 것들이 많습니다.
그 이유는 PE 파일(주로 DLL)이 프로세스 가상 메모리의 특정 위치에 로딩되는 순간
이미 그 위치에 다른 PE 파일(DLL) 이 로딩 되어 있을 수 있습니다.

그럴때는 재배치(Relocation) 과정을 통해서 비어 있는 다른 위치에 로딩되어야 하는데,
만약 PE header 정보들이 VA (Virtual Address - 절대주소) 로 되어 있다면 정상적인 엑세스가 이루어지지 않을것입니다.

정보들이 RVA (Relative Virtual Address - 상대주소) 로 되어 있으면 Relocation 이 발생해도
기준위치에 대한 상대주소는 변하지 않기 때문에 아무런 문제없이 원하는 정보에 엑세스 할 수 있을 것입니다.


DOS Header



Microsoft 는 PE 파일 포멧을 만들때 당시에 널리 사용되던 DOS 파일에 대한 하위 호환성을 고려해서 만들었습니다.
그 결과로 PE header 의 제일 앞부분에는 기존 DOS EXE header 를 확장시킨 IMAGE_DOS_HEADER 구조체가 존재합니다.

typedef struct _IMAGE_DOS_HEADER {     
    WORD   e_magic;          // DOS signature : 4D5A ("MZ")
    WORD   e_cblp;                    
    WORD   e_cp;                      
    WORD   e_crlc;                    
    WORD   e_cparhdr;                 
    WORD   e_minalloc;                
    WORD   e_maxalloc;                
    WORD   e_ss;                      
    WORD   e_sp;                      
    WORD   e_csum;                    
    WORD   e_ip;                      
    WORD   e_cs;                      
    WORD   e_lfarlc;                  
    WORD   e_ovno;                    
    WORD   e_res[4];                  
    WORD   e_oemid;                   
    WORD   e_oeminfo;                 
    WORD   e_res2[10];                  
    LONG   e_lfanew;         // offset to NT header 
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h


 

<IMAGE_DOS_HEADER>

IMAGE_DOS_HEADER 구조체의 크기는 40h 입니다.
이 구조체에서 꼭 알아둬야 할 중요한 멤버는 e_magic 과 e_lfanew 입니다.

  • e_magic : DOS signature (4D5A => ASCII 값 "MZ")
  • e_lfanew : NT header 의 옵셋을 표시 (가변적인 값을 가짐)

모든 PE 파일은 시작 부분(e_magic)에 DOS signature ("MZ") 가 존재하고,
e_lfanew 값이 가리키는 위치에 NT header 구조체가 존재해야 합니다.
(NT header 구조체의 이름은 IMAGE_NT_HEADERS 이며 나중에 소개됩니다.)


* 'MZ' 는 Microsoft 에서 DOS 실행파일을 설계한 Mark Zbikowski 라는 사람의 이니셜입니다.
  (출처 : http://en.wikipedia.org/wiki/Mark_Zbikowski)

notepad.exe 를 hex editor 로 열어서 IMAGE_DOS_HEADER 구조체를 확인해 보겠습니다.


과연 PE 스펙에 맞게 파일 시작 2 byte 는 4D5A 이며, e_lfanew 값은 000000E0 입니다. (E0000000 이 아닙니다.)
(참고 : Intel 계열 PC 는 자료를 역순으로 저장합니다. 이것을 Little Endian 표기법이라고 합니다.)

시험삼아 이 값들을 변경한 후 저장해서 실행해 보세요.
정상 실행되지 않을 것입니다. (PE 스펙에 따라서 더 이상 PE 파일이 아니거든요.)



DOS Stub


DOS Header 밑에는 DOS Stub 이 존재합니다.
DOS Stub 의 존재여부는 옵션이며 크기도 일정하지 않습니다. 
(DOS Stub 은 없어도 파일 실행에는 문제가 없습니다.)

DOS Stub 은 코드와 데이타의 혼합으로 이루어져 있으며, 아래 그림에 notepad.exe 의 DOS Stub 이 나타나 있습니다.


위 그림에서 붉은색으로 표시된 부분은 16 bit 어셈블리 명령어 입니다.
32 bit 윈도우즈에서는 이쪽 명령어가 실행되지 않습니다. (PE 파일로 인식하기 때문에 아예 이쪽 코드를 무시하지요.) 

DOS 환경에서 실행하거나, DOS 용 디버거 (debug.exe) 를 이용해서 실행할 수 있습니다.
(DOS EXE 파일로 인식합니다. 이들은 PE 파일 포멧을 모르니까요...)

콘솔 윈도우(cmd.exe)를 띄워서 아래와 같이 명령을 입력합니다.

C:\WINDOWS>debug notepad.exe
-u
0D1E:0000 0E        PUSH    CS
0D1E:0001 1F        POP     DS
0D1E:0002 BA0E00    MOV     DX,000E   ; DX = 0E : "This program cannot be run in DOS mode"
0D1E:0005 B409      MOV     AH,09
0D1E:0007 CD21      INT     21        ; AH = 09 : WriteString()
0D1E:0009 B8014C    MOV     AX,4C01
0D1E:000C CD21      INT     21        ; AX = 4C01 : Exit()

코드는 매우 간단합니다. 문자열을 출력하고 종료해버리지요.

즉, notepad.exe 는 32 bit 용 PE 파일이지만, MS-DOS 호환 모드를 가지고 있어서
DOS 환경에서 실행하면 "This program cannot be run in DOS mode" 문자열을 출력하고 종료합니다.

이 특성을 잘 이용하면 하나의 실행(EXE) 파일에 DOS 와 Windows 에서 모두 실행 가능한 파일을 만들 수도 있습니다.
실제로 세계적인 보안업체 McAfee 에서 무료로 배포했던 scan.exe 라는 파일이 이와 같은 특징을 가지고 있었습니다.
(DOS 환경에서는 16 bit DOS 용 코드가, Windows 환경에서는 32 bit Windows 코드가 각각 실행됨.)

앞에서 말씀드린대로 DOS Stub 은 옵션이기 때문에, 개발 도구에서 지원해 줘야 합니다.
(VB, VC++, Delphi 등은 DOS Stub 을 기본 지원합니다.)


NT header



NT header 구조체 IMAGE_NT_HEADERS 입니다.

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                          // PE Signature : 50450000 ("PE"00)
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h


위 구조체는 32 bit 용이며, 64 bit 용은 세번째 멤버가 IMAGE_OPTIONAL_HEADER64 입니다.

IMAGE_NT_HEADER 구조체는 3개의 멤버로 되어 있는데요,
제일 첫 멤버는 Signature 로서 50450000h ("PE"00) 값을 가집니다. (변경불가!)
그리고 FileHeader OptionalHeader 구조체 멤버가 있습니다.

notepad.exe 의 IMAGE_NT_HEADERS 의 내용을 hex editor 로 살펴보겠습니다.


IMAGE_NT_HEADERS 구조체의 크기는 F8h 입니다. 상당히 큰 구조체 입니다.
FileHeader 와 OptionalHeader 구조체를 하나하나 살펴보겠습니다.



IMAGE_NT_HEADERS - IMAGE_FILE_HEADER



파일의 개략적인 속성을 나타내는 IMAGE_FILE_HEADER 구조체 입니다.

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;

    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h


IMAGE_FILE_HEADER 구조체에서 아래 4 가지 멤버들이 중요합니다.
(이 값들이 정확히 세팅되어 있지 않으면 파일은 정상적으로 실행되지 않습니다.)


#1. Machine
Machine
넘버는 CPU 별로 고유한 값이며 32 bit Intel 호환 칩은 14Ch 의 값을 가집니다.
아래는 winnt.h 파일에 정의된 Machine 넘버의 값들입니다. (일반적인 14Ch 의 값을 기억하면 됩니다.)

#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64



#2. NumberOfSections
PE 파일은 코드, 데이타, 리소스 등이 각각의 섹션에 나뉘어서 저장된다고 설명드렸습니다.
NumberOfSections 는 바로 그 섹션의 갯수를 나타냅니다.

이 값은 반드시 0 보다 커야 합니다.

정의된 섹션 갯수보다 실제 섹션이 적다면 실행 에러가 발생하며,
정의된 섹션 갯수보다 실제 섹션이 많다면 정의된 갯수만큼만 인식됩니다.


#3. SizeOfOptionalHeader
IMAGE_NT_HEADERS 구조체의 마지막 멤버는 IMAGE_OPTIONAL_HEADER32 구조체입니다.
SizeOfOptionalHeader 멤버는 바로 이 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 나타냅니다.

IMAGE_OPTIONAL_HEADER32 는 C 언어의 구조체이기 때문에 이미 그 크기가 결정되어 있습니다.
그런데 Windows 의 PE Loader 는 IMAGE_FILE_HEADER 의 SizeOfOptionalHeader 값을 보고
IMAGE_OPTIONAL_HEADER32 구조체의 크기를 인식합니다.


IMAGE_DOS_HEADER 의 e_lfanew 멤버와 IMAGE_FILE_HEADER 의 SizeOfOptionalHeader 멤버 때문에
일반적인(상식적인) PE 파일 형식을 벗어나는 일명 '꽈배기' PE 파일(PE Patch) 이 만들 수 있습니다.
(나중에 PE Patch 에 대해서 상세히 설명하도록 하겠습니다.)


#4. Characteristics
파일의 속성을 나타내는 값으로써, 실행이 가능한 형태인지(executable or not)
혹은 DLL 파일인지 등의 정보들이 bit OR 형식으로 조합됩니다.

아래는 winnt.h 파일에 정의된 Characteristics 값들입니다. (0002h 와 2000h 의 값을 기억해 두세요.)

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable 
                                                     // (i.e. no unresolved externel references).

#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from
                                                     // file in .DBG file

#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, 
                                                     // copy and run from the swap file.

#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net,
                                                     // copy and run from the swap file.

#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.


참고로 PE 파일중에 Characteristics 값에 0002h 가 없는 경우(not executable)가 있을까요?
네, 있습니다. 예를 들어 *.obj 와 같은 object 파일이 그런 경우이고, resource DLL 같은 파일이 그런 경우 입니다.

이 정도면 IMAGE_FILE_HEADER 의 구조를 이해하는데 부족함이 없을 것입니다.

마지막으로 IMAGE_FILE_HAEDER 의 TimeDateStamp 멤버에 대해서 설명드리겠습니다.
이 값은 파일의 실행에 영향을 미치지 않는 값으로써 해당 파일의 빌드 시간을 나타낸 값입니다.
단, 개발 도구에 따라서 이 값을 세팅해주는 도구(VB, VC++)가 있고, 그렇지 않은 도구(Delphi)가 있습니다.
(또한 개발 도구의 옵션에 따라서 달라질 수 있습니다.)


이제 실제로 notepad.exe 의 IMAGE_FILE_HEADER 를 확인해 보겠습니다.


위 그림은 hex editor 로 봤을때의 그림이고, 이를 알아보기 쉽게 구조체 멤버로 표현하면 아래와 같습니다.

[ IMAGE_FILE_HEADER ] - notepad.exe

 offset   value   description

-------------------------------------------------------------------------------
000000E4     014C machine
000000E6     0003 number of sections
000000E8 48025287 time date stamp (Mon Apr 14 03:35:51 2008)
000000EC 00000000 offset to symbol table
000000F0 00000000 number of symbols
000000F4     00E0 size of optional header
000000F6     010F characteristics
                      IMAGE_FILE_RELOCS_STRIPPED
                      IMAGE_FILE_EXECUTABLE_IMAGE
                      IMAGE_FILE_LINE_NUMS_STRIPPED
                      IMAGE_FILE_LOCAL_SYMS_STRIPPED
                      IMAGE_FILE_32BIT_MACHINE


다음에는 NT header 의 세번째 멤버인 IMAGE_OPTIONAL_HEADER32 에 대해서 알아보겠습니다.

IMAGE_NT_HEADERS - IMAGE_OPTIONAL_HEADER32



PE header 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32 입니다.
(64 bit PE 파일의 경우 IMAGE_OPTIONAL_HEADER64 구조체를 사용합니다.)

 

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h


IMAGE_OPTIONAL_HEADER32 구조체에서 주목해야 할 멤버들은 아래와 같습니다.
이 값들 역시 파일 실행에 필수적인 값들이라서 잘 못 세팅되면 파일이 정상 실행 되지 않습니다.


#1. Magic
IMAGE_OPTIONAL_HEADER32 인 경우 10Bh, IMAGE_OPTIONAL_HEADER64 인 경우 20Bh 값을 가지게 됩니다.


#2. AddressOfEntryPoint
EP(Entry Point) 의 RVA(Relative Virtual Address) 값을 가지고 있습니다.


#3. ImageBase
프로세스의 가상 메모리는 0 ~ FFFFFFFFh 범위입니다. (32 bit 의 경우)
ImageBase 는 이렇게 광활한 메모리내에서 PE 파일이 로딩(매핑)되는 시작 주소를 나타냅니다.

EXE, DLL 파일은 user memory 영역인 0 ~ 7FFFFFFFh 범위에 위치하고,
SYS 파일은 kernel memory 영역인 80000000h ~ FFFFFFFFh 범위에 위치합니다.


일반적으로 개발 도구(VB/VC++/Delphi)들이 만들어내는 EXE 파일의 ImageBase 값은 00400000h 이고,
DLL 파일의 ImageBase 값은 01000000h 입니다. (물론 다른 값도 가능합니다.)

PE loader 는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩(매핑) 시킨 후
EIP 레지스터 값을 ImageBase + AddressOfEntryPoint 값으로 세팅합니다.


#4. SectionAlignment, FileAlignment
PE 파일은 섹션으로 나뉘어져 있는데 파일에서 섹션의 최소단위를 나타내는 것이 FileAlignment 이고
메모리에서 섹션의 최소단위를 나타내는 것이 SectionAlignment 입니다.
(하나의 파일에서 FileAlignment 와 SectionAlignment 의 값은 같을 수도 있고 틀릴 수도 있습니다.)

따라서 파일/메모리의 섹션 크기는 반드시 각각 FileAlignment/SectionAlignment 의 배수가 되어야 합니다.


#5. SizeOfImage
PE 파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image 가 차지하는 크기를 나타냅니다.
일반적으로 파일의 크기와 메모리에 로딩된 크기는 다릅니다. 
(각 섹션의 로딩 위치와 메모리 점유 크기는 나중에 소개할 Section Header 에 정의 되어 있습니다.)


#6. SizeOfHeader
PE header 의 전체 크기를 나타냅니다.
이 값 역시 FileAlignment 의 배수 이어야 합니다.

파일 시작에서 SizeOfHeader 옵셋만큼 떨어진 위치에 첫번째 섹션이 위치합니다.


#7. Subsystem
1 : Driver file (*.sys)
2 : GUI (Graphic User Interface) 파일 -> notepad.exe 와 같은 윈도우 기반 어플리케이션
3 : CUI (Console User Interface) 파일 -> cmd.exe 와 같은 콘솔 기반 어플리케이션


#8. NumberOfRvaAndSizes
마지막 멤버인 DataDirectory 배열의 갯수

구조체 정의에 분명히 배열 갯수가 IMAGE_NUMBEROF_DIRECTORY_ENTRIES (16) 이라고 명시 되어 있지만,
PE loader 는 NumberOfRvaAndSizes 의 값을 보고 배열의 크기를 인식합니다.



#9. DataDirectory
IMAGE_DATA_DIRECTORY 구조체의 배열로써, 배열의 각 항목마다 정의된 값을 가지게 됩니다.

아래에 각 배열 항목을 나열하였습니다.

DataDirectory[0] = EXPORT Directory        
DataDirectory[1] = IMPORT Directory        
DataDirectory[2] = RESOURCE Directory      
DataDirectory[3] = EXCEPTION Directory     
DataDirectory[4] = SECURITY Directory      
DataDirectory[5] = BASERELOC Directory     
DataDirectory[6] = DEBUG Directory         
DataDirectory[7] = COPYRIGHT Directory     
DataDirectory[8] = GLOBALPTR Directory     
DataDirectory[9] = TLS Directory           
DataDirectory[A] = LOAD_CONFIG Directory   
DataDirectory[B] = BOUND_IMPORT Directory  
DataDirectory[C] = IAT Directory           
DataDirectory[D] = DELAY_IMPORT Directory  
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory       


여기서 말하는 Directory 란 그냥 어떤 구조체의 배열이라고 생각하시면 됩니다.

빨간색으로 표시한 EXPORT, IMPORT, RESOURCE, TLS Directory 를 눈여겨 보시기 바랍니다.
특히 IMPORT 와 EXPORT Directory 구조는 PE header 에서 매우 중요하기 때문에 나중에 따로 설명하도록 하겠습니다.

나머지는 크게 중요하지 않다고 보시면 됩니다.


이제 실제로 notepad.exe 의 IMAGE_OPTIONAL_HEADER32 를 확인해 보겠습니다.


구조체 멤버별 값과 설명은 아래와 같습니다.

 

[ IMAGE_OPTIONAL_HEADER ] - notepad.exe

 offset   value   description
-------------------------------------------------------------------------------
000000F8     010B magic
000000FA       07 major linker version
000000FB       0A minor linker version
000000FC 00007800 size of code
00000100 00008C00 size of initialized data
00000104 00000000 size of uninitialized data
00000108 0000739D address of entry point
0000010C 00001000 base of code
00000110 00009000 base of data
00000114 01000000 image base
00000118 00001000 section alignment
0000011C 00000200 file alignment
00000120     0005 major OS version
00000122     0001 minor OS version
00000124     0005 major image version
00000126     0001 minor image version
00000128     0004 major subsystem version
0000012A     0000 minor subsystem version
0000012C 00000000 win32 version value
00000130 00014000 size of image
00000134 00000400 size of headers
00000138 000126CE checksum
0000013C     0002 subsystem
0000013E     8000 DLL characteristics
00000140 00040000 size of stack reserve
00000144 00011000 size of stack commit
00000148 00100000 size of heap reserve
0000014C 00001000 size of heap commit
00000150 00000000 loader flags
00000154 00000010 number of directories
00000158 00000000 RVA  of EXPORT Directory
0000015C 00000000 size of EXPORT Directory
00000160 00007604 RVA  of IMPORT Directory
00000164 000000C8 size of IMPORT Directory
00000168 0000B000 RVA  of RESOURCE Directory
0000016C 00008304 size of RESOURCE Directory
00000170 00000000 RVA  of EXCEPTION Directory
00000174 00000000 size of EXCEPTION Directory
00000178 00000000 RVA  of SECURITY Directory
0000017C 00000000 size of SECURITY Directory
00000180 00000000 RVA  of BASERELOC Directory
00000184 00000000 size of BASERELOC Directory
00000188 00001350 RVA  of DEBUG Directory
0000018C 0000001C size of DEBUG Directory
00000190 00000000 RVA  of COPYRIGHT Directory
00000194 00000000 size of COPYRIGHT Directory
00000198 00000000 RVA  of GLOBALPTR Directory
0000019C 00000000 size of GLOBALPTR Directory
000001A0 00000000 RVA  of TLS Directory
000001A4 00000000 size of TLS Directory
000001A8 000018A8 RVA  of LOAD_CONFIG Directory
000001AC 00000040 size of LOAD_CONFIG Directory
000001B0 00000250 RVA  of BOUND_IMPORT Directory
000001B4 000000D0 size of BOUND_IMPORT Directory
000001B8 00001000 RVA  of IAT Directory
000001BC 00000348 size of IAT Directory
000001C0 00000000 RVA  of DELAY_IMPORT Directory
000001C4 00000000 size of DELAY_IMPORT Directory
000001C8 00000000 RVA  of COM_DESCRIPTOR Directory
000001CC 00000000 size of COM_DESCRIPTOR Directory
000001D0 00000000 RVA  of Reserved Directory
000001D4 00000000 size of Reserved Directory


여기까지 NT Header 의 설명을 마치고 다음에 Section Header 에 대해서 살펴보도록 하겠습니다.

Section Header



각 Section 의 속성(property)을 정의한 것이 Section Header 입니다.

section header 구조체를 보기 전에 한번 생각을 해보겠습니다.

앞서 PE 파일은 code, data, resource 등을 각각의 section 으로 나눠서 저장한다고 설명드렸습니다.
분명 PE 파일 포멧을 설계한 사람들은 어떤 장점이 있기 때문에 그랬을 겁니다.

PE 파일을 여러개의 section 구조로 만들었을때 (제가 생각하는) 장점은 바로 프로그램의 안정성입니다.

code 와 data 가 하나의 섹션으로 되어 있고 서로 뒤죽박죽 섞여 있다면, (실제로 구현이 가능하긴 합니다.)
그 복잡한은 무시하고라도 안정성에 문제가 생길 수 있습니다.

가령 문자열 data 에 값을 쓰다가 어떤 이유로 overflow 가 발생(버퍼 크기를 초과해서 입력) 했을때
바로 다음의 code (명령어) 를 그대로 덮어써버릴 것입니다. 프로그램은 그대로 뻗어 버리겠죠.

즉, code/data/resource 마다 각각의 성격(특징, 엑세스 권한)이 틀리다는 것을 알게 된 것입니다.

  • code - 실행, 읽기 권한
  • data - 비실행, 읽기, 쓰기 권한
  • resource - 비실행, 읽기 권한


그래서 PE 파일 포멧 설계자들은 비슷한 성격의 자료를 section 이라고 이름 붙인 곳에 모아두기로 결정하였고,
각각의 section 의 속성을 기술할 section header 가 필요하게 된 것입니다.
(section 의 속성에는 file/memory 에서의 시작위치, 크기, 엑세스 권한 등이 있어야 겠지요.)

이제 section header 가 무슨 역할을 하는지 이해 되셨나요?



IMAGE_SECTION_HEADER


section header 는 각 section 별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있습니다. 

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;

    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

* 출처 : Microsoft 의 Visual C++ 에서 제공하는 winnt.h


IMAGE_SECTION_HEADER 구조체에서 알아야 할 중요 멤버는 아래와 같습니다. (나머지는 사용되지 않습니다.)

  • VirtualSize      : 메모리에서 섹션이 차지하는 크기
  • VirtualAddress   : 메모리에서 섹션의 시작 주소 (RVA)
  • SizeOfRawData    : 파일에서 섹션이 차지하는 크기
  • PointerToRawData : 파일에서 섹션의 시작 위치
  • Characteristics  : 섹션의 특징 (bit OR)


VirtualAddress 와 PointerToRawData 의 값은 아무 값이나 가질 수 없고,
각각 (IMAGE_OPTIONAL_HEADER32 에 정의된) SectionAlignment 와 FileAlignment 에 맞게 결정됩니다.

VirtualSize 와 SizeOfRawData 는 일반적으로 서로 틀린값을 가집니다.
즉, 파일에서의 섹션 크기와 메모리에 로딩된 섹션의 크기는 틀리다는 얘기가 되는 거죠.

Characteristics 는 아래 값들의 조합(bit OR)으로 이루어 집니다.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.


마지막으로 Name 항목에 대해서 얘기해보겠습니다.

Name 멤버는 C 언어의 문자열처럼 NULL 로 끝나지 않습니다. 또한 ASCII 값만 와야한다는 제한도 없습니다.
PE 스펙에는 섹션 Name 에 대한 어떠한 명시적인 규칙이 없기 때문에 어떠한 값을 넣어도 되고 심지어 NULL 로 채워도 됩니다.

또한 개발 도구에 따라서 섹션 이름/갯수 등이 달라집니다.

따라서 섹션의 Name 은 그냥 참고용 일뿐 어떤 정보로써 활용하기에는 100% 장담할 수 없습니다.
(데이타 섹션 이름을 ".code" 로 해도 되거든요.)


자 그러면 실제 notepad.exe 의 Section Header 배열을 살펴보죠. (총 3 개의 섹션이 있습니다.)


구조체 멤버별로 살펴보면 아래와 같습니다.

 

[ IMAGE_SECTION_HEADER ]

 offset   value   description
-------------------------------------------------------------------------------
000001D8 2E746578 Name (.text)
000001DC 74000000
000001E0 00007748 virtual size
000001E4 00001000 RVA
000001E8 00007800 size of raw data
000001EC 00000400 offset to raw data
000001F0 00000000 offset to relocations
000001F4 00000000 offset to line numbers
000001F8     0000 number of relocations
000001FA     0000 number of line numbers
000001FC 60000020 characteristics
                    IMAGE_SCN_CNT_CODE
                    IMAGE_SCN_MEM_EXECUTE
                    IMAGE_SCN_MEM_READ

00000200 2E646174 Name (.data)
00000204 61000000
00000208 00001BA8 virtual size
0000020C 00009000 RVA
00000210 00000800 size of raw data
00000214 00007C00 offset to raw data
00000218 00000000 offset to relocations
0000021C 00000000 offset to line numbers
00000220     0000 number of relocations
00000222     0000 number of line numbers
00000224 C0000040 characteristics
                    IMAGE_SCN_CNT_INITIALIZED_DATA
                    IMAGE_SCN_MEM_READ
                    IMAGE_SCN_MEM_WRITE

00000228 2E727372 Name (.rsrc)
0000022C 63000000
00000230 00008304 virtual size
00000234 0000B000 RVA
00000238 00008400 size of raw data
0000023C 00008400 offset to raw data
00000240 00000000 offset to relocations
00000244 00000000 offset to line numbers
00000248     0000 number of relocations
0000024A     0000 number of line numbers
0000024C 40000040 characteristics
                             IMAGE_SCN_CNT_INITIALIZED_DATA
                             IMAGE_SCN_MEM_READ






___________________________________________________________________________________________________

 

 

 

 

 

______실습파일 & 수업자료______

 

 

asynchronous_serial 

asynchronous_serial.c

 

 

PE101-A4.pdf 

PE101-v1KO.pdf

 

 

 

monitor2.asm

 

monitor_c.c

 

monitor_c.exe

 

 

 

20130927.zip

 

 

_______________________________

'스마트 컨트롤러 게시물' 카테고리의 다른 글

20131001  (0) 2013.10.01
20130930  (0) 2013.09.30
20130926  (0) 2013.09.26
20130925  (0) 2013.09.25
20130827  (0) 2013.08.27