JoyChou 发表于 2013-12-15 18:00

【吾爱2013CM大赛解答】 -- 驱动crackme -- 网际座山雕 KeyGen分析

本帖最后由 JoyChou 于 2013-12-19 14:20 编辑

程序拿到手后是MFC程序,还是用MFC按钮入口查看器查看,在上一篇破文中,我已经发了附件了。
找到按钮入口地址为00401A50,下断,F9,输入序列号LoveKido,断下来,先看看程序整理流程。

豁然的看到DeviceIoControl这个API,由此可以看出,此程序是通过DeviceIoControl产生IRP_MJ_DEVICE_CONTROL例程来和sys通信的。
不过这个函数,可以看到发送到sys的数据已经返回到exe的数据,通过DeviceIoControl函数的参数
发送到sys的InBuffer:

即机器码+自己输入的序列号

返回到exe的OutBuffer:

可以看出只有9位,中间被00截断了,由此推断序列号只有9位,刚好和机器码相同

继续往下看就可以看到程序中唯一的一个关键跳

00401BC3   .803D 22684400>cmp byte ptr ds:,0x21          ;关键判断 446822-446780 = A2
00401BCA   .75 21         jnz Xmycrack.00401BED
00401BCC   .B8 80674400   mov eax,mycrack.00446780
00401BD1   .C605 D8674400>mov byte ptr ds:,0xDB
00401BD8   .FFD0          call eax                                 ;正确提示
00401BDA   .8B4C24 14   mov ecx,dword ptr ss:
00401BDE   .64:890D 00000>mov dword ptr fs:,ecx
00401BE5   .59            pop ecx
00401BE6   .5F            pop edi
00401BE7   .5E            pop esi
00401BE8   .5B            pop ebx
00401BE9   .83C4 10       add esp,0x10
00401BEC   .C3            retn
00401BED   >6A 00         push 0x0
00401BEF   .6A 00         push 0x0
00401BF1   .68 30984300   push mycrack.00439830                  ;胜败乃兵家常事,大侠请重新来过。
00401BF6   .8BCF          mov ecx,edi
00401BF8   .E8 47690000   call mycrack.00408544                  ;错误提示


分析到这,可能有的Cracker都想这么简单?直接就爆破就完事了?
不过发现00401BD8 这个call里面的内容是加密后的,必须靠正确的序列号才能解码。
现在就可以开始算法分析了……

先看驱动层
IDA载入,在DriverEntry中找到IRP_MJ_DEVICE_CONTROL例程函数,它的宏是十进制的14,对应sub_10748这个函数,F5一目了然


最后的算法:buffer = buffer - buffer + 1

至于为什么Irp堆栈指针偏移0x3就是控制码,用Windbg dt下IO_STACK_LOCATION结构,就可以很清楚的看到。

0:000> dt _IO_STACK_LOCATION
ntdll!_IO_STACK_LOCATION
   +0x000 MajorFunction    : UChar
   +0x001 MinorFunction    : UChar
   +0x002 Flags            : UChar
   +0x003 Control          : UChar


接着就可以继续分析exe的算法
先从00401BCA关键跳入手,的值必须为0x21,很明显可以发现的值是由0x446780地址引起的。
446822 - 446780 = 0xA2,当ecx为0xA2的时候,edx = ecx % 9(取9的余) = 0,

00401B70   > /8A99 F8384400 mov bl,byte ptr ds:      ;d 004438f8+0a2 = 0x22
00401B76   . |80FB E0       cmp bl,0xE0
00401B79   . |73 16         jnb Xmycrack.00401B91
00401B7B   . |B8 398EE338   mov eax,0x38E38E39                     ;edx = ecx mod 9
00401B80   . |F7E1          mul ecx
00401B82   . |D1EA          shr edx,1
00401B84   . |8D04D2      lea eax,dword ptr ds:
00401B87   . |8BD1          mov edx,ecx
00401B89   . |2BD0          sub edx,eax                              ;edx = 0,1,2,3,4到8
00401B8B   . |2A9A 70654400 sub bl,byte ptr ds:      ;446570 驱动返回出来的buffer
00401B91   > |8899 80674400 mov byte ptr ds:,bl      ;ecx=A2时,bl必须=0x21


先要这段的作用是edx = ecx % 9,
原理可以看看《C++反汇编与逆向分析技术揭密》,里面有相关的证明 或 百度398EE338这个Magic Number

00401B7B   .B8 398EE338   mov eax,0x38E38E39                     ;edx = ecx mod 9
00401B80   .F7E1          mul ecx
00401B82   .D1EA          shr edx,1
00401B84   .8D04D2      lea eax,dword ptr ds:
00401B87   .8BD1          mov edx,ecx
00401B89   .2BD0          sub edx,eax                              ;edx = 0,1,2,3,4到8


现在看00401B8B这句,当ecx = 0xA2,此时的edx=0,bl = 0x22,
因为执行完00401B8B这句后,bl必须=0x21,所以等于1(数字的1,而不是ASCII的'1')
也就是说,驱动返回的OutBuffer第一个必须是数字1,那其它的呢?可耻的猜驱动返回的OutBuffer为{1,2,3,4,5,6,7,8,9}
那么根据buffer = buffer - buffer + 1即等价于{1,2,3,4,5,6,7,8,9} = buffer - buffer + 1
buffer是输入的,buffer即机器码
做一个等号两边移位的等价操作,可以推算出: buffer = buffer + {0,1,2,3,4,5,6,7,8}

简单的写了个C语言的注册机,测试下,成功。
像类似这样的代码,推荐直接在OD里面汇编操作,或者python



#include "stdafx.h"
#include <string.h>
#include <windows.h>

