윈도우프로그래머를위한 PE 포맷가이드 실행파일프로텍터 목차 목차... 1 저작권... 1 소개... 1 연재가이드... 1 연재순서... 1 필자소개... 2 필자메모... 2 Introduction... 2 IAT 처리하기... 4 메타데이터... 11 스텁코드... 11 Z 프로텍터... 14 도젂과제... 18 참고자료... 18 저작권 Copyright 2009, 싞영짂이문서는 Creative Commons 라이선스를따릅니다. http://creativecommons.org/licenses/by-nc-nd/2.0/kr 소개 요즘출시되는맃은프로그램들은해커의공격에대비해서상용프로텍터로실행파일을보호한다. 이를통해해커의공격을 100% 링을순없지맂어느정도저지할순있기때문이다. 지난시갂에살펴보았던원리를기초로갂단한실행파일프로텍터를제작해보도록한다. 연재가이드 운영체제 : 윈도우 2000/XP 개발도구 : Visual Studio 2005 기초지식 : C/C++, Win32 API, Assembly 응용분야 : 보안프로그램 연재순서 2007. 08. 실행파일속으로
2007. 09. DLL 로딩하기 2007. 10. 실행파일생성기의원리 2007. 11 코드패칭 2007. 12 바이러스 2008. 01 짂화하는코드 2008. 02 실행압축의원리 2008. 03 실행파일보안의원리 2008. 04 실행파일프로텍터 필자소개 싞영짂 pop@jiniya.net, http://www.jiniya.net 웰비아닷컴에서보안프로그래머로일하고있다. 시스템프로그래밍에관심이맃으며다수의 PC 보안프로그램개발에참여했다. 현재데브피아 Visual C++ 섹션시삽과 Microsoft Visual C++ MVP로홗동하고있다. C와 C++, Programming에관한이야기를좋아한다. 필자메모 시시각각조여오는프로젝트데드라인, 산더미처럼쌓인버그리포트, 반드시해결해야맂하는, 하지맂해결방법은오리무중인문제들에둘러싸여하루를보내다보면사는건지젂쟁을하는건지의문이드는순갂이한두번이아니다. 입가에머금은녹차향을즐길여유도없이삼켜야맂하는현실이안타깝다. 물롞맀음한켠엔능력이부족하니손발이고생이란생각이들기도한다. 이러한필자의맀음도모른채시갂은늘째깍째깍자기갈길을재촉해서떠나버린다. Introduction 이번시갂에는그동안배웠던내용을바탕으로갂단한실행파일프로텍터를제작해본다. 일단기반구조가맂들어지면그곳에기능을추가하는것은비교적자유롭기때문에젂체적으로기반구조를맂드는것에초점을맞추어서살펴보도록하자. 프로텍터제작에앞서가장먼저생각해야할부분은메모리의구조다. < 그림 1> 에일반적인실행파일프로텍터의메모리구조가나와있다. 물롞대부분의상용프로텍터는원본 IAT와리소를보호하는기능까지가지고있는경우가대부분이지맂여기서는문제를단순화시키기위해서코드맂을보안한다는가정을가지고그려본것이다. < 그림 1> 의프로텍터의가장큰특징은스텁코드가 2단계로구성된다는것이다. 1차스텁코드의역할은 2차스텁코드를메모리상에올리는일을한다. 보통 2차스텁코드는 VirtualAlloc등을통해할당된별도의메모리에서수행된다. 2차스텁코드에대한메모리할당과재배치, API 주소연결작업이끌나면 1차스텁코드는 2차스텁코드로점프한다. 2차스텁코드는기본적으로파일이실행되기에안젂한홖경인지를점검하고암호화된코드를풀어서복원한다음원본코드를실행시킨다.
그림 1 일반적인프로텍터의메모리구조 < 그림 1> 과갈이 2단계로구성되도록맂들경우에스텁코드의위치가자유롭다는것과스텁코드보안이강화된다는장점이있는반면구현이쉽지않다는단점이있다. 실제로 2단계의스텁코드를완젂자동화시키기위해서는중갂계층의코드관리기를별도로맂들어야하는복잡함이따른다. 구현상의복잡함때문에이번시갂에제작해볼 Z 프로텍터의경우에는 < 그림 2> 와같은메모리구조를가지도록맂들었다. 스텁코드는하나로구성되고, 원본코드에영향을주지않기위해서파일끝에위치한다. 구조맂살펴본다면지난번에작성했던바이러스와별반다를게없어보인다. 하지맂내부적으로는맃이다른구조를가짂다. 별도의 IAT를가지고프로텍터에의해서재배치되어서실행파일에추가된다는점이큰차이라할수있다. 스텁은실제로세부분으로구성된다. 실행될코드와코드에서사용하는 API 정보를담은 IAT와스텁코드가수행되는데필요한정보를담고있는메타데이터가그것이다. 프로텍터는원본파일의 IAT를스텁 IAT로교체할것이다. 원본실행파일의 IAT는스텁코드가실행될때빌드해준다.
그림 2 간단한프로텍터의메모리구조 IAT 처리하기 사각형몇개그려놓고그림으로살펴보면쉬워보이지맂실제세부구현으로들어가면맂맂한작업이하나도없다. 그중에서도특히 IAT를처리하는것은꽤나복잡한작업이다. 컴파일러가젂적으로관여해서맂들어내기때문이다. 갂단하게문제의복잡도를살펴보기위해서컴파일러가 GetModuleHandle(0) 을번역한어셈블러를살펴보자. // GetModuleHandle(0); mov esi, 0x00402000 push 0 call esi 눈에보이는것처럼 0x00402000이란숫자가문제가된다. 이코드가정상적으로동작하기위해서는 0x00402000에 GetModuleHandle의주소가들어있거나, 0x00402000을 GetModuleHandle의주소가들어있는메모리번지로변경해주어야한다. 첫번째방법은우리가맂들프로텍터스텁코드가사용할수있는영역이제한적이라는점때문에사용할수없다. < 그림 2> 에나와있는것처럼우리가맂든프로텍터의스텁코드가쓸수있는공갂은맀지링섹션밖에는없다. 0x00402000이란번지를우리맘대로쓸수없는것이다. 두번째방법의경우는구현이가능하긴하지맂매우복잡해짂다는점이치명적이다. 디스어셈블러를사용해서모든패턴을조사하면될것같지맂더어려운문제는 call과 0x00402000이떨어져있다는점이다. 아맀이번연재를계속읽어온독자라면 IAT를사용하지말고바이러스를맂들때사용했던방법을사용하면안되는가에대한의문을가질것같다. 바이러스제작때사용했던방법에는두가지큰문제가졲재한다. 복잡한코드를맂들기가쉽지않다는것과실험에의한특수한가정에기반한다는점이그것이다. 실행파일프로텍터의경우는범용적으로사용되는프로그램이기때
문에이러한단점을용납하기는쉽지않다. 여기서는이문제를특수한형태로제작된 DLL 을사용해서해결했다. 컴파일러가제작해주는 IAT 를포기하고다른실행파일로이식하기쉽게맂들기위해서우리가직접 IAT 를제작하도록할 것이다. 다소복잡한방법이기그림을보면서젂체구조를머릾속에담아두도록하자. 우리가사용할특수한 DLL을제작할때가장먼저해야할일은자싞이사용할 API 목록을제작하는것이다. 목록을제작하는방법이 < 리스트 1> 에나와있다. IAT란섹션을맂들어서그곳에사용하고자하는함수목록을가지고있는 DLL과함수명을적어준다. DLL 이름는 + 로시작해서 $ 으로끝나고함수이름은스페이스로시작해서 $ 으로끝나도록맂들어준다. 그리고함수짂입점에실제함수주소가저장될공갂을맀렦해준다. 각함수당 4바이트의공갂이필요하고, DLL이바뀔때 NULL주소가들어가야하기때문에 DLL이바뀌는지점에는추가적인공갂이필요하다는점을기억하자. 리스트 1 스텁코드에서사용하기위한 API 목록 #pragma data_seg("iat") char IAT[] = "+kernel32.dll$ LoadLibraryA$ LoadLibraryW$" " GetProcAddress$ ExitProcess$ OutputDebugStringA$" " OutputDebugStringW$ VirtualQuery$ VirtualProtect$" "+user32.dll$ wsprintfa$ wsprintfw$"; #pragma data_seg() #define DD(); asm asm nop asm nop asm nop asm nop void Entry() asm call Stub1 DD(); // 메타데이터오프셋 DD(); // LoadLibraryA 주소 DD(); // LoadLibraryW 주소 DD(); // GetProcAddress 주소 DD(); // ExitProcess 주소 DD(); // OutputDebugStringA 주소 DD(); // OutputDebugStringW 주소 DD(); // VirtualQuery 주소 DD(); // VirtualProtect 주소 DD(); // kernel32.dll 종료
DD(); // wsprintfa 주소 DD(); // wsprintfw 주소 DD(); // user32.dll 종료 이렇게맂든후에 DLL 을빌드하면 < 화면 1> 에나타난것과같이 IAT 섹션이추가되고그곳에우 리가사용할함수이름이가지런히들어가있게된다. Z 프로텍터는이정보를참조해서 IAT 를생 성한다. 화면 1 컴파일된 DLL < 리스트 2> 와 < 리스트 3> 에 IAT 섹션의문자열을분석해서자료구조를생성하는코드가나와있다. 가장핵심적인자료구조는 DLL_ENTRY다. DLL_ENTRY는가져다쓰는각각의 DLL을저장하는용도로사용된다. DLL_ENTRY의 name은 dll 이름이, funcs에는그 dll에서가져다쓰는함수이름이저장된다. 사용하는 DLL의젂체목록은 m_iat에저장된다. m_iatsize는 IAT를기록하기위해서할당해야하는크기를나타내고, m_functionsize는룩업테이블 (IMPORT LOOKUP TABLE) 을저장하기위해서할당해야하는크기를나타낸다. 코드가금방이해하기힘들다면우선 < 그림 3> 을살펴보도록하자. < 그림 3> 은우리가생성할 최종적인 IAT 구조를나타내고있다. 왼쪽그림은메모리상에서각데이터가위치하고있는구조 를, 오른쪽은각구조가어떤형태로연결이되어있는지를보여준다.
그림 3 IAT 구조 리스트 2 IAT 생성에사용되는자료구조 typedef struct _DLL_ENTRY string name; // dll 이름 StringVec funcs; // 함수이름목록 DLL_ENTRY, *PDLL_ENTRY; typedef std::vector<dll_entry> DllEntryVec; typedef std::vector<dll_entry>::iterator DllEntryVit; DllEntryVec m_iat; // DLL, 함수이름저장공갂 DWORD m_iatsize; // IAT 크기 DWORD m_functionsize; // 함수크기 리스트 3 IAT 생성함수 void CStubCode::MakeIAT(LPBYTE data) char name[max_path]; int index = 0; FSM_STATE state = START; DLL_ENTRY entry; m_iat.clear(); m_iatsize = sizeof(image_import_descriptor); m_functionsize = 0; while(*data)
switch(state) case START: if(*data == '+') state = DLL; else if(*data == ' ') state = FUNC; break; case DLL: if(*data == '$') name[index] = '\0'; index = 0; state = START; entry.name = name; entry.funcs.clear(); m_iat.push_back(entry); m_iatsize += sizeof(image_import_descriptor); m_iatsize += entry.name.length() + 1; m_functionsize += sizeof(dword); else name[index] = *data; ++index; break; case FUNC: if(*data == '$') name[index] = '\0'; index = 0; state = START;
m_iat.back().funcs.push_back(name); m_iatsize += strlen(name) + 1 + sizeof(word); m_functionsize += sizeof(dword); else name[index] = *data; ++index; break; ++data; m_iatsize += m_functionsize; 실제로생성된구조체를사용해서 IAT를기록하는부분이 < 리스트 4> 에나와있다. 원본코드에서 IAT 관렦부분맂발췌한것이다. 인자로넘어오는 buf는스텁데이터가기록될버퍼를, bufsize 는버퍼크기를, imagebase는기록될이미지의기준주소를, codebase는기록될이미지에서코드가들어갈부분의 RVA를나타낸다. functions는 Entry에포함된실제 IAT가저장되는부분의시작주소를, lookups는룩업테이블의시작주소를, dllnames는 dll 이름을기록할부분의시작주소를, funcnames는함수이름을기록할부분의시작주소를나타낸다. 각오프셋의의미와젂체구조 (< 그림 3> 참고 ) 를정확하게이해하고있다면코드를쉽게이해할수있다. 한가지중요한사실은 IAT, ILT, 디스크립터테이블모두맀지링에 NULL을채워주어야한다는점이다. 리스트 4 IAT 기록함수 void CStubCode::Write(LPBYTE buf, SIZE_T bufsize, DWORD imagebase, DWORD codebase) IMAGE_IMPORT_DESCRIPTOR idesc; LPBYTE iat = buf + CodeSize(); LPDWORD lookups = (LPDWORD) (iat + (m_iat.size() + 1) * sizeof(idesc)); LPBYTE dllnames = (LPBYTE) lookups + m_functionsize; LPBYTE funcnames = dllnames; LPDWORD functions = (LPDWORD)(buf + m_entry + 9); StringVit sit;
StringVit send; DllEntryVit it; DllEntryVit end = m_iat.end(); for(it = m_iat.begin(); it!= end; ++it) funcnames += (*it).name.length() + 1; idesc.forwarderchain = 0; idesc.timedatestamp = 0; for(it = m_iat.begin(); it!= end; ++it) idesc.originalfirstthunk = Offset(lookups, buf, codebase); idesc.firstthunk = Offset(functions, buf, codebase); idesc.name = (DWORD)(dllNames - buf) + codebase; memcpy(iat, &idesc, sizeof(idesc)); iat += sizeof(idesc); strcpy((char *) dllnames, (*it).name.c_str()); dllnames += (*it).name.length() + 1; send = (*it).funcs.end(); for(sit = (*it).funcs.begin(); sit!= send; ++sit) *functions = Offset(funcNames, buf, codebase); ++functions; *lookups = Offset(funcNames, buf, codebase); ++lookups; funcnames[0] = 0; funcnames[1] = 0; strcpy((char *) funcnames+2, (*sit).c_str()); funcnames += (*sit).length() + 3; *functions = 0; ++functions;
*lookups = 0; ++lookups; idesc.originalfirstthunk = 0; idesc.firstthunk = 0; idesc.name = 0; memcpy(iat, &idesc, sizeof(idesc)); 메타데이터 메타데이터는스텁코드와원본실행파일사이를연결해주는데이터다. 프로텍터에의해서훼손된원본데이터중에서나중에필요한데이터를저장해두는공갂이라고생각하면쉽다. 필수적으로저장해야하는정보로는원본실행파일의짂입점과 IAT다. < 리스트 5> 에는 Z 프로텍터에사용된메타데이터의구조체가나와있다. 원본코드오프셋은암호화된부분을복호화시키기위해서사용된다. 리스트 5 메타데이터구조체 typedef struct _METADATA DWORD codeoffset; // 원본코드오프셋 DWORD codesize; // 원본코드사이즈 IMAGE_DATA_DIRECTORY import; // 원본 IAT DWORD originalentry; // 원본짂입점 METADATA, *PMETADATA; 이런필수적인정보외에도추가적으로필요한정보는어떠한것이든포함시킬수있다. 예를들 어사용자가지정한암호를입력해야맂프로그램이실행되도록해주는프로텍터라면메타데이터 에사용자가입력한암호가저장되어야할것이다. 프로텍터가 DLL 이나 OCX 를지원하도록맂들기위해서는원본실행파일의재배치정보도저장 해둘필요가있다. 더불어 IAT 를처리한방법과같은형태로스텁코드에대한재배치정보도재 구성해서같이저장해주어야한다. 스텁코드 앞서설명한내용을충분히이해했다면스텁코드를맂드는일은갂단한작업이다. < 리스트 6> 에 Z 프로텍터의스텁코드젂문이나와있다. 이코드는프로그램실행젂에갂단한디버그출력을
하고코드섹션을복호화한다음원본코드를실행시켜준다. 핵심은함수들을선얶해서사용하는부분이다. 앞서살펴보았던 IAT 처리하기부분과연결해서살펴보도록하자. 모든데이터를코드섹션에모으기위해서젂역변수는.L 섹션에선얶하고릿커명령어를통해서.text 섹션과병합해준다. 프로텍터가재배치를수행해주기때문에바이러스를맂들때처럼젂역변수를복잡하게사용할필요는없다. 한가지주의해야할점은디버그버젂으로빌드된스텁코드의경우단순히.text 섹션맂으로동작하지않는다는점이다. 따라서디버그버젂으로빌드된스텁코드를 Z 프로텍터로실행파일과병합시킬경우에는잘못된연산오류를맂날수있다. 스텁코드는항상릴리즈버젂으로빌드해서사용하도록한다. 리스트 6 프로그램실행젂에간단한디버그메시지를출력하는스텁코드 #pragma comment(linker, "/section:.text,rwe") #pragma comment(linker, "/merge:.l=.text") #define METADATA_OFFSET 5 #define LOADLIBRARYA_OFFSET (METADATA_OFFSET + 4) #define LOADLIBRARYW_OFFSET (METADATA_OFFSET + 8) // 중략 #define InitAPI(V, B, O) (*(FARPROC *) V = *(FARPROC *) GetPtr(B, O)) #pragma data_seg(".l") HMODULE (WINAPI *g_loadlibrarya)(lpcstr) = NULL; HMODULE (WINAPI *g_loadlibraryw)(lpcwstr) = NULL; FARPROC (WINAPI *g_getprocaddress)(hmodule, LPCSTR) = NULL; // 중략 char g_msg[] = "Z 프로텍터 \n"; PVOID g_base = NULL; PMETADATA g_meta = NULL; #pragma data_seg() void Decrypt() LPBYTE ptr; ptr = (LPBYTE) GetPtr(g_base, g_meta->codeoffset);
DWORD oldprotect; g_virtualprotect(ptr, g_meta->codesize, PAGE_EXECUTE_READWRITE, &oldprotect); for(dword i=0; i<g_meta->codesize; ++i) ptr[i] ^= 0x7f; g_virtualprotect(ptr, g_meta->codesize, oldprotect, NULL); void Stub1() InitAPI(&g_LoadLibraryA, Entry, LOADLIBRARYA_OFFSET); InitAPI(&g_LoadLibraryW, Entry, LOADLIBRARYW_OFFSET); InitAPI(&g_GetProcAddress, Entry, GETPROCADDRESS_OFFSET); // 중략 g_outputdebugstringa(g_msg); MEMORY_BASIC_INFORMATION mbi; g_virtualquery(stub1, &mbi, sizeof(mbi)); g_base = mbi.allocationbase; DWORD *meta_offset; GetPtr(&meta_offset, Entry, METADATA_OFFSET); g_meta = (PMETADATA) GetPtr(g_base, *meta_offset); // 복호화 Decrypt(); // 원본 IAT 빌드 BuildIAT(); // 원본코드로점프 DWORD oep; GetPtr(&oep, g_base, g_meta->originalentry); asm push oep asm ret
Z 프로텍터 이제실제스텁코드를실행파일과연결시키는프로텍터부분을살펴보도록하자. 이부분은기본적인 PE 파일의구조에대해서맂이해하고있다면어려운부분이아니다. 단지작업과정이복잡하기때문에어떤부분을해야하는지를정확하게알아둘필요가있다. 젂체작업과정을개략적으로살펴보면아래나와있는것과같다. IAT 생성부분은앞에서살펴보았고, 재배치나섹션을추가하는등의작업은이젂연재를통해서충분히다룪내용이기때문에특별히어려운부분은없다. 각작업과정과그부분이소스에서어떻게구현되어있는지를연결시켜보도록하자. 섹션의정렧 (align) 을맞추지않는것과같은사소한실수때문에실행파일이동작하지않는일이맃기때문에 PE 헤더를수정할때에는항상주의를해야한다. 젂반적인과정에대한소스코드가 < 리스트 7> 에, NT 헤더에서수정해야할부분에대한별도의설명이 < 표 1> 에나와있다. < 화면 2> 에는스텁코드와프로텍터를빌드해서실행시키는방법이나와있다. 1. 스텁을추가할섹션생성 2. 메타데이터수집 3. 스텁코드기록 4. 스텁코드재배치 5. 스텁 IAT 생성및기록 6. 메타데이터기록 7. 추가할섹션헤더생성및기록 8. NT 헤더수정 9. 원본코드암호화 스텁코드의 IAT 생성과재배치를담당하는 CStubCode, PE 포맷을추상화한 CRawPeformat, 메모 리맵을쓰기편하게맂든 CMmap 등은지면관계상담지못했다. 이부분이궁금한독자들은 이달의디스크를참고하도록하자. 표 1 NT 헤더에서수정해야할부분들수정부분설명 AddressOfEntryPoint 스텁코드의짂입점으로변경해주어야한다. SizeOfImage 추가된스텁코드의크기맂큼증가시켜주어야한다. IMAGE_DIRECTORY_ENTRY_IMPORT 추가된스텁코드의임포트정보를가리키도록변경해주어야한다. NumberOfSection 스텁코드를별도의섹션에저장한경우에는 1증가시켜준다. BaseOfCode 스텁코드가저장된섹션의시작주소로변경해준다.
리스트 7 Z 프로텍터소스코드 int _tmain(int argc, TCHAR *argv[]) if(argc < 4) printf(" 사용법 : %s 실행파일명스텁파일명출력파일명 \n", argv[0]); return 0; try CMmap mmap(argv[1], 0); CRawPeformat pe(mmap.ptr()); int nsec = pe.numberofsection(); PIMAGE_SECTION_HEADER sec = pe.sectionheader(nsec-1); DWORD eof = sec->pointertorawdata + sec->sizeofrawdata; if(eof < mmap.filesize()) return 0; PIMAGE_NT_HEADERS nt = pe.ntheader(); DWORD salign = nt->optionalheader.sectionalignment; DWORD falign = nt->optionalheader.filealignment; PIMAGE_SECTION_HEADER codesection = FindCodeSection(pe); if(codesection == NULL) printf(" 오류 : 코드섹션을찾을수없습니다."); return 0; CStubCode stub(argv[2]); METADATA meta; meta.codeoffset = codesection->virtualaddress; meta.codesize = codesection->misc.virtualsize; meta.originalentry = pe.entrypoint(); meta.import = *pe.datadirectory(image_directory_entry_import); stub.metadata(&meta, sizeof(meta));
FILE *fp; _tfopen_s(&fp, argv[3], _T("wb")); if(!fp) printf(" 오류 : 파일을열수없습니다.\n"); return 0; // 파일젂체복사 fwrite(mmap.ptr(), 1, mmap.filesize(), fp); // 스텁섹션헤더기록 IMAGE_SECTION_HEADER sechdr; sechdr.characteristics = IMAGE_SCN_CNT_CODE IMAGE_SCN_MEM_EXECUTE IMAGE_SCN_MEM_READ IMAGE_SCN_MEM_WRITE; sechdr.misc.virtualsize = stub.size() + 10; sechdr.numberoflinenumbers = 0; sechdr.numberofrelocations = 0; sechdr.pointertolinenumbers = 0; sechdr.pointertorelocations = 0; sechdr.pointertorawdata = Align(sec->PointerToRawData + sec->sizeofrawdata, falign); sechdr.sizeofrawdata = Align(stub.Size() + 10, falign); sechdr.virtualaddress = Align(sec->VirtualAddress + sec->misc.virtualsize, salign); memcpy(sechdr.name, ".Z", 3); fseek(fp, mmap.offset(sec+1), SEEK_SET); fwrite(&sechdr, sizeof(sechdr), 1, fp); // NT 헤더수정 IMAGE_NT_HEADERS hdr;
memcpy(&hdr, pe.ntheader(), sizeof(hdr)); hdr.optionalheader.sizeofimage = Align(hdr.OptionalHeader.SizeOfImage + sechdr.misc.virtualsize, salign); hdr.optionalheader.datadirectory[image_directory_entry_import].virtualaddress = sechdr.virtualaddress + stub.codesize(); hdr.optionalheader.datadirectory[image_directory_entry_import].size = stub.iatsize(); hdr.optionalheader.addressofentrypoint = sechdr.virtualaddress + stub.entry(); hdr.fileheader.numberofsections++; hdr.optionalheader.baseofcode = sechdr.virtualaddress; fseek(fp, mmap.offset(pe.ntheader()), SEEK_SET); fwrite(&hdr, sizeof(hdr), 1, fp); // 코드섹션암호화 CCodeEncryptor enc(pe, codesection); fseek(fp, enc.offset(), SEEK_SET); fwrite(enc.ptr(), 1, enc.size(), fp); // 스텁데이터기록 PBYTE buf = new BYTE[secHdr.SizeOfRawData]; if(!buf) printf(" 오류 : 메모리가부족합니다.\n"); fclose(fp); return 0; ZeroMemory(buf, sechdr.sizeofrawdata); stub.write(buf, sechdr.misc.virtualsize, nt->optionalheader.imagebase, sechdr.virtualaddress); fseek(fp, sechdr.pointertorawdata, SEEK_SET); fwrite(buf, 1, sechdr.sizeofrawdata, fp); fclose(fp);
catch(ewin32 &err) printf(" 오류 : %d\n", err.errorcode()); return 0; 화면 2 Z 프로텍터실행화면 도젂과제 이번시갂에짂행한내용은실행파일프로텍터제작을위한기반구조에관한내용들이었다. 실제프로텍터제작은여러분이어느정도로강력한스텁코드를제작하는가에달려있다. 지난시갂에배운내용을토대로다양한스텁코드를제작해보자. 심도있는 PE 프로텍터를제작해보고싶은독자라면상용 PE 프로텍터에포함된기능들을구현해보는것도도움이될것같다. 참고자료 Make your owner PE Protector Part 1: Your first EXE Protector http://www.codeproject.com/kb/cpp/peprotector1.aspx