-
-
[原创]看雪 2017 CTF 第五题 DebugPort MD5
-
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漏洞挖掘与利用;代码审计。