Make your owner PE Protector Part 1: Your first EXE Protector

本文介绍如何使用Visual C++ Win32编程制作强大的EXE保护器和打包器。涵盖PE文件结构、打开与验证、创建额外部分、加密与打包、导入表处理、反调试方法等内容,还给出示例代码,适用于除Windows NT 4.0和95外的Windows系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Yoda's Protector v1.01 - Free EXE Protector

Preface

This article was written to provide the better understanding to people who do not have any experience in this field. I am not going to describe about PE structure, so I think it was explained enough in Matt Pietrek’s articles in MSDN. I strongly recommend to read his article before continuing to read this article if you don’t have any previous experience with PE structure. You could find useful the relevant reference link at the end of article.

This article will appear in three parts:

  1. Your first EXE Protector

    This part will describe how to make a powerful EXE protector and packer by using Visual C++ Win32 Programming.

  2. Support OCX, DLL, and SCR

    This part will introduce to include other PE type in additional to EXE file type. It will explain some tricks which you should know for OCX and DLL protecting.

  3. Use Cryptography API

    This part will demonstrate how to use Microsoft Cryptography API function to encrypt and decrypt PE section information.

This article contains yoda’s protector source from version 1.01 to 1.03. It is first time people can look at its source after six month of appearance on Web. It is based on [6] yoda’s Crypter assembly source by Danilo Bzdok and compression source from [7] UPX library by Markus F.X.J. Oberhumer & László Molnár and [9] aPLib compression library by Joergen Ibsen. Thus, we should appreciate them for helping me to create this tool. I also should be grateful to people for trying and testing it on different Windows versions around the world. I think this article will be a small present to all people who involve and assist to make yoda’s protector.

Contents

Introduction

The Portable Executable format is standard format under Microsoft Windows NT ® operating system. It contains information for code, data, resource, dynamic link libraries importation. It is modifiable by using recent powerful debuggers such as OllyDbg or SoftICE with a little knowledge about assembly language. It causes to waste the time of software development companies to obtain money for their productions. Therefore, they are led to purchase the tool like EXE protector to prevent from illegal copy. My idea is what will happen if every person has owner EXE protector. Cracker person will face different EXE protector with different methods. Thus, I think each of us can have their own EXE protector.

A short aspect about PE Structure

The Portable Executable includes information for the MS-DOS, the Windows NT, and Sections. This information is provided for Windows Operating System to allocate memory, import dynamic link libraries, and perform code.

Table 1
MS-DOS informationIMAGE_DOS_HEADER
MS-DOS Stub Program
Windows NT informationPE Signature ("PE")
IMAGE_FILE_HEADER
IMAGE_OPTIONAL_HEADER
Sections information

IMAGE_SECTION_HEADER[0]

IMAGE_SECTION_HEADER[n]

SECTION[0]

SECTION[n]

You can find more about PE file format in [1] “Microsoft Portable Executable and Common Object File Format Specification”. Matt Pietrek clarifies it enough in [2] “Peering Inside the PE: A Tour of the Win32 Portable Executable File Format”, and [3a/b] “An In-Depth Look into the Win32 Portable Executable File Format”.

Moreover, PEView [4] by Wayne J. Radburn will help you to find all aspects about PE file format.

Figure 1

PEView

All the required data structures for PE file format are included in winnt.h file inside your Visual C++. IMAGE_DOS_HEADER, IMAGE_NT_HEADERS, IMAGE_SECTION_HEADER structures represent all you need to work with PE file format. The relevant information for these structures could be found in [5] MSDN library.

Open PE files

We have to load PE file format to memory for working with its information. Some Windows API function will help us do it very easy: CreateFile(), GetFileSize(), GlobalAlloc(), ReadFile(), CloseHandle().

I make a class to work with PE files. It helps me to open files and put DOS header, NT headers, Section Headers, and Sections into separate places in memory and then rebuild all as new PE files.

