首页
社区
课程
招聘
[原创]看雪 2017 CTF 第五题 DebugPort MD5
2017-6-10 17:53 3925

[原创]看雪 2017 CTF 第五题 DebugPort MD5

HHHso 活跃值
22
2017-6-10 17:53
3925

一、通过驱动和客户进程逆向可驱动下述校验算法

char inputkey[6];

char salt[6]={1,1,2,3,4,5};

0==mbsicmp("888aeda4ab",MD5.hexdigest(MD5.hexdigest(reverse(tolow(inputkey))+salt)))

二、采用python枚举(海量工作需要策略,补刀策略(2)一个多小时命中)

(1)最初策略

#------- ------- ------- ------- ------- ------- ------- 

#python

import md5

saltv = [1,1,2,3,4,5]

chset = b"".join([chr(i) for i in xrange(ord('a'),ord('z')+1)])

nset = "1234567890"

chset = chset + nset   #-------[diff1]

for k0 in chset:       #-------[diff2]

  for k1 in chset:

    for k2 in chset:

      for k3 in chset:

        for k4 in chset:

          for k5 in chset:

            irk = b"".join([k0,k1,k2,k3,k4,k5])

            ik = b"".join([chr(ord(irk[i])+saltv[i]) for i in xrange(0,6)])

            m1 = md5.new()

            m2 = md5.new()

            m1.update(ik)

            m1d = m1.hexdigest()

            m2.update(m1d)

            m2d = m2.hexdigest()

            if m2d[2:0x0C] == "888aeda4ab":

              print irk

#------- ------- ------- ------- ------- ------- ------- 

(2)上述策略单进程跑一个多小时不见好,于是补充策略

将#-------[diff2]修改为下述后,开另一个进程协力跑

for k0 in chset[::-1]:       #-------[diff2]

其意思就是将chset反转,即

策略(1)从'abcdefghijklmnopqrstuvwxyz1234567890'顺着遍历

策略(2)从'0987654321zyxwvutsrqponmlkjihgfedcba'反着遍历

如此在得到有效结果前,两者向中间遍历靠拢,在完成全部遍历前时间可以缩减一半

实际上策略(2)在一个小时左右就得到了结果 "6891us" > 反过来是 "su1986"

#------- ------- ------- ------- ------- ------- ------- 

如果(1)(2)长时间还不出结果或遍历完

还可以(1)(2)基础上继续将#-------[diff2]做下述改动,另外加两个进程跑,时间可以再缩减一半

//(3)for k0 in chset[:chset.__len__()]: 

//(4)for k0 in chset[::-1][chset.__len__():]: 

前提是机器处理核心足够,可以采用多线程或进程的方式。

hashcat能否派上用场未知。

三、庖丁解牛

从程序执行入口到注册输入对话框初始化函数

在入口函数 0040C073 start 中,调用c全局对象初始化

0040C118 call    __cinit

在__cinit函数中,有c全局初始化函数数组

.text:0040C228 push    offset Hi_z_dword_42D0A8

.text:0040C22D push    offset Hi_x_dword_42D000

.text:0040C232 call    __initterm

在Hi_x_dword_42D000与Hi_z_dword_42D0A8之间,有下述CWinApp全局变量初始化函数

.data:0042D000 Hi_x_dword_42D000 dd 0                  ; DATA XREF: __cinit+1Fo

.data:0042D004                 dd offset sub_417D76

//省略

.data:0042D040                 dd offset Hi_global_CWinAppObj_init_sub_4010E0 //CWinApp全局变量初始化函数

//省略

.data:0042D0A8 Hi_z_dword_42D0A8 dd 0

CWinApp全局变量初始化函数

int Hi_global_CWinAppObj_init_sub_4010E0()

{

  Hi_CWinApp_ctor_sub_4010F0(); //CWinApp对象构造函数

  return atexit(Hi_WinApp_dtor_sub_401110);//CWinApp对象析构函数,在程序退出时调用释放资源

}

在CWinApp构造函数中,全局静态CWinApp对象(变量)位于Hi_CWinAppObj_dword_4312B8出

由下述初始化过程可知虚表为Hi_CWinApp_vft_off_423550

int *Hi_CWinApp_ctor_sub_4010F0()

{

  CWinApp::CWinApp(0);

  Hi_CStringList_sub_415E6C(10);

  Hi_CWinAppObj_dword_4312B8 = (int)&Hi_CWinApp_vft_off_423550;

  return &Hi_CWinAppObj_dword_4312B8;

}

在CWinApp的虚拟函数表Hi_CWinApp_vft_off_423550中,可定位到CWinApp对象初始化函数,

位于CWinApp:Run()函数之上的位置(MFC不成文规定,实际是CWinApp类的成员定义顺序决定)

.rdata:004235A0 dd offset Hi_CWinApp_initialize_sub_401120 //CWinApp对象初始化函数

