首页
论坛
专栏
课程
13

[原创] CTF2017 第5题 独行孤客 CrackMe 详细破解分析

爱琴海 2017-6-10 22:16 1892

这题考察驱动调试、驱动反调试、MD5算法穷举还有电脑性能(开玩笑的),分析算法不会费什么功夫。
本文按照实际破解顺序撰写,符合初学者的理解逻辑,尽量条理清晰,比较细致,希望耐心看,大牛略过吧!


一、静态了解目标:未加壳,OD载入主程序,迅速查看字符,发现几处有用,记录下来,养成好习惯:


    \vmxdrv.sys
    DRV
    vmxdrv
    888aeda4ab
    SCManger
    42D378 Success

//发现驱动,成功提示信息,算法侦测:MD5,大概有了分析思路——>先搞定主程序,再搞定驱动


二、释放并运行驱动


//下断点:CreateFileA

00401566   .  E8 B5090000   CALL CrackMe.00401F20

// 释放驱动

00401F43  |.  53            PUSH EBX                                 ; /hTemplateFile => NULL
00401F44  |.  68 80000000   PUSH 0x80                                ; |Attributes = NORMAL
00401F49  |.  6A 02         PUSH 0x2                                 ; |Mode = CREATE_ALWAYS
00401F4B  |.  53            PUSH EBX                                 ; |pSecurity => NULL
00401F4C  |.  6A 02         PUSH 0x2                                 ; |ShareMode = FILE_SHARE_WRITE
00401F4E  |.  68 00000040   PUSH 0x40000000                          ; |Access = GENERIC_WRITE
00401F53  |.  50            PUSH EAX                                 ; |FileName
00401F54  |.  895C24 28     MOV DWORD PTR SS:[ESP+0x28],EBX          ; |
00401F58  |.  FF15 88324200 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; \CreateFileA

0012F760   00AA3E68  |FileName = "C:\ctf2017\5\vmxdrv.sys"
0012F764   40000000  |Access = GENERIC_WRITE
0012F768   00000002  |ShareMode = FILE_SHARE_WRITE
0012F76C   00000000  |pSecurity = NULL
0012F770   00000002  |Mode = CREATE_ALWAYS
0012F774   00000080  |Attributes = NORMAL
0012F778   00000000  \hTemplateFile = NULL

