1. 개요

 응용 프로그램의 PE 헤더를 분석하고, 코드 영역과 데이터 영역을 가상 머신 내에 적재하여 프로그램을 가상으로 실행하는 프로그램이다. 함수가 실행되고 되돌아가는 원리와 컨텍스트 스위칭 기법을 이용한 것으로, 실행 전 메모리 상태와 실행 후 메모리 상태, 현재 레지스터 상태를 볼 수 있도록 되어있다.


사용 언어 : C / ASM



2. main.c

2-1. 프로그램 흐름

1. 준비

1. 커맨드를 받기 전에 미리 컨텍스트를 저장해 놓는다.

- 저장하는 시점이 가상 프로그램 실행 전이 되서는 안된다(무한루프의 가능성)

2. 커맨드 받기를 기다린다.

2. 프로그램 로드(커맨드가 LOAD일 경우)

1. 파일 체크

- 유효한 파일인가?

- 실행 파일인가?(매직 넘버가 'MZ'인지 체크)

2. 헤더를 분석해 헤더의 전체 크기를 구하고, 이를 이용해 코드 영역과 데이터 영역의 시작위치를 찾음

3. 찾아낸 시작위치만큼 lseek를 이용해 건너뛴 뒤, 미리 만들어 둔 코드/데이터 영역에 적재

3. 실행

1. 새로운 컨텍스트 구조체를 만듬

- eax에 이전에 미리 만들어 둔 구조체의 주소를 넣는다

-> 이후 실행 될 프로그램이 마지막에 호출할 함수의 인자가 된다

- eip에 적재해 둔 코드 영역의 시작 주소를 넣는다

-> 코드 영역으로 점프하여 수행하게 된다

- esp에 stack 영역의 마지막+4를 가르키도록 한다

-> push는 한 칸 움직인 후 값을 넣는다

-> 스택은 끝에서 부터 +4씩 이동한다

2. 새로운 컨텍스트로 스위칭

- 프로그램이 실행되며, 이 때 제일 먼저 eax를 push하게 되어 있어야 한다

3. 등록해 둔 실행파일의 마지막 함수(LDST)를 수행

- push된 eax의 값으로 컨텍스트 스위칭을 시도

4. 커맨드를 받기 전의 시점(1-1)로 점프하여 되돌아온다


2-2. 소스

(들여쓰기가 원본 소스에 비해 조금 이상하게 되어 있습니다)

#include <stdio.h>

#include <string.h>

#include <fcntl.h>

#include <windows.h>

#include <winnt.h>


#define MAX_PROGRAM_SIZE 0x10000 // 64KB

#define SECTION_SIZE 512


#define MEMORY_START 0

#define MEMORY_END 1

#define CODE 2

#define DATA 3

#define STACK 4


typedef struct _command

{

const unsigned char *ucpCommand;

const unsigned char *ucpDetail;

void( *fp )();

} COMMAND;


typedef struct _CONTEXT_INFO

{

int efl;

int eip;

int edi;

int esi;

int ebp;

int esp;

int ebx;

int edx;

int ecx;

int eax;

} CONTEXT_INFO;


void ChaseAddress( unsigned char * _uCp, int _iSize );

void DrawHeader();


void STST( CONTEXT_INFO *stpReg ); // CPU의 정보를 메모리에 저장

void LDST( CONTEXT_INFO *stpReg ); // 메모리에 저장된 CPU 정보를 재등록


unsigned char MD( int * ); // Memory Display Function By Assembly

void MM( int *, char); // Memory Modify Function By Assembly


void PrintRegister( CONTEXT_INFO *stpReg );


void Command();


void ViewCode();

void ViewData();

void ViewStack();

void OpenExcutable();

void Exit();

void ViewRegister();

void MemoryClear();

void Go();


unsigned char ucMemory[MAX_PROGRAM_SIZE * 2];


static unsigned char *ucpPoint[5]; // 메모리 공간을 구분하는 변수. 이곳에서만 쓰이기 때문에 static

unsigned int uiLoadFlag = 0;


CONTEXT_INFO stOldReg;


COMMAND stCommand[] =

{

{ "CODE", "코드 영역을 봅니다", ViewCode },

{ "DATA", "데이터 영역을 봅니다", ViewData },

{ "STACK", "스택 영역을 봅니다", ViewStack },

{ "REGVIEW", "레지스터를 봅니다", ViewRegister },

{ "LOAD", "실행 파일을 메모리에 적재합니다", OpenExcutable },

{ "CLEAR", "메모리를 비웁니다", MemoryClear },

{ "GO", "적재한 실행 파일을 실행합니다", Go },

{ "EXIT", "종료합니다", Exit },

{ 0, 0 }

};