.rdata:004235A4 dd offset CWinApp::Run(void)

在CWinApp对象初始化函数Hi_CWinApp_initialize_sub_401120中,有注册码输入框构造函数调用

.text:00401149 push    0

.text:0040114B call    Hi_CDialogKey_ctor_sub_4012D0

在 Hi_CDialogKey_ctor_sub_4012D0 

.text:004012D0 push    0FFFFFFFFh

.text:004012D2 push    offset SEH_4012D0

.text:004012D7 mov     eax, large fs:0

.text:004012DD push    eax

.text:004012DE mov     large fs:0, esp

.text:004012E5 push    ecx

.text:004012E6 mov     eax, [esp+10h+arg_0]

.text:004012EA push    esi

.text:004012EB push    edi

.text:004012EC mov     esi, ecx

.text:004012EE push    eax

.text:004012EF push    66h   //-----------------------注册对话框资源ID

.text:004012F1 mov     [esp+20h+var_10], esi

.text:004012F5 call    CDialog::CDialog(uint,CWnd *)

.text:004012FA mov     ecx, Hi_COccManager_typeinfo_off_42D540 //CString初始化

.text:00401300 mov     [esp+18h+var_4], 0

.text:00401308 mov     [esi+5Ch], ecx

.text:0040130B mov     edx, Hi_COccManager_typeinfo_off_42D540//CString初始化

.text:00401311 lea     ecx, [esi+68h]  ; this

.text:00401314 mov     [ecx], edx

.text:00401316 mov     eax, Hi_COccManager_typeinfo_off_42D540//CString初始化

.text:0040131B lea     edi, [esi+6Ch]

.text:0040131E mov     [edi], eax

.text:00401320 push    offset Hi_lpsznull_431398 ; lpString

.text:00401325 mov     byte ptr [esp+1Ch+var_4], 3

.text:0040132A mov     dword ptr [esi], offset Hi_CDialog_inputkey_vft_off_423760 //注册对话框虚表

.text:00401330 call    CString::operator=(char const *)

.text:00401335 push    offset Hi_lpsznull_431398 ; lpString

.text:0040133A mov     ecx, edi        ; this

.text:0040133C call    CString::operator=(char const *)

.text:00401341 call    AfxGetModuleState(void)

.text:00401346 call    AfxGetModuleState(void)

.text:0040134B mov     eax, [eax+0Ch]

.text:0040134E push    80h             ; lpIconName

.text:00401353 push    eax             ; hInstance

.text:00401354 call    ds:LoadIconA

.text:0040135A mov     ecx, [esp+18h+var_C]

.text:0040135E mov     [esi+70h], eax

.text:00401361 mov     eax, esi

.text:00401363 pop     edi

.text:00401364 pop     esi

.text:00401365 mov     large fs:0, ecx

.text:0040136C add     esp, 10h

.text:0040136F retn    4

.text:0040136F Hi_CDialogKey_ctor_sub_4012D0 endp

有上述初始化过程,可以得到注册对话框类对象的内存结构(包括虚表)

Hi_CDialog_inputkey_ctor_sub_4012D0

 .00hww Hi_CDialog_inputkey_vft_off_423760 ******************

 .5Chww.saltMD5_hexdigest_str = saltMD5(inKey)

 .64hww.isLoadSysDirverOK

 .68hww.inKey = CStr inputkey

 .6Chww.SuccessTips = CStr 

 .70hww = LoadIconA(MODULE_STATE.m_bDLL,0x80)

 

在虚拟函数表中,定位对话框初始化函数

.rdata:0042381C dd offset Hi_CDialogKey_OnInitDiaglog_sub_4013E0

在注册码输入对话框初始化函数Hi_CDialogKey_OnInitDiaglog_sub_4013E0中有以下主要操作

1.通过Hi_is_WinVersion_5_1_sub_402210函数检测系统是否为xp (5.1)

2.获取程序执行路径,构建驱动释放文件全路径名(即在当前目录释放vmxdr.sys驱动)

3.加载安装上述释放的驱动并启动驱动服务

4.删除释放的驱动文件并启动5个反调式线程(反调试线程具有控制开启驱动MD5功能作用,不能简单屏蔽处理)

1.通过Hi_is_WinVersion_5_1_sub_402210函数检测系统是否为xp (5.1)

.text:004014C0 call    Hi_is_WinVersion_5_1_sub_402210

.text:004014C5 mov     ebx, ds:PostMessageA

.text:004014CB test    eax, eax

.text:004014CD jnz     short loc_4014E9

.text:004014CF push    0               ; int

.text:004014D1 push    0               ; uType

.text:004014D3 push    offset Text     ; "请在xp平台中重新运行CrackMe"

.text:004014D8 call    Hi_kindly_reminder_sub_41DB51

2.获取程序执行路径,构建驱动释放文件全路径名(即在当前目录释放vmxdr.sys驱动)

