首页 电脑 电脑学堂 查看内容

几种常用的保护应用程序的方法

2010-5-10 15:29 527 0

摘要:     1、前言     目前很多程序员都没有软件安全的意识,对自己辛辛苦苦的劳动成果不加保护,而这些缺乏保护意识的程序在日益强大的Sof...
关键词: nbsp PVOID ULONG 函数 EXPLORER 检测 地址 调试器 代码 IsDebuggerPresen

    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或者OllyDbg     if(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     N     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断点来跟踪我们的程序。偷系统的代码实现如下:     15296     (系统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结构来检测debugger     GetStartupInfo(&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、基于以上几种手段的编写的一个类
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部