응용 프로그램의 PE 헤더를 분석하고, 코드 영역과 데이터 영역을 가상 머신 내에 적재하여 프로그램을 가상으로 실행하는 프로그램이다. 함수가 실행되고 되돌아가는 원리와 컨텍스트 스위칭 기법을 이용한 것으로, 실행 전 메모리 상태와 실행 후 메모리 상태, 현재 레지스터 상태를 볼 수 있도록 되어있다.
1. 커맨드를 받기 전에 미리 컨텍스트를 저장해 놓는다.
2. 커맨드 받기를 기다린다.
1. 파일 체크
2. 헤더를 분석해 헤더의 전체 크기를 구하고, 이를 이용해 코드 영역과 데이터 영역의 시작위치를 찾음
3. 찾아낸 시작위치만큼 lseek를 이용해 건너뛴 뒤, 미리 만들어 둔 코드/데이터 영역에 적재
1. 새로운 컨텍스트 구조체를 만듬
2. 새로운 컨텍스트로 스위칭
3. 등록해 둔 실행파일의 마지막 함수(LDST)를 수행
4. 커맨드를 받기 전의 시점(1-1)로 점프하여 되돌아온다
#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" );
}
}
1. 플래그 레지스터 백업
2. ESP를 함수의 인자로 이동
3. ESP의 위치를 10칸 내린다
4. PUSHAD를 이용해 다수의 레지스터의 값을 넣는다
5. ESP의 값을 저장한다
6. Old EBP의 값을 저장
7. EIP의 값을 Return Address의 값으로 지정
8. 플래그 레지스터 푸쉬
1. ESP를 컨텍스트 구조체가 있는 곳으로 옮긴다.
2. EFL의 값을 읽어온다.
3. 이전 EIP를 EAX에 저장한다
4. EBX에 현재 ESP를 저장한다
5. 과거 ESP로 점프한다.
6. 과거 ESP의 위치에 EAX를 PUSH하여 RA를 덮어쓴다
7. ESP를 다시 복원시켜 현재로 돌아온다
8. ESP를 제외한 모든 값을 복원시켜 STST로 저장한 당시 그대로의 상황을 만든다
1. 인자(주소)를 EAX에 넣는다
2. EAX에 EAX의 주소를 따라가서 나오는 값을 다시 넣는다
3. 리턴한다
1. 인자(주소)를 EAX에 넣는다
2. BL에 인자(데이터)를 넣는다
3. EAX의 주소에 BL의 값을 넣는다
.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