00401F78  |.  51            PUSH ECX                                 ; /ResourceType
00401F79  |.  52            PUSH EDX                                 ; |ResourceName
00401F7A  |.  53            PUSH EBX                                 ; |hModule => NULL
00401F7B  |.  FF15 7C324200 CALL DWORD PTR DS:[<&KERNEL32.FindResour>; \FindResourceA
00401F81  |.  8BF0          MOV ESI,EAX
00401F83  |.  56            PUSH ESI                                 ; /hResource
00401F84  |.  53            PUSH EBX                                 ; |hModule => NULL
00401F85  |.  FF15 80324200 CALL DWORD PTR DS:[<&KERNEL32.LoadResour>; \LoadResource
00401F8B  |.  56            PUSH ESI                                 ; /hResource
00401F8C  |.  53            PUSH EBX                                 ; |hModule => NULL
00401F8D  |.  8BE8          MOV EBP,EAX                              ; |
00401F8F  |.  FF15 84324200 CALL DWORD PTR DS:[<&KERNEL32.SizeofReso>; \SizeofResource

//从资源释放 6144 字节驱动

0012F76C   00000000  |hModule = NULL
0012F770   00000084  |ResourceName = 0x84
0012F774   00AA3E18  \ResourceType = "DRV"

00401FA6  |.  53            PUSH EBX                                 ; /pOverlapped => NULL
00401FA7  |.  51            PUSH ECX                                 ; |pBytesWritten
00401FA8  |.  50            PUSH EAX                                 ; |nBytesToWrite
00401FA9  |.  55            PUSH EBP                                 ; |Buffer
00401FAA  |.  57            PUSH EDI                                 ; |hFile
00401FAB  |.  FF15 90324200 CALL DWORD PTR DS:[<&KERNEL32.WriteFile>>; \WriteFile

//释放驱动,此处将驱动文件拷贝副本,留作分析用

0012F768   00000098  |hFile = 00000098 (window)
0012F76C   00437108  |Buffer = CrackMe.00437108
0012F770   00001800  |nBytesToWrite = 1800 (6144.)
0012F774   0012F788  |pBytesWritten = 0012F788
0012F778   00000000  \pOverlapped = NULL

int __stdcall sub_401F20(LPCSTR lpFileName, int a2, LPCSTR lpType)
{
  HANDLE v3; // edi@1
  HRSRC v4; // esi@2
  HGLOBAL v5; // ebp@2
  DWORD v6; // eax@2
  int result; // eax@5
  DWORD NumberOfBytesWritten; // [sp+Ch] [bp-10h]@1
  int v9; // [sp+18h] [bp-4h]@1

  v9 = 0;
  NumberOfBytesWritten = 0;
  v3 = CreateFileA(lpFileName, 0x40000000u, 2u, 0, 2u, 0x80u, 0);
  if ( v3 != (HANDLE)-1
    && (v4 = FindResourceA(0, (LPCSTR)(unsigned __int16)a2, lpType),
        v5 = LoadResource(0, v4),
        v6 = SizeofResource(0, v4),
        v4)
    && v5
    && v6 )
  {
    WriteFile(v3, v5, v6, &NumberOfBytesWritten, 0);
    CloseHandle(v3);
    LOBYTE(v9) = 0;
    sub_417FCE(&lpFileName);
    v9 = -1;
    sub_417FCE(&lpType);
    result = 1;
  }
  else
  {
    sub_417FCE(&lpFileName);
    sub_417FCE(&lpType);
    result = 0;
  }
  return result;
}

0040158D   .  52            PUSH EDX
0040158E   .  68 0CD14200   PUSH CrackMe.0042D10C                    ;  vmxdrv
00401593   .  E8 08050000   CALL CrackMe.00401AA0

//安装驱动

00401AD7  |.  68 3F000F00   PUSH 0xF003F
00401ADC  |.  6A 00         PUSH 0x0
00401ADE  |.  6A 00         PUSH 0x0
00401AE0  |.  FF15 1C304200 CALL DWORD PTR DS:[<&ADVAPI32.OpenSCMana>;  advapi32.OpenSCManagerA

00401B27  |.  6A 00         PUSH 0x0                                 ; /Password = NULL
00401B29  |.  6A 00         PUSH 0x0                                 ; |ServiceStartName = NULL
00401B2B  |.  6A 00         PUSH 0x0                                 ; |pDependencies = NULL
00401B2D  |.  6A 00         PUSH 0x0                                 ; |pTagId = NULL
00401B2F  |.  6A 00         PUSH 0x0                                 ; |LoadOrderGroup = NULL
00401B31  |.  52            PUSH EDX                                 ; |BinaryPathName
00401B32  |.  6A 00         PUSH 0x0                                 ; |ErrorControl = SERVICE_ERROR_IGNORE
00401B34  |.  6A 03         PUSH 0x3                                 ; |StartType = SERVICE_DEMAND_START
00401B36  |.  6A 01         PUSH 0x1                                 ; |ServiceType = SERVICE_KERNEL_DRIVER
00401B38  |.  68 FF010F00   PUSH 0xF01FF                             ; |DesiredAccess = SERVICE_ALL_ACCESS
00401B3D  |.  57            PUSH EDI                                 ; |DisplayName
00401B3E  |.  57            PUSH EDI                                 ; |ServiceName
00401B3F  |.  55            PUSH EBP                                 ; |hManager
00401B40  |.  FF15 20304200 CALL DWORD PTR DS:[<&ADVAPI32.CreateServ>; \CreateServiceA

0012F658   0017F018  |hManager = 0017F018
0012F65C   0042D10C  |ServiceName = "vmxdrv"
0012F660   0042D10C  |DisplayName = "vmxdrv"
0012F664   000F01FF  |DesiredAccess = SERVICE_ALL_ACCESS
0012F668   00000001  |ServiceType = SERVICE_KERNEL_DRIVER
0012F66C   00000003  |StartType = SERVICE_DEMAND_START
0012F670   00000000  |ErrorControl = SERVICE_ERROR_IGNORE
0012F674   0012F69C  |BinaryPathName = "C:\ctf2017\5\vmxdrv.sys"
0012F678   00000000  |LoadOrderGroup = NULL
0012F67C   00000000  |pTagId = NULL
0012F680   00000000  |pDependencies = NULL
0012F684   00000000  |ServiceStartName = NULL
0012F688   00000000  \Password = NULL

//此处注意 ServiceName = "vmxdrv" 后续动态分析驱动时设置断点有

00401BBF  |.  6A 00         PUSH 0x0
00401BC1  |.  6A 00         PUSH 0x0
00401BC3  |.  56            PUSH ESI
00401BC4  |.  FF15 28304200 CALL DWORD PTR DS:[<&ADVAPI32.StartServi>;  advapi32.StartServiceA

//驱动加载后删除驱动文件,防止被破解者发现

004015C9   .  51            PUSH ECX                                 ; /FileName = "C:\ctf2017\5\vmxdrv.sys"
004015CA   .  FF15 A8324200 CALL DWORD PTR DS:[<&KERNEL32.DeleteFile>; \DeleteFileA


三、静态分析驱动:


//首先了解一下相关知识,为了后续准确断点IRP回调函数:

#ifndef DEVICE_TYPE
#define DEVICE_TYPE ULONG
#endif
#define IRP_MJ_MAXIMUM_FUNCTION           0x1b
#define IRP_MJ_CREATE                     0x00
#define IRP_MJ_CREATE_NAMED_PIPE          0x01
#define IRP_MJ_CLOSE                      0x02
#define IRP_MJ_READ                       0x03
#define IRP_MJ_WRITE                      0x04
#define IRP_MJ_QUERY_INFORMATION          0x05
#define IRP_MJ_SET_INFORMATION            0x06
#define IRP_MJ_QUERY_EA                   0x07
#define IRP_MJ_SET_EA                     0x08
#define IRP_MJ_FLUSH_BUFFERS              0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION   0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION     0x0b
#define IRP_MJ_DIRECTORY_CONTROL          0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL        0x0d
#define IRP_MJ_DEVICE_CONTROL             0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL    0x0f
#define IRP_MJ_SHUTDOWN                   0x10
#define IRP_MJ_LOCK_CONTROL               0x11
#define IRP_MJ_CLEANUP                    0x12
#define IRP_MJ_CREATE_MAILSLOT            0x13
#define IRP_MJ_QUERY_SECURITY             0x14
#define IRP_MJ_SET_SECURITY               0x15
#define IRP_MJ_POWER                      0x16
#define IRP_MJ_SYSTEM_CONTROL             0x17
#define IRP_MJ_DEVICE_CHANGE              0x18
#define IRP_MJ_QUERY_QUOTA                0x19
#define IRP_MJ_SET_QUOTA                  0x1a
#define IRP_MJ_PNP                        0x1b

//使用 IDA 很容易根据MajorFunction[i]下标查到对应IRP回调函数类型:

memset32(DriverObject->MajorFunction, (int)sub_106EC, 0x1Bu);
DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)sub_106C8;// IRP_MJ_Create
DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)sub_105A8;// IRP_MJ_Read
DriverObject->MajorFunction[4] = (PDRIVER_DISPATCH)sub_1061C;// IRP_MJ_Write
DriverObject->MajorFunction[0xE] = (PDRIVER_DISPATCH)sub_1071A;// IRP_MJ_Device_Control
DriverObject->MajorFunction[0x12] = (PDRIVER_DISPATCH)sub_106C8;// IRP_MJ_Cleanup
DriverObject->MajorFunction['\x02'] = (PDRIVER_DISPATCH)sub_106C8;
DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_10564;

//找到了关键几个MajorFunction先进行简单整理,便于后续查看:
//IRP_MJ_Read 暗桩等

int __stdcall sub_105A8(int a1, PIRP Irp)
{
  struct _IRP *v2; // edi@1
  signed int v3; // edx@2
  int v4; // edi@5

  v2 = Irp->AssociatedIrp.MasterIrp;
  if ( !dword_114DC )  //暗桩,必须跳
  {
    v3 = 3;
    do
    {
      byte_114C8[v3] = 3 * v3 - 100;  //如果到这里来,输出都是固定字符
      ++v3;
    }
    while ( v3 < 16 );
    byte_114C8[0] = -53;
    byte_114C9 = -86;
    byte_114CA = -34;
    byte_114CB = -80;
  }
  *(_DWORD *)&v2->Type = *(_DWORD *)byte_114C8;
  v4 = (int)&v2->MdlAddress;
  *(_DWORD *)v4 = *(_DWORD *)&byte_114C8[4];
  v4 += 4;
  *(_DWORD *)v4 = *(_DWORD *)&byte_114C8[8];
  *(_DWORD *)(v4 + 4) = *(_DWORD *)&byte_114C8[12];
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 16;
  IofCompleteRequest(Irp, 0);
  return 0;
}

//IRP_MJ_Write 内核字符串变形及MD5

unsigned int __stdcall sub_1061C(int a1, PIRP Irp)
{
  PIRP v2; // esi@1
  size_t v3; // edi@1
  PVOID v4; // eax@1
  void *v5; // ebx@1
  unsigned int result; // eax@2
  struct _IRP *Irpa; // [sp+18h] [bp+Ch]@1

  v2 = Irp;
  Irpa = Irp->AssociatedIrp.MasterIrp;
  v3 = *(_DWORD *)(v2->Tail.Overlay.PacketType + 4);
  v4 = ExAllocatePoolWithTag(PagedPool, *(_DWORD *)(v2->Tail.Overlay.PacketType + 4), 0x4C4D544Eu);
  v5 = v4;
  if ( v4 )
  {
    memset(v4, 0, v3);
    memcpy(v5, Irpa, v3);
    if ( dword_114D8 )
    {
      sub_104B6(v5, (int)byte_114C8); //核心算法,字符第1到第6位依次ASCII:+1、+1、+2、+3、+4、+5,然后进行MD5
      dword_114DC = 1;
    }
    ExFreePoolWithTag(v5, 0);
    v2->IoStatus.Status = 0;
    v2->IoStatus.Information = v3;
    IofCompleteRequest(v2, 0);
    DbgPrint("DispatchWrite called\n");
    result = 0;
  }
  else
  {
    v2->IoStatus.Information = 0;
    v2->IoStatus.Status = 0xC000009A;
    IofCompleteRequest(v2, 0);
    result = 0xC000009A;
  }
  return result;
}

//IRP_MJ_Device_Control 内核反调试及其他

int __stdcall sub_1071A(int a1, PIRP Irp)
{
  switch ( *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12) )
  {
    case 0x222004:  //该分支定时并多次在流程中调用,包含内核反调试和暗桩
      dword_114D8 = 1;  //暗桩,必须为1,否则跟踪不到后续算法
      dword_114E0 = (int)IoGetCurrentProcess();
      sub_10486();  //DebugPort清零反调试
      break;
    case 0x222008:
      DbgPrint("%s\n", Irp->AssociatedIrp.IrpCount);
      break;
    case 0x22200C:
      DbgPrint("bye!\n");
      break;
    default:
      DbgPrint("unkonw code!\n");
      break;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 0;
  IofCompleteRequest(Irp, 0);
  return 0;
}


四、动态分析RING3主程序:

//首先OD载入主程序,搜索所有“push 0x222004”调用IRP_MJ_Device_Control处,一共两处,全部修改为“push 0x22200C”,
//简单跳过内核反调试,否则死机少不了,但可能会影响到后续算法或者留暗桩,不过并不影响RING3主程序的算法流程分析。

地址       反汇编                        注释
————————————————————————————————
00401E22   PUSH 0x222004                 修改为“push 0x22200C”
0040230E   PUSH 0x222004                 修改为“push 0x22200C”

//下断点:GetWindowTextA,运行断下:

0041DCFD  |.  50            PUSH EAX                                 ; |Buffer
0041DCFE  |.  56            PUSH ESI                                 ; |hWnd
0041DCFF  |.  FF15 90334200 CALL DWORD PTR DS:[<&USER32.GetWindowTex>; \GetWindowText

//一路返回 401760 这个算法程序:

00401760   .  6A FF         PUSH -0x1
00401762   .  68 581E4200   PUSH CrackMe_.00421E58                   ;  SE 处理程序安装
00401767   .  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
0040176D   .  50            PUSH EAX
0040176E   .  64:8925 00000>MOV DWORD PTR FS:[0],ESP
00401775   .  83EC 10       SUB ESP,0x10
00401778   .  A1 40D54200   MOV EAX,DWORD PTR DS:[0x42D540]
0040177D   .  55            PUSH EBP
0040177E   .  56            PUSH ESI
0040177F   .  57            PUSH EDI
00401780   .  8BF1          MOV ESI,ECX
00401782   .  894424 10     MOV DWORD PTR SS:[ESP+0x10],EAX
00401786   .  6A 01         PUSH 0x1
00401788   .  C74424 28 000>MOV DWORD PTR SS:[ESP+0x28],0x0
00401790   .  E8 628D0100   CALL CrackMe_.0041A4F7
00401795   .  8D7E 68       LEA EDI,DWORD PTR DS:[ESI+0x68]

004017AB   .  E8 4A6B0100   CALL CrackMe_.004182FA                   ;  大写字符转小写,此处提醒你穷举时不需要跑大写字符
004017B0   .  8D4C24 0C     LEA ECX,DWORD PTR SS:[ESP+0xC]
004017B4   .  E8 536B0100   CALL CrackMe_.0041830C                   ;  倒置字符,比如输入“123456”-->“654321”
004017B9   .  E8 BDD60100   CALL CrackMe_.0041EE7B
004017BE   .  8B4C24 0C     MOV ECX,DWORD PTR SS:[ESP+0xC]
004017C2   .  8B40 04       MOV EAX,DWORD PTR DS:[EAX+0x4]
004017C5   .  8379 F8 06    CMP DWORD PTR DS:[ECX-0x8],0x6           ;  长度6位
004017C9   .  0F85 F3000000 JNZ CrackMe_.004018C2

004017CF   .  8BC8          MOV ECX,EAX
004017D1   .  E8 6AFAFFFF   CALL <JMP.&KERNEL32.IsDebuggerPresent>   ; [IsDebuggerPresent 反调试
004017D6   .  85C0          TEST EAX,EAX
004017D8   .  0F85 E4000000 JNZ CrackMe_.004018C2

//得跟进核心算法 401D50 了,很明显 WriteFile、ReadFile 与驱动沟通,将倒置后的6位小写字符给驱动加密,然后再读取出来
//读出来的结果类似MD5值,怀疑驱动里头也进行了MD5

004017F8   .  50            PUSH EAX
004017F9   .  8BCE          MOV ECX,ESI
004017FB   .  E8 50050000   CALL CrackMe_.00401D50

00401DE8   .  53            PUSH EBX                                 ; /hTemplateFile => NULL
00401DE9   .  68 80000000   PUSH 0x80                                ; |Attributes = NORMAL
00401DEE   .  6A 03         PUSH 0x3                                 ; |Mode = OPEN_EXISTING
00401DF0   .  53            PUSH EBX                                 ; |pSecurity => NULL
00401DF1   .  53            PUSH EBX                                 ; |ShareMode => 0
00401DF2   .  68 000000C0   PUSH 0xC0000000                          ; |Access = GENERIC_READ|GENERIC_WRITE
00401DF7   .  68 58D34200   PUSH CrackMe_.0042D358                   ; |FileName = "\\.\vmxdrv"
00401DFC   .  FF15 88324200 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; \CreateFileA
00401E02   .  8BF8          MOV EDI,EAX
00401E04   .  83FF FF       CMP EDI,-0x1
00401E07   .  0F84 CE000000 JE CrackMe_.00401EDB
00401E0D   .  8D4424 1C     LEA EAX,DWORD PTR SS:[ESP+0x1C]
00401E11   .  53            PUSH EBX                                 ; /pOverlapped => NULL
00401E12   .  50            PUSH EAX                                 ; |pBytesReturned
00401E13   .  8D8C24 380200>LEA ECX,DWORD PTR SS:[ESP+0x238]         ; |
00401E1A   .  68 00010000   PUSH 0x100                               ; |OutBufferSize = 100 (256.)
00401E1F   .  51            PUSH ECX                                 ; |OutBuffer
00401E20   .  53            PUSH EBX                                 ; |InBufferSize => 0x0
00401E21   .  53            PUSH EBX                                 ; |InBuffer => NULL
00401E22      68 0C202200   PUSH 0x22200C
00401E27   .  57            PUSH EDI                                 ; |hDevice
00401E28   .  FF15 8C324200 CALL DWORD PTR DS:[<&KERNEL32.DeviceIoCo>; \DeviceIoControl
00401E2E   .  3BC3          CMP EAX,EBX
00401E30   .  0F84 A5000000 JE CrackMe_.00401EDB
00401E36   .  56            PUSH ESI
00401E37   .  8D5424 34     LEA EDX,DWORD PTR SS:[ESP+0x34]
00401E3B   .  55            PUSH EBP
00401E3C   .  52            PUSH EDX
00401E3D   .  E8 4E9B0000   CALL CrackMe_.0040B990
00401E42   .  83C4 0C       ADD ESP,0xC
00401E45   .  885C34 30     MOV BYTE PTR SS:[ESP+ESI+0x30],BL
00401E49   .  8D4424 18     LEA EAX,DWORD PTR SS:[ESP+0x18]
00401E4D   .  46            INC ESI
00401E4E   .  53            PUSH EBX                                 ; /pOverlapped
00401E4F   .  50            PUSH EAX                                 ; |pBytesWritten
00401E50   .  8D4C24 38     LEA ECX,DWORD PTR SS:[ESP+0x38]          ; |
00401E54   .  56            PUSH ESI                                 ; |nBytesToWrite
00401E55   .  51            PUSH ECX                                 ; |Buffer
00401E56   .  57            PUSH EDI                                 ; |hFile
00401E57   .  FF15 90324200 CALL DWORD PTR DS:[<&KERNEL32.WriteFile>>; \WriteFile
00401E5D   .  85C0          TEST EAX,EAX
00401E5F   .  74 73         JE XCrackMe_.00401ED4
00401E61   .  8D5424 14     LEA EDX,DWORD PTR SS:[ESP+0x14]
00401E65   .  53            PUSH EBX                                 ; /pOverlapped
00401E66   .  52            PUSH EDX                                 ; |pBytesRead
00401E67   .  8D8424 380100>LEA EAX,DWORD PTR SS:[ESP+0x138]         ; |
00401E6E   .  6A 10         PUSH 0x10                                ; |BytesToRead = 10 (16.)
00401E70   .  50            PUSH EAX                                 ; |Buffer
00401E71   .  57            PUSH EDI                                 ; |hFile
00401E72   .  FF15 94324200 CALL DWORD PTR DS:[<&KERNEL32.ReadFile>] ; \ReadFile

//离开核心算法程序,回到主流程

00401829   .  E8 F2000000   CALL CrackMe_.00401920                   ;  对驱动返回的HASH再进行1次MD5
0040182E   .  6A 0A         PUSH 0xA
00401830   .  8D4424 18     LEA EAX,DWORD PTR SS:[ESP+0x18]
00401834   .  6A 02         PUSH 0x2
00401836   .  50            PUSH EAX
00401837   .  8D4C24 1C     LEA ECX,DWORD PTR SS:[ESP+0x1C]
0040183B   .  E8 38420100   CALL CrackMe_.00415A78                   ;  将MD5结果从第3位开始截取10位

//将截取的10位字符与内置的“888aeda4ab”进行比较,相同则成功

00401876   .  68 6CD14200   PUSH CrackMe_.0042D16C                   ;  ASCII "888aeda4ab"
0040187B   .  51            PUSH ECX
0040187C   .  E8 309F0000   CALL CrackMe_.0040B7B1                   ;  关键比较
00401881   .  83C4 08       ADD ESP,0x8
00401884   .  85C0          TEST EAX,EAX
00401886   .  75 09         JNZ XCrackMe_.00401891                   ;  关键跳


五、小结RING3主程序算法流程:

1、输入字符:“123456”
2、转小写字符:“123456”
3、倒置字符:“654321”
4、驱动返回HASH:“17f73e13bad89038392692e82d63b17d” (类似MD5)
5、MD5(HASH):“7c766e2a1ca057c7b0e91f935edaad73”
6、从第3位开始取10位:“766e2a1ca0”
7、与 “888aeda4ab” 比较

//显然只要再搞清楚第4步骤驱动里头在做什么就可以弄清楚算法了


六、驱动动态调试分析


//接下来该分析驱动,参考第三步静态分析的关键几个MajorFunction,开启windbg双机调试,断点:

bu vmxdrv+0x5A8 --> IRP_MJ_Read
bu vmxdrv+0x71A --> IRP_MJ_Device_Control
bu vmxdrv+0x61C --> IRP_MJ_Write
bu vmxdrv+0x685 --> Algorithm

//重新开始运行未修改过的主程序,断下IRP_MJ_Device_Control:

f8c0071a 8bff            mov     edi,edi
f8c0071c 55              push    ebp
f8c0071d 8bec            mov     ebp,esp
f8c0071f 51              push    ecx
f8c00720 a1c014c0f8      mov     eax,dword ptr [vmxdrv+0x14c0 (f8c014c0)]
f8c00725 33c5            xor     eax,ebp
f8c00727 8945fc          mov     dword ptr [ebp-4],eax
f8c0072a 56              push    esi
f8c0072b 8b750c          mov     esi,dword ptr [ebp+0Ch]
f8c0072e 8b4660          mov     eax,dword ptr [esi+60h]
f8c00731 8b400c          mov     eax,dword ptr [eax+0Ch]
f8c00734 2d04202200      sub     eax,222004h

//参考第三节程序,“push 0x222004”对应选择分支
//IRP_MJ_Device_Control 内核反调试及其他

int __stdcall sub_1071A(int a1, PIRP Irp)
{
  switch ( *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12) )
  {
    case 0x222004:  //该分支定时并多次在流程中调用,包含内核反调试和暗桩
      dword_114D8 = 1;  //暗桩,必须为1,否则跟踪不到后续算法
      dword_114E0 = (int)IoGetCurrentProcess();
      sub_10486();  //DebugPort清零反调试
      break;
    case 0x222008:
      DbgPrint("%s\n", Irp->AssociatedIrp.IrpCount);
      break;
    case 0x22200C:
      DbgPrint("bye!\n");
      break;
    default:
      DbgPrint("unkonw code!\n");
      break;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 0;
  IofCompleteRequest(Irp, 0);
  return 0;
}

f8c00774 ff158013c0f8    call    dword ptr [vmxdrv+0x1380 (f8c01380)]

nt!IoGetCurrentProcess:
804ef678 64a124010000    mov     eax,dword ptr fs:[00000124h] fs:0030:00000124=8211b4f0
804ef67e 8b4044          mov     eax,dword ptr [eax+44h]

eax=82148708

f8c0077a a3e014c0f8      mov     dword ptr [vmxdrv+0x14e0 (f8c014e0)],eax ds:0023:f8c014e0=00000000
f8c0077f e802fdffff      call    vmxdrv+0x486 (f8c00486)

//跟进 call vmxdrv+0x486 (f8c00486) ,“and dword ptr [eax+0BCh],0”对DebugPort清零,反调试

f8c00486 ff158013c0f8    call    dword ptr [vmxdrv+0x1380 (f8c01380)] ds:0023:f8c01380={nt!IoGetCurrentProcess (804ef678)}
f8c0048c 8b0de014c0f8    mov     ecx,dword ptr [vmxdrv+0x14e0 (f8c014e0)]
f8c00492 8bd0            mov     edx,eax
f8c00494 eb0f            jmp     vmxdrv+0x4a5 (f8c004a5)
f8c00496 8b8088000000    mov     eax,dword ptr [eax+88h]
f8c0049c 2d88000000      sub     eax,88h
f8c004a1 3bc2            cmp     eax,edx
f8c004a3 740b            je      vmxdrv+0x4b0 (f8c004b0)
f8c004a5 3bc1            cmp     eax,ecx
f8c004a7 75ed            jne     vmxdrv+0x496 (f8c00496)  //第一遍跟踪没有跳
f8c004a9 83a0bc00000000  and     dword ptr [eax+0BCh],0   //DebugPort清零,反调试
f8c004b0 c3              ret

//将[f8c004a9]的“83”修改为“C3”,跳过DebugPort清零反调试

PEPROCESS sub_10486() //内核反调试
{
  PEPROCESS result; // eax@1
  struct _EPROCESS *v1; // edx@1

  result = IoGetCurrentProcess();
  v1 = result;
  while ( result != (PEPROCESS)dword_114E0 )
  {
    result = (PEPROCESS)(*((_DWORD *)result + 34) - 136);
    if ( result == v1 )
      return result;
  }
  *((_DWORD *)result + 0x2F) = 0; //DebugPort清零反调试
  return result;
}

//至此已经清楚 IRP_MJ_Device_Control 内核反调试手段,记得取消该断点。
//继续运行,显示出CrackMe窗口,输入“123456”,按回车,断下 IRP_MJ_Write:

f8c0061c 8bff            mov     edi,edi
f8c0061e 55              push    ebp
f8c0061f 8bec            mov     ebp,esp
f8c00621 53              push    ebx
f8c00622 56              push    esi
f8c00623 8b750c          mov     esi,dword ptr [ebp+0Ch]
f8c00626 8b460c          mov     eax,dword ptr [esi+0Ch]

if ( dword_114D8 ) 
 {
   sub_104B6(v5, (int)&byte_114C8); //核心算法,字符第1到第6位依次ASCII:+1、+1、+2、+3、+4、+5,然后进行MD5
   dword_114DC = 1;  //暗桩
  }

//算法 (call vmxdrv+0x4b6 将倒置输入的ASCII第1位+1,第2位+1,第3位+2,第4位+3,第5位+4,第6位+5,如果是数字可理解为+112345,并取MD5)

badd667f 68c874ddba      push    offset vmxdrv+0x14c8 (badd74c8)
badd6684 53              push    ebx
badd6685 e82cfeffff      call    vmxdrv+0x4b6 (badd64b6)
badd668a c705dc74ddba01000000 mov dword ptr [vmxdrv+0x14dc (badd74dc)],1

//跟进 call vmxdrv+0x4b6

[ebx]:
e17a8690 36 35 34 33 32 31 00 ff 02 02 01 00 46 4d 66 6e  654321......FMf

//读取输入,计算位数=6

badd64e5 8a10            mov     dl,byte ptr [eax]          ds:0023:e17a8690=36
badd64e7 40              inc     eax
badd64e8 84d2            test    dl,dl
badd64ea 75f9            jne     vmxdrv+0x4e5 (badd64e5)
badd64ec 2bc6            sub     eax,esi

//比较位数

badd64f0 83fe10          cmp     esi,10h
badd64f3 7f58            jg      vmxdrv+0x54d (badd654d)

//注册码[1]+1

badd650d fe45ec          inc     byte ptr [ebp-14h]         ss:0010:b0ed4ba0=36
badd6510 3bf0            cmp     esi,eax
badd6512 7e09            jle     vmxdrv+0x51d (badd651d)

//注册码循环+AL 此时 AL=0,1,2,3,4,5

badd6514 004405ec        add     byte ptr [ebp+eaw-14h],al  ss:0010:b0ed4ba1=35
badd6518 40              inc     eax
badd6519 3bc6            cmp     eax,esi
badd651b 7cf7            jl      vmxdrv+0x514 (badd6514)

//654321+112345=766666
//MD5准备

badd6520 50              push    eax
badd6521 e88c030000      call    vmxdrv+0x8b2 (badd68b2)

badd6535 50              push    eax
badd6536 8d45ec          lea     eax,[ebp-14h]
badd6539 50              push    eax
badd653a 8d4594          lea     eax,[ebp-6Ch]
badd653d 50              push    eax
badd653e e8e10b0000      call    vmxdrv+0x1124 (badd7124)

b0ed4b48 30 00 00 00 00 00 00 00 01 23 45 67 89 ab cd ef  0........#Eg....
b0ed4b58 fe dc ba 98 76 54 32 10 37 36 36 36 36 36 00 00  ....vT2.766666..

//MD5(“766666”)= 17f73e13bad89038392692e82d63b17d

badd6543 53              push    ebx
badd6544 8d4594          lea     eax,[ebp-6Ch]
badd6547 50              push    eax
badd6548 e8810c0000      call    vmxdrv+0x11ce (badd71ce)

badd74c8 17 f7 3e 13 ba d8 90 38 39 26 92 e8 2d 63 b1 7d  ..>....89&..-c.}

//call vmxdrv+0x4b6 已经跟完了,我们继续运行
//断在 IRP_MJ_Read

f8c005a8 8bff            mov     edi,edi
f8c005aa 55              push    ebp
f8c005ab 8bec            mov     ebp,esp
f8c005ad 833ddc14c0f800  cmp     dword ptr [vmxdrv+0x14dc (f8c014dc)],0  //此处暗桩,必须为1

if ( !dword_114DC )  //暗桩
  {
    v3 = 3;
    do
    {
      byte_114C8[v3] = 3 * v3 - 100; //暗桩,生成固定假HASH
      ++v3;
    }
    while ( v3 < 16 );
    byte_114C8[0] = -53;
    byte_114C9 = -86;
    byte_114CA = -34;
    byte_114CB = -80;  //暗桩,更新固定假HASH“cbaadeb0a8abaeb1b4b7babdc0c3c6c9”
  }

//正确流程

badd65f3 bec874ddba      mov     esi,offset vmxdrv+0x14c8 (badd74c8)
badd65f8 a5              movs    dword ptr es:[edi],dword ptr [esi]
badd65f9 a5              movs    dword ptr es:[edi],dword ptr [esi]
badd65fa a5              movs    dword ptr es:[edi],dword ptr [esi]
badd65fb a5              movs    dword ptr es:[edi],dword ptr [esi]

89c05d28 17 f7 3e 13 ba d8 90 38 39 26 92 e8 2d 63 b1 7d  ..>....89&..-c.}

//至此,驱动重要的几个MajorFunction已经分析完成,并且完成了反反调试


七、驱动算法小结


1、输入的字符(RING3程序已经完成转小写、倒置)从第1-6位ASCII按顺序依次加上“1、1、2、3、4、5”
   比如“654321”-->“766666”
2、对字符进行MD5,比如“7666666”-->“17f73e13bad89038392692e82d63b17d”


八、全部算法总结:


1、输入字符:“123456”
2、转小写字符:“123456”
3、倒置字符:“654321”
4、驱动返回HASH:
4.1、从第1-6位ASCII按顺序依次加上“1、1、2、3、4、5”,比如“654321”-->“766666”
4.2、对字符进行MD5,比如“7666666”-->“17f73e13bad89038392692e82d63b17d”
5、MD5(HASH):“17f73e13bad89038392692e82d63b17d”-->“7c766e2a1ca057c7b0e91f935edaad73”
6、从第3位开始取10位:“766e2a1ca0”
7、与 “888aeda4ab” 比较


九、算法破解


//Python:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Ctf.pediy.com ctf2017 crackme 5 keygen
# python 3.6.0 32bit
# written by 爱琴海
# 2017/06/09
# 根据比赛规则缩小穷举范围,理论上破电脑需7小时左右才能跑完,好电脑大概1.5小时跑完,实际不需要全部跑完,大概1x分钟到1.x小时

import hashlib
import time

def Main():
    start_time = time.clock()
    # 考虑比赛规则,对于驱动来说,即将参与MD5计算的字符满足如下范围,进一步提高效率:(小写字母、数字再进一步缩小范围)
    # +1  第1位 0x31-0x3a、0x62-0x7b
    # +1  第2位 0x31-0x3a、0x62-0x7b
    # +2  第3位 0x32-0x3b、0x63-0x7c
    # +3  第4位 0x33-0x3c、0x64-0x7d
    # +4  第5位 0x34-0x3d、0x65-0x7e
    # +5  第6位 0x35-0x3e、0x66-0x7f
    # 穷举完的话,总共2176782336种组合
    sn1 = [0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b]
    sn2 = [0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b]
    sn3 = [0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c]
    sn4 = [0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d]
    sn5 = [0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e]
    sn6 = [0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f]
    # 开始穷举
    for count1 in range(0x0,len(sn1)):
        for count2 in range(0x0,len(sn2)):
            for count3 in range(0x0,len(sn3)):
                for count4 in range(0x0,len(sn4)):
                    for count5 in range(0x0,len(sn5)):
                        for count6 in range(0x0,len(sn6)):
                            sn_str = chr(sn1[count1])+chr(sn2[count2])+chr(sn3[count3])+chr(sn4[count4])+chr(sn5[count5])+chr(sn6[count6])
                            if Md5_inc_check(sn_str) == '888aeda4ab':
                                print ('found sn:',(chr(sn6[count6]-5)+chr(sn5[count5]-4)+chr(sn4[count4]-3)+chr(sn3[count3]-2)+chr(sn2[count2]-1)+chr(sn1[count1]-1)))
                                print ('use time: %.3f second' % (time.clock()-start_time))
                                return
    print ('use time: %.3f second' % (time.clock()-start_time))
    return

def Md5_inc_check(input_str):
    out_str = hashlib.md5(input_str.encode('utf-8')).hexdigest()
    out_str = hashlib.md5(out_str.encode('utf-8')).hexdigest()
    return out_str[2:12]

if __name__=="__main__":
    Main()

//跑出正确注册码:


十、总结


这题考察驱动调试、驱动反调试、MD5算法穷举,对新手来说光一个驱动反调试就足以让他系统死掉很多回,驱动调试需要相关经验,MD5算法穷举没有意思,不好玩。
穷举跑出正确注册码“su1986”,由于本题作者只将输入的大写字符转换为小写而没有拒绝,所以本题至少存在4个答案,另一个大写的“SU1986”,大小写组合“Su1986”、“sU1986”,多解不加分



快讯:看雪智能设备漏洞挖掘公开课招生中!

最新回复 (6)
流年似风 2017-6-11 15:20
2
windbg调试内核,学习到了。
poyoten 2017-6-11 22:15
3
我当时系统没死,但是OD会跑飞。
poyoten 2017-6-11 22:16
4
windbg姿势不会啊,学习了
梦中的香 2017-6-12 10:03
5
请问下,你这个伪代码      在  WINDBG里怎么显示的~~大神,求解
fengyunabc 2017-6-12 10:40
6
感谢分享!
hbcld 2017-6-12 21:20
7
学习,谢谢分享
返回