int main(int argc, char *argv[])
{
      char szMachineCode = {0};

      char szResult = {0};
      printf("输入机器码:");
      scanf("%s", szMachineCode);

      for (int i = 0; i < 9; i++)
      {
                szResult = szMachineCode + i;
      }
      szResult = '\0';

      puts(szResult);
      return 0;
}

最后分析下为什么这个shellcode在win7上不能运行

004467C6    33C0            xor eax,eax                              ; mycrack.00446780
004467C8    64:3340 30      xor eax,dword ptr fs:          ; ppeb
004467CC    8B40 0C         mov eax,dword ptr ds:         ; pldr
004467CF    8B70 1C         mov esi,dword ptr ds:          ; InInitializationOrderModuleList->Flink(即ntdll.dll)
004467D2    AD            lods dword ptr ds:                  ; kernel32.dll
004467D3    8B48 08         mov ecx,dword ptr ds:         ; ldr_data_talbe_entry结果中距离InInitializationOrderModuleList偏移0x8,即dllbase


在win7上,InInitializationOrderModuleList模块的顺序是ntdll.dll->kernelBase.dll->Kernel32.dll

最后给一个win7和xp可以用的shellcode
int main()
{
    _asm{
            nop
            nop         
            nop
            nop
            nop
            CLD               ; clear flag DF
            ;store hash
            push 0x1e380a6a   ;hash of MessageBoxA
            push 0x4fd18963   ;hash of ExitProcess
            push 0x0c917432   ;hash of LoadLibraryA
            mov esi,esp         ; esi = addr of first function hash
            lea edi,   ; edi = addr to start writing function
            ; make some stack space
            xor ebx,ebx
            mov bh, 0x04            
            sub esp, ebx
            ; push a pointer to "user32" onto stack
            mov bx, 0x3233      ; rest of ebx is null
            push ebx
            push 0x72657375
            push esp
            xor edx,edx
      ; find base addr of kernel32.dll
            mov ebx,fs:   //得到peb结构体的地址
            mov ebx, //得到Ldr结构体的地址
            mov ebx, //得到ldr.InLoadOrderModuleList.Flink 第一个模块,当前进程
            mov ebx,   //得到第二个模块地址 ntdll.dll
            mov ebx,   //得到第三个模块地址 kernel32.dll
            mov ebx,//得到第三个模块地址(kernel32模块的dllbase)
            mov ebp,ebx
      find_lib_functions:
            lodsd                   ; load next hash into al and increment esi
            cmp eax, 0x1e380a6a   ; hash of MessageBoxA - trigger
                                    ; LoadLibrary("user32")
            jne find_functions
            xchg eax, ebp         ; save current hash
            call       ; LoadLibraryA
            xchg eax, ebp         ; restore current hash, and update ebp
                                    ; with base address of user32.dll
      find_functions:
            pushad                      ; preserve registers
            mov eax,        ; eax = start of PE header
            mov ecx, ; ecx = relative offset of export table
            add ecx, ebp                ; ecx = absolute addr of export table
            mov ebx,        ; ebx = relative offset of names table
            add ebx, ebp                ; ebx = absolute addr of names table
            xor edi, edi                ; edi will count through the functions
      next_function_loop:
            inc edi                     ; increment function counter
            mov esi,     ; esi = relative offset of current function name
            add esi, ebp                ; esi = absolute addr of current function name
            cdq                         ; dl will hold hash (we know eax is small)
      hash_loop:
            movsx eax, byte ptr
            cmp al,ah
            jz compare_hash
            ror edx,7
            add edx,eax
            inc esi
            jmp hash_loop
      compare_hash:   
            cmp edx,        ; compare to the requested hash (saved on stack from pushad)
            jnz next_function_loop
            mov ebx,        ; ebx = relative offset of ordinals table
            add ebx, ebp                ; ebx = absolute addr of ordinals table
            mov di,    ; di = ordinal number of matched function
            mov ebx,        ; ebx = relative offset of address table
            add ebx, ebp                ; ebx = absolute addr of address table
            add ebp,     ; add to ebp (base addr of module) the
                                        ; relative offset of matched function
            xchg eax, ebp               ; move func addr into eax
            pop edi                     ; edi is last onto stack in pushad
            stosd                     ; write function addr to and increment edi
            push edi
            popad                   ; restore registers
                                    ; loop until we reach end of last hash
            cmp eax,0x1e380a6a
            jne find_lib_functions
      function_call:
            xor ebx,ebx
            push ebx            // cut string
            push 0x20756F68   //push" uoh"
            push 0x43796F4A   //push"CyoJ"
            mov eax,esp         //load address of JoyChou
            push ebx   
            push eax
            push eax
            push ebx
            call ; //call MessageboxA
            push ebx
            call ; // call ExitProcess
            nop
            nop
            nop
            nop
    }
    return 0;
}

JoyChou 发表于 2013-12-15 18:04

沙发还是自己坐。

wanghongmin1 发表于 2013-12-15 18:25

学习了,感谢。

xjun 发表于 2013-12-15 18:25

膜拜大大,让小菜学习。

bambooqj 发表于 2013-12-15 19:02

a070458 发表于 2013-12-15 19:39

{:1_931:}学习了 感谢大大

Sandman 发表于 2013-12-15 21:22

为毛那个最终的KeyBuffer = {0,1,2,3,4,5,6,7,8}都是猜出来的

网际座山雕 发表于 2013-12-17 09:38


楼主做的好,非常详细的分析。
页: [1]
查看完整版本: 【吾爱2013CM大赛解答】 -- 驱动crackme -- 网际座山雕 KeyGen分析