【  标题  】 堀北压缩 0.28 beta脱壳
【  作者  】 linxer
【  Q  Q  】 3568599
【破解平台】 盗版xp sp2
【脱壳工具】 OllyICE + OllyDump插件
【待脱软件】 mouserate.exe 见附件(用堀北压缩 0.28 beta加壳)
【  声明  】 初学脱壳,只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

这款壳,脱法很简单,不过,在网上搜了下,貌似还没人写这个脱文,这里就粗略写一篇出来吧

OD加载程序,程序停留在入口

00401000 >/$  68 37A34000   push    0040A337
00401005  |.  E8 01000000   call    0040100B        ;调用一个空函数
0040100A  \.  C3            retn          ;F7后 到0x0040A337处执行
0040100B   $  C3            retn


0040A337    B8 49A30000     mov     eax, 0A349        ;随着指令的执行,等下这个指令的立即数会改变,目前不管,F7
0040A33C    BA 00004000     mov     edx, 00400000
0040A341    03C2            add     eax, edx
0040A343    FFE0            jmp     eax          ;跳到0x0040A349执行
0040A345    B1 15           mov     cl, 15        ;它和下一条指令,其实是代码中夹杂的一个4字节数据
0040A347    0000            add     byte ptr [eax], al
0040A349    60              pushad
0040A34A    E8 00000000     call    0040A34F        ;这个地方应该在重定位程序上有用吧
0040A34F    5E              pop     esi                                ; mouserat.0040A34F
0040A350    83EE 0A         sub     esi, 0A
0040A353    8B06            mov     eax, dword ptr [esi]
0040A355    03C2            add     eax, edx
0040A357    8B08            mov     ecx, dword ptr [eax]
0040A359    894E F3         mov     dword ptr [esi-D], ecx
0040A35C    83EE 0F         sub     esi, 0F
0040A35F    56              push    esi
0040A360    52              push    edx
0040A361    8BF0            mov     esi, eax
0040A363    AD              lods    dword ptr [esi]
0040A364    AD              lods    dword ptr [esi]
0040A365    03C2            add     eax, edx
0040A367    8BD8            mov     ebx, eax
0040A369    6A 04           push    4
0040A36B    BF 00100000     mov     edi, 1000
0040A370    57              push    edi
0040A371    57              push    edi
0040A372    6A 00           push    0
0040A374    FF53 08         call    dword ptr [ebx+8]      ;VirtualAlloc调用
0040A377    5A              pop     edx
0040A378    59              pop     ecx
0040A379    BD 00800000     mov     ebp, 8000
0040A37E    55              push    ebp
0040A37F    6A 00           push    0
0040A381    50              push    eax
0040A382    51              push    ecx
0040A383    52              push    edx
0040A384    50              push    eax
0040A385    8906            mov     dword ptr [esi], eax
0040A387    AD              lods    dword ptr [esi]
0040A388    AD              lods    dword ptr [esi]
0040A389    03C2            add     eax, edx
0040A38B    50              push    eax
0040A38C    AD              lods    dword ptr [esi]
0040A38D    03C2            add     eax, edx
0040A38F    FFD0            call    eax          ;F8步过(这里解密出来一部分代码到刚分配内存中)
0040A391    6A 04           push    4
0040A393    57              push    edi
0040A394    AD              lods    dword ptr [esi]
0040A395    50              push    eax
0040A396    6A 00           push    0
0040A398    FF53 08         call    dword ptr [ebx+8]      ;VirtualAlloc调用
0040A39B    5A              pop     edx
0040A39C    55              push    ebp
0040A39D    6A 00           push    0
0040A39F    50              push    eax          ;压入VirtualFree的参数
0040A3A0    FF73 0C         push    dword ptr [ebx+C]      ;压入VirtualFree地址,在处理完导入表后,用ret指令触发这个函数释放内存
0040A3A3    52              push    edx
0040A3A4    6A 04           push    4
0040A3A6    57              push    edi
0040A3A7    8BF8            mov     edi, eax
0040A3A9    55              push    ebp
0040A3AA    6A 00           push    0
0040A3AC    FF53 08         call    dword ptr [ebx+8]      ;VirtualAlloc调用
0040A3AF    5A              pop     edx
0040A3B0    55              push    ebp
0040A3B1    6A 00           push    0
0040A3B3    50              push    eax          ;压入VirtualFree的参数
0040A3B4    FF73 0C         push    dword ptr [ebx+C]      ;压入VirtualFree地址,在处理完导入表后,用ret指令触发这个函数释放内存
0040A3B7    FF3424          push    dword ptr [esp]      ;再次压入VirtualFree地址
0040A3BA    8BC8            mov     ecx, eax
0040A3BC    50              push    eax
0040A3BD    54              push    esp
0040A3BE    FF76 FC         push    dword ptr [esi-4]
0040A3C1    57              push    edi
0040A3C2    AD              lods    dword ptr [esi]
0040A3C3    50              push    eax
0040A3C4    AD              lods    dword ptr [esi]
0040A3C5    03C2            add     eax, edx
0040A3C7    50              push    eax
0040A3C8    55              push    ebp
0040A3C9    51              push    ecx
0040A3CA    8BEA            mov     ebp, edx
0040A3CC    FF56 E8         call    dword ptr [esi-18]      ;F8步过(这里跳到第一次分的内存中执行,里面貌似在解压代码到第二次分配内存)
0040A3CF    58              pop     eax
0040A3D0    012E            add     dword ptr [esi], ebp
0040A3D2    8B3E            mov     edi, dword ptr [esi]
0040A3D4    AD              lods    dword ptr [esi]
0040A3D5    50              push    eax
0040A3D6    AD              lods    dword ptr [esi]
0040A3D7    50              push    eax
0040A3D8    AD              lods    dword ptr [esi]
0040A3D9    50              push    eax
0040A3DA    AD              lods    dword ptr [esi]
0040A3DB    50              push    eax
0040A3DC    33C0            xor     eax, eax
0040A3DE    B9 80010000     mov     ecx, 180
0040A3E3    F3:AB           rep     stos dword ptr es:[edi]
0040A3E5    8B7424 28       mov     esi, dword ptr [esp+28]
0040A3E9    AC              lods    byte ptr [esi]
0040A3EA    8BD0            mov     edx, eax
0040A3EC    AD              lods    dword ptr [esi]
0040A3ED    8BF8            mov     edi, eax
0040A3EF    03FD            add     edi, ebp
0040A3F1    AD              lods    dword ptr [esi]
0040A3F2    8BC8            mov     ecx, eax
0040A3F4    F3:A4           rep     movs byte ptr es:[edi], byte ptr [esi]  ;拷解压代码到程序中
0040A3F6    4A              dec     edx
0040A3F7  ^ 75 F3           jnz     short 0040A3EC        ;这里一个小循环,在它下一指令F4
0040A3F9    59              pop     ecx            ;F4
0040A3FA    5A              pop     edx
0040A3FB    58              pop     eax
0040A3FC    5F              pop     edi
0040A3FD    85C0            test    eax, eax          ;又要出现循环了,到0x0040A41D处F4
0040A3FF    74 1C           je      short 0040A41D
0040A401    57              push    edi
0040A402    8A07            mov     al, byte ptr [edi]
0040A404    47              inc     edi
0040A405    2C E8           sub     al, 0E8
0040A407    3C 01           cmp     al, 1
0040A409  ^ 77 F7           ja      short 0040A402
0040A40B    8B07            mov     eax, dword ptr [edi]
0040A40D    3AC2            cmp     al, dl
0040A40F  ^ 75 F1           jnz     short 0040A402
0040A411    32C0            xor     al, al
0040A413    0FC8            bswap   eax
0040A415    030424          add     eax, dword ptr [esp]
0040A418    2BC7            sub     eax, edi
0040A41A    AB              stos    dword ptr es:[edi]
0040A41B  ^ E2 E5           loopd   short 0040A402        ;循环里的代码在搞啥东东,俺不知道
0040A41D    55              push    ebp            ;F4
0040A41E    AD              lods    dword ptr [esi]
0040A41F    85C0            test    eax, eax

  //下面代码处理导入表,dll名称,函数序号,函数名称在esi指定的地方,每个dll信息导入信息排列如下:
  1.最开始是dll名称,然后紧接是其导出的函数序号/函数名,最后是一个长整型的0
