几种常用的保护应用程序的方法,编写成类库

来源:互联网 发布:软件封装教程 编辑:程序博客网 时间:2024/05/17 21:43
几种常用的保护应用程序的方法 ( 作者:老实和尚  coverlove@163.com )1、前言  目前很多程序员都没有软件安全的意识,对自己辛辛苦苦的劳动成果不加保护,而这些缺乏保护意识的程序在日益强大的SoftIce, OllyDBG(OllyICE), W32DASM等工具面前显得如此脆弱,一个稍微有点经验的Cracker可能在几分钟之内就可以轻易的突破防线;有些程序员虽然有了保护自己程序的意识,但是一般都完全依赖于专业的保护软件进行加壳保护,对自己的程序本身在编码阶段却完全不设防,而这些知名的壳也并非安全,目前对这些知名的壳都有专门的脱壳工具和脱壳教材,一旦这些壳被剥掉,那么呈现在Cracker面前的也完全是一些赤裸裸的代码。  本文主要介绍在软件编码阶段进行一些必要的保护手段,在编译生成二进制代码后再进行外部加壳保护处理,这样应该更安全一些,一旦壳被攻破,还有内部的保护机制在起作用,已经写成类库,觉得有用的可以下载稍微修改即可使用。2、常用的几种编码阶段的保护手段A、检测SoftIce驱动是否安装检测SoffIce的驱动是否存在来判断是否安装了SoftIce,代码如下: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;   }B. 程序窗口句柄检测  检测是否存在窗口Ollyice或者OllyDbg等等,读者可以自行添加char name1[]="OLLYDBG";char name2[]="OLLYICE";HWND hwnd=BlurFindWindow(name1); //循环检测windows所有顶层窗口,只要窗口名称中含有(模糊匹配)字符串name1,name2的窗口则说明存在Ollyice或者OllyDbgif(hwnd!=NULL){  There is OLLYDBG on your system;}hwnd=BlurFindWindow(name2);if(hwnd!=NULL){  There is OLLYICE on your system;}C. 用线程环境块检测TEB(Thread Environment Block) 在 Windows 9x 系列中被称为 TIB(Thread Information Block),它记录了线程的重要信息,而且每一个线程都会对应一个 TEB 结构。在TEB结构的 30h 偏移处存放着另外一个重要的数据结构的首地址PPEB:typedef struct _NT_TEB{ NT_TIB Tib; // 00h PVOID EnvironmentPointer; // 1Ch CLIENT_ID Cid; // 20h PVOID ActiveRpcInfo; // 28h PVOID ThreadLocalStoragePointer; // 2Ch PPEB Peb; // 30h ULONG LastErrorValue; // 34h ULONG CountOfOwnedCriticalSections; // 38h PVOID CsrClientThread; // 3Ch PVOID Win32ThreadInfo; // 40h ULONG Win32ClientInfo[0x1F]; // 44h PVOID WOW32Reserved; // C0h ULONG CurrentLocale; // C4h ULONG FpSoftwareStatusRegister; // C8h PVOID SystemReserved1[0x36]; // CCh PVOID Spare1; // 1A4h LONG ExceptionCode; // 1A8h ULONG SpareBytes1[0x28]; // 1ACh PVOID SystemReserved2[0xA]; // 1D4h GDI_TEB_BATCH GdiTebBatch; // 1FCh ULONG gdiRgn; // 6DCh ULONG gdiPen; // 6E0h ULONG gdiBrush; // 6E4h CLIENT_ID RealClientId; // 6E8h PVOID GdiCachedProcessHandle; // 6F0h ULONG GdiClientPID; // 6F4h ULONG GdiClientTID; // 6F8h PVOID GdiThreadLocaleInfo; // 6FCh PVOID UserReserved[5]; // 700h PVOID glDispatchTable[0x118]; // 714h ULONG glReserved1[0x1A]; // B74h PVOID glReserved2; // BDCh PVOID glSectionInfo; // BE0h PVOID glSection; // BE4h PVOID glTable; // BE8h PVOID glCurrentRC; // BECh PVOID glContext; // BF0h NTSTATUS LastStatusValue; // BF4h UNICODE_STRING StaticUnicodeString; // BF8h WCHAR StaticUnicodeBuffer[0x105]; // C00h PVOID DeallocationStack; // E0Ch PVOID TlsSlots[0x40]; // E10h LIST_ENTRY TlsLinks; // F10h PVOID Vdm; // F18h PVOID ReservedForNtRpc; // F1Ch PVOID DbgSsReserved[0x2]; // F20h ULONG HardErrorDisabled; // F28h PVOID Instrumentation[0x10]; // F2Ch PVOID WinSockData; // F6Ch ULONG GdiBatchCount; // F70h ULONG Spare2; // F74h ULONG Spare3; // F78h ULONG Spare4; // F7Ch PVOID ReservedForOle; // F80h ULONG WaitingOnLoaderLock; // F84h PVOID StackCommit; // F88h PVOID StackCommitMax; // F8Ch PVOID StackReserve; // F90h PVOID MessageQueue; // ???} NT_TEB, *PNT_TEB; PPEB  Peb;       // 30h Pointer to owning process database这个偏移地址处的内容非常有用,它指向本线程的拥有者的 PEB(Process Database) 的线性地址。这个结构的02h 偏移处存放着进程是否被调试的标志。当有调试器调试进程时这个BeingDebugged会被置1,否则为0,可以通过这个标志来检测是否被调试器调试。typedef struct _PEB{      UCHAR InheritedAddressSpace;                     // 00h      UCHAR ReadImageFileExecOptions;                  // 01h      UCHAR BeingDebugged;                             // 02h      UCHAR Spare;                                     // 03h      PVOID Mutant;                                    // 04h      PVOID ImageBaseAddress;                          // 08h      PPEB_LDR_DATA Ldr;                               // 0Ch      PRTL_USER_PROCESS_PARAMETERS ProcessParameters;  // 10h      PVOID SubSystemData;                             // 14h      PVOID ProcessHeap;                               // 18h      PVOID FastPebLock;                               // 1Ch      PPEBLOCKROUTINE FastPebLockRoutine;              // 20h      PPEBLOCKROUTINE FastPebUnlockRoutine;            // 24h      ULONG EnvironmentUpdateCount;                    // 28h      PVOID* KernelCallbackTable;                      // 2Ch      PVOID EventLogSection;                           // 30h      PVOID EventLog;                                  // 34h      PPEB_FREE_BLOCK FreeList;                        // 38h      ULONG TlsExpansionCounter;                       // 3Ch      PVOID TlsBitmap;                                 // 40h      ULONG TlsBitmapBits[0x2];                        // 44h      PVOID ReadOnlySharedMemoryBase;                  // 4Ch      PVOID ReadOnlySharedMemoryHeap;                  // 50h      PVOID* ReadOnlyStaticServerData;                 // 54h      PVOID AnsiCodePageData;                          // 58h      PVOID OemCodePageData;                           // 5Ch      PVOID UnicodeCaseTableData;                      // 60h      ULONG NumberOfProcessors;                        // 64h      ULONG NtGlobalFlag;                              // 68h      UCHAR Spare2[0x4];                               // 6Ch      LARGE_INTEGER CriticalSectionTimeout;            // 70h      ULONG HeapSegmentReserve;                        // 78h      ULONG HeapSegmentCommit;                         // 7Ch      ULONG HeapDeCommitTotalFreeThreshold;            // 80h      ULONG HeapDeCommitFreeBlockThreshold;            // 84h      ULONG NumberOfHeaps;                             // 88h      ULONG MaximumNumberOfHeaps;                      // 8Ch      PVOID** ProcessHeaps;                            // 90h      PVOID GdiSharedHandleTable;                      // 94h      PVOID ProcessStarterHelper;                      // 98h      PVOID GdiDCAttributeList;                        // 9Ch      PVOID LoaderLock;                                // A0h      ULONG OSMajorVersion;                            // A4h      ULONG OSMinorVersion;                            // A8h      ULONG OSBuildNumber;                             // ACh      ULONG OSPlatformId;                              // B0h      ULONG ImageSubSystem;                            // B4h      ULONG ImageSubSystemMajorVersion;                // B8h      ULONG ImageSubSystemMinorVersion;                // C0h      ULONG GdiHandleBuffer[0x22];                     // C4h      PVOID ProcessWindowStation;                      } PEB, *PPEB;代码如下:int exist=0;__asm  {    pushad    //在fs:[0x18]再加上0x30就是指向了TEB结构    mov   eax,fs:[0x18]     //pTeb 线性地址        mov   eax, dword ptr  [eax+0x30]    //pPeb的首地址    //获取PEB偏移2h处BeingDebugged的值      movzx eax,byte ptr[eax+0x02]    or al,al      jz No    jnz Yes     No:     mov exist,0     jmp Exit     Yes:     mov exist,1     jmp Exit     Exit:        popad  }    if(exist)  {    // The process is debugged  }D、用API函数IsDebuggerPresent检测if(IsDebuggerPresent()){  // The process is debugged}E、利用异常SEH机制利用异常检测调试器,下面这段代码执行throw(&exce)的时候会抛出异常,然后进入到我们设置好的异常处理例程将bIsInDebugger设置为FALSE,而一旦调试器运行这段代码的时候,异常首先被调试器截获,如果调试器截获后进行了处理而没有继续传递给我们的异常处理例程,那么将不会执行bIsInDebugger=FALSE从而达到检测调试器的目的。BOOL bIsInDebugger = TRUE ; CFileException exce;try{     // 抛出异常         throw(&exce);    //throw(1);}catch(int ){  bIsInDebugger = TRUE ;}catch(CFileException *){    bIsInDebugger = FALSE ;}  if( bIsInDebugger ){  // The process is debugged}F、使用自己的函数替代部分系统函数,完成同样的功能  比如:替换上面的IsDebuggerPresent函数,拷贝IsDebuggerPresent函数的实现代码为自己所用,这样可以避免Cracker使用bpx IsDebuggerPresent断点来跟踪我们的程序。偷系统的代码实现如下:名称:  11.jpg查看次数: 430文件大小:  77.5 KB (系统API IsDebuggerPresent实现的二进制代码 )__declspec( naked )  BOOL CDebugProtect::IsDebuggerPresentEx(){  __asm  {    mov     eax, dword ptr fs:[18h]    mov     eax, dword ptr [eax+30h]    movzx   eax, byte ptr [eax+2h]    retn  }}这样在调用IsDebuggerPresent的地方都可以用IsDebuggerPresentEx替换掉。当然有些函数只能替换高层API,对于底层API还是无法替换的,这样Cracker可以在更底层的API上面下断点,再跟踪到调用的地方。G、使用动态获取DLL函数地址替代使用隐式调用DLL隐式调用DLL中函数的地方,很容易让Crack通过引用API参考找到下断点的地方,Cracker只需要简单的双击引用API地址的行就能定位到调用API的地方。DWORD CDebugProtect::TerminateFun=GetProcAddr("kernel32.dll","TerminateProcess");DWORD CDebugProtect::GetProcAddr(const char *dll,const char *fun){  //const char * pszModName = "kernel32.dll";  //const char * pszTerminatelName = "TerminateProcess";  const char * pszModName=dll;  const char * pszTerminatelName =fun;  HMODULE hKernel = GetModuleHandle(pszModName);    FARPROC proc=GetProcAddress(hKernel,pszTerminatelName);  return (DWORD)proc;}如在类的静态变量中获取TerminateProcess函数的地址,然后在调用TerminateProcess的地方使用这个变量来终止进程,这样Cracker就不能简单的下断点bpx TerminateProcess。如下:在类的静态变量初始化时候完成API地址的获取,在实际使用的时候使用这个地址直接CALL,下面是两个API地址隐藏的实现方法。DWORD apiAddr=TerminateFun; // TerminateFun是一个静态变量,在类第一次使用前已经被初始化HANDLE currentp=GetCurrentProcess(); //当前进程句柄  _asm{      pushad      push 0            push currentp      call apiAddr //形成的汇编代码就很难通过函数参考来找到      popad}//获取硬盘ID,通过隐藏API地址调用DWORD   GenerateID(void){  const char * lpRootPathName="c://";   //取C盘的序列号  char lpVolumeNameBuffer[12];  //磁盘卷标  unsigned long int nVolumeNameSize=12;    unsigned long int VolumeSerialNumber;  //磁盘序列号  unsigned long int MaximumComponentLength;  char  lpFileSystemNameBuffer[10];  unsigned long int nFileSystemNameSize=10;  unsigned long int FileSystemFlags;  DWORD apiAddr=GetVolumeInformationFun;  __asm  {    pushad      push nFileSystemNameSize    lea eax, lpFileSystemNameBuffer        push eax    lea eax, FileSystemFlags      push eax    lea eax, MaximumComponentLength     push eax    lea eax, VolumeSerialNumber     push eax    push nVolumeNameSize    lea eax, lpVolumeNameBuffer         push eax    push lpRootPathName //常量字符串变量本身就代表地址,直接使用即可不再使用lea    call apiAddr    popad   }//原来的调用方式//  GetVolumeInformation(lpRootPathName,//    lpVolumeNameBuffer, nVolumeNameSize,//    &VolumeSerialNumber, &MaximumComponentLength,//    &FileSystemFlags,//    lpFileSystemNameBuffer, nFileSystemNameSize);  VolumeSerialNumber^=0x12345678;  return  VolumeSerialNumber;  //给用户显示机器码使用,硬盘伪序列号  }H、 检查程序的父进程是否为EXPLORER.EXE一个正常的EXE文件应该是由EXPLORER.EXE来加载的,所以其父进程应该是EXPLORER.EXE,可以通过这个检测点来检测是否被调试。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);    res=TRUE;          //TerminateProcess(hh_parnet, 0);     _asm     {      pushad      push 0          push hh_parnet      call apiAddr      popad     }    }  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);              res=TRUE;              //TerminateProcess(hh_parnet, 0);                _asm                 {                  pushad                  push 0                        push hh_parnet                  call apiAddr                  popad                 }            }          }                }            }while (Module32Next(hSnapshot, &me32));    }  }I、检查STARTUPINFO结构Windows操作系统中的explorer.exe创建进程的时候会把STARTUPINFO结构中的值设为0,而非explorer.exe创建进程的时候会忽略这个结构中的值,也就是结构中的值不为0,所以可以利用这个来判断是否在调试程序STARTUPINFO Info;//通过检测STARTUPINFO结构来检测debuggerGetStartupInfo(&Info);   if ( (Info.dwX != 0) || (Info.dwY !=0) || (Info.dwXCountChars != 0) || (Info.dwYCountChars !=0 ) || (Info.dwFillAttribute != 0) || (Info.dwXSize != 0) || (Info.dwYSize != 0) )   {   //TerminateProcess(GetCurrentProcess(), 0);      _asm     {      pushad      push 0        push currentp      call apiAddr      popad     }   }J、 所有的检测函数返回值只作为参考,而更多的返回值可以通过unsigned char * output来判断  在主程序中判断函数调用是否成功,可以通过返回值EAX来作初步判断,更准确的判断可以通过复杂的数据结构output来判断,而output的值在函数正常执行完成后进行填充,防止Cracker直接修改函数的返回值EAX来暴力破解。K、敏感的字符串都进行异或或者可逆变换处理,再使用的地方动态解密出来  对于敏感的字符串信息需要进行简单的加密处理,否则Cracker可以通过简单的字符串参考就能找到敏感信息的代码地址,在引用字符串的地方先解密再使用,也不要弹出对话框等暴露敏感字符串信息。L、对文件进行代码段的CRC校验处理  可以对内存中的PE文件的进行一些CRC校验处理而不要对文件系统中的文件进行CRC校验,因为访问文件本身就是CRC破解的一个突破口。由于编译后我们的二进制代码又进行了加壳等外部处理,整个PE文件的结构发生了重大变化,进行CRC校验已经非常困难,所以此种方法对加壳的PE文件处理比较麻烦。M、使用花指令等方法防止静态分析W32Dasm等工具反编译出来的汇编指令如果没有花指令干扰,反编译出来的结果非常容易让Cracker分析,因此可以故意设置一些花指令“陷阱”,让W32Dasm落入我们设下的“陷阱”了。花指令的实质是利用了反编译工具没有人脑那么智能的,它们往往会把人为加入的一些指令理解错,从而错误地确定了指令的起始位置,导致分析错误,而实质上加入花指令后并没有改变原来的程序执行流程,从而达到干扰目的。更详细的介绍请参看罗聪的博客:(http://www.luocong.com/articles/show_article.asp?Article_ID=14)3、基于以上几种手段的编写的一个类下载:DebugProtect.rar
为了防止这个类库将VC IDE也当作调试经常杀死,可以加#ifndef  _DEBUG宏,参考如下:尽量分散多个地方多调用几次:)#ifndef  _DEBUG  unsigned char output[100];  BOOL resutl=FALSE;  resutl=CDebugProtect::AntiDebug(output,100);  if(resutl) return FALSE;  //BOOL res=CDebugProtect::Ollydbg(output,10);  srand( (unsigned)time( NULL ) );  int index= rand() % 99 ; //rand() 0 -0x7fff  if(output[index]!=index+1)  //随机检查返回缓冲区,用户可以自己定义,防止eax爆破  {    return FALSE;  }    resutl=CDebugProtect::Ollydbg(output,100);  if(resutl) return FALSE;  srand( (unsigned)time( NULL ) );  index= rand() % 99 ; //rand() 0 -0x7fff  if(output[index]!=index+1)  {    return FALSE;  }  resutl=CDebugProtect::SoftIceDeteck(output,100);  if(resutl) return FALSE;  srand( (unsigned)time( NULL ) );  index= rand() % 99 ; //rand() 0 -0x7fff  if(output[index]!=index+1)  {    return FALSE;  }#endif