int main()

{

int a = 0x12345678;


ucpPoint[MEMORY_START] = ucMemory;

ucpPoint[MEMORY_END] = ucMemory + sizeof( ucMemory );


ucpPoint[CODE] = ( unsigned char * )( ( int )( ucMemory+MAX_PROGRAM_SIZE ) & 0xFFFF0000 );

ucpPoint[DATA] = ucpPoint[CODE] + 0x2000;

ucpPoint[STACK] = ucMemory+0x10000;


printf( "Start of memory\t: 0x%08X\n", ucpPoint[MEMORY_START] );

printf( "End of memory\t: 0x%08X\n", ucpPoint[MEMORY_END] );

printf( "CODE\t: 0x%08X\n", ucpPoint[CODE] );

printf( "DATA\t: 0x%08X\n", ucpPoint[DATA] );

printf( "STACK\t: 0x%08X\n", ucpPoint[STACK] );


STST( &stOldReg );


while( 1 )

{

Command();

}


//printf( "0x%08X : %02X\n", &a, MD( &a ) );

//MM( &a, 0xFF );


//printf( "0x%08X : %02X\n", &a, MD( &a ) );

//printf( "0x%08X : %08X\n", &a, a );


//ChaseAddress( ( unsigned char * )&a, 4 );


//PrintRegister( &stOldReg );


//STST( &stReg );

//PrintRegister( &stReg );


return 0;

}


void PrintRegister( CONTEXT_INFO *stpReg )

{

printf( "┌──────RegisterStatus──────┐\n" );

printf( "│EAX : 0x%08X\t", stpReg->eax );

printf( "ECX : 0x%08X│\n", stpReg->ecx );

printf( "│EDX : 0x%08X\t", stpReg->edx );

printf( "EBX : 0x%08X│\n", stpReg->ebx );

printf( "│ESP : 0x%08X\t", stpReg->esp );

printf( "EBP : 0x%08X│\n", stpReg->ebp );

printf( "│ESI : 0x%08X\t", stpReg->esi );

printf( "EDI : 0x%08X│\n", stpReg->edi );

printf( "│EIP : 0x%08X\t", stpReg->eip );

printf( "EFL : 0x%08X│\n", stpReg->efl );

printf( "└───────────────────┘\n\n" );

}


void ChaseAddress( unsigned char * _uCp, int _iSize )

{

unsigned char *m_uCp = _uCp;

int m_count=0;

int i=0;

int m_bytes = 0;


printf( "\n" );

DrawHeader();


while( 1 )

{

if( ( i % 22 == 0 )&&( i != 0 ) )

{

printf( "<Press any key to view more... <q> to exit>" );

if( getch() == 'q' )

{

break;

}

putchar( '\n' );

DrawHeader();

}


printf( "  %08X  ", m_uCp );

for( m_count=0; m_count<=15; m_count++ )

{

if( m_bytes < _iSize )

{

printf( "%02X", MD( ( int * )(m_uCp + m_count) ) );

if( m_count != 15 )

{

putchar(' ');

}

m_bytes = m_bytes + 1;

}

else

{

printf( "  " );

if( m_count != 15 )

{

putchar(' ');

}

}

}

printf( "  " );


for( m_count=0; m_count<=15; m_count++ )

{

if( m_uCp + m_count < _uCp+_iSize )

{

if( 0 == *( m_uCp + m_count ) )

{

printf( "." );

}

else if( 32 > *( m_uCp + m_count )  )

{

printf( "*" );

}

else if( 127 < *( m_uCp + m_count )  )

{

printf( "*" );

}

else

{

printf( "%c", MD( ( int * )(m_uCp + m_count) ) );

}

}

else

{

printf( " " );

}

}

if( m_bytes == _iSize )

{

printf( "\n\n<End of memory.>\n" );

break;

}

putchar( '\n' );

i = i + 1;

m_uCp = m_uCp + 16;

}

printf( "\n\n" );

}


void DrawHeader()

{

int i;


printf( "  Address                          HEX                             ASCII\n" );

printf( "            " );

for( i=0; i<=15; i++ )

{

printf( "%02X", i );

if( i != 15 )

{ putchar(' ');

}

}

printf( "  " );

for( i=0; i<=15; i++ )

{

printf( "%1X", i );

}

printf( "\n" );

}


void Command()