.text:004014E9 lea     eax, [esp+12Ch+loc_vmxdrv_sys_path]

.text:004014ED push    eax             ; lpBuffer

.text:004014EE push    104h            ; nBufferLength

.text:004014F3 call    ds:GetCurrentDirectoryA

.text:004014F9 mov     edi, offset aVmxdrv_sys ; "\\vmxdrv.sys"

//省略

.text:00401566 call    Hi_release_P2ResType_P3ResId_to_P1FullFilePath_sub_401F20("./vmxdrv.sys",ResType:"DRV",ResID:0x84)

.text:0040156B test    eax, eax

.text:0040156D jnz     short loc_401587

.text:0040156F push    eax             ; int

.text:00401570 push    eax             ; uType

.text:00401571 push    offset asc_42D114 ;

.text:00401571 ; 驱动释放失败,

.text:00401571 ; 请在xp平台中重新运行CrackMe

.text:00401576 call    Hi_kindly_reminder_sub_41DB51

上述释放驱动函数过程如下

Hi_release_P2ResType_P3ResId_to_P1FullFilePath_sub_401F20{

 hFileSys = CreateFileA(lpszReleaseFullFilePath, 0x40000000u, 2u, 0, 2u, 0x80u, 0);

 if hFileSys != HANDLE_TYPE_INVALID:

    hResInfo = FindResourceA(0, lpResType, lpResId);

    hResDataPtr = LoadResource(0, hResInfo);

    cbResDataSize = SizeofResource(0, hResInfo);

    WriteFile(hFileSys, hResDataPtr, cbResDataSize, &NumberOfBytesWritten, 0);

    CloseHandle(hFileSys);

}

3.加载安装上述释放的驱动并启动驱动服务

.text:00401587 lea     edx, [esp+12Ch+loc_vmxdrv_sys_path]

.text:0040158B mov     ecx, ebp

.text:0040158D push    edx             ; lpVmxdrvSysFileName

.text:0040158E push    offset ServiceName ; "vmxdrv"

.text:00401593 call    Hi_Load_CreateOrOpenServiceAndStart_P1ServiceName_P2SysFileName_sub_401AA0 //参考后续分析

.text:00401598 test    eax, eax

.text:0040159A mov     [ebp+64h], eax   // .64hww.isLoadSysDirverOK 逆向对象结构

.text:0040159D jnz     short loc_4015C5

.text:0040159F push    offset ServiceName ; "vmxdrv"

.text:004015A4 mov     ecx, ebp

.text:004015A6 call    Hi_unLoad_OpenAndDeleteService_P1Servicename_sub_401C40

.text:004015AB push    0               ; int

.text:004015AD push    0               ; uType

.text:004015AF push    offset asc_42D0E0 ;

.text:004015AF ; 驱动加载失败,

.text:004015AF ; 请在xp平台中重新运行CrackMe

.text:004015B4 call    Hi_kindly_reminder_sub_41DB51

服务安装与启动过程如下

Hi_Load_CreateOrOpenServiceAndStart_P1ServiceName_P2SysFileName_sub_401AA0(lpVmxdrvSysFileName){

    GetFullPathNameA(lpVmxdrvSysFileName, 0x100u, &FullPathName, 0);

    hSCManager = OpenSCManagerA(0, 0, 0xF003Fu);

    if hSCManager == 0:

      Hi_fsprint_sub_40B946("OpenSCManager() Faild %d ! \n",GetLastError())

    else:

      Hi_fsprint_sub_40B946("OpenSCManager() ok ! \n")

      hService = CreateServiceA(hSCManager, lpServiceName, lpServiceName, 0xF01FFu, 1u, 3u, 0, &FullPathName, 0, 0, 0, 0, 0);

      if hService != 0:

        Hi_fsprint_sub_40B946("CrateService() ok ! \n")

      else:

        lastEr = GetLastError()

        if (lastEr == ERROR_IO_PENDING) or (lastEr == ERROR_SERVICE_EXISTS):

          Hi_fsprint_sub_40B946("CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! ")

          hService = OpenServiceA(v2, lpServiceName, 0xF01FFu);

          if hService == 0:

            Hi_fsprint_sub_40B946("OpenService() Faild %d ! \n",GetLastError())

            goto exitreturn;

          else:

            Hi_fsprint_sub_40B946("OpenService() ok ! \n")

        else:

         Hi_fsprint_sub_40B946("OpenService() Faild %d ! \n",GetLastError())

         goto exitreturn;

      //"OpenService() or CreateService() OK"

      if 0==StartServiceA(hService, 0, 0) ):

        lastEr = GetLastError()

        if (lastEr == ERROR_IO_PENDING):

          Hi_fsprint_sub_40B946("StartService() Faild ERROR_IO_PENDING !")

        elif (lastEr == ERROR_SERVICE_ALREADY_RUNNING):

          Hi_fsprint_sub_40B946("StartService() Faild ERROR_SERVICE_ALREADY_RUNNING !")

    exitreturn:

      if hSCManager:

        CloseServiceHandle(hSCManager);

}