class PEStructure 
{
private:
      DWORD ReservedHeaderSize;
      DWORD ReservedHeaderRO;
public:
      DWORD                   dwRO_first_section;
      IMAGE_DOS_HEADER        image_dos_header;
      char                    *reservedheader;
      IMAGE_NT_HEADERS        image_nt_headers;
      IMAGE_SECTION_HEADER    image_section_header[MAX_SECTION_NUM];
      char                    *image_section[MAX_SECTION_NUM];
      void OpenFileName(char* FileName);
      void UpdateHeaders(BOOL bSaveAndValidate);
      void UpdateHeadersSections(BOOL bSaveAndValidate);
      void Free();
};

OpenFileName() will open PE files to place in image_dos_header, reservedheader, image_nt_headers, image_section_header[], and image_section[]. All PE structures will abstract by this function in my tool.

void PEStructure::OpenFileName(char* FileName)
{
      hFile=CreateFile(FileName,
                       GENERIC_READ,
                       FILE_SHARE_WRITE | FILE_SHARE_READ,
                       NULL,OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL,NULL);
      if(hFile==INVALID_HANDLE_VALUE)
      {
            ShowErr(FileErr);
            return;
      }
      dwFsize=GetFileSize(hFile,0);
      if(dwFsize == 0)
      {
            CloseHandle(hFile);
            ShowErr(FsizeErr);
            return;
      }
      dwOutPutSize=
          dwFsize+IT_SIZE+DEPACKER_CODE_SIZE+ALIGN_CORRECTION;
      pMem=(char*)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
                                             dwOutPutSize);
      if(pMem == NULL)
      {
            CloseHandle(hFile);
            ShowErr(MemErr);
            return;
      }
      ReadFile(hFile,pMem,dwFsize,&dwBytesRead,NULL);
      CloseHandle(hFile);
      CopyMemory(&image_dos_header,pMem,sizeof(IMAGE_DOS_HEADER));
      ReservedHeaderRO=sizeof(IMAGE_DOS_HEADER);
      ReservedHeaderSize=
             image_dos_header.e_lfanew-sizeof(IMAGE_DOS_HEADER);
      reservedheader=new TCHAR[ReservedHeaderSize];
      CopyMemory(&image_nt_headers,
                 pMem+image_dos_header.e_lfanew,
                 sizeof(IMAGE_NT_HEADERS));
      dwRO_first_section = 
           image_dos_header.e_lfanew + sizeof(IMAGE_NT_HEADERS);
      UpdateHeadersSections(TRUE);
}

Verify if PE file is valid

It is important to verify whether the file is a Win32 Portable Executable file by checking e_magic of image_dos_header and Signature of image_nt_header to prevent of unpredictable fault.

if(PEfile.image_dos_header.e_magic!='ZM')
{
      GlobalFree(pMem);
      CloseHandle(hFile);
      if(MakeBackup) DeleteFile(szFnameBackup);
      ShowErr(PEErr);
      return;
}
if(PEfile.image_nt_headers.Signature!='EP')
{
      GlobalFree(pMem);
      CloseHandle(hFile);
      if(MakeBackup) DeleteFile(szFnameBackup);
      ShowErr(PEErr);
      return;
}

Make Extra Section

There is a trick inside yoda’s Protector to create extra section for protection and unpacking purposes. Visual C++ aids it to make this extra part without using of assembler compiler and linker. If you look at PE_LOADER_CODE(), subroutine inside the CryptStuff.cpp you will find what I am talking about. This is same as the method that was done by Danilo Bzdok in his [6] yoda’s Crypter. Of course, he did it only by using assembly language in MASM32 without any high level language. I made GetFunctionRVA(), GetFunctionSize() ,and CopyFunction() to rob code from PE_LOAD_CODE() and use it to create additional section for target PE file.

