这题考察驱动调试、驱动反调试、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”,多解不加分
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。