好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Total Commander 自校验算法分析 - 网站安全 - 自学

作 者: uuk 【软件名称】: Total Commander 【软件版本】: 7.56a 【加壳方式】: 新版不加壳 【编写语言】: Borland Delphi 2.0 [Overlay] 【使用工具】: OD PEID IDA 【操作平台】: Windows XP 【软件介绍】: 一款挺不错的双栏文件管理软件 【作者声明】: 只是研究用,使用请购买正版   我们知道Total Commander是有自校验的,通过跟踪CreateFile和ReadFile函数,确定校验过程是在函数sub_47823C中进行的。   代码: 0047823C    push    ebp            ;  自校验函数 0047823D    mov    ebp, esp 0047823F    add    esp, -824 00478245    mov    dword ptr [ebp-4], -1    ;  初始化校验和为CHECKSUM=-1 0047824C    lea    eax, dword ptr [ebp-814] 00478252    mov    dword ptr [ebp-18], eax 00478255    lea    eax, dword ptr [ebp-824] 0047825B    mov    edx, 3FF 00478260    call    006BE3AC            ;  GetModuleFileName() 00478265    lea    edx, dword ptr [ebp-1C] 00478268    mov    eax, dword ptr [6C7D58] 0047826D    mov    ecx, 4 00478272    call    004027B4 00478277    mov    eax, 8001 0047827C    call    00402684             ;  AllocateMem() 00478281    mov    dword ptr [ebp-C], eax 00478284    lea    eax, dword ptr [ebp-824] 0047828A    mov    ecx, 1 0047828F    xor    edx, edx 00478291    call    0041D70C            ;  CreateFile() 00478296    mov    dword ptr [ebp-10], eax 00478299    call    004035AC 0047829E    cmp    dword ptr [eax+C], 0 004782A5    je    short 004782BF 上面是函数的初始化。   代码: 004782F7    xor    eax, eax 004782F9    mov    dword ptr [ebp-24], eax 004782FC    mov    eax, dword ptr [ebp-10] 004782FF    call    00419874            ;  计算文件大小 00478304    sub    eax, 18 00478307    sub    eax, 0C            ;  文件大小减去36字节 0047830A    mov    dword ptr [ebp-20], eax 0047830D    cmp    dword ptr [ebp-20], 8000 00478314    jle    short 0047831E 00478316    mov    word ptr [ebp-12], 8000 0047831C    jmp    short 00478326 0047831E    mov    ax, word ptr [ebp-20] 00478322    mov    word ptr [ebp-12], ax 00478326    lea    eax, dword ptr [ebp-14] 00478329    push    eax 0047832A    mov    edx, dword ptr [ebp-C] 0047832D    mov    cx, word ptr [ebp-12] 00478331    mov    eax, dword ptr [ebp-10] 00478334    call    0041DE4C    ;  ReadFile():从文件中读取0x8000大小的数据块 00478339    cmp    dword ptr [ebp-24], 0 0047833D    jnz    short 0047834A 0047833F    lea    edx, dword ptr [ebp-20] 00478342    mov    eax, dword ptr [ebp-C] 00478345    call    00478054        ;  首次计算校验和前先清除Checksum和数字签名,并计算要校验的数据大小 0047834A    lea    ecx, dword ptr [ebp-4] 0047834D    movzx    edx, word ptr [ebp-14] 00478351    mov    eax, dword ptr [ebp-C] 00478354    call    0067FF10        ;  分块分片计算校验和 00478359    movzx    eax, word ptr [ebp-14] 0047835D    sub    dword ptr [ebp-20], eax 00478360    movzx    eax, word ptr [ebp-14] 00478364    add    dword ptr [ebp-24], eax 00478367    movzx    eax, word ptr [ebp-14] 0047836B    cmp    eax, 8000 00478370    jnz    short 00478378 00478372    cmp    dword ptr [ebp-20], 0 00478376    jnz    short 0047830D 00478378    mov    edx, 8001 0047837D    mov    eax, dword ptr [ebp-C] 00478380    call    0040269C        ;  FreeMem() 00478385    lea    eax, dword ptr [ebp-14] 00478388    push    eax 00478389    lea    edx, dword ptr [ebp-824] 0047838F    mov    cx, 24 00478393    mov    eax, dword ptr [ebp-10] 00478396    call    0041DE4C        ;  读取最后36的字节,校验和存储在第16~20字节里面 0047839B    mov    eax, dword ptr [ebp-10] 0047839E    call    0041DDE4        ;  CloseHandle() 004783A3    mov    eax, dword ptr [ebp-18] 004783A6    mov    eax, dword ptr [eax] 004783A8    xor    eax, 2A67BE65        ;  对存储的校验和进行异或 004783AD    mov    dword ptr [ebp-8], eax 004783B0    push    ebp 004783B1    call    004781E4        ;  比较异或后的两个校验和,并利用两个校验和计算其它数据 至此完成了校验和的计算和比较。 sub_0067FF10函数的反汇编代码如下:   代码: 0067FF10    push    ebx 0067FF11    push    esi 0067FF12    push    edi 0067FF13    add    esp, -0C 0067FF16    mov    dword ptr [esp+4], ecx 0067FF1A    mov    dword ptr [esp], edx 0067FF1D    mov    ecx, eax 0067FF1F    mov    ebx, dword ptr [esp+4] 0067FF23    mov    ebx, dword ptr [ebx] 0067FF25    and    ebx, 0FFFF        ;  取上次校验和的低16位给LOW 0067FF2B    mov    esi, dword ptr [esp+4] 0067FF2F    mov    esi, dword ptr [esi] 0067FF31    shr    esi, 10 0067FF34    and    esi, 0FFFF        ;  取上次校验和的高16位给HIGH 0067FF3A    test    ecx, ecx 0067FF3C    jnz    short 0067FF4A 0067FF3E    mov    eax, dword ptr [esp+4] 0067FF42    mov    dword ptr [eax], 1 0067FF48    jmp    short 0067FFB8 0067FF4A    cmp    dword ptr [esp], 0 0067FF4E    jle     short 0067FFAD 0067FF50    cmp    dword ptr [esp], 15B0        ;  分成0x15B0大小的数据来计算 0067FF57    jge     short 0067FF62 0067FF59    mov     eax, dword ptr [esp] 0067FF5C    mov     dword ptr [esp+8], eax 0067FF60    jmp     short 0067FF6A 0067FF62    mov     dword ptr [esp+8], 15B0 0067FF6A    mov     eax, dword ptr [esp+8] 0067FF6E    sub     dword ptr [esp], eax 0067FF71    mov     eax, dword ptr [esp+8] 0067FF75    dec     eax 0067FF76    test    eax, eax 0067FF78    jb      short 0067FF89 0067FF7A    inc     eax 0067FF7B    xor     edx, edx 0067FF7D    movzx   edi, byte ptr [ecx+edx]        ;  每次取一个字节,循环计算 0067FF81    add    ebx, edi 0067FF83    add    esi, ebx 0067FF85    inc    edx 0067FF86    dec    eax 0067FF87    jnz    short 0067FF7D 0067FF89    mov    eax, dword ptr [esp+8] 0067FF8D    add    ecx, eax 0067FF8F    mov    eax, ebx 0067FF91    mov    ebx, 0FFF1 0067FF96    cdq 0067FF97    idiv    ebx    ;  将LOW对0xFFF1取模,结果给CHECKSUM的低16位 0067FF99    mov    ebx, edx 0067FF9B    mov    eax, esi 0067FF9D    mov    esi, 0FFF1 0067FFA2    cdq 0067FFA3    idiv    esi    ;  将HIGH对0xFFF1取模,结果给CHECKSUM的高16位 0067FFA5    mov    esi, edx 0067FFA7    cmp     dword ptr [esp], 0 0067FFAB    jg      short 0067FF50 0067FFAD    shl     esi, 10 0067FFB0    or      ebx, esi 0067FFB2    mov     eax, dword ptr [esp+4] 0067FFB6    mov     dword ptr [eax], ebx 0067FFB8    add     esp, 0C 0067FFBB    pop     edi 0067FFBC    pop     esi 0067FFBD    pop     ebx 0067FFBE    retn 自校验的C语言代码如下:   代码: // Totalcmd_FixCRC.cpp : 自校验 #include "stdafx.h" // 使用说明 void Usage(); // 计算校验和 void CalculateCRC(unsigned char * pData, unsigned int nSize, unsigned int * nCRC); // 修复数据及其大小 void FixData(char * pData, unsigned int * nSize);   int _tmain(int argc, _TCHAR* argv[]) {   TCHAR * szFileName = NULL;   char lpBuffer[0x8001] = {0};   unsigned int nNumberOfBytesToRead = 0;   unsigned int nNumberOfBytesRead = 0;   unsigned int nSize = 0;   unsigned int nSizeRead = 0;   unsigned int nCRC = 0xffffffff;   HANDLE hFile = NULL;     printf("\nTotalcmd_FixCRC by uuk   2012.03.23\n\n");     if ( argc != 2 )   {     Usage();     return 0;   }     szFileName = argv[1];     hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);   if ( hFile == INVALID_HANDLE_VALUE )   {     printf("CreateFile Error!\n");     return 1;   }     nSize = SetFilePointer(hFile, 0, NULL, FILE_END);   if ( nSize == INVALID_SET_FILE_POINTER)   {     printf("SetFilePointer Error!\n");     CloseHandle(hFile);     return 2;   }   nSize -= 36;   SetFilePointer(hFile, 0, NULL, FILE_BEGIN);     do   {     if ( nSize >= 0x8000 )       nNumberOfBytesToRead = 0x8000;     else       nNumberOfBytesToRead = nSize;       ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, (LPDWORD)&nNumberOfBytesRead, NULL);       if ( nSizeRead == 0 )       FixData(lpBuffer, &nSize);       CalculateCRC((unsigned char *)lpBuffer, nNumberOfBytesRead, &nCRC);       nSizeRead += nNumberOfBytesRead;     nSize -= nNumberOfBytesRead;   } while ( (nNumberOfBytesRead==0x8000) && nSize );     // 读取文件中存储的校验和   ReadFile(hFile, lpBuffer, 36, (LPDWORD)&nNumberOfBytesRead, NULL);   unsigned int nCRC2 = *(unsigned int *)(lpBuffer+0x10);     nCRC = nCRC ^ 0xF5A3E289;   nCRC2 = nCRC2 ^ 0x2A67BE65;   if ( nCRC != nCRC2 )   {     unsigned int nCRC3 = nCRC ^ 0x2A67BE65;     SetFilePointer(hFile, nSizeRead+0x10, NULL, FILE_BEGIN);     int nWrite = 0;     WriteFile(hFile, &nCRC3, 4, (LPDWORD)&nWrite, NULL);     printf("File is Fixed!\n");   }   else   {  printf("File is not modified!\n");  }     CloseHandle(hFile);   return 0; }   void Usage() {  printf("Usage: Totalcmd_FixCRC.exe FileName\n");  }   void CalculateCRC(unsigned char * pData, unsigned int nSize, unsigned int * nCRC) {   unsigned int nSizeRead;   int nCRCLow, nCRCHigh;     nCRCLow = *nCRC & 0xffff;   nCRCHigh = (*nCRC>>16) & 0xffff;     if ( pData )   {     if ( nSize > 0 )     {       do       {         if ( nSize >= 5552 )           nSizeRead = 5552;         else           nSizeRead = nSize;         nSize -= nSizeRead;         do         {           nCRCLow += *pData;           nCRCHigh += nCRCLow;           pData++;           nSizeRead--;         } while ( nSizeRead );         nCRCLow %= 65521;         nCRCHigh %= 65521;       } while ( nSize > 0 );     }     *nCRC = ( nCRCHigh << 16 ) | nCRCLow;   }   else   {  *nCRC = 1;  } }   void FixData(char * pData, unsigned int * nSize) {   int i = 0;   if ( pData[0x198] != '\0' )   {     *nSize = *(unsigned int *)(pData+0x198) - 36;     for (i=0; i<8; i++)     {  pData[0x198+i] = '\0';  }   }   for (i=0; i<4; i++)   {  pData[0x158+i] = '\0';  } } 新程序采用的注册算法没有变,只是将网上公开的许可文件加入黑名单。黑名单中是许可文件的许可号(每个占4字节)。Totalcmd756a版本的黑名单如下所示: 004E0ABC    Totalcmd    mov    eax, 006C8A3C        ;  黑名单1 004E0AD5    Totalcmd    mov    eax, 006C8BE0        ;  黑名单2 004E0AEB    Totalcmd    mov    eax, 006C90F4        ;  黑名单3 004E0B07    Totalcmd    mov    eax, 006C9168        ;  黑名单4   比较有意思的是我们可以通过特殊的方法修改黑名单里面的数据而不改变校验和。推导CalculateCRC() 函数中校验和的计算公式得:    (Bn代表第n个字节的值)   由于(n - 1) + (n+1) = 2n,所以根据校验码的计算公式可以将Bn-1和Bn+1的值各加1,然后将Bn值减2,则总结果不变。因此可以通过这个办法将许可证黑名单中的许可号改掉。如许可号#1283在黑名单中,其HEX值为0x00000503,在文件中是以0x03050000存储的,将其改成0x04030100则许可文件可继续使用,程序也不报错

查看更多关于Total Commander 自校验算法分析 - 网站安全 - 自学的详细内容...

  阅读:41次