0040A421    74 37           je      short 0040A45A        ;这个循环里,出口是0x0040A45A,可以到这个地方F4
0040A423    8BF8            mov     edi, eax
0040A425    033C24          add     edi, dword ptr [esp]
0040A428    56              push    esi
0040A429    FF13            call    dword ptr [ebx]        ;LoadLibraryA调用
0040A42B    8BE8            mov     ebp, eax
0040A42D    AC              lods    byte ptr [esi]
0040A42E    84C0            test    al, al
0040A430  ^ 75 FB           jnz     short 0040A42D        ;这个小循环在寻找dll名称结束地址
0040A432    AD              lods    dword ptr [esi]
0040A433    85C0            test    eax, eax          ;看是否处理完这个dll
0040A435  ^ 74 E7           je      short 0040A41E        ;处理完则跳,处理下一个dll
0040A437    83EE 04         sub     esi, 4
0040A43A    AD              lods    dword ptr [esi]        ;装函数序号或函数名前4字节到eax
0040A43B    A9 00000080     test    eax, 80000000        ;判定是否序号导出,如是函数名也能正常工作,因为ascii最高位为0
0040A440    75 0B           jnz     short 0040A44D
0040A442    83EE 04         sub     esi, 4          ;函数名导出情况
0040A445    56              push    esi
0040A446    55              push    ebp
0040A447    FF53 04         call    dword ptr [ebx+4]        ;GetProcAddress调用
0040A44A    AB              stos    dword ptr es:[edi]
0040A44B  ^ EB E0           jmp     short 0040A42D
0040A44D    25 FFFFFF7F     and     eax, 7FFFFFFF        ;序号导出情况
0040A452    50              push    eax
0040A453    55              push    ebp
0040A454    FF53 04         call    dword ptr [ebx+4]        ;GetProcAddress调用
0040A457    AB              stos    dword ptr es:[edi]
0040A458  ^ EB D8           jmp     short 0040A432
0040A45A    5D              pop     ebp            ;在这个地方F4,可以不详细跟导入表处理
0040A45B    5F              pop     edi
0040A45C    C3              retn            ;运行到这个地方,看下堆栈吧