4.删除释放的驱动文件并启动5个反调式线程(反调试线程具有控制开启驱动MD5功能作用,不能简单屏蔽处理)

.text:004015C5 lea     ecx, [esp+12Ch+loc_vmxdrv_sys_path]

.text:004015C9 push    ecx             ; lpFileName

.text:004015CA call    ds:DeleteFileA

.text:004015D0 push    5

.text:004015D2 mov     ecx, ebp

.text:004015D4 call    Hi_fork_1to20_ThreadsToCtrl_sys_TurnOnMD5SaltFlag_and_antidbg_sub_402250

Hi_fork_1to20_ThreadsToCtrl_sys_TurnOnMD5SaltFlag_and_antidbg_sub_402250(threadCount:5){

  check: 1 <= threadCount <= 20

  do{

    AfxBeginThread(Hi_control_sys_to_TurnOnTheMD5SaltFlag_and_clear_DebugPort_of_process_sub_4022A0,0,0,0,0,0);

    Sleep(100)

  }while(--threadCount)

}

上述并发5个驱动控制线程,线程函数为Hi_control_sys_to_TurnOnTheMD5SaltFlag_and_clear_DebugPort_of_process_sub_4022A0

其每3秒向驱动发出一个0x222004控制信号,vmxdrv.sys 控制响应函数参考随后的Hi_IRP_MJ_DEVICE_CONTROL_sub_1071A

并进一步影响后续写和读取驱动设备功能

Hi_control_sys_to_TurnOnTheMD5SaltFlag_and_clear_DebugPort_of_process_sub_4022A0{

  memset(&OutBuffer, 0, 0x100);

  BytesReturned = 0;

  while ( 1 )

  {

    if HANDLE_TYPE_INVALID == CreateFileA(FileName, 0xC0000000, 0, 0, 3u, 0x80u, 0);

      break;

    DeviceIoControl(v0, 0x222004u, 0, 0, &OutBuffer, 0x100u, &BytesReturned, 0);

    CloseHandle(v0);

    Sleep(3000);

  }

  return 0;

}

在vmxdrv.sys的 IRP_MJ_DEVICE_CONTROL响应函数 由用户进程反调试线程控制调用

int __stdcall Hi_IRP_MJ_DEVICE_CONTROL_sub_1071A(int a1, PIRP Irp)

{

  switch ( *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12) )

  {

    case 0x222004:

      Hi_MD5SaltFlagSwitch_dword_114D8 = 1; //算法功能开关

      Hi_CurrentProcess_dword_114E0 = (int)IoGetCurrentProcess();

      Hi_clear_DebugPort_of_process_sub_10486(); //反调试功能函数调用

      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;

}

在控制信号0x222004处理分支中:

(1)打开MD5正确加盐标记Hi_MD5SaltFlagSwitch_dword_114D8=1,此为算法功能开关

   (1.a)该开关决定驱动Hi_IRP_MJ_WRITE_sub_1061C响应时是否执行MD5哈希摘要处理

   (1.b)同时决定在MD5哈希摘要处理加盐时,对key[0]是否执行正确的"加1"加盐处理

(2)通过Hi_clear_DebugPort_of_process_sub_10486检测清除进程调试端口(参考后续分析)

(1.a)算法功能开关Hi_MD5SaltFlagSwitch_dword_114D8影响机制

(1.a.1)在写驱动设备响应函数 Hi_IRP_MJ_WRITE_sub_1061C 中,由客户进程WriteFile控制响应

判断Hi_MD5SaltFlagSwitch_dword_114D8开关是否打开而决定是否实现加盐摘要处理

   if ( Hi_MD5SaltFlagSwitch_dword_114D8 )

    {

      Hi_MD5salt_inkey_outDigest_sub_104B6(lpkey, (int)Hi_MD5Digest_byte_114C8);

      Hi_MD5DigestOK_dword_114DC = 1;

    }

(1.a.2)上述流程中,加盐MD5处理函数如下,

Hi_MD5salt_inkey_outDigest_sub_104B6(IN lpkey,OUT outMD5SaltDigest)

{

  char loc_keybuf[0x10];

  keylen = strlen(lpkey)

  if keylen <= 0x10:

    memcpy(loc_keybuf, lpkey, keylen);

    //Add Salt  [1,1,2,3,4,5,...]

    if ( Hi_MD5SaltFlagSwitch_dword_114D8 )

      loc_keybuf[0]++; //Hi_MD5SaltFlagSwitch_dword_114D8 开关决定首字节加1

    for(int i = 0,i < keylen,i++){//

      loc_keybuf[i]+=i

    }

    //

    Hi_MD5Init_sub_108B2(&loc_MD5ctx);

    Hi_MD5Update_sub_11124((int)&loc_MD5ctx, loc_keybuf, keylen);

    result = Hi_MD5_digest_sub_111CE(&loc_MD5ctx, outMD5SaltDigest)

}