{

int i;

int iRet;


unsigned char ucCommand[30];


fflush( stdin );

printf( "COMMAND> " );

iRet = read( 0, ucCommand, 30 );

ucCommand[iRet-1] = 0;


for( i=0; i<iRet-1; i++ )

{

ucCommand[i] = toupper( ucCommand[i] );

}


i = 0;

while( 1 )

{

if( stCommand[i].fp == 0 )

{

printf( "오류 : 없는 명령어 입니다!\n\n<명령어 목록>\n" );


for( i=0; stCommand[i].fp!=0; i++ )

{

printf( "%s\t: %s\n", stCommand[i].ucpCommand, stCommand[i].ucpDetail );

}

putchar( '\n' );

break;

}


if( strcmp( stCommand[i].ucpCommand, ucCommand ) == 0 )

{

(stCommand[i].fp)();

break;

}

i++;

}

}


void ViewCode()

{

ChaseAddress( ucpPoint[CODE], ucpPoint[DATA]-ucpPoint[CODE] );

return;

}

void ViewData()

{

ChaseAddress( ucpPoint[DATA], ucpPoint[DATA]-ucpPoint[CODE] );

return;

}

void ViewStack()

{

ChaseAddress( ucpPoint[MEMORY_END]-159, 160 );

return;

}

void ViewRegister()

{

CONTEXT_INFO stContext;


STST( &stContext );

PrintRegister( &stContext );

return;

}

void Exit()

{

exit( 0 );

return;

}


void OpenExcutable()

{

int iFd = 0; // File descripter

int iRet;

unsigned int uiTotalCount = 0;

unsigned int iCount = 0;


unsigned char *ucpCursor;

unsigned char *ucpCodeStart;


unsigned char ucFileName[30];


IMAGE_DOS_HEADER *stpDOSHeader;

IMAGE_NT_HEADERS *stpNTHeader;


printf( "파일 이름<실행파일> : " );


iRet = read( 0, ucFileName, 30 );

ucFileName[iRet-1] = 0;


iFd = open( ucFileName, O_RDONLY );


if( iFd < 0 )

{

printf( "\n파일을 열 수 없습니다...\n\n" );

return;

}


MemoryClear();


iRet = read( iFd, ucMemory, MAX_PROGRAM_SIZE );


if( iRet < 0 )

{

printf( "\n파일을 읽을 수 없습니다...\n\n" );

close( iFd );

return;

}


stpDOSHeader = ( IMAGE_DOS_HEADER* )ucMemory;


printf( "파일을 열었습니다.\n" );


if( stpDOSHeader->e_magic != 0x5A4D )

{

printf( "올바른 파일 형식이 아닙니다. 실행 파일을 입력해 주시기 바랍니다.\n\n" );

close( iFd );

return;

}


stpNTHeader = ( IMAGE_NT_HEADERS* )( ucMemory + stpDOSHeader->e_lfanew -1 );


printf( "%d\n", stpNTHeader->OptionalHeader.SizeOfInitializedData );


uiTotalCount = stpNTHeader->OptionalHeader.SizeOfHeaders;

iRet = lseek( iFd, stpNTHeader->OptionalHeader.SizeOfHeaders, SEEK_SET );


MemoryClear();


if( iRet < 0 )

{

printf( "파일 적재 오류!\n" );

close( iFd );

return;

}


iRet = read( iFd, ucpPoint[CODE], SECTION_SIZE );

if( iRet < 0 )

{

printf( "코드 영역 적재 오류!\n" );

close( iFd );

return;

}

uiTotalCount = uiTotalCount + iRet;


iRet = read( iFd, ucpPoint[DATA], SECTION_SIZE );

if( iRet < 0 )

{

printf( "데이터 영역 적재 오류!\n" );

close( iFd );

return;

}

uiTotalCount = uiTotalCount + iRet;


printf( "총 읽어들인 바이트 수 : %d bytes\n", uiTotalCount );


close( iFd );


uiLoadFlag = 1;


return;

}


void MemoryClear()

{

printf( "메모리를 초기화합니다...\n" );

memset( ucpPoint[CODE], 0, ucpPoint[MEMORY_END]-ucpPoint[CODE] );


uiLoadFlag = 0;

}


void Go()

{

CONTEXT_INFO stNewReg = { 0, };


if( uiLoadFlag )

{

stNewReg.eax = ( int )&stOldReg;

stNewReg.eip = ( int )ucpPoint[CODE];

stNewReg.esp = ( int )(ucpPoint[MEMORY_END]+4); // PUSH는 이동하고 값을 넣으므로...


LDST( &stNewReg );


printf( "Kernel Panic\n" );

}

else

{

printf( "프로그램을 적재하시기 바랍니다...\n" );

}

}



3. monitor.asm

3-1. 프로그램의 흐름

1. STST

1. 플래그 레지스터 백업