堆栈如下:
0012FF70   7C809AE4  kernel32.VirtualFree  ;retn指令触发这个VirtualFree调用,这个调用的返回地址还是VirtualFree,参数是
0012FF74   7C809AE4  kernel32.VirtualFree  ;第一次VirtualFree调用时返回地址
0012FF78   00390000        ;第一次VirtualFree调用参数
0012FF7C   00000000        ;第一次VirtualFree调用参数
0012FF80   00008000        ;第一次VirtualFree调用参数
0012FF84   7C809AE4  kernel32.VirtualFree
0012FF88   00380000
0012FF8C   00000000
0012FF90   00008000
0012FF94   0040A336  返回到 mouserat.0040A336
0012FF98   00370000
0012FF9C   00000000
0012FFA0   00008000


将上面堆栈产生效果分解下:

第一个VirtualFree调用完后,马上会执行第二个VirtualFree,这个时候的堆栈为:
0012FF84   7C809AE4  kernel32.VirtualFree      ;第二次VirtualFree调用时返回地址,可见又是一个VirtualFree
0012FF88   00380000                            ;第二次VirtualFree调用参数      
0012FF8C   00000000                            ;第二次VirtualFree调用参数      
0012FF90   00008000                            ;第二次VirtualFree调用参数      
0012FF94   0040A336  返回到 mouserat.0040A336
0012FF98   00370000
0012FF9C   00000000
0012FFA0   00008000

第二个VirtualFree调用完后,马上会执行第三个VirtualFree,这个时候的堆栈为:
0012FF94   0040A336  返回到 mouserat.0040A336  ;这个是返回地址,终于要返回我们想要的地方了
0012FF98   00370000        ;VirtualFree的三个参数
0012FF9C   00000000
0012FFA0   00008000


从上面分析知,程序在完成三个VirtualFree调用后,将返回0040A336这个地方执行,在这个地方下断点,bp 0x0040a336,然后F9,到如下地方:

0040A336    61              popad
0040A337    B8 F01B0000     mov     eax, 1BF0
0040A33C    BA 00004000     mov     edx, 00400000
0040A341    03C2            add     eax, edx
0040A343    FFE0            jmp     eax      ;终于要到OEP了,F7进入到下面代码