DWORD GetFunctionRVA(void* FuncName)
{
      void *_tempFuncName=FuncName;
      char *ptempFuncName=PCHAR(_tempFuncName);
      DWORD _jmpdwRVA,dwRVA;
      CopyMemory(&_jmpdwRVA,ptempFuncName+1,4);
      dwRVA=DWORD(ptempFuncName)+_jmpdwRVA+5;
      return(dwRVA);
}
DWORD GetFunctionSize(void* FuncName)
{
      DWORD dwRVA=GetFunctionRVA(FuncName);
      char* pFuncBody=PCHAR(dwRVA);
      UCHAR _temp;
      bool notEnd=TRUE;
      char *DepackerCodeEnd=new TCHAR[10];
      DWORD l=0;
      do
      {
            CopyMemory(&_temp,pFuncBody+l,1);
            if(_temp==0xC3)
            {
                  CopyMemory(DepackerCodeEnd,pFuncBody+l+0x01,10);
                  DepackerCodeEnd[9]=0x00;
                  if(strcmp(DepackerCodeEnd,"ETGXZKATZ")==0)
                  {
                        notEnd=FALSE;
                  }
            }
            l++;
      }while(notEnd);
      return(l);
}

GetFunctionRVA() will seek to Relative Virtual Address of specific subroutine to use by GetFunctionSize() and CopyFunction(). GetFunctionSize() will return the size of target routine to be exploited by CopyFunction(). It seeks a keyword (“ETGXZKATZ”) to calculate routine size. Finally, CopyFunction() is a complete routine to steal all code from PE_LOADER_CODE() subroutine to place in packed PE file.

char* CopyFunction(void* FuncName)
{
    DWORD dwRVA=GetFunctionRVA(FuncName);
    DWORD dwSize=GetFunctionSize(FuncName);
    char* pFuncBody=PCHAR(dwRVA);
    char* filebuff=new TCHAR[dwSize+1];
    CopyMemory(filebuff,pFuncBody,dwSize);
    return(filebuff);
}

This method can illuminate in the following code:

char    *pDepackerCode;
DWORD    DEPACKER_CODE_SIZE;
…
DEPACKER_CODE_SIZE=GetFunctionSize(PE_LOADER_CODE);
pDepackerCode=new TCHAR[DEPACKER_CODE_SIZE];
pDepackerCode=CopyFunction(PE_LOADER_CODE);
…

void PE_LOADER_CODE()
{
      _asm
      {
      //----------------------------------------------------------
      //-------------- START OF THE PE LOADER CODE ---------------
DepackerCode:
      …
      …
      …
DepackerCodeEND:
    RET
    //"ETGXZKATZ" <<-- KEY WORD
    INC EBP     //'E'
    PUSH ESP    //'T'
    INC EDI     //'G'
    POP EAX     //'X'
    POP EDX     //'Z'
    DEC EBX     //'K'
    INC ECX     //'A'
    PUSH ESP    //'T'
    POP EDX     //'Z'
    }
}

Pack and Crypt Sections

UPX compressor source [7] code is an alternative choice to pack sections of PE files. I use [8] LZO data compression library by Markus F.X.J. Oberhumer to pack code and data section. Polymorphism encryption and decryption method [6] by Danilo Bzdok is simple and good enough to crypt PE section by some modification in C++ language as you see inside EncryptBuff() and DecryptBuff() in PER.CPP.

This protector separates Sections in different allocation parts of memory. Afterwards it packs and crypts part of sections by CompressPE() and CryptPE().

//------ ENCRYPT THE SECTIONS -----
// generate PER
PEfile.UpdateHeadersSections(TRUE);
SecEncryptBuff=new TCHAR[SEC_PER_SIZE];
SecDecryptBuff=new TCHAR[SEC_PER_SIZE];
MakePER(SecEncryptBuff,SecDecryptBuff,SEC_PER_SIZE);
CopyMemory(pDepackerCode+dwRO_SEC_DECRYPT,
           SecDecryptBuff,
           SEC_PER_SIZE);    
// encrypt !
CompressPE(pMem);
CryptPE(pMem);
RemoveSectionNames(pMem);
newsection.Misc.VirtualSize=DepackCodeVirtualSize+0x2000;
PEfile.image_section_header[
        PEfile.image_nt_headers.FileHeader.NumberOfSections-1]
        .Misc.VirtualSize = newsection.Misc.VirtualSize;
