Loading... # 记第一次windbg逆向驱动 第一次做出来驱动逆向,选择了VNCTF2026的Shadow,大体掌握了双机调试配置方法和windbg各种指令。 具体参考了墨水师傅的建议搭配了环境,然后按照Liv师傅在RE分享会上的演示和他的wp完成了逆向驱动 1. 【【第拾壹期 REVERSE 分享会】驱动逆向 & Ebpf & 某实战逆向 & Angr & 自定义ROM & Frida】 https://www.bilibili.com/video/BV17hBQBqEda/?share_source=copy_web&vd_source=98a9009f933b295fc6897f7fc047ffee 2. https://tkazer.github.io/2026/02/02/VNCTF2026-Shadow-WP/ ## 调试建立 Maze.exe实现了一个迷宫,没有什么猫腻,直接看给的Shadow.sys。 首先建立驱动调试,win10 kmd注册,然后windbg先手动断下输入`sxe ld Shadow`,注意这个命令在第一次加载Shadow驱动前运行,否则之后Shadow里的操作会把真正的驱动已经写入到内存,即使unregister后也无法再启动了。 sxe指令会在驱动加载时匹配Shadow并中断下来,这样我们就可以查看驱动入口点  ## Shadow.sys分析 进入DriverEntry的主逻辑,可以看到先分配了一个大数组Pool,然后是常量aM做了sub_140001168处理(AES)后赋值给了Pool,Pool再分配给P做一系列操作,最终调用的实际上是SMC后的新代码。后面才知道这一系列操作正是**反射注入** ~~~c++ __int64 __fastcall sub_14000C000(__int64 a1) { int i; // [rsp+30h] [rbp-88h] int j; // [rsp+34h] [rbp-84h] __m128 *P; // [rsp+38h] [rbp-80h] char *Pool; // [rsp+48h] [rbp-70h] char *v6; // [rsp+50h] [rbp-68h] __int64 v7; // [rsp+58h] [rbp-60h] char *v8; // [rsp+60h] [rbp-58h] __int64 v9; // [rsp+68h] [rbp-50h] SIZE_T NumberOfBytes; // [rsp+70h] [rbp-48h] Pool = (char *)ExAllocatePool(PagedPool, 0x5E00uLL); sub_140002640(Pool, &aM, 0x5E00uLL); sub_140001168((unsigned int)&unk_14000A000, 16, (unsigned int)&aM, (_DWORD)Pool, 24064); v6 = &Pool[*((int *)Pool + 15)]; v7 = (__int64)&v6[*((unsigned __int16 *)v6 + 10) + 24]; NumberOfBytes = *((unsigned int *)v6 + 20); P = (__m128 *)ExAllocatePool(NonPagedPool, NumberOfBytes); sub_140002900(P, 0, NumberOfBytes); sub_140002640((char *)P, Pool, *((unsigned int *)v6 + 21)); for ( i = 0; i < *((unsigned __int16 *)v6 + 3); ++i ) { if ( *(_DWORD *)(v7 + 40LL * i + 16) ) { _mm_lfence(); sub_140002640( (char *)P + *(unsigned int *)(v7 + 40LL * i + 12), &Pool[*(unsigned int *)(v7 + 40LL * i + 20)], *(unsigned int *)(v7 + 40LL * i + 16)); } } sub_140002044((__int64)P); if ( (int)sub_140001C78((__int64)P) < 0 || (sub_140002250((__int64)P), v8 = (char *)P + P[3].m128_i32[3], ((int (__fastcall *)(__int64, _QWORD))((char *)P + *((unsigned int *)v8 + 10)))(a1, 0LL) < 0) ) { _mm_lfence(); ExFreePoolWithTag(P, 0); } else { _mm_lfence(); v9 = (__int64)&v8[*((unsigned __int16 *)v8 + 10) + 24]; for ( j = 0; j < *((unsigned __int16 *)v8 + 3); ++j ) { _mm_lfence(); if ( !stricmp((const char *)(40LL * j + v9), "INIT") ) { _mm_lfence(); sub_140002900( (__m128 *)((char *)P + *(unsigned int *)(v9 + 40LL * j + 12)), 0, *(unsigned int *)(v9 + 40LL * j + 8)); break; } } sub_140002900(P, 0, 0x1000uLL); } ExFreePoolWithTag(Pool, 0); *(_QWORD *)(a1 + 104) = sub_140001C70; return 3221225473LL; } ~~~ ## Dump sys 在驱动中,可以通过定位`__guard_dispatch_icall_fptr`来查找shellcode的调用,这个函数是Windows CFG机制里一个组件,用于防止内存损坏和shellcode任意执行  我们先计算下`__guard_dispatch_icall_fptr`在windows内核里真正的偏移,下个断点继续go  可以发现断下来了,此时可以尝试dump P,他是真正的驱动,Shadow.sys只是一个壳 P的内容存在rsp+38h指向的地址中,查看可以看到PE头  利用`!dh`可以查看指定模块Image的头信息 ~~~ 1: kd> !dh poi(@rsp+0x38) File Type: EXECUTABLE IMAGE FILE HEADER VALUES 8664 machine (X64) 6 number of sections 695CE348 time date stamp Tue Jan 6 18:26:16 2026 0 file pointer to symbol table 0 number of symbols F0 size of optional header 22 characteristics Executable App can handle >2gb addresses OPTIONAL HEADER VALUES 20B magic # 14.29 linker version 4200 size of code 1800 size of initialized data 0 size of uninitialized data 8000 address of entry point 1000 base of code ----- new ----- ffffcc8ad6b13000 image base 1000 section alignment 200 file alignment 1 subsystem (Native) 10.00 operating system version 10.00 image version 10.00 subsystem version A000 size of image 400 size of headers EA2F checksum 0000000000100000 size of stack reserve 0000000000001000 size of stack commit 0000000000100000 size of heap reserve 0000000000001000 size of heap commit 4160 DLL characteristics High entropy VA supported Dynamic base NX compatible Guard 0 [ 0] address [size] of Export Directory 805C [ 28] address [size] of Import Directory 0 [ 0] address [size] of Resource Directory 7000 [ 1D4] address [size] of Exception Directory 0 [ 0] address [size] of Security Directory 9000 [ 14] address [size] of Base Relocation Directory 5224 [ 38] address [size] of Debug Directory 0 [ 0] address [size] of Description Directory 0 [ 0] address [size] of Special Directory 0 [ 0] address [size] of Thread Storage Directory 5260 [ 118] address [size] of Load Configuration Directory 0 [ 0] address [size] of Bound Import Directory 5000 [ 138] address [size] of Import Address Table Directory 0 [ 0] address [size] of Delay Import Directory 0 [ 0] address [size] of COR20 Header Directory 0 [ 0] address [size] of Reserved Directory SECTION HEADER #1 .text name 3B85 virtual size 1000 virtual address 3C00 size of raw data 400 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 68000020 flags Code Not Paged (no align specified) Execute Read SECTION HEADER #2 .rdata name 6B0 virtual size 5000 virtual address 800 size of raw data 4000 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 48000040 flags Initialized Data Not Paged (no align specified) Read Only Debug Directories(2) Type Size Address Pointer cv 4f 5378 4378 Format: RSDS, guid, 1, D:\åºé¢\VNCTF2026\src\DLoader\x64\Debug\LoadTest.pdb ( 13) 148 53c8 43c8 SECTION HEADER #3 .data name BB8 virtual size 6000 virtual address C00 size of raw data 4800 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C8000040 flags Initialized Data Not Paged (no align specified) Read Write SECTION HEADER #4 .pdata name 1D4 virtual size 7000 virtual address 200 size of raw data 5400 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 48000040 flags Initialized Data Not Paged (no align specified) Read Only SECTION HEADER #5 INIT name 534 virtual size 8000 virtual address 600 size of raw data 5600 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 62000020 flags Code Discardable (no align specified) Execute Read SECTION HEADER #6 .reloc name 14 virtual size 9000 virtual address 200 size of raw data 5C00 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42000040 flags Initialized Data Discardable (no align specified) Read Only ~~~ 关注image base(ffffcc8ad6b13000)和image size(A000)即可 ~~~ 1: kd> .writemem D:\dumped-shadow.sys ffffcc8ad6b13000 LA000 Writing a000 bytes.................... ~~~ 此时导出了dump后的驱动,但是发现RVA地址错误,所以前面搞错了,应该dump的是刚解密完的驱动。如果在往后dump的话程序自身会自动修复rva转为当前的相对地址,所以dump后无法反编译 按下g后反射注入的驱动已经释放到内存,如果没有在新的驱动里下断点就run的话不会再断下了(这个非常关键,卡了我好几次,最后发现每次第一次加载才能断下驱动),此时需要恢复快照到第一次加载前。重新断点到刚Pool解密完的结果  取rsp+48h指向的地址,可以看到MZ头,再次按照上面的方法dump,此时image-base已经是正常的`0000000140000000` ~~~ 0000000140000000 image base 1000 section alignment 200 file alignment 1 subsystem (Native) 10.00 operating system version 10.00 image version 10.00 subsystem version A000 size of image 400 size of headers EA2F checksum ~~~ 这时候直接`.writemem D:\dumped-shadow.sys poi(rsp+48) LA000`即可获取可以反编译的真正sys ## dumped-shadow.sys分析 入口函数有字符串解密 ~~~c++ __int64 __fastcall sub_140001C10(struct _DRIVER_OBJECT *a1) { // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND] sub_140003610(&unk_140006B20); SourceString = -17423; v18 = -39; v19 = -67; v20 = -6; v21 = -65; v22 = -91; v23 = -63; v24 = -82; v25 = -61; v26 = -91; v27 = -59; v28 = -65; v29 = -70; v30 = -2; v31 = -68; v32 = -59; v33 = -66; v34 = -38; v35 = -64; v36 = -94; v37 = -62; v38 = -74; v39 = -60; v40 = -79; v41 = -58; v42 = -45; v43 = -69; v44 = -45; v45 = -67; v46 = -48; v47 = -65; v48 = -108; v49 = -63; v50 = -86; v51 = -61; v52 = -74; v53 = -59; v54 = -93; v55 = -70; v56 = -38; v57 = -68; v58 = -39; v59 = -66; v60 = -65; v61 = -64; LOBYTE(v1) = -70; sub_140001000(&SourceString, 46LL, v1); RtlInitUnicodeString(&DestinationString, &SourceString); SystemRoutineAddress = MmGetSystemRoutineAddress(&DestinationString); v11 = 0LL; for ( i = 12; i < 0x100000; ++i ) { _mm_lfence(); Process = 0LL; if ( PsLookupProcessByProcessId((HANDLE)i, &Process) >= 0 && PsGetProcessExitStatus(Process) == 259 ) { pImageFileName = 0LL; if ( SeLocateProcessImageName(Process, &pImageFileName) >= 0 ) { v9 = wcsrchr(pImageFileName->Buffer, 0x5Cu); if ( v9 ) { if ( *(_DWORD *)++v9 == 6357069 && *((_DWORD *)v9 + 1) == 6619258 ) { ExFreePoolWithTag(pImageFileName, 0); v11 = Process; break; } ExFreePoolWithTag(pImageFileName, 0); } else { ExFreePoolWithTag(pImageFileName, 0); ObfDereferenceObject(Process); } } else { ObfDereferenceObject(Process); } } } ProcessId = 0LL; if ( v11 ) ProcessId = PsGetProcessId(v11); if ( !ProcessId ) return 3221225473LL; sub_140003F80( (unsigned int)&unk_140006B20, (_DWORD)ProcessId, (_DWORD)SystemRoutineAddress, (unsigned int)sub_1400012C0, (__int64)&qword_140006BB0); a1->DriverUnload = (PDRIVER_UNLOAD)sub_140001230; for ( j = 0; j <= 0x1B; ++j ) a1->MajorFunction[j] = (PDRIVER_DISPATCH)sub_1400010A0; a1->MajorFunction[3] = (PDRIVER_DISPATCH)sub_140001100; Device = IoCreateDevice(a1, 0x78u, 0LL, 0xBu, 0, 0, &DeviceObject); if ( Device >= 0 ) { Lock = (PIO_REMOVE_LOCK)DeviceObject->DeviceExtension; IoInitializeRemoveLockEx(Lock, 0x4662644Bu, 0, 0, 0x78u); IoAcquireRemoveLockEx(Lock, a1, File, 0x25Eu, 0x78u); DeviceObject->Flags |= 4u; v62 = -28212; v63 = -42; v64 = -109; v65 = -15; v66 = -107; v67 = -32; v68 = -105; v69 = -15; v70 = -103; v71 = -7; v72 = -101; v73 = -7; v74 = -112; v75 = -51; v76 = -110; v77 = -40; v78 = -108; v79 = -16; v80 = -106; v81 = -18; v82 = -104; v83 = -5; v84 = -102; v85 = -12; v86 = -100; v87 = -15; v88 = -111; v89 = -32; v90 = -109; v91 = -16; v92 = -107; v93 = -43; v94 = -105; v95 = -12; v96 = -103; v97 = -5; v98 = -101; v99 = -17; v100 = -112; v101 = -30; v102 = -110; v103 = -93; v104 = -108; v105 = -107; v106 = -106; LOBYTE(v3) = -112; sub_140001000(&v62, 46LL, v3); RtlInitUnicodeString(&TargetDevice, &v62); v5 = IoAttachDevice(DeviceObject, &TargetDevice, &AttachedDevice); if ( v5 >= 0 ) { DeviceObject->Flags &= ~0x80u; return 0LL; } else { _mm_lfence(); IoReleaseRemoveLockAndWaitEx(Lock, a1, 0x78u); IoDeleteDevice(DeviceObject); DeviceObject = 0LL; return (unsigned int)v5; } } else { _mm_lfence(); return (unsigned int)Device; } } ~~~ windbg调试获得解密字符串为`KeDelayExecutionThread`  所以相当于在内核里遍历查找了这个函数。sub_140003F80函数里注册了回调函数sub_1400012C0 ~~~c++ __int64 __fastcall sub_1400012C0(unsigned __int8 a1, unsigned __int8 a2, _QWORD *a3) { char *v4; // [rsp+20h] [rbp-B8h] char *v5; // [rsp+20h] [rbp-B8h] char *v6; // [rsp+20h] [rbp-B8h] char *v7; // [rsp+20h] [rbp-B8h] void *v8; // [rsp+20h] [rbp-B8h] int j; // [rsp+28h] [rbp-B0h] int k; // [rsp+2Ch] [rbp-ACh] int m; // [rsp+30h] [rbp-A8h] int n; // [rsp+34h] [rbp-A4h] int ii; // [rsp+38h] [rbp-A0h] int jj; // [rsp+3Ch] [rbp-9Ch] int v15; // [rsp+40h] [rbp-98h] int i; // [rsp+44h] [rbp-94h] int v17; // [rsp+48h] [rbp-90h] __int64 v18; // [rsp+70h] [rbp-68h] BYREF unsigned __int64 v19; // [rsp+78h] [rbp-60h] __int64 v20; // [rsp+80h] [rbp-58h] PVOID P; // [rsp+88h] [rbp-50h] SIZE_T NumberOfBytes; // [rsp+90h] [rbp-48h] _BYTE *v23; // [rsp+98h] [rbp-40h] char *v24; // [rsp+A0h] [rbp-38h] char *v25; // [rsp+A8h] [rbp-30h] __int64 v26; // [rsp+B0h] [rbp-28h] v18 = 0x17658990C729C992LL; for ( i = 0; i < 57; ++i ) v18 = *a3 ^ (65539 * v18); v19 = 430LL; NumberOfBytes = 2146LL; P = ExAllocatePoolWithTag(NonPagedPool, 0x862uLL, 0x454E434Du); v4 = (char *)P; qmemcpy(P, &unk_140006000, 0x1ADuLL); sub_140001B60(P, 429LL); for ( j = 0; (unsigned __int64)j < 0x1AD; ++j ) v4[j] ^= byte_140006870[0]; v5 = v4 + 429; qmemcpy(v5, &unk_1400061B0, 0x1ADuLL); sub_140001B60(v5, 429LL); for ( k = 0; (unsigned __int64)k < 0x1AD; ++k ) v5[k] ^= byte_140006870[1]; v6 = v5 + 429; qmemcpy(v6, &unk_140006360, 0x1ADuLL); sub_140001B60(v6, 429LL); for ( m = 0; (unsigned __int64)m < 0x1AD; ++m ) v6[m] ^= byte_140006870[2]; v7 = v6 + 429; qmemcpy(v7, &unk_140006510, 0x1ADuLL); sub_140001B60(v7, 429LL); for ( n = 0; (unsigned __int64)n < 0x1AD; ++n ) v7[n] ^= byte_140006870[3]; v8 = v7 + 429; qmemcpy(v8, &unk_1400066C0, v19); sub_140001B60(v8, v19); for ( ii = 0; ii < v19; ++ii ) *((_BYTE *)v8 + ii) ^= byte_140006870[4]; v24 = (char *)P + 0x775; v23 = byte_140006B40; v20 = -1LL; do ++v20; while ( v23[v20] ); v15 = v20; if ( (_DWORD)v20 ) { v17 = (int)v20 % 8; for ( jj = v20; jj < v17; ++jj ) byte_140006B40[jj] = 1; v25 = v24; ((void (__fastcall *)(_BYTE *, _QWORD, __int64 *))v24)(byte_140006B40, v17 + v15, &v18); if ( RtlCompareMemory(&unk_140006AC0, byte_140006B40, 0x28uLL) == 40 ) KeBugCheck(0x11111111u); } ExFreePoolWithTag(P, 0x454E434Du); v26 = qword_140006BB0; return ((__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD *))qword_140006BB0)(a1, a2, a3); } ~~~ 可以看到又有一个shellcode 先跳过这部分,去分析下dumped-shadow.sys都做了什么 ~~~c++ NTSTATUS __fastcall sub_140001100(struct _DEVICE_OBJECT *a1, IRP *a2) { NTSTATUS v3; // [rsp+40h] [rbp-18h] struct _IO_REMOVE_LOCK *RemoveLock; // [rsp+48h] [rbp-10h] if ( AttachedDevice ) { RemoveLock = (struct _IO_REMOVE_LOCK *)a1->DeviceExtension; v3 = IoAcquireRemoveLockEx(RemoveLock, a2, File, 0xAAu, 0x78u); if ( v3 >= 0 ) { sub_1400022E0(a2); if ( IoSetCompletionRoutineEx(a1, a2, (PIO_COMPLETION_ROUTINE)CompletionRoutine, 0LL, 1u, 1u, 1u) < 0 ) { _mm_lfence(); IoReleaseRemoveLockEx(RemoveLock, a2, 0x78u); sub_140002400((__int64)a2); } return IofCallDriver(AttachedDevice, a2); } else { _mm_lfence(); a2->IoStatus.Status = v3; IofCompleteRequest(a2, 0); return v3; } } else { a2->IoStatus.Status = 0xC00000A3; IofCompleteRequest(a2, 0); return 0xC00000A3; } } ~~~ IoSetCompletionRoutineEx是Windows驱动程序中用于注册IO完成例程的函数,因此我们直接分析CompletionRoutine ~~~c++ __int64 __fastcall CompletionRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context) { char v4; // [rsp+20h] [rbp-78h] unsigned __int16 v5; // [rsp+24h] [rbp-74h] unsigned int i; // [rsp+2Ch] [rbp-6Ch] unsigned int v7; // [rsp+38h] [rbp-60h] struct _IRP *MasterIrp; // [rsp+40h] [rbp-58h] struct _IO_REMOVE_LOCK *RemoveLock; // [rsp+48h] [rbp-50h] CHAR Format[11]; // [rsp+50h] [rbp-48h] BYREF CHAR v11[10]; // [rsp+5Bh] [rbp-3Dh] BYREF CHAR v12[24]; // [rsp+68h] [rbp-30h] BYREF RemoveLock = (struct _IO_REMOVE_LOCK *)DeviceObject->DeviceExtension; if ( Irp->IoStatus.Status >= 0 ) { MasterIrp = Irp->AssociatedIrp.MasterIrp; v7 = Irp->IoStatus.Information / 0xC; for ( i = 0; i < v7; ++i ) { v5 = *(&MasterIrp->Size + 6 * i); if ( v5 == 42 || v5 == 54 ) { byte_140006BA5 = (*(&MasterIrp->Size + 6 * i + 1) & 1) == 0; } else if ( (*(&MasterIrp->Size + 6 * i + 1) & 1) == 0 ) { v4 = 0; if ( v5 < 0x54u ) { if ( byte_140006BA5 ) v4 = byte_1400051D0[v5]; else v4 = byte_140005170[v5]; } if ( v4 && byte_140006BA4 ) byte_140006B40[dword_140006BA8++] = v4; if ( v5 == 88 ) { byte_140006BA4 = byte_140006BA4 == 0; if ( byte_140006BA4 ) { dword_140006BA8 = 0; memset(byte_140006B40, 0, sizeof(byte_140006B40)); Format[0] = 2; Format[1] = 22; Format[2] = 31; Format[3] = 46; Format[4] = 52; Format[5] = 40; Format[6] = 58; Format[7] = 18; Format[8] = 60; Format[9] = 66; Format[10] = 12; qmemcpy(v11, "\nE04+))pU`", sizeof(v11)); sub_140001000((__int64)Format, 0x15uLL, 89); DbgPrintEx(0, 0x4Du, Format); //[LDriver] on input.\n } else { v12[0] = -79; v12[1] = -89; v12[2] = -88; v12[3] = -97; v12[4] = -121; v12[5] = -103; v12[6] = -107; v12[7] = -125; v12[8] = -81; v12[9] = -45; v12[10] = -99; v12[11] = -101; v12[12] = -122; v12[13] = -97; v12[14] = -97; v12[15] = -52; v12[16] = -120; v12[17] = 0x80; v12[18] = -117; v12[19] = -34; v12[20] = -5; v12[21] = -14; sub_140001000((__int64)v12, 0x16uLL, 234); DbgPrintEx(0, 0x4Du, v12); //[LDriver] input end.\n } } } } } if ( Irp->PendingReturned ) sub_1400023C0(Irp); IoReleaseRemoveLockEx(RemoveLock, Irp, 0x78u); return 0LL; } ~~~ 分析可知实现了一个**键盘过滤/键盘类驱动的 IRP 完成例程**,在键盘读 IRP 完成时解析 `KEYBOARD_INPUT_DATA`,**把按键转换成字符并存储**,同时用 **F12(88)当作“开始/结束记录”的开关**。 所以我们知道驱动触发的关键是F12,之后会检查输入是否是大小写来读取键盘输入并存入byte_140006B40。 交叉引用byte_140006B40发现正好在前面的sub_1400012C0里调用,分析sub_1400012C0可知 1. 对一个大数组进行SMC得到新的代码段P,并从中取偏移0x775调用函数 2. 对byte_140006B40按8字节倍数补充后进行加密,加密的结果和unk_140006AC0前40字节比较 下面我们调试dump 代码段P,可以断在读取偏移0x775之前,然后我们按下F12走完迷宫,即可断下 此时rax指向地址存储着代码,长度是0x862  dump出来 ~~~ 1: kd> .writemem d:\\func.bin rax L862 Writing 862 bytes.. ~~~  此时可以看到函数反编译出来了,这个是真正的加密逻辑 ## func.bin分析 主函数是sub_775,sub_55和sub_225分析可知a1、a2参数没用,根据a5前者生成了256字节的v15,后者生成了32个dword的v14 ~~~c++ __int64 __fastcall sub_775(__int64 a1, __int64 a2, unsigned __int64 a3, __int64 a4, _DWORD *a5, int a6) { __int64 result; // rax __int64 v7; // [rsp+0h] [rbp-2E8h] __int64 v8; // [rsp+8h] [rbp-2E0h] __int64 v9; // [rsp+10h] [rbp-2D8h] __int64 v10; // [rsp+18h] [rbp-2D0h] int v11; // [rsp+30h] [rbp-2B8h] __int64 i; // [rsp+38h] [rbp-2B0h] unsigned int v13[4]; // [rsp+40h] [rbp-2A8h] BYREF _BYTE v14[128]; // [rsp+50h] [rbp-298h] BYREF _BYTE v15[256]; // [rsp+D0h] [rbp-218h] BYREF _BYTE v16[280]; // [rsp+1D0h] [rbp-118h] BYREF sub_55(a1, a2, (__int64)v15, a5, (__int64)v16); sub_225(a1, a2, (__int64)v14, a5); v11 = 0; for ( i = 0LL; ; i += 8LL ) { result = i + 8; if ( i + 8 > a3 ) break; *(_QWORD *)v13 = *(_QWORD *)(i + a4); sub_476(a1, a2, a5, v13, (__int64)v14, (__int64)v15, v7, v8, v9, v10, v11); *(_QWORD *)(i + a4) = *(_QWORD *)v13; ++v11; } return result; } __int64 __fastcall sub_55(__int64 a1, __int64 a2, __int64 a3, _DWORD *a4, __int64 a5) { int v5; // ecx __int64 v6; // rdx __int64 result; // rax unsigned __int8 v8; // [rsp+20h] [rbp-38h] int j; // [rsp+24h] [rbp-34h] int i; // [rsp+28h] [rbp-30h] int k; // [rsp+2Ch] [rbp-2Ch] signed int v12; // [rsp+30h] [rbp-28h] unsigned int v13[7]; // [rsp+3Ch] [rbp-1Ch] BYREF for ( i = 0; i < 256; ++i ) *(_BYTE *)(a3 + i) = i; v5 = (a4[1] >> 21) | (a4[1] << 11); v6 = (__int64)a4; result = v5 ^ *a4 ^ 0x1244F4C6u; v13[0] = v5 ^ *a4 ^ 0x1244F4C6; for ( j = 255; j > 0; --j ) { v12 = (unsigned int)sub_0(a1, a2, v6, v13) % (j + 1); v8 = *(_BYTE *)(a3 + j); *(_BYTE *)(a3 + j) = *(_BYTE *)(a3 + v12); v6 = v8; *(_BYTE *)(a3 + v12) = v8; result = (unsigned int)(j - 1); } for ( k = 0; k < 256; ++k ) { *(_BYTE *)(a5 + *(unsigned __int8 *)(a3 + k)) = k; result = (unsigned int)(k + 1); } return result; } __int64 __fastcall sub_225(_DWORD a1, _DWORD a2, __int64 a3, _DWORD *a4) { __int64 result; // rax unsigned int i; // [rsp+0h] [rbp-28h] int v6; // [rsp+4h] [rbp-24h] unsigned int v7; // [rsp+8h] [rbp-20h] unsigned int v8; // [rsp+Ch] [rbp-1Ch] v7 = *a4 ^ 0xB7E15163; result = (unsigned int)(a4[1] - 1640531527); v6 = a4[1] - 1640531527; for ( i = 0; i < 0x20; ++i ) { v8 = ((-1640531527 * i) ^ 0xB7E15163) + (__ROL4__(v6, v7 & 0x1F) ^ v7); *(_DWORD *)(a3 + 4LL * i) = __ROR4__(v6 + v7, v6 & 0x1F) ^ v8; v7 = *(_DWORD *)(a3 + 4LL * i) ^ v6; v6 = __ROL4__(*(_DWORD *)(a3 + 4LL * i), v8 & 0x1F) + v8; result = i + 1; } return result; } ~~~ a5是两个dword大小来自上一层dumped-shadow,我们可以忽略这种密钥生成的逻辑,直接调试拿到扩展完的v14、v15 ~~~c++ __int64 __fastcall sub_476( __int64 a1, __int64 a2, _DWORD *a3, unsigned int *a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10, int a11) { int v11; // eax __int64 result; // rax unsigned int v13; // [rsp+20h] [rbp-38h] int v14; // [rsp+20h] [rbp-38h] int v15; // [rsp+20h] [rbp-38h] int v16; // [rsp+20h] [rbp-38h] int v17; // [rsp+24h] [rbp-34h] int v18; // [rsp+24h] [rbp-34h] int v19; // [rsp+24h] [rbp-34h] unsigned int i; // [rsp+28h] [rbp-30h] unsigned int v21; // [rsp+2Ch] [rbp-2Ch] unsigned int v22; // [rsp+30h] [rbp-28h] int v23; // [rsp+40h] [rbp-18h] v13 = *a4; v17 = a4[1]; v21 = 0; v22 = sub_392(a1, a2, a3, a11); v14 = (v22 + *a3) ^ v13; v18 = ((v22 << 25) | ((unsigned __int64)v22 >> 7)) ^ a3[1] ^ v17; for ( i = 0; i < 0x20; ++i ) { v21 += *(_DWORD *)(a5 + 4LL * i) ^ 0xB7E15163; v15 = sub_1AD(a1, a2, a6, v14); v11 = sub_1AD(a1, a2, a6, v18); v16 = __ROL4__(*(_DWORD *)(a5 + 4LL * i), v11 & 0x1F) ^ (((v21 << 29) | ((unsigned __int64)v21 >> 3)) + v11) ^ ((*(_DWORD *)(a5 + 4LL * i) ^ v21) + v15); v18 = (*(_DWORD *)(a5 + 4LL * i) ^ v16) + __ROR4__(v11, *(_BYTE *)(a6 + (unsigned __int8)v16) & 0x1F); v14 = __ROL4__(v16, ((*(_DWORD *)(a5 + 4LL * i) >> 1) + (v21 ^ v18)) & 0x1F); if ( (v21 & 1) != 0 ) { v23 = v14; v14 = v18; v18 = v23; } } v19 = v22 ^ *a3 ^ v18; *a4 = (((v22 >> 21) | (v22 << 11)) + a3[1]) ^ v14; result = 4LL; a4[1] = v19; return result; } __int64 __fastcall sub_1AD(__int64 a1, __int64 a2, __int64 a3, int a4) { return (*(unsigned __int8 *)(a3 + HIBYTE(a4)) << 24) | (*(unsigned __int8 *)(a3 + BYTE2(a4)) << 16) | (*(unsigned __int8 *)(a3 + BYTE1(a4)) << 8) | (unsigned int)*(unsigned __int8 *)(a3 + (unsigned __int8)a4); } __int64 __fastcall sub_392(_DWORD a1, _DWORD a2, _DWORD *a3, int a4) { unsigned int v5; // [rsp+0h] [rbp-18h] unsigned int v6; // [rsp+0h] [rbp-18h] v5 = (a3[1] ^ 0xDEADBEEF) + (__ROL4__(*a3, a4 & 0x1F) ^ (73244475 * (a4 + 1))); v6 = -2073254261 * (((2146121005 * (HIWORD(v5) ^ v5)) >> 15) ^ (2146121005 * (HIWORD(v5) ^ v5))); return HIWORD(v6) ^ v6; } ~~~ 上面算法不难,先动态调试把传入block的a5、v14、v15拿到。下断点到sub_476前  dump上面的参数,a5是rdx,拿到两个dword为`[0xd2172c16,0xe7d1cc85]` ~~~ 1: kd> dq rdx L1 fffff40d`1f6c3a60 e7d1cc85`d2172c16 ~~~ v14是r8,v15是r9,直接writemem导出对应大小即可 逆向算法非常简单,注意4字节限制即可,此外注意密文在运行过程中修改了 ~~~python MASK = 0xFFFFFFFF def rol(value, shift): return ((value << shift) | (value >> (32 - shift))) & MASK def ror(value, shift): return (value >> shift) | ((value << (32 - shift)) & MASK) def HIWORD(value): return (value >> 16) & 0xFFFF def gen_key(key, block_id): v5 = (key[1] ^ 0xDEADBEEF) + (rol(key[0], block_id & 0x1F) ^ ((0x45D9F3B * (block_id + 1))&MASK)) v5 &= MASK tmp = (0x7FEB352D * (HIWORD(v5) ^ v5)) tmp &= MASK v6 = 0x846CA68B * ((tmp >> 15) ^ tmp) v6 &= MASK return HIWORD(v6) ^ v6 def SBOX(block_value, sbox): return sbox[block_value & 0xFF] | (sbox[(block_value >> 8) & 0xFF]<<8) | (sbox[(block_value >> 16) & 0xFF]<<16) | (sbox[(block_value >> 24) & 0xFF]<<24) def INV_SBOX(block_value, sbox): return sbox.index(block_value & 0xFF) | (sbox.index((block_value >> 8) & 0xFF)<<8) | (sbox.index((block_value >> 16) & 0xFF)<<16) | (sbox.index((block_value >> 24) & 0xFF)<<24) def enc_block(key, block_id, block, sbox, dword_s): k = gen_key(key, block_id) tmp_block = [(k+key[0])^block[0], rol(k,25)^key[1]^block[1]] v = 0 for i in range(32): v += dword_s[i] ^ 0xB7E15163 v &= MASK v0 = SBOX(tmp_block[0], sbox) v1 = SBOX(tmp_block[1], sbox) tmp = rol(dword_s[i], v1 & 0x1F) ^ (rol(v, 29)+v1) ^ ((dword_s[i]^v)+v0) tmp_block[1] = (dword_s[i]^tmp)+ror(v1, sbox[tmp&0xFF]&0x1F) tmp_block[0] = rol(tmp, ((dword_s[i]>>1)+(v^tmp_block[1]))&0x1F) if v & 1: tmp_block = tmp_block[::-1] block = [(rol(k, 11)+key[1])^tmp_block[0], k ^ key[0] ^ tmp_block[1]] return block def dec_block(key, block_id, block, sbox, dword_s): k = gen_key(key, block_id) v = sum(dword_s[i] ^ 0xB7E15163 for i in range(32)) & MASK tmp_block = [((rol(k, 11)+key[1])^block[0]) & MASK, k ^ key[0] ^ block[1]] for i in range(31, -1, -1): if v & 1: tmp_block = tmp_block[::-1] tmp = ror(tmp_block[0], ((dword_s[i]>>1)+(v^tmp_block[1]))&0x1F) v1 = rol((tmp_block[1]-(dword_s[i]^tmp))&MASK, sbox[tmp&0xFF]&0x1F) v0 = ((tmp ^ rol(dword_s[i], v1 & 0x1F) ^ (rol(v, 29)+v1)) - (dword_s[i]^v))&MASK tmp_block[0] = INV_SBOX(v0, sbox) tmp_block[1] = INV_SBOX(v1, sbox) v -= dword_s[i] ^ 0xB7E15163 v &= MASK block = [((k+key[0])^tmp_block[0]) & MASK, rol(k,25)^key[1]^tmp_block[1]] return block if __name__ == "__main__": cmp = [0x4D, 0xC6, 0xA4, 0x4E, 0x6F, 0xA5, 0x0B, 0x1C, 0xFC, 0x1E, 0xE8, 0xAE, 0x30, 0x43, 0x3E, 0x7E, 0x2F, 0x10, 0x1D, 0x58, 0xA7, 0x6C, 0x81, 0x8E, 0x96, 0x1A, 0xE5, 0x30, 0x01, 0x93, 0x16, 0xB5, 0x3E, 0x67, 0x98, 0x2C, 0x6D, 0x0F, 0xCC, 0xE5, 0xBC, 0x5F, 0x58, 0x36, 0xD6, 0x7D, 0x8A, 0x66, 0x4F, 0x6E, 0x03, 0x3B, 0x5D, 0x2E, 0x01, 0xEB, 0x5B, 0x3A, 0xFB, 0x9D, 0x74, 0x93, 0x24, 0xCA, 0x82, 0x04, 0x12, 0xE5, 0x9D, 0x07, 0x03, 0xC7, 0xA6, 0x82, 0x57, 0xD5, 0x10, 0xEE, 0x42, 0x13][:40] cmp = [i^0x1c for i in cmp] sbox = list(open("sbox.bin", "rb").read()) dword_data = open("dword.bin", "rb").read() dword_s = [int.from_bytes(dword_data[i*4:(i+1)*4], "little") for i in range(32)] for i in range(0, 40, 8): block = [int.from_bytes(cmp[j:j+4], "little") for j in range(i, i+8, 4)] block = dec_block([0xd2172c16,0xe7d1cc85], i//8, block, sbox, dword_s) print(bytes(block[0].to_bytes(4, "little")+block[1].to_bytes(4, "little")).decode(), end="") ~~~ 得到flag为`ebbc8827-c040-4a7d-8bc7-0aeccb1ce094` ## 其他 因为第一次做驱动题,尝试用KMD加载驱动却发现一直报错`连到系统上的设备没有发挥作用`或者`系统找不到指定的文件`,排查了各种问题包括路径、虚拟机版本、禁止签名等,最后实在没搞懂去问了liv师傅才知道,那个返回值可以代码设置的,在Shadow.sys最后可以看到return了`STATS_UNSUCCESSFUL`  到这里就明白是我大意了,驱动没学好就过来做题了,原来是正常的  **感谢Liv的视频和博客,带我入了门** 后面继续做一些基础驱动题目,然后同步去学驱动开发 最后修改:2026 年 02 月 12 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