即经过 Hi_IRP_MJ_WRITE_sub_1061C 响应,计算得到输入key的加盐MD5摘要信息存储在Hi_MD5Digest_byte_114C8处

上述既然提到了WriteFile,也顺带提下ReadFile触发的驱动响应函数,

在下述读驱动设备响应函数中,根据是否有有效(Hi_MD5DigestOK_dword_114DC)摘要返回默认或处理结果

即,只有开启相应的算法功能处理才会返回得到正确的加盐MD5摘要值,否则只有默认的错误摘要值

Hi_IRP_MJ_READ_sub_105A8{

  outkeydigest = Irp->AssociatedIrp.SystemBuffer

  if ( !Hi_MD5DigestOK_dword_114DC ){

    //若没有有效输入key的摘要信息,则返回虚构摘要信息

    for(int i = 3,i < 0x10,i++){

      Hi_MD5Digest_byte_114C8[i]=(i*3 - 100)&0xFF

    }

    Hi_MD5Digest_byte_114C8[0] = 0xCBu;

    Hi_MD5Digest_byte_114C8[1] = 0xAAu;

    Hi_MD5Digest_byte_114C8[2] = 0xDEu;

    Hi_MD5Digest_byte_114C8[3] = 0xB0u;

  }

  memcpy(outkeydigest,Hi_MD5Digest_byte_114C8,0x10)

  Irp->IoStatus.Status = 0;

  Irp->IoStatus.Information = 16;

  IofCompleteRequest(Irp, 0);

}

在注册码输出对话框虚拟函数表中Hi_CDialog_inputkey_vft_off_423760有以下注册码Enter输入相应函数

00423824 dd offset Hi_EnterKey_sub_401760

这是怎么找到的?

一般自定义的成员函数都会放在虚表后,随机IDA点进去看看哪个像,这是虚招。

我定位时在IDA TEXT的模式下,顺序浏览下代码的分布大概,留意到该函数的以下代码段所以笔记敏感的,

还记得上述提到注册对话框的内存结构吗,其有几个CString成员变量,其中一个就是偏移0x6C位置,

即该函数有可能对敏感字符串成员变量操作的可能,所以要多留个心眼。

.text:0040184E lea     ebp, [esi+6Ch]

.text:00401851 push    offset Hi_lpsznull_431398 ; lpString

.text:00401856 mov     ecx, ebp        ; this

.text:00401858 call    CString::operator=(char const *)

.text:004018C7 lea     ecx, [esi+6Ch]  ; this