PEfile.UpdateHeadersSections(FALSE);
//---------------------------------

LZO data compression library [8] has compressor source in C++ and decompressor source in both C++ and assembly. Hence, we have all sources to pack in high level language and unpack in low level language. I used lzo1x_999_compress_level() from LZO library to compress sections inside CompressPE() and its assembly source lzo1f_decompress_asm_fast_safe() to decompress sections inside _DecompressPE() in assembly source of PE_LOADER_CODE().

It is important to pay attention to image_nt_headers.OptionalHeader.SectionAlignment and image_nt_headers.OptionalHeader.FileAlignment to prevent of Win32 incompatible file error in Windows version above Windows 98. Variables in IMAGE_SECTION_HEADER should be adapted to file-alignment and section-alignment. I reserved UpdateHeadersSections() function to retrieve and rebuilt all PE file format structures.

void PEStructure::UpdateHeadersSections(BOOL bSaveAndValidate)
{
    DWORD i;
    if(bSaveAndValidate)//TRUE = data is being retrieved
    {
        DWORD SectionNum = PEfile.image_nt_headers
                           .FileHeader.NumberOfSections;
        CopyMemory(&image_dos_header,pMem,sizeof(IMAGE_DOS_HEADER));
        ReservedHeaderSize = image_dos_header.e_lfanew –
                             sizeof(IMAGE_DOS_HEADER);
        if((ReservedHeaderSize&0x80000000)==0x00000000)
        {
            CopyMemory(reservedheader, 
                       pMem+ReservedHeaderRO,
                       ReservedHeaderSize);
        }
        CopyMemory(&image_nt_headers,
             pMem+image_dos_header.e_lfanew,
             sizeof(IMAGE_NT_HEADERS));
     dwRO_first_section = image_dos_header.e_lfanew +
                            sizeof(IMAGE_NT_HEADERS);
       CopyMemory(&image_section_header, 
                  pMem+dwRO_first_section, 
                  SectionNum*sizeof(IMAGE_SECTION_HEADER));
       for(i=0;i< SectionNum;i++)
       {
           image_section[i] = (char*)GlobalAlloc(
                GMEM_FIXED | GMEM_ZEROINIT,
            PEAlign(image_section_header[i].SizeOfRawData,
                PEfile.image_nt_headers.OptionalHeader.FileAlignment));
           CopyMemory(image_section[i],
               pMem + image_section_header[i].PointerToRawData,
               image_section_header[i].SizeOfRawData);
       }
    }
   else//FALSE = data is being initialized 
   {
       DWORD SectionNum = PEfile.image_nt_headers
                          .FileHeader.NumberOfSections;
       CopyMemory(pMem,
            &image_dos_header,sizeof(IMAGE_DOS_HEADER));
            ReservedHeaderSize=image_dos_header.e_lfanew –
                               sizeof(IMAGE_DOS_HEADER);
       if((ReservedHeaderSize&0x80000000)==0x00000000)
       {
          CopyMemory(pMem + ReservedHeaderRO,
                     reservedheader,
                     ReservedHeaderSize);
       }
       CopyMemory(pMem+image_dos_header.e_lfanew,
            &image_nt_headers,
            sizeof(IMAGE_NT_HEADERS));
       dwRO_first_section = image_dos_header.e_lfanew +
                            sizeof(IMAGE_NT_HEADERS);
       CopyMemory(pMem+dwRO_first_section,
                 &image_section_header,
                 SectionNum*sizeof(IMAGE_SECTION_HEADER));
       for(i=0;i< SectionNum;i++)
       {
           CopyMemory(pMem+image_section_header[i].PointerToRawData,
                      image_section[i],
                      image_section_header[i].SizeOfRawData);
       }
    }
} 

Built Import Table Directory