00401BF0   .  55            push    ebp      ;在这个地方用ollydump插件dump吧
00401BF1   .  8BEC          mov     ebp, esp
00401BF3   .  6A FF         push    -1
00401BF5   .  68 90254000   push    00402590
00401BFA   .  68 001E4000   push    00401E00                                 ;  jmp 到 msvcrt._except_handler3; SE 处理程序安装
00401BFF   .  64:A1 0000000>mov     eax, dword ptr fs:[0]


dump出来程序可以运行,优化就不做了,因为我不太会,还有待学习呢

这里总结下这个壳的脱法:
  1.bp VirtualFree,在第三次执行这个函数时,在返回地址在下断点,运行到这个断点处
  2.在单步执行5条指令到OEP
  
当然这个壳也可以用ESP定律秒杀

  • 标 题:答复
  • 作 者:skylly
  • 时 间:2007-05-08 14:32

这是我以前写的脱kbys的插件代码

//关键脱壳函数--API
extern "C" void WINAPI StartUnpack(PROCESS_INFORMATION pi, DWORD dwBaseAddress, DWORD dwEntryPoint)
{  
  DWORD dwPid = pi.dwProcessId;
  DWORD dwTid = pi.dwThreadId;
  HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
  HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, dwTid);
  ASSERT(hProcess);
  ASSERT(hThread);

  //发消息表示开始脱壳
  DWORD dwOep = 0;
  DWORD dwRead = 0;
  CONTEXT context;

  UCHAR szFirst[] = {0xE8, 0x01, 0x00, 0x00, 0x00, 0xC3, 0xC3};
  DWORD dwMagic = FindMemory(hProcess, dwEntryPoint, szFirst, sizeof(szFirst), 0x100);
  if (dwMagic != dwEntryPoint + 5)
  {//shoooo超级伪装壳 
    TellUnpacker(g_szError);
    return  Terminate(hProcess, hThread);
  }  
  
beginning:
  DWORD dwNewPP = 0;
  ReadMemory(hProcess, dwEntryPoint + 1, &dwNewPP, 4, &dwRead);
  GO(hProcess, hThread, dwNewPP, context);

  UCHAR szNewJmp[] = {0xFF, 0xE0};
  dwMagic = FindMemory(hProcess, dwNewPP, szNewJmp, sizeof(szNewJmp), 0x100);
  if (dwMagic != (dwNewPP + 0xC))
  {
    DWORD dwLoadLib = GetAddress("kernel32.dll", "LoadLibraryA");

    GO(hProcess, hThread, dwLoadLib, context);

    RTU(hProcess, hThread, context);
    DWORD dwIAT = context.Edi;

    UCHAR szMagic[] = {0xFF, 0x63, 0x0C};
    dwMagic = FindMemory(hProcess, context.Eip,  szMagic, sizeof(szMagic), 0x100);
    if (0 == dwMagic)
    {  
      //出错 
      TellUnpacker(g_szError);
      return  Terminate(hProcess, hThread);
    }
    //0.22
    TellUnpacker("kbys 0.22");
    GO(hProcess, hThread, dwMagic, context);
    ReadMemory(hProcess, context.Ebx + 0xC, &dwOep, 4, &dwRead);
    GO(hProcess, hThread, dwOep, context);
    RTU(hProcess, hThread, context);
    dwOep = context.Eip;
    DumeNow(dwOep, dwIAT);
    return  Terminate(hProcess, hThread);
  }
  TellUnpacker("kbys 0.28");
  DWORD dwLoadLib = GetAddress("kernel32.dll", "LoadLibraryA");

  GO(hProcess, hThread, dwLoadLib, context);

  RTU(hProcess, hThread, context);
  DWORD dwIAT = context.Edi;

  GO(hProcess, hThread, dwMagic, context);
  dwOep = context.Eax;
  
  dwEntryPoint = dwOep;

  dwMagic = FindMemory(hProcess, dwEntryPoint, szFirst, sizeof(szFirst), 0x100);
  if (dwMagic == dwEntryPoint + 5)
  {
    if (::MessageBox(g_hWndList, "似乎还有一层,是否继续脱?", "INFO", MB_YESNO) == IDYES)
      goto beginning;
  }

  DumeNow(dwOep, dwIAT);
  return  Terminate(hProcess, hThread);

}