.text:004018CA call    CString::operator=(char const

正规的确定过程套路是:

(1)GetWindowTextA,GetWindowTextW下断

(2)在调用堆栈中可见 DDX_Text >> GetWindowTextA

    可以很敏感地知道,下述注册码对话框的虚表函数更新了成员变量 .68 和 .6C CString

通过资源查看工具如eXeScope,可以确定资源ID 0x3E8(1000)和0x3EA(10002)

分别属于Edit编辑框和Static文本框,即 可确定注册对话框的两个成员变量名

.68.inKey CString 

.6C.SuccessTips CString;

.text:004013A0 push    esi

.text:004013A1 mov     esi, ecx

.text:004013A3 push    edi

.text:004013A4 mov     edi, [esp+8+arg_0]

.text:004013A8 lea     eax, [esi+68h]

.text:004013AB push    eax             ; struct CString *

.text:004013AC push    3E8h            ; int                       //RES_ID Edit 控件,即注册码编辑框

.text:004013B1 push    edi             ; struct CDataExchange *

.text:004013B2 call    DDX_Text(CDataExchange *,int,CString &)

.text:004013B7 add     esi, 6Ch

.text:004013BA push    esi             ; struct CString *

.text:004013BB push    3EAh            ; int                       //Static 控件,成功信息提示信息

.text:004013C0 push    edi             ; struct CDataExchange *

.text:004013C1 call    DDX_Text(CDataExchange *,int,CString &)

.text:004013C6 pop     edi

.text:004013C7 pop     esi

.text:004013C8 retn    4

(3)在.text:004013B7 add     esi, 6Ch处下断点,执行返回

   在注册对话框成员变量esi+0x68处下硬件访问Dword断点,执行触发

断在 Hi_CStr_ctor_CStr_sub_417D43中

    00417D4A mov     eax, [ecx]

由下述地方调用,

.text:0040179D call    Hi_CStr_ctor_CStr_sub_417D43

.text:004017A2 lea     ecx, [esp+2Ch+var_20]

上述代码位于我们之前提的 Enter响应函数中 Hi_EnterKey_sub_401760(套路还行)

回到Hi_EnterKey_sub_401760的代码

在函数的开头,通过UpDateData(True)更新读入控件变量(MFC设计中给控件添加的变量),

另,后续的UpDateData(False.0)的更新控件变量输出到GUI,见于该函数代码末端。

.text:00401786 push    1

.text:00401788 mov     [esp+2Ch+var_4], 0

.text:00401790 call    Hi_UpDateData_P1bTrueInFalseOut_sub_41A4F7

.text:00401795 lea     edi, [esi+68h]

.text:00401798 lea     ecx, [esp+2Ch+loc_inputkey]

.text:0040179C push    edi

.text:0040179D call    Hi_CStr_ctor_CStr_sub_417D43

.text:004017A2 lea     ecx, [esp+2Ch+loc_inputkey]

.text:004017A6 mov     [esp+2Ch+var_8], 1

.text:004017AB call    Hi_CStr_tolowcase_sub_4182FA

.text:004017B0 lea     ecx, [esp+2Ch+loc_inputkey]

.text:004017B4 call    Hi_CStr_reverse_sub_41830C

.text:004017B9 call    AfxGetModuleState(void)

.text:004017BE mov     ecx, [esp+2Ch+loc_inputkey]

.text:004017C2 mov     eax, [eax+4]

.text:004017C5 cmp     dword ptr [ecx-8], 6

上述代码对.68.inputKey 进行小写转换,和反转操作,并判断注册码长度为6

.text:004017C9 jnz     loc_4018C2

.text:004017CF mov     ecx, eax

.text:004017D1 call    IsDebuggerPresent

.text:004017D6 test    eax, eax

.text:004017D8 jnz     loc_4018C2

.text:004017DE mov     eax, [esi+64h]

.text:004017E1 test    eax, eax

紧接着来个调试检查,这个可以看做是驱动反调试被攻破后的挣扎式反调试;

从另一个角度也可以看做是尝试掩盖驱动反调试的虚招。

成员变量 .64hww.isLoadSysDirverOK 前面驱动加载的过程已经确定,

这里判断是否承购加载驱动,再决定执行后续算法校验,比较校验算法依赖驱动。

.text:004017E5 mov     edx, [esp+2Ch+loc_inputkey]

.text:004017E9 lea     ecx, [esp+2Ch+loc_inputkey]

.text:004017ED mov     eax, [edx-8]

.text:004017F0 push    eax             ; P2_keylen

.text:004017F1 push    0

.text:004017F3 call    Hi_substrPtr_sub_418263

.text:004017F8 push    eax             ; P1_key

.text:004017F9 mov     ecx, esi

.text:004017FB call    Hi_update_saltMD5_sub_401D50

.text:00401800 jmp     short loc_401812

接着获取经过小写和反转的inputKey所有字符经由驱动进行加盐MD5

Hi_update_saltMD5_sub_401D50(lpkey,keylen){

  check:keylen <= 0x10

  char inbuf[0x100]

  _strncpy(inbuf,lpkey,keylen)

  hDrvDevFile = CreateFileA("\\\\.\\vmxdrv", 0xC0000000, 0, 0, 3u, 0x80u, 0);

  DeviceIoControl(hDrvDevFile, 0x222004u, 0, 0, OutBuffer, 0x100u, &BytesReturned, 0)

  WriteFile(hDrvDevFile, inbuf,keylen, &NumberOfBytesWritten, 0) )

  ReadFile(hDrvDevFile, loc_saltMD5, 0x10u, &NumberOfBytesRead, 0);

  LenPtr{.00hww strLen,.04hww strPtr} = Hi_conver_binary_to_hexstr_sub_403200(&loc_00_hexstrlen, loc_saltMD5, 16)

  CloseHandle(hDrvDevFile);

}

上述控制驱动执行加盐MD5处理,假定为无出错的流程;

(1)DeviceIoControl如前面所提,开启驱动的算法功能开关Hi_MD5SaltFlagSwitch_dword_114D8,并执行Clear DbgPort进行反调试

(2)WriteFile 触发前述所提 Hi_IRP_MJ_WRITE_sub_1061C 和 Hi_MD5salt_inkey_outDigest_sub_104B6

(3)ReadFile  触发前述所提 Hi_IRP_MJ_READ_sub_105A8

(4) Hi_conver_binary_to_hexstr_sub_403200 将加盐MD5摘要信息字节saltMD5转换为十六进制字符串信息

  该函数返回 LenPtr{.00hww strLen,.04hww strPtr} 结构

  下述代码逻辑,判断strPtr若不为零,则将其复制给注册对话框成员变量 .5C.saltMD5_hexdigest_str Cstr

.text:00401E87 call    Hi_conver_binary_to_hexstr_sub_403200

.text:00401E8C add     esp, 0Ch

.text:00401E8F mov     eax, [eax+4]

.text:00401E92 mov     [esp+33Ch+var_4], ebx