- 저장하는 차원에서 플래그 레지스터가 변경되어 이를 변경

-> 함수 내 스택에서 고쳐야 한다

2. ESP를 함수의 인자로 이동

- 컨텍스트 구조체가 저장된 메모리를 가르키게 된다

3. ESP의 위치를 10칸 내린다

- 구조체에 직접 값을 넣기 위해

4. PUSHAD를 이용해 다수의 레지스터의 값을 넣는다

- 이 때, ESP는 무시되므로 직접 넣어 주어야 한다

-> ESP는 도중에 이동되면 문제를 일으킬 수 있으므로

5. ESP의 값을 저장한다

-> 함수의 EBP 위치에서 8바이트 떨어진 곳이 ESP의 위치

6. Old EBP의 값을 저장

7. EIP의 값을 Return Address의 값으로 지정

8. 플래그 레지스터 푸쉬

2. LDST

1. ESP를 컨텍스트 구조체가 있는 곳으로 옮긴다.

2. EFL의 값을 읽어온다.

3. 이전 EIP를 EAX에 저장한다

4. EBX에 현재 ESP를 저장한다

5. 과거 ESP로 점프한다.

6. 과거 ESP의 위치에 EAX를 PUSH하여 RA를 덮어쓴다

7. ESP를 다시 복원시켜 현재로 돌아온다

8. ESP를 제외한 모든 값을 복원시켜 STST로 저장한 당시 그대로의 상황을 만든다

- 이 때, ESP를 한 칸 올려 주어야 한다

-> RET할 때, POP된 주소로 점프하는데 올려주지 않으면 제대로 RET되지 않기 때문

3. MD

1. 인자(주소)를 EAX에 넣는다

2. EAX에 EAX의 주소를 따라가서 나오는 값을 다시 넣는다

3. 리턴한다

4. MM

1. 인자(주소)를 EAX에 넣는다

2. BL에 인자(데이터)를 넣는다

3. EAX의 주소에 BL의 값을 넣는다


3-2. 소스

.386

.MODEL FLAT


PUBLIC _STST ; Store Status

PUBLIC _LDST ; Load Status

PUBLIC _MD ; Memory Display

PUBLIC _MM ; Memory Modify


.CODE

_STST PROC NEAR32

PUSH EBP

MOV EBP, ESP


PUSHFD


AND DWORD PTR [EBP-4], 0FFFFFEFFh


MOV ESP, [EBP+8]


ADD ESP, 40


PUSHAD


ADD ESP, 16

;Old EBP, RA, 인자, Main과의 경계

MOV EAX, EBP

ADD EAX, 8


PUSH EAX ; 메인 ESP


PUSH [EBP] ; Old EBP


SUB ESP, 8


PUSH [EBP+4] ; EIP(Return Address)

PUSH [EBP-4] ; EFL


MOV ESP, EBP

POP EBP

RET

_STST ENDP


_LDST PROC NEAR32


MOV ESP, [ESP+4] ; ESP = &Context


POPFD ; EFL = Context.EFL

; 플래그 레지스터에 값을 넣음


POP EAX ; EAX = Context.EIP

; 예전 EIP를 EAX 저장


MOV EBX, ESP ; EBX = Current ESP

; 현재 ESP 백업


MOV ESP, [ESP+12] ; ESP = Old ESP

; 과거 ESP로 점프


PUSH EAX ; Save Old EIP

; 과거 ESP에 EAX를 푸쉬하여 RA덮어씀


MOV ESP, EBX ; ESP = Current ESP

; 백업해둔 ESP를 다시 복원


POPAD ; ESP를 제외한 모든 값을 복원


MOV ESP, [ESP-20] ; Old ESP값을 복원

SUB ESP, 4 ; ESP를 RA위로 올려주어야 한다(RET이 POP하기때문)


RET

_LDST ENDP


_MD PROC NEAR32

PUSH EBP

MOV EBP, ESP


MOV EAX, [EBP+8]

MOV EAX, [EAX]


MOV ESP, EBP

POP EBP

RET

_MD ENDP


_MM PROC NEAR32

PUSH EBP

MOV EBP, ESP


PUSH EBX


MOV EAX, [EBP+8] ; EAX = Address

MOV BL, [EBP+12] ; BL = Data


MOV BYTE PTR [EAX], BL ; *EAX = BL


POP EBX


MOV ESP, EBP

POP EBP

RET

_MM ENDP


END



4. 실행 결과

<명령어 목록>


<바르지 않은 GO 명령>


<실행 파일 적재>


<실행 전 데이터 영역>


<실행 후 데이터 영역>

+ Recent posts