PE unpack section need to import two essential API functions to load dynamically all other API functions in Run-time load. LoadLibraryA() and GetProcAddress() from Kernel32.dll are vital functions to import other API function with some tricky methods. AssembleIT() is reserved to undertake this task. We should change the import table address and size for turning to new import table directory inside extra section. To retrieve the old import table, it has to reload and rebuild import table directory in the next step to perform program code. Loader section uses LoadLibraryA() and GetProcAddress() to seek relative virtual address of importation function of dynamic link libraries.

Table 2
Import Table Address ->"Kernel32.dll", 0x00
 LoadLibrary_RVA
 GetProcessAddres_RVA
 0x00,0x00
LoadLibrary_RVA ->0x00,0x00,"LoadLibraryA"
GetProcessAddres_RVA ->0x00,0x00,"GetProcessAddress"

Reload Import Table and API Redirection

It is important to protect import table directory from reverse engineering process. Danilo Bzdok has used technical methods to destroy import thunk data and crypt import information in [6] yoda’s Crypter. This part is retrievable again by loader code section. I applied his methods in my PE Protector with bringing some part of code to C and remind other part in assembly. ProcessOrgIT() provides all we need to do our purpose. In loader section, INIT_IMPORT_TABLE() routine implements our point about API redirection and Import table rebuilt.

Anti-debug methods

PE Protector should able to detect if program debugs and prevent from debugging. OllyDbg and SoftICE are two important debuggers that can bypass many tricks which cause to halt debuggers. However, I should introduce some simple methods to detect debuggers. I know all of these methods do not have any effect in recently added plug-ins for the mentioned debuggers.

  1. IsDebuggerPresent Windows API: IsDebuggerPresent() will return non zero value whenever the current process is running in the context of a debugger.
  2. SoftICE detection: It can find SoftICE debugger by check if NTICE driver in windows NT and SICE driver in windows 98 are active.
    if(CreateFile( ".//NTICE", GENERIC_READ | GENERIC_WRITE, 
          FILE_SHARE_READ | FILE_SHARE_WRITE, 
          NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
          NULL)!=INVALID_HANDLE_VALUE) 
    { 
        There is SoftICE NT on your system; 
    } 
    
    if(CreateFile( ".//SICE", GENERIC_READ | GENERIC_WRITE, 
          FILE_SHARE_READ | FILE_SHARE_WRITE, 
          NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
          NULL)!=INVALID_HANDLE_VALUE) 
    { 
        There is SoftICE98 on your system; 
    }
    
  3. Probe Processes: Sometimes, it needs to search for specific process or isolates to specific process. It was demonstrated how finding parent process and check if it is EXPLORER.EXE and killing all parent process except Explorer windows in sample source code. There are both C++ and assembly code inside CryptStuff.CPP to illuminate this task.
    void GetFileNameFromPath(char* szSource)
    {
          char *szTemp=strrchr(szSource,'//');    
          if(szTemp!=NULL)
          {
                szTemp++;
                DWORD l=DWORD(strlen(szTemp))+1;
                CopyMemory(szSource,szTemp,l);
          }
    }
    
    void AntiDebug()
    {
          char lpszSystemInfo[MAX_PATH];
          HANDLE hSnapshot=NULL;
          DWORD PID_child;
          DWORD PID_parent,PID_explorer;
          HANDLE hh_parnet = NULL;
          PROCESSENTRY32    pe32 = {0};
          pe32.dwSize = sizeof(PROCESSENTRY32);//0x128;
          PID_child=GetCurrentProcessId();//getpid();
          hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
          if (Process32First(hSnapshot, &pe32))
          {
                while (Process32Next(hSnapshot, &pe32))
                {
                      GetFileNameFromPath(pe32.szExeFile);
                      CharUpperBuff(pe32.szExeFile,strlen(pe32.szExeFile));
                      if(strcmp(pe32.szExeFile,"EXPLORER.EXE")==0)
                      {
                            PID_explorer=pe32.th32ProcessID;
                      }
                      if(pe32.th32ProcessID==PID_child)
                      {
                            PID_parent=pe32.th32ParentProcessID;
                      }
                }
          }
          if(PID_parent!=PID_explorer)
          {
                hh_parnet= OpenProcess(PROCESS_ALL_ACCESS, 
                                        TRUE, PID_parent);
                TerminateProcess(hh_parnet, 0);
          }
          else
          {
                MODULEENTRY32    me32 = {0};
                me32.dwSize = sizeof(MODULEENTRY32);
                hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
                            PID_explorer);
                if (Module32First(hSnapshot, &me32))
                {
                      do
                      {
                            if(PID_explorer==me32.th32ProcessID)
                            {
                                  GetWindowsDirectory(lpszSystemInfo,
                                   MAX_PATH+1);
                                  strcat(lpszSystemInfo,"//");
                                  strcat(lpszSystemInfo,"EXPLORER.EXE");
                          CharUpperBuff(me32.szExePath,
                                        strlen(me32.szExePath));
                                  if(strncmp(me32.szExePath,
                                              lpszSystemInfo,
                                        strlen(lpszSystemInfo)))
                                  {
                                     GetFileNameFromPath(me32.szExePath);
                                     if(strcmp(me32.szExePath,
                                        "EXPLORER.EXE")==0)
                                     {
                                        hh_parnet= 
                                            OpenProcess(PROCESS_ALL_ACCESS,
                                              TRUE, PID_explorer);
                                        TerminateProcess(hh_parnet, 0);
                                     }
                                  }
                            }
                      }while (Module32Next(hSnapshot, &me32));
                }
          }
    }
    

    This code will not work under Windows NT 4.0 because of absence CreateToolhelp32Snapshot(), Process32First(), Process32Next(), Module32First(), Module32Next(). But all of these API functions could be build by undocumented API function from NTDLL.DLL. You can take a look at [10] ‘Windows NT (2000) Native API reference’ by Gary Nebbett.

    Furthermore, I recommend using [11] Process Explorer by Mark Russinovich to explore all running processes in your systems. It will help to understand them better.

    Figure 2

    Process Explorer

Eliminate unnecessary data

Some time you need to clean all unnecessary data such as debug information, relocation section and make small DOS header and remove MS-DOS stub Program. You should reserve these items in your PE Protector with some considerations. For instance, Relocation section do not have any effect in running EXE files but it plays an important role in OLE-Active Controls and Dynamic Link Libraries.

Sample code

The project compiles with Visual C++ .NET 2003 and doesn’t require any spare tools. It works under all Windows version except Windows NT 4.0 and Windows 95.

Conclusion

This article and its source could be an introduction to PE protector tools and demonstrating how these tools works. I hope it covers the absence of this kind of interesting topics and tools in open source area.

References

[1] "Microsoft Portable Executable and Common Object File Format Specification", Microsoft Corporation, Revision 6.0, February 1999.

[2] " Peering Inside the PE: A Tour of the Win32 Portable Executable File Format", Matt Pietrek, MSDN Library, March 1994.

[3a] "An In-Depth Look into the Win32 Portable Executable File Format", part 1, Matt Pietrek, MSDN Magazine, February 2002.

[3b] "An In-Depth Look into the Win32 Portable Executable File Format", part 2, Matt Pietrek, MSDN Magazine, March 2002.

[4] PEview Version 0.67, Wayne J. Radburn.

[5] MSDN Library, Microsoft Corporation, April 2003.

[6] yoda’s Crypter, Danilo Bzdok.

[7] UPX, the Ultimate Packer for eXecutables, Markus F.X.J. Oberhumer & László Molnár.

[8] LZO real-time data compression library, Markus F.X.J. Oberhumer.

[9] aPLib compression library, Joergen Ibsen.

[10] "Windows NT (2000) Native API reference", Gary Nebbet.

[11] Process Explorer, Mark Russinovich.

Ashkbiz Danehkar


Click here to view Ashkbiz Danehkar's online profile.


Other popular articles:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值