.text:00401E99 cmp     eax, ebx

.text:00401E9B jnz     short loc_401EA2

.text:00401E9D mov     eax, offset Hi_lpnull_unk_423830

.text:00401EA2 loc_401EA2:   

.text:00401EA2 push    eax             ; lpString

.text:00401EA3 mov     eax, [esp+340h+var_32C]

.text:00401EA7 lea     ecx, [eax+5Ch]  ; this

.text:00401EAA call    CString::operator=(char const *)

让我们回到Hi_EnterKey_sub_401760,紧接着驱动的加盐MD5结果

函数将上述结果 5C.saltMD5_hexdigest_str 再经Hi_normal_MD5_sub_401920进行

一次普通的MD5处理,让后提取位置Pos=2,长度为Len=0x0A的部分经过小写转换得到最终比对key

Hi_normal_MD5_sub_401920 中调用的函数原义命名参考如下{

  Hi_MD5Init_sub_402630  初始化

  Hi_MD5Update_sub_402690

  Hi_doMD5hex_sub_4033A0{

    Hi_MD5Final_sub_402640

    Hi_conver_binary_to_hexstr_sub_403200 参考前述分析

  }

  Hi_MD5_dtor_sub_402660 析构

  Hi_CStr_dtor_sub_417FCE CString析构

}

.text:00401812 lea     ecx, [esp+2Ch+loc_saltMD5hexstr]

.text:00401816 lea     edx, [esi+5Ch]

.text:00401819 push    ecx

.text:0040181A push    ecx

.text:0040181B mov     ecx, esp

.text:0040181D mov     [esp+34h+var_14], esp

.text:00401821 push    edx

.text:00401822 call    Hi_CStr_ctor_CStr_sub_417D43

.text:00401827 mov     ecx, esi

.text:00401829 call    Hi_normal_MD5_sub_401920

.text:0040182E push    0Ah             ; size_t

.text:00401830 lea     eax, [esp+30h+loc_MD5HexStrPos2Len0A_of_saltMD5HexStr]

.text:00401834 push    2               ; int

.text:00401836 push    eax             ; int

.text:00401837 lea     ecx, [esp+38h+loc_saltMD5hexstr]

.text:0040183B call    Hi_CStr_Mid_sub_415A78

.text:00401840 lea     ecx, [esp+2Ch+loc_MD5HexStrPos2Len0A_of_saltMD5HexStr]

.text:00401844 mov     [esp+2Ch+var_8], 2

.text:00401849 call    Hi_CStr_tolowcase_sub_4182F

将得到的比对key与 "888aeda4ab"匹配,

若成功测通过 Hi_set_mSusscetips_sub_402030 设置 .6C.SuccessTips = "Success^!"

.text:00401876                 push    offset a888aeda4ab ; "888aeda4ab"

.text:0040187B                 push    ecx             ; unsigned __int8 *

.text:0040187C                 call    __mbsicmp

.text:00401881                 add     esp, 8

.text:00401884                 test    eax, eax

.text:00401886                 jnz     short loc_401891

.text:00401888                 mov     ecx, esi

.text:0040188A                 call    Hi_set_mSusscetips_sub_402030

于是得到了"一、"所描述整个校验算法,接着就是怎么设计枚举算法解决问题了。

四、MORE?

关于驱动的反调试和清除

(1)可以通过eXeScope等资源编辑器直接提取vmxdrv.sys驱动

(2)驱动入口调用下述两个初始化函数

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)

{

  Hi_Driver_init0_sub_11505();

  return Hi_Driver_init1_sub_107AC(DriverObject, RegistryPath);

}

其中Hi_Driver_init0_sub_11505初始化 Hi_BugCheckParameter_securitycookes_dword_114C0

主要看Hi_Driver_init1_sub_107AC的初始化逻辑

.text:00010856                 lea     edx, [esi+38h]

.text:00010859                 push    1Bh

.text:0001085B                 pop     ecx

.text:0001085C                 mov     eax, offset Hi_Default_handler_sub_106EC

.text:00010861                 mov     edi, edx

.text:00010863                 rep stosd

.text:00010865                 push    offset aDriverLoadOk ; "Driver load ok"

.text:0001086A                 mov     dword ptr [edx], offset Hi_IRP_MJ_CREATE__IRP_MJ_CLEANUP__IRP_MJ_CLOSE__sub_106C8

.text:00010870                 mov     dword ptr [esi+44h], offset Hi_IRP_MJ_READ_sub_105A8

.text:00010877                 mov     dword ptr [esi+48h], offset Hi_IRP_MJ_WRITE_sub_1061C

.text:0001087E                 mov     dword ptr [esi+70h], offset Hi_IRP_MJ_DEVICE_CONTROL_sub_1071A

.text:00010885                 mov     dword ptr [esi+80h], offset Hi_IRP_MJ_CREATE__IRP_MJ_CLEANUP__IRP_MJ_CLOSE__sub_106C8

.text:0001088F                 mov     dword ptr [esi+40h], offset Hi_IRP_MJ_CREATE__IRP_MJ_CLEANUP__IRP_MJ_CLOSE__sub_106C8

.text:00010896                 mov     dword ptr [esi+34h], offset Hi_DriverUnload_sub_10564

.text:0001089D                 call    DbgPrint

上述初始化驱动的IRP请求响应处理函数,esi为_DRIVER_OBJECT对象,结构如下(Windbg 得到)

0:000> dt _DRIVER_OBJECT

ntdll!_DRIVER_OBJECT

   +0x000 Type             : Int2B

   +0x002 Size             : Int2B

   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT

   +0x008 Flags            : Uint4B

   +0x00c DriverStart      : Ptr32 Void

   +0x010 DriverSize       : Uint4B

   +0x014 DriverSection    : Ptr32 Void

   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION

   +0x01c DriverName       : _UNICODE_STRING

   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING

   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH

   +0x02c DriverInit       : Ptr32     long 

   +0x030 DriverStartIo    : Ptr32     void 

   +0x034 DriverUnload     : Ptr32     void 

   +0x038 MajorFunction    : [28] Ptr32     long 

DDK\inc\ddk\wdm.h中有个IRP序号定义

函数指针的换算 IRP_MJ_index = (Offset - 0x38)/4

如上述 0x48 偏移,对应的函数指针为 (0x48 - 0x38)/4 == 4,即对于下述的 IRP_MJ_WRITE

同理可以得到各IRP函数

//

// Define the major function codes for IRPs.

//

#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

#define IRP_MJ_PNP_POWER                IRP_MJ_PNP      // Obsolete....

#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

(3)Hi_clear_DebugPort_of_process_sub_10486 反调试

.text:00010486 Hi_clear_DebugPort_of_process_sub_10486 proc near

.text:00010486 call    ds:IoGetCurrentProcess

.text:0001048C mov     ecx, Hi_CurrentProcess_dword_114E0

.text:00010492 mov     edx, eax

.text:00010494 jmp     short loc_104A5

.text:00010496 loc_10496:; -------------------------------------

.text:00010496 mov     eax, [eax+88h]

.text:0001049C sub     eax, 88h

.text:000104A1 cmp     eax, edx

.text:000104A3 jz      short locret_104B0

.text:000104A5 loc_104A5:; -------------------------------------

.text:000104A5 cmp     eax, ecx

.text:000104A7 jnz     short loc_10496

.text:000104A9 and     dword ptr [eax+0BCh], 0

.text:000104B0 locret_104B0:

.text:000104B0                 retn

.text:000104B0 Hi_clear_DebugPort_of_process_sub_10486 endp

进程对象结构如下(Windbg 得到)

0:000> dt _EPROCESS  

ntdll!_EPROCESS

   +0x000 Pcb              : _KPROCESS

   +0x06c ProcessLock      : _EX_PUSH_LOCK

   +0x070 CreateTime       : _LARGE_INTEGER

   +0x078 ExitTime         : _LARGE_INTEGER

   +0x080 RundownProtect   : _EX_RUNDOWN_REF

   +0x084 UniqueProcessId  : Ptr32 Void

   +0x088 ActiveProcessLinks : _LIST_ENTRY

   +0x090 QuotaUsage       : [3] Uint4B

   +0x09c QuotaPeak        : [3] Uint4B

   +0x0a8 CommitCharge     : Uint4B

   +0x0ac PeakVirtualSize  : Uint4B

   +0x0b0 VirtualSize      : Uint4B

   +0x0b4 SessionProcessLinks : _LIST_ENTRY

   +0x0bc DebugPort        : Ptr32 Void

   +0x0c0 ExceptionPort    : Ptr32 Void

即偏移0x88处为活动进程对象链表,上述遍历活动进程并匹配当前调用进程

在匹配当前调用进程时,将进程 +0x0bc DebugPort 设置为0,关闭进程调试端口,完成反调试。

(4)针对性去掉该反调试。

(4.1)上述通过and 0 操作完成清零,所以只需修改为 and 0xFFFFFFFF保持原值不变即可

.text:000104A9 83 A0 BC 00 00 00 00 and     dword ptr [eax+0BCh], 0

  即将上述偏移位置 0x4A9+3=0x4AC开始的四个00 替换为 FF即可

  这个需要在客户进程的镜像中修改

(4.2)通过CFF Explorer 可确定上述偏移等于在 vmxdrv.sys 文件中的偏移 0x4AC

     通过eXeScope可确定 vmxdrv.sys 资源位于 exe 文件中的 00034108中,

     通过十六进制编辑器修改 exe 镜像文件偏移 00034108+0x4AC 处的四个字节 00 00 00 00 为 FF FF FF FF

     至此即可清除驱动反调试



[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回