diff --git a/node-vlmcs/.gitignore b/node-vlmcs/.gitignore new file mode 100644 index 00000000..b9470778 --- /dev/null +++ b/node-vlmcs/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/node-vlmcs/package.json b/node-vlmcs/package.json new file mode 100644 index 00000000..82e2021f --- /dev/null +++ b/node-vlmcs/package.json @@ -0,0 +1,18 @@ +{ + "name": "node-vlmcs", + "version": "1.0.0", + "description": "Node.js implementation of vlmcs KMS client", + "main": "dist/cli.js", + "bin": { + "vlmcs": "dist/cli.js" + }, + "scripts": { + "build": "tsc", + "start": "node dist/cli.js" + }, + "license": "MIT", + "devDependencies": { + "typescript": "^5.4.0", + "@types/node": "^20.0.0" + } +} diff --git a/node-vlmcs/src/cli.ts b/node-vlmcs/src/cli.ts new file mode 100644 index 00000000..a709ca13 --- /dev/null +++ b/node-vlmcs/src/cli.ts @@ -0,0 +1,739 @@ +#!/usr/bin/env node + +/** + * vlmcs 命令行客户端 — KMS 激活请求的主入口 + * + * 参考原版源码: + * - src/vlmcs.c (main, parseArgs, buildRequest, sendRequest, displayResponse 等) + * - src/vlmcs.h (全局状态变量定义) + */ + +import * as net from 'net'; +import { KmsData } from './data'; +import { + guidToBuffer, stringToGuidLE, + utf8ToUcs2, ucs2ToUtf8, unixTimeToFileTime, + VERSION, PID_BUFFER_SIZE, WORKSTATION_NAME_BUFFER, + ResponseResult, +} from './types'; +import { + createRequestV4, createRequestV6, + decryptResponseV4, decryptResponseV6, + ParsedResponse, +} from './kms'; +import { rpcBindClient, rpcSendRequest, RpcFlags, RpcDiag } from './rpc'; +import { connectToAddress, isDisconnected } from './network'; +import { stringToInt, getArgumentBool } from './helpers'; +import { get16RandomBytes } from './crypto'; +import { + logRequestVerbose, logResponseVerbose, uuid2StringLE, + printPlatform, printCommonFlags, printClientFlags, +} from './output'; + +// ─── 常量 (参考 src/vlmcs.c) ──────────────────────────────────────────────── +const VLMCS_OPTION_GRAB_INI = 1; // -G 模式(获取 ePID/HwId 数据) +const VLMCS_OPTION_NO_GRAB_INI = 2; // 与 -G 不兼容的选项 + +// ─── 随机工作站名的 DNS 域名组件 (参考 src/vlmcs.c 中的 DnsNames) ───────── +const DnsNames = { + first: ['www', 'ftp', 'kms', 'hack-me', 'smtp', 'ns1', 'mx1', 'ns1', 'pop3', 'imap', 'mail', 'dns', 'headquarter', 'we-love', '_vlmcs._tcp', 'ceo-laptop'], + second: ['.microsoft', '.apple', '.amazon', '.samsung', '.adobe', '.google', '.yahoo', '.facebook', '.ubuntu', '.oracle', '.borland', '.htc', '.acer', '.windows', '.linux', '.sony'], + tld: ['.com', '.net', '.org', '.cn', '.co.uk', '.de', '.com.tw', '.us', '.fr', '.it', '.me', '.info', '.biz', '.co.jp', '.ua', '.at', '.es', '.pro', '.by', '.ru', '.pl', '.kr'], +}; + +const alphanum = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + +// ─── 全局状态变量 (参考 src/vlmcs.c 中的全局变量) ──────────────────────────── + +let verbose = false; // -v 详细输出 +let vmInfo = false; // -m 伪装为虚拟机 +let dnsnames = true; // 使用 DNS 风格的随机工作站名(-d 可禁用) +let fixedRequests = 0; // -n 固定请求次数 +let licenseStatus = 0x02; // -t 许可证状态(默认 OOB grace) +let cmidStr: string | null = null; // -c 自定义客户端机器 ID +let cmidPrevStr: string | null = null; // -o 自定义前一个客户端机器 ID +let workstationName: string | null = null; // -w 自定义工作站名 +let bindingExpiration = 43200; // -g 绑定过期时间(分钟) +let remoteAddr = ''; // 目标 KMS 服务器地址 +let reconnectForEachRequest = false; // -T 每次请求重新连接 +let addressFamily = 0; // -i IP 协议版本(0=自动, 4=IPv4, 6=IPv6) +let incompatibleOptions = 0; // 选项兼容性跟踪 +let fnIniClient: string | null = null; // -G 输出文件路径 +let activeProductIndex = 0; // 当前选中的产品索引 +let nCountPolicy = 0; // 最小客户端计数策略 +let appGuid: Buffer = Buffer.alloc(16); // 应用程序 GUID +let kmsGuid: Buffer = Buffer.alloc(16); // KMS 计数 ID +let skuGuid: Buffer = Buffer.alloc(16); // 激活 ID (SKU GUID) +let minorVersion = 0; // 协议次版本号 +let majorVersion = 0; // 协议主版本号 +let useClientRpcNDR64 = true; // -N 是否使用 NDR64 +let useClientRpcBTFN = true; // -B 是否使用绑定时间特性协商 +let useMultiplexedRpc = true; // -p 是否使用多路复用 RPC +let rpcFlags: RpcFlags = { hasNDR32: false, hasNDR64: false, hasBTFN: false }; +let firstPacketSent = false; // 是否已发送第一个数据包 + +/** 输出错误信息到 stderr */ +function errorout(msg: string): void { + process.stderr.write(msg); +} + +/** 解析命令行中的 GUID 参数,无效时退出进程 */ +function parseGuidArg(input: string): Buffer { + if (input.length !== 36) { + errorout('Fatal: Command line contains an invalid GUID.\n'); + process.exit(22); + } + const guid = stringToGuidLE(input); + if (!guid) { + errorout('Fatal: Command line contains an invalid GUID.\n'); + process.exit(22); + } + return guidToBuffer(guid); +} + +/** 显示帮助信息并退出,参考 src/vlmcs.c 中的 usage() */ +function showHelp(programName: string): never { + errorout( + `vlmcs ${VERSION}\n\n` + + `Usage: ${programName} [options] [ [:] | . | - ] [options]\n\n` + + `Options:\n\n` + + ` -v Be verbose\n` + + ` -l \n` + + ` -4 Force V4 protocol\n` + + ` -5 Force V5 protocol\n` + + ` -6 Force V6 protocol\n` + + ` -i Use IP protocol (4 or 6)\n` + + ` -j Load external KMS data file \n` + + ` -e Show some valid examples\n` + + ` -x Show valid Apps\n` + + ` -d no DNS names, use Netbios names (no effect if -w is used)\n` + + ` -V show version information and exit\n\n` + + `Advanced options:\n\n` + + ` -a Use custom Application GUID\n` + + ` -s Use custom Activation Configuration GUID\n` + + ` -k Use custom KMS GUID\n` + + ` -c Use custom Client GUID. Default: Use random\n` + + ` -o Use custom Prevoius Client GUID. Default: ZeroGUID\n` + + ` -K Use a specific (possibly invalid) protocol version\n` + + ` -w Use custom workstation name. Default: Use random\n` + + ` -r Fake required clients\n` + + ` -n Fixed # of requests (Default: Enough to charge)\n` + + ` -m Pretend to be a virtual machine\n` + + ` -G Get ePID/HwId data and write to . Can't be used with -l, -4, -5, -6, -a, -s, -k, -r and -n\n` + + ` -T Use a new TCP connection for each request.\n` + + ` -N <0|1> disable or enable NDR64. Default: 1\n` + + ` -B <0|1> disable or enable RPC bind time feature negotiation. Default: 1\n` + + ` -t Use specfic license status (0 <= T <= 6)\n` + + ` -g Use a specfic binding expiration time in minutes. Default 43200\n` + + ` -P Ignore priority and weight in DNS SRV records\n` + + ` -p Don't use multiplexed RPC bind\n` + + `\n` + + `:\t\tTCP port name of the KMS to use. Default 1688.\n` + + `:\t\thost name of the KMS to use. Default 127.0.0.1\n` + + `.:\tfind KMS server in via DNS\n` + + `:\t\t(Type ${programName} -x to see a list of valid apps)\n\n` + ); + process.exit(22); +} + +/** 显示用法示例 */ +function showExamples(programName: string): never { + process.stdout.write( + `\nRequest activation for Office 2013 using V4 protocol from 192.168.1.5:1688\n` + + `\t${programName} -l "Office 2013 Professional" -4 192.168.1.5\n` + + `\t${programName} -l "Office 2013 Professional" -4 192.168.1.5:1688\n\n` + + `Request activation for Windows Server 2012 using V4 protocol from localhost:1688\n` + + `\t${programName} -4 -l "Windows Server 2012" -k 8665cb71-468c-4aa3-a337-cb9bc9d5eaac\n` + + `\t${programName} -4 -l "Windows Server 2012"\n` + + `\t${programName} -4 -l "Windows Server 2012" [::1]:1688\n` + + `\t${programName} -4 -l "Windows Server 2012" 127.0.0.2:1688\n\n` + + `Send 100,000 requests to localhost:1688\n` + + `\t${programName} -n 100000\n\n` + + `Request Activation for Windows 8 from 10.0.0.1:4711 and pretend to be Steve Ballmer\n` + + `\t${programName} -l "Windows 8 Professional" -w steveb1.redmond.microsoft.com 10.0.0.1:4711\n\n` + ); + process.exit(0); +} + +/** 显示所有可用产品列表(-x 选项),参考 src/vlmcs.c 中的 showProducts() */ +function showProducts(): never { + const cols = process.stdout.columns || 80; + const items = KmsData.skuItems; + let longestString = 0; + for (const item of items) { + if (item.name.length > longestString) longestString = item.name.length; + } + const itemsPerLine = Math.max(1, Math.floor(cols / (longestString + 10))); + const lines = Math.ceil(items.length / itemsPerLine); + + process.stdout.write('You may use these product names or numbers:\n\n'); + + for (let i = 0; i < lines; i++) { + let line = ''; + for (let k = 0; k < itemsPerLine; k++) { + const index = k * lines + i; + if (index >= items.length) break; + const num = (index + 1).toString().padStart(3, ' '); + const name = items[index].name; + line += `${num} = ${name}${' '.repeat(longestString + 4 - name.length)}`; + } + process.stdout.write(line + '\n'); + } + process.stdout.write('\n'); + process.exit(0); +} + +// ─── getopt 风格的参数解析器 (参考 src/vlmcs.c 中的参数处理) ──────────────── +interface ParsedOpt { opt: string; arg: string | null; } + +function parseArgs(argv: string[]): { opts: ParsedOpt[]; positional: string[] } { + const opts: ParsedOpt[] = []; + const positional: string[] = []; + const requiresArg = 'NBijlaskcwrntgGoK'; + let i = 0; + while (i < argv.length) { + const arg = argv[i]; + if (arg === '--') { i++; positional.push(...argv.slice(i)); break; } + if (arg.startsWith('-') && arg.length > 1 && arg[1] !== '-') { + const optChar = arg[1]; + if (requiresArg.includes(optChar)) { + const optArg = arg.length > 2 ? arg.substring(2) : argv[++i]; + opts.push({ opt: optChar, arg: optArg || null }); + } else { + // May have multiple flags: -v4m etc. + for (let j = 1; j < arg.length; j++) { + const c = arg[j]; + if (requiresArg.includes(c)) { + const optArg = j + 1 < arg.length ? arg.substring(j + 1) : argv[++i]; + opts.push({ opt: c, arg: optArg || null }); + break; + } else { + opts.push({ opt: c, arg: null }); + } + } + } + } else { + positional.push(arg); + } + i++; + } + return { opts, positional }; +} + +/** 根据选中的产品设置默认参数(版本号、计数策略、GUID),参考 src/vlmcs.c */ +function setProductDefaults(): void { + const sku = KmsData.skuItems[activeProductIndex]; + majorVersion = sku.protocolVersion; + nCountPolicy = sku.nCountPolicy; + skuGuid = guidToBuffer(stringToGuidLE(sku.guid)!); + kmsGuid = guidToBuffer(stringToGuidLE(KmsData.kmsItems[sku.kmsIndex].guid)!); + appGuid = guidToBuffer(stringToGuidLE(KmsData.appItems[sku.appIndex].guid)!); +} + +/** + * 构建 236 字节的 KMS 请求基础结构 + * 参考: src/vlmcs.c 中的 CreateRequest() + */ +function buildRequest(): Buffer { + const buf = Buffer.alloc(260); // Enough for V6 request + // Version + buf.writeUInt16LE(minorVersion, 0); + buf.writeUInt16LE(majorVersion, 2); + // VMInfo + buf.writeUInt32LE(vmInfo ? 1 : 0, 4); + // LicenseStatus + buf.writeUInt32LE(licenseStatus, 8); + // BindingExpiration + buf.writeUInt32LE(bindingExpiration, 12); + // AppID + appGuid.copy(buf, 16); + // ActID (SkuGuid) + skuGuid.copy(buf, 32); + // KMSID + kmsGuid.copy(buf, 48); + // CMID + if (cmidStr) { + const g = parseGuidArg(cmidStr); + g.copy(buf, 64); + } else { + const rnd = get16RandomBytes(); + rnd.copy(buf, 64); + // Set UUID v4 bits + buf[64 + 8] = (buf[64 + 8] & 0x3F) | 0x80; + const d3 = buf.readUInt16LE(64 + 6); + buf.writeUInt16LE((d3 & 0x0FFF) | 0x4000, 64 + 6); + } + // N_Policy + buf.writeUInt32LE(nCountPolicy, 80); + // ClientTime + const ft = unixTimeToFileTime(); + ft.copy(buf, 84); + // CMID_prev + if (cmidPrevStr) { + const g = parseGuidArg(cmidPrevStr); + g.copy(buf, 92); + } else { + buf.fill(0, 92, 108); + } + // WorkstationName + if (workstationName) { + const ws = utf8ToUcs2(workstationName.substring(0, 63), WORKSTATION_NAME_BUFFER); + ws.copy(buf, 108); + } else if (dnsnames) { + const first = DnsNames.first[Math.floor(Math.random() * DnsNames.first.length)]; + const second = DnsNames.second[Math.floor(Math.random() * DnsNames.second.length)]; + const tld = DnsNames.tld[Math.floor(Math.random() * DnsNames.tld.length)]; + const name = first + second + tld; + const ws = utf8ToUcs2(name, WORKSTATION_NAME_BUFFER); + ws.copy(buf, 108); + } else { + const size = Math.floor(Math.random() * 14) + 1; + let name = ''; + for (let i = 0; i < size; i++) { + name += alphanum[Math.floor(Math.random() * alphanum.length)]; + } + const ws = utf8ToUcs2(name, WORKSTATION_NAME_BUFFER); + ws.copy(buf, 108); + } + + return buf.subarray(0, 236); // REQUEST is 236 bytes +} + +/** 建立 TCP 连接并执行 RPC 绑定,参考 src/vlmcs.c 中的连接逻辑 */ +async function connectRpc(sock: net.Socket | null): Promise<{ sock: net.Socket }> { + const s = await connectToAddress(remoteAddr, addressFamily, remoteAddr.startsWith('.') || remoteAddr === '-'); + if (verbose) process.stdout.write('\nPerforming RPC bind ...\n'); + const bindResult = await rpcBindClient(s, verbose, useClientRpcNDR64, useClientRpcBTFN, useMultiplexedRpc); + if (bindResult.status) { + errorout('Fatal: Could not bind RPC\n'); + process.exit(bindResult.status); + } + rpcFlags = bindResult.rpcFlags; + if (verbose) process.stdout.write('... successful\n'); + return { sock: s }; +} + +/** + * 显示 KMS 响应结果,包括各项验证检查 + * 参考: src/vlmcs.c 中的 displayResponse() + */ +function displayResponse( + result: ResponseResult, response: ParsedResponse, hwid: Buffer +): void { + if (!result.rpcOK) errorout('\n\x07ERROR: Non-Zero RPC result code.\n'); + if (!result.decryptSuccess) errorout('\n\x07ERROR: Decryption of V5/V6 response failed.\n'); + if (!result.ivsOK) errorout('\n\x07ERROR: AES CBC initialization vectors (IVs) of request and response do not match.\n'); + if (!result.pidLengthOK) errorout('\n\x07ERROR: The length of the PID is not valid.\n'); + if (!result.hashOK) errorout('\n\x07ERROR: Computed hash does not match hash in response.\n'); + if (!result.clientMachineIDOK) errorout('\n\x07ERROR: Client machine GUIDs of request and response do not match.\n'); + if (!result.timeStampOK) errorout('\n\x07ERROR: Time stamps of request and response do not match.\n'); + if (!result.versionOK) errorout('\n\x07ERROR: Protocol versions of request and response do not match.\n'); + if (!result.hmacSha256OK) errorout('\n\x07ERROR: Keyed-Hash Message Authentication Code (HMAC) is incorrect.\n'); + if (!result.ivNotSuspicious) errorout('\nWARNING: The KMS server is an emulator because the response uses an IV following KMSv5 rules in KMSv6 protocol.\n'); + + if (result.effectiveResponseSize !== result.correctResponseSize) { + errorout(`\n\x07WARNING: Size of RPC payload (KMS Message) should be ${result.correctResponseSize} but is ${result.effectiveResponseSize}.`); + } + + // Check RPC level + if (!rpcFlags.hasNDR32) errorout('\nWARNING: Server\'s RPC protocol does not support NDR32.\n'); + if (useClientRpcBTFN && useClientRpcNDR64 && rpcFlags.hasNDR64 && !rpcFlags.hasBTFN) + errorout('\nWARNING: Server\'s RPC protocol has NDR64 but no BTFN.\n'); + + if (!result.decryptSuccess) return; + + const ePID = ucs2ToUtf8(response.kmsPID, PID_BUFFER_SIZE); + + if (!verbose) { + process.stdout.write(` -> ${ePID}`); + if (response.majorVer > 5) { + const hwidHex = Array.from(hwid.subarray(0, 8)).map(b => b.toString(16).toUpperCase().padStart(2, '0')).join(''); + process.stdout.write(` (${hwidHex})`); + } + process.stdout.write('\n'); + } else { + logResponseVerbose(ePID, hwid, response, result.effectiveResponseSize); + } +} + +/** + * 发送激活请求并处理响应 + * 根据协议版本选择 V4 或 V5/V6 加密方式 + * 参考: src/vlmcs.c 中的 SendActivationRequest() + */ +async function sendActivationRequest( + sock: net.Socket, requestBase: Buffer +): Promise<{ status: number; response: ParsedResponse; result: ResponseResult; hwid: Buffer }> { + let request: Buffer; + const majorVer = requestBase.readUInt16LE(2); + + if (majorVer < 5) { + request = createRequestV4(requestBase); + } else { + request = createRequestV6(requestBase); + } + + const rpcResult = await rpcSendRequest(sock, request, rpcFlags, useClientRpcNDR64, firstPacketSent); + + if (rpcResult.status === 0 && rpcResult.kmsResponse) { + const rawResponse = rpcResult.kmsResponse; + const responseMajorVer = rawResponse.readUInt16LE(2); + + let response: ParsedResponse; + let result: ResponseResult; + let hwid = Buffer.alloc(8); + + if (responseMajorVer === 4) { + const v4Result = decryptResponseV4(rawResponse, request); + response = v4Result.response; + result = v4Result.result; + } else { + const v6Result = decryptResponseV6(rawResponse, request); + response = v6Result.response; + result = v6Result.result; + hwid = Buffer.from(v6Result.hwid); + } + + result.rpcOK = true; + firstPacketSent = true; + return { status: 0, response, result, hwid }; + } + + firstPacketSent = true; + const emptyResult: ResponseResult = { + mask: 0, hashOK: false, timeStampOK: false, clientMachineIDOK: false, + versionOK: false, ivsOK: false, decryptSuccess: false, hmacSha256OK: false, + pidLengthOK: false, rpcOK: false, ivNotSuspicious: false, + effectiveResponseSize: 0, correctResponseSize: 0, + }; + const emptyResponse: ParsedResponse = { + majorVer: 0, minorVer: 0, pidSize: 0, kmsPID: Buffer.alloc(0), + cmid: Buffer.alloc(16), clientTime: Buffer.alloc(8), count: 0, + vlActivationInterval: 0, vlRenewalInterval: 0, + }; + return { status: rpcResult.status, response: emptyResponse, result: emptyResult, hwid: Buffer.alloc(8) }; +} + +/** 显示请求错误信息,参考 src/vlmcs.c 中的错误处理 */ +function displayRequestError(status: number, currentRequest: number, totalRequests: number): void { + errorout(`\nError 0x${(status >>> 0).toString(16).toUpperCase().padStart(8, '0')} while sending request ${currentRequest} of ${totalRequests}\n`); + switch (status) { + case 0xC004F042: errorout('The KMS server has declined to activate the requested product\n'); break; + case 0x8007000D: errorout('The KMS host you are using is unable to handle your product. It only supports legacy versions\n'); break; + case 0xC004F06C: errorout('The time stamp differs too much from the KMS server time\n'); break; + case 0xC004D104: errorout('The security processor reported that invalid data was used\n'); break; + case 1: errorout('An RPC protocol error has occured\n'); break; + } +} + +/** 主函数 — 解析命令行参数并执行激活请求,参考 src/vlmcs.c 中的 main() */ +async function main(): Promise { + const argv = process.argv.slice(2); + const programName = 'vlmcs'; + const { opts, positional } = parseArgs(argv); + + // Pass 0: handle -j + for (const o of opts) { + if (o.opt === 'j') { + // External data file - not fully implemented, just accept + } + } + + // Determine host + let useDefaultHost = true; + if (positional.length > 0) { + remoteAddr = positional[0]; + useDefaultHost = false; + } + + // Pass 1: handle -l + for (const o of opts) { + if (o.opt === 'l' && o.arg) { + const num = stringToInt(o.arg, 1, KmsData.skuItems.length); + if (num !== null) { + activeProductIndex = num - 1; + } else { + const idx = KmsData.skuItems.findIndex( + s => s.name.toLowerCase() === o.arg!.toLowerCase() + ); + if (idx < 0) { + errorout(`Invalid client application. "${o.arg}" is not valid for -l.\n\n`); + showProducts(); + } + activeProductIndex = idx; + } + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + } + } + + setProductDefaults(); + + // Pass 2: handle all other options + for (const o of opts) { + switch (o.opt) { + case 'h': case '?': showHelp(programName); + case 'e': showExamples(programName); + case 'x': showProducts(); + case 'V': + process.stdout.write(`vlmcs ${VERSION} ${process.arch === 'x64' ? '64' : '32'}-bit\n`); + printPlatform(); + printCommonFlags(); + printClientFlags(); + process.exit(0); + case 'v': verbose = true; break; + case 'm': vmInfo = true; break; + case 'd': dnsnames = false; break; + case 'T': reconnectForEachRequest = true; break; + case 'P': break; // DNS SRV 优先级(未实现,仅接受参数) + case 'p': useMultiplexedRpc = false; break; + case '4': case '5': case '6': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + majorVersion = parseInt(o.opt); + minorVersion = 0; + break; + case 'N': + if (o.arg) { + const r = getArgumentBool(o.arg); + if (r === null) showHelp(programName); + useClientRpcNDR64 = r!; + } + break; + case 'B': + if (o.arg) { + const r = getArgumentBool(o.arg); + if (r === null) showHelp(programName); + useClientRpcBTFN = r!; + } + break; + case 'i': + if (o.arg) { + const v = parseInt(o.arg); + if (v === 4) addressFamily = 4; + else if (v === 6) addressFamily = 6; + else { errorout('IPv5 does not exist.\n'); process.exit(22); } + } + break; + case 'n': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + if (o.arg) fixedRequests = parseInt(o.arg) || 1; + break; + case 'r': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + if (o.arg) nCountPolicy = parseInt(o.arg) || 0; + break; + case 'c': + if (!fixedRequests) fixedRequests = 1; + cmidStr = o.arg; + break; + case 'o': cmidPrevStr = o.arg; break; + case 'a': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + if (o.arg) appGuid = parseGuidArg(o.arg); + break; + case 's': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + if (o.arg) skuGuid = parseGuidArg(o.arg); + break; + case 'k': + incompatibleOptions |= VLMCS_OPTION_NO_GRAB_INI; + if (o.arg) kmsGuid = parseGuidArg(o.arg); + break; + case 'K': + if (o.arg) { + const parts = o.arg.split('.'); + if (parts.length !== 2) { errorout('Fatal: Protocol version must be in the format #.#\n'); process.exit(22); } + majorVersion = parseInt(parts[0]) || 0; + minorVersion = parseInt(parts[1]) || 0; + } + break; + case 'w': + workstationName = o.arg; + if (workstationName && workstationName.length > 63) { + errorout(`\x07WARNING! Truncating workstation name to 63 characters (${workstationName}).\n`); + } + break; + case 't': + if (o.arg) { + licenseStatus = parseInt(o.arg) || 0; + if (licenseStatus > 6) errorout('Warning: Correct license status is 0 <= license status <= 6.\n'); + } + break; + case 'g': + if (o.arg) bindingExpiration = parseInt(o.arg) || 0; + break; + case 'G': + incompatibleOptions |= VLMCS_OPTION_GRAB_INI; + fnIniClient = o.arg; + break; + case 'l': case 'j': break; // Already handled + } + } + + if ((incompatibleOptions & (VLMCS_OPTION_NO_GRAB_INI | VLMCS_OPTION_GRAB_INI)) === + (VLMCS_OPTION_NO_GRAB_INI | VLMCS_OPTION_GRAB_INI)) { + showHelp(programName); + } + + if (useDefaultHost) { + remoteAddr = addressFamily === 6 ? '[::1]' : '127.0.0.1'; + } + + try { + if (fnIniClient !== null) { + await grabServerData(); + } else { + await runNormalMode(); + } + } catch (err: any) { + errorout(`Fatal: ${err.message || err}\n`); + process.exit(1); + } +} + +/** 普通激活模式 — 发送一个或多个激活请求,参考 src/vlmcs.c */ +async function runNormalMode(): Promise { + let sock: net.Socket | null = null; + let requests = 0; + let requestsToGo = nCountPolicy === 1 ? 1 : nCountPolicy - 1; + let firstRequestSentLocal = false; + + while (requestsToGo > 0) { + const requestBase = buildRequest(); + + if (verbose) logRequestVerbose(requestBase); + + if (!sock) { + const conn = await connectRpc(null); + sock = conn.sock; + } else { + const disconnected = isDisconnected(sock); + if (disconnected) { + errorout('\nWarning: Server closed RPC connection (probably non-multitasked KMS emulator)\n'); + } + if (reconnectForEachRequest || disconnected) { + sock.destroy(); + const conn = await connectRpc(null); + sock = conn.sock; + } + } + + process.stdout.write(`Sending activation request (KMS V${majorVersion}) `); + + const { status, response, result, hwid } = await sendActivationRequest(sock, requestBase); + + if (fixedRequests) requestsToGo = fixedRequests - requests - 1; + + if (status) { + displayRequestError(status, requests + 1, requestsToGo + requests + 1); + if (!fixedRequests) requestsToGo = 0; + } else { + if (!fixedRequests) { + if (firstRequestSentLocal && nCountPolicy - response.count >= requestsToGo) { + errorout('\nThe KMS server does not increment it\'s active clients. Aborting...\n'); + requestsToGo = 0; + } else { + requestsToGo = nCountPolicy - response.count; + if (requestsToGo < 0) requestsToGo = 0; + } + } + + process.stdout.write(`${requests + 1} of ${requestsToGo + requests + 1} `); + displayResponse(result, response, hwid); + firstRequestSentLocal = true; + } + + requests++; + } + + if (sock) sock.destroy(); +} + +/** -G 模式 — 获取服务器所有 CSVLK 组的 ePID/HwId 数据,参考 src/vlmcs.c 中的 grabServerData() */ +async function grabServerData(): Promise { + let sock: net.Socket | null = null; + let currentMajorVer = 6; + const fs = await import('fs'); + const lines: string[] = []; + + for (let i = 0; i < KmsData.csvlkData.length && currentMajorVer > 3; i++) { + // Find a KMS item with this EPid index + let kmsIdx = -1; + for (let j = 0; j < KmsData.kmsItems.length; j++) { + if (KmsData.kmsItems[j].ePidIndex === i) { kmsIdx = j; break; } + } + if (kmsIdx < 0) { lines.push(''); continue; } + + // Find a SKU item with this kms index + let skuIdx = -1; + for (let j = KmsData.skuItems.length - 1; j >= 0; j--) { + if (KmsData.skuItems[j].kmsIndex === kmsIdx) { skuIdx = j; break; } + } + if (skuIdx < 0) { lines.push(''); continue; } + + const sku = KmsData.skuItems[skuIdx]; + activeProductIndex = skuIdx; + nCountPolicy = sku.nCountPolicy; + skuGuid = guidToBuffer(stringToGuidLE(sku.guid)!); + kmsGuid = guidToBuffer(stringToGuidLE(KmsData.kmsItems[sku.kmsIndex].guid)!); + appGuid = guidToBuffer(stringToGuidLE(KmsData.appItems[sku.appIndex].guid)!); + majorVersion = currentMajorVer; + minorVersion = 0; + + const requestBase = buildRequest(); + if (verbose) logRequestVerbose(requestBase); + + if (!sock) { + const conn = await connectRpc(null); + sock = conn.sock; + } + + process.stdout.write(`Sending activation request (KMS V${majorVersion}) `); + + try { + const { status, response, result, hwid } = await sendActivationRequest(sock, requestBase); + const ePidGroup = KmsData.csvlkData[i].ePidGroup; + process.stdout.write(`${ePidGroup.padEnd(11)}`); + + if (status) { + displayRequestError(status, i + 1, KmsData.csvlkData.length); + if (status === 1) break; + if ((status & 0xF0000000) === 0x80000000) { currentMajorVer--; i--; } + lines.push(''); + continue; + } + + process.stdout.write(`${i + 7 - currentMajorVer} of ${KmsData.csvlkData.length + 6 - currentMajorVer}`); + displayResponse(result, response, hwid); + + const ePID = ucs2ToUtf8(response.kmsPID, PID_BUFFER_SIZE); + let line = `${ePidGroup} = ${ePID}`; + if (response.majorVer > 5) { + const hwidHex = Array.from(hwid.subarray(0, 8)).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); + line += ` / ${hwidHex}`; + } + lines.push(line); + } catch (err: any) { + errorout(`\nError: ${err.message}\n`); + lines.push(''); + } + } + + // Write output + if (fnIniClient === '-') { + process.stdout.write('\n'); + for (const line of lines) { + if (line) process.stdout.write(line + '\n'); + } + } else if (fnIniClient) { + const content = lines.filter(l => l).join('\n') + '\n'; + fs.writeFileSync(fnIniClient, content); + process.stdout.write(`\nCreating ${fnIniClient}\n`); + } + + if (sock) sock.destroy(); +} + +main().catch((err) => { + errorout(`Fatal: ${err.message || err}\n`); + process.exit(1); +}); diff --git a/node-vlmcs/src/crypto.ts b/node-vlmcs/src/crypto.ts new file mode 100644 index 00000000..5615b3d7 --- /dev/null +++ b/node-vlmcs/src/crypto.ts @@ -0,0 +1,443 @@ +/** + * 加密操作模块 — 自定义 AES-128 实现,完全匹配原版 vlmcs C 代码 + * 仅使用 Node.js 内建 crypto 模块处理 SHA256、HMAC-SHA256 和随机数生成 + * + * 参考原版源码: + * - src/crypto.c (AES 加密/解密核心实现) + * - src/crypto_internal.c (AES S-Box、密钥扩展、块操作) + * - src/crypto.h (AES 常量和接口定义) + */ + +import * as crypto from 'crypto'; + +// ─── 常量定义 (参考 src/crypto.h) ────────────────────────────────────────── + +/** AES 块大小: 16 字节 = 128 位 */ +export const AES_BLOCK_BYTES = 16; +/** AES 密钥大小: 16 字节 = 128 位 (AES-128) */ +export const AES_KEY_BYTES = 16; +/** V4 密钥大小: 20 字节 (前 16 字节为 AES 密钥,后 4 字节用于 CMAC) */ +export const V4_KEY_BYTES = 20; + +/** AES 块中的 32 位字数 */ +const AES_BLOCK_WORDS = AES_BLOCK_BYTES >>> 2; + +// ─── AES 密钥 (参考 src/crypto.c) ────────────────────────────────────────── + +/** V4 协议密钥 (20 字节),用于 AES-CMAC 计算 */ +export const AesKeyV4 = Buffer.from([ + 0x05, 0x3D, 0x83, 0x07, 0xF9, 0xE5, 0xF0, 0x88, + 0xEB, 0x5E, 0xA6, 0x68, 0x6C, 0xF0, 0x37, 0xC7, + 0xE4, 0xEF, 0xD2, 0xD6 +]); + +/** V5 协议密钥 (16 字节),用于 AES-CBC 加密/解密 */ +export const AesKeyV5 = Buffer.from([ + 0xCD, 0x7E, 0x79, 0x6F, 0x2A, 0xB2, 0x5D, 0xCB, + 0x55, 0xFF, 0xC8, 0xEF, 0x83, 0x64, 0xC4, 0x70 +]); + +/** V6 协议密钥 (16 字节),用于 AES-CBC 加密/解密,密钥扩展时有额外 XOR 修改 */ +export const AesKeyV6 = Buffer.from([ + 0xA9, 0x4A, 0x41, 0x95, 0xE2, 0x01, 0x43, 0x2D, + 0x9B, 0xCB, 0x46, 0x04, 0x05, 0xD8, 0x4A, 0x21 +]); + +// ─── AES S-Box (参考 src/crypto_internal.c) ──────────────────────────────── +// 标准 Rijndael S-Box 替换表,用于 SubBytes 步骤 + +const SBox: readonly number[] = [ + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 +]; + +// ─── AES 逆 S-Box ────────────────────────────────────────────────────────── +// 用于 InvSubBytes 步骤(解密) + +const SBoxR: readonly number[] = [ + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D +]; + +// ─── 轮常量 (参考 src/crypto_internal.c 中的 AesRcon) ──────────────────────── +// 大端序值,在密钥扩展中通过 BE32 进行字节序转换 + +const RCon: readonly number[] = [ + 0x00000000, 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000 +]; + +// ─── 32 位工具函数 (参考 src/crypto_internal.c) ───────────────────────────── + +/** 32 位字节序交换(等同于小端系统上的 BE32 宏) */ +function be32(v: number): number { + return ( + ((v & 0xFF) << 24) | + ((v & 0xFF00) << 8) | + ((v >>> 8) & 0xFF00) | + ((v >>> 24) & 0xFF) + ) >>> 0; +} + +/** 32 位无符号右旋转 */ +function ror32(v: number, n: number): number { + return ((v << (32 - n)) | (v >>> n)) >>> 0; +} + +/** 对 DWORD 的每个字节应用 S-Box 替换(本机小端字节序) */ +function subDword(v: number): number { + return ( + SBox[v & 0xFF] | + (SBox[(v >>> 8) & 0xFF] << 8) | + (SBox[(v >>> 16) & 0xFF] << 16) | + (SBox[(v >>> 24) & 0xFF] << 24) + ) >>> 0; +} + +// ─── GF(2^8) 有限域乘法 (参考 src/crypto_internal.c) ─────────────────────── +// 在 32 位打包字上执行 GF(2^8) 乘法,用于 MixColumns 步骤 + +function mul2(w: number): number { + return (((w & 0x7f7f7f7f) << 1) ^ (((w & 0x80808080) >>> 7) * 0x1b)) >>> 0; +} + +function mul3(w: number): number { + return (mul2(w) ^ w) >>> 0; +} + +function mul4(w: number): number { + return mul2(mul2(w)); +} + +function mul8(w: number): number { + return mul2(mul4(w)); +} + +function mul9(w: number): number { + return (mul8(w) ^ w) >>> 0; +} + +function mulB(w: number): number { + return (mul8(w) ^ mul3(w)) >>> 0; +} + +function mulD(w: number): number { + return (mul8(w) ^ mul4(w) ^ w) >>> 0; +} + +function mulE(w: number): number { + return (mul8(w) ^ mul4(w) ^ mul2(w)) >>> 0; +} + +// ─── AES 块操作 (参考 src/crypto_internal.c) ──────────────────────────────── + +/** SubBytes: 对块中每个字节执行 S-Box 替换 */ +function subBytesBlock(buf: Buffer, off: number): void { + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + buf[off + i] = SBox[buf[off + i]]; + } +} + +/** InvSubBytes: 对块中每个字节执行逆 S-Box 替换 */ +function subBytesRBlock(buf: Buffer, off: number): void { + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + buf[off + i] = SBoxR[buf[off + i]]; + } +} + +/** ShiftRows: 行移位操作 */ +function shiftRowsBlock(buf: Buffer, off: number): void { + const tmp = Buffer.allocUnsafe(AES_BLOCK_BYTES); + buf.copy(tmp, 0, off, off + AES_BLOCK_BYTES); + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + buf[off + i] = tmp[(i + ((i & 3) << 2)) & 0xf]; + } +} + +/** InvShiftRows: 逆行移位操作 */ +function shiftRowsRBlock(buf: Buffer, off: number): void { + const tmp = Buffer.allocUnsafe(AES_BLOCK_BYTES); + buf.copy(tmp, 0, off, off + AES_BLOCK_BYTES); + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + buf[off + i] = tmp[(i - ((i & 3) << 2)) & 0xf]; + } +} + +/** MixColumns: 列混合操作(加密方向) */ +function mixColumnsBlock(buf: Buffer, off: number): void { + for (let i = 0; i < AES_BLOCK_WORDS; i++) { + const byteOff = off + i * 4; + const w = buf.readUInt32LE(byteOff); + buf.writeUInt32LE( + (mul2(w) ^ ror32(mul3(w), 8) ^ ror32(w, 16) ^ ror32(w, 24)) >>> 0, + byteOff + ); + } +} + +/** InvMixColumns: 逆列混合操作(解密方向) */ +function mixColumnsRBlock(buf: Buffer, off: number): void { + for (let i = 0; i < AES_BLOCK_WORDS; i++) { + const byteOff = off + i * 4; + const w = buf.readUInt32LE(byteOff); + buf.writeUInt32LE( + (mulE(w) ^ ror32(mulB(w), 8) ^ ror32(mulD(w), 16) ^ ror32(mul9(w), 24)) >>> 0, + byteOff + ); + } +} + +/** AddRoundKey: 将轮密钥与块进行异或 */ +function addRoundKey(buf: Buffer, blockOff: number, keyBuf: Buffer, keyByteOff: number): void { + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + buf[blockOff + i] ^= keyBuf[keyByteOff + i]; + } +} + +// ─── 异或块操作 ───────────────────────────────────────────────────────────── + +/** 将 inBuf 的 16 字节异或到 outBuf(就地修改 outBuf) */ +export function xorBlock( + inBuf: Buffer, + outBuf: Buffer, + inOffset: number = 0, + outOffset: number = 0 +): void { + for (let i = 0; i < AES_BLOCK_BYTES; i++) { + outBuf[outOffset + i] ^= inBuf[inOffset + i]; + } +} + +// ─── AES 上下文 (参考 src/crypto_internal.c 中的 AES_KEY) ────────────────── + +/** + * AES 加密上下文,包含扩展密钥和轮数 + * 对应原版 AES_KEY 结构体 + */ +export class AesCtx { + /** 扩展密钥(最多 48 个 DWORD = 192 字节) */ + private keyBuf: Buffer; + /** AES 轮数 */ + rounds: number; + + constructor() { + this.keyBuf = Buffer.alloc(48 * 4); + this.rounds = 0; + } + + /** + * 初始化 AES 密钥扩展 + * 完全匹配原版 AesInitKey 函数,包括 V6 协议的特殊修改 + * + * 参考: src/crypto_internal.c 中的 AesInitKey() + * + * @param key 原始密钥 + * @param isV6 是否为 V6 协议(需要额外的密钥修改) + * @param keyBytes 密钥长度(字节) + */ + initKey(key: Buffer, isV6: boolean, keyBytes: number): void { + const keyDwords = (keyBytes >>> 2); + this.rounds = keyDwords + 6; + + // 复制原始密钥到密钥缓冲区 + key.copy(this.keyBuf, 0, 0, keyBytes); + + // 密钥扩展算法 + const totalDwords = (this.rounds + 1) << 2; + for (let i = keyDwords; i < totalDwords; i++) { + let temp = this.keyBuf.readUInt32LE((i - 1) * 4); + + if ((i % keyDwords) === 0) { + const rconIdx = (i / keyDwords) | 0; + temp = be32( + (subDword(ror32(be32(temp), 24)) ^ RCon[rconIdx]) >>> 0 + ); + } + + const prev = this.keyBuf.readUInt32LE((i - keyDwords) * 4); + this.keyBuf.writeUInt32LE((prev ^ temp) >>> 0, i * 4); + } + + // V6 协议密钥修改: 对扩展密钥的特定字节执行 XOR + // 这是 vlmcsd 独有的修改,使 V6 协议的加密与标准 AES 不同 + if (isV6) { + this.keyBuf[4 * 16] ^= 0x73; + this.keyBuf[6 * 16] ^= 0x09; + this.keyBuf[8 * 16] ^= 0xE4; + } + } + + /** + * 就地加密单个 16 字节 AES 块 + * 标准 Rijndael 流程: AddRoundKey → SubBytes → ShiftRows → MixColumns + * + * 参考: src/crypto_internal.c 中的 AesEncryptBlock() + */ + encryptBlock(block: Buffer, offset: number = 0): void { + for (let keyIdx = 0; ; keyIdx += 4) { + addRoundKey(block, offset, this.keyBuf, keyIdx * 4); + subBytesBlock(block, offset); + shiftRowsBlock(block, offset); + if (keyIdx >= (this.rounds - 1) << 2) break; + mixColumnsBlock(block, offset); + } + // 最后一轮 AddRoundKey + addRoundKey(block, offset, this.keyBuf, this.rounds << 4); + } + + /** + * 就地解密单个 16 字节 AES 块 + * 逆 Rijndael 流程: AddRoundKey → InvShiftRows → InvSubBytes → InvMixColumns + * + * 参考: src/crypto_internal.c 中的 AesDecryptBlock() + */ + decryptBlock(block: Buffer, offset: number = 0): void { + // 初始 AddRoundKey(使用最后一轮密钥) + addRoundKey(block, offset, this.keyBuf, this.rounds << 4); + + for (let keyIdx = (this.rounds - 1) << 2; ; keyIdx -= 4) { + shiftRowsRBlock(block, offset); + subBytesRBlock(block, offset); + addRoundKey(block, offset, this.keyBuf, keyIdx * 4); + if (keyIdx === 0) break; + mixColumnsRBlock(block, offset); + } + } +} + +// ─── AES-CBC 加密 (参考 src/crypto.c 中的 AesEncryptCbc) ──────────────────── + +/** + * AES-CBC 加密,带 PKCS#7 填充 + * 始终添加 1-16 字节的填充(即使原始数据已对齐也会添加一整块填充) + * + * 参考: src/crypto.c 中的 AesEncryptCbc() + */ +export function aesEncryptCbc( + ctx: AesCtx, + iv: Buffer | null, + data: Buffer +): { encrypted: Buffer; totalLen: number } { + const origLen = data.length; + // 计算 PKCS#7 填充长度: 1 到 16 字节 + const pad = (~origLen & (AES_BLOCK_BYTES - 1)) + 1; + const totalLen = origLen + pad; + + const result = Buffer.alloc(totalLen); + data.copy(result, 0, 0, origLen); + result.fill(pad, origLen, totalLen); + + // 第一块: 与 IV 异或后加密 + if (iv) xorBlock(iv, result, 0, 0); + ctx.encryptBlock(result, 0); + + // 后续块: 与前一块密文异或后加密(CBC 模式链接) + for (let off = AES_BLOCK_BYTES; off < totalLen; off += AES_BLOCK_BYTES) { + xorBlock(result, result, off - AES_BLOCK_BYTES, off); + ctx.encryptBlock(result, off); + } + + return { encrypted: result, totalLen }; +} + +// ─── AES-CBC 解密 (参考 src/crypto.c 中的 AesDecryptCbc) ──────────────────── + +/** + * AES-CBC 就地解密 + * 从最后一块到第一块逆序处理(匹配原版行为) + * + * 参考: src/crypto.c 中的 AesDecryptCbc() + */ +export function aesDecryptCbc( + ctx: AesCtx, + iv: Buffer | null, + data: Buffer, + len: number +): void { + // 从最后一块到第二块逆序处理 + for (let cc = len - AES_BLOCK_BYTES; cc > 0; cc -= AES_BLOCK_BYTES) { + ctx.decryptBlock(data, cc); + xorBlock(data, data, cc - AES_BLOCK_BYTES, cc); + } + + // 处理第一块 + ctx.decryptBlock(data, 0); + if (iv) xorBlock(iv, data, 0, 0); +} + +// ─── AES-CMAC V4 (参考 src/crypto.c 中的 AesCmacV4) ──────────────────────── + +/** + * 计算 V4 协议的 AES-CMAC + * 使用 one-zero 填充 (0x80 后跟零字节) 和 V4 的 20 字节密钥 + * + * 参考: src/crypto.c 中的 AesCmacV4() + */ +export function aesCmacV4(message: Buffer, messageSize: number): Buffer { + const ctx = new AesCtx(); + ctx.initKey(AesKeyV4, false, V4_KEY_BYTES); + + const mac = Buffer.alloc(AES_BLOCK_BYTES); + + // 创建填充消息: 原始数据 + 0x80 + 零填充(到下一个块边界) + const padded = Buffer.alloc(messageSize + AES_BLOCK_BYTES); + message.copy(padded, 0, 0, messageSize); + padded[messageSize] = 0x80; + + // 逐块异或并加密,累积 MAC 值 + for (let i = 0; i <= messageSize; i += AES_BLOCK_BYTES) { + xorBlock(padded, mac, i, 0); + ctx.encryptBlock(mac, 0); + } + + return Buffer.from(mac); +} + +// ─── SHA-256 哈希 ─────────────────────────────────────────────────────────── + +/** 计算 SHA-256 哈希(使用 Node.js 内建 crypto) */ +export function sha256(data: Buffer): Buffer { + return crypto.createHash('sha256').update(data).digest(); +} + +// ─── HMAC-SHA256 ──────────────────────────────────────────────────────────── + +/** 计算 HMAC-SHA256(使用 Node.js 内建 crypto) */ +export function sha256Hmac(key: Buffer, data: Buffer): Buffer { + return crypto.createHmac('sha256', key).update(data).digest(); +} + +// ─── 随机数生成 ───────────────────────────────────────────────────────────── + +/** 生成 16 字节密码学安全随机数 */ +export function get16RandomBytes(): Buffer { + return crypto.randomBytes(16); +} diff --git a/node-vlmcs/src/data.ts b/node-vlmcs/src/data.ts new file mode 100644 index 00000000..e5365b38 --- /dev/null +++ b/node-vlmcs/src/data.ts @@ -0,0 +1,2765 @@ +/** + * KMS 产品数据库 — 从原版 C 代码的 kmsdata-full.c / kmsdata.h 中提取 + * + * 包含 CSVLK(客户端-服务器卷许可密钥)、应用程序、KMS 项目、SKU 和主机构建信息。 + * 数据通过编译一个小型 C 程序对 kmsdata-full.c 中的 VlmcsdHeader_t 结构序列化为 JSON 后提取。 + * + * 参考原版源码: + * - src/kmsdata-full.c (产品数据的二进制 blob) + * - src/kmsdata.h (VlmcsdHeader_t 结构定义) + */ + +// ─── 数据接口定义 (对应 src/kmsdata.h 中的结构体) ──────────────────────────── + +/** CSVLK 数据项 — 对应 CsvlkData_t,包含 ePID 模板和密钥范围 */ +export interface CsvlkItem { + ePid: string; // ePID 模板字符串 + ePidGroup: string; // ePID 分组名称(如 Windows、Office2010) + groupId: number; // 分组 ID + minKeyId: number; // 最小密钥 ID + maxKeyId: number; // 最大密钥 ID + minActiveClients: number; // 最小活跃客户端数 + releaseDate: number; // 发布日期(Unix 时间戳) +} + +/** 应用程序项 — 对应 kmsdata.h 中的应用数据 */ +export interface AppItem { + guid: string; // 应用程序 GUID + name: string; // 应用程序名称 + nCountPolicy: number; // N 计数策略(最小客户端数) +} + +/** KMS 项目 — 对应 KmsData_t,描述可激活的产品组 */ +export interface KmsItem { + guid: string; // KMS 计数 ID (GUID) + name: string; // 产品组名称 + appIndex: number; // 关联的应用程序索引 + ePidIndex: number; // 关联的 ePID 索引 + nCountPolicy: number; // N 计数策略 + protocolVersion: number; // 协议版本(4/5/6) + isRetail: number; // 是否为零售版 + isPreview: number; // 是否为预览版 +} + +/** SKU 项目 — 描述具体的产品 SKU(最终用户可见的产品) */ +export interface SkuItem { + guid: string; // 激活 ID (SKU GUID) + name: string; // 产品名称 + appIndex: number; // 关联的应用程序索引 + kmsIndex: number; // 关联的 KMS 项目索引 + protocolVersion: number; // 协议版本 + nCountPolicy: number; // N 计数策略 + isRetail: number; // 是否为零售版 + isPreview: number; // 是否为预览版 + ePidIndex: number; // 关联的 ePID 索引 +} + +/** 主机构建信息 — 对应 VlmcsdHeader_t 中的构建信息 */ +export interface HostBuild { + buildNumber: number; // Windows 构建号 + platformId: number; // 平台 ID + displayName: string; // 显示名称 + flags: number; // 标志位(UseNdr64, UseForEpid, MayBeServer) + releaseDate: number; // 发布日期(Unix 时间戳) +} + +/** KMS 数据库总结构 — 对应 VlmcsdHeader_t */ +export interface KmsDatabase { + csvlkCount: number; + appItemCount: number; + kmsItemCount: number; + skuItemCount: number; + hostBuildCount: number; + csvlkData: CsvlkItem[]; + appItems: AppItem[]; + kmsItems: KmsItem[]; + skuItems: SkuItem[]; + hostBuilds: HostBuild[]; +} + +// ─── 主机构建标志位 (参考 src/kmsdata.h) ──────────────────────────────────── + +/** 使用 NDR64 传输语法 */ +export const UseNdr64 = 1 << 0; +/** 用于 ePID 生成 */ +export const UseForEpid = 1 << 1; +/** 可能是服务器版本 */ +export const MayBeServer = 1 << 2; + +// ─── 产品数据库 (从 src/kmsdata-full.c 提取) ──────────────────────────────── + +export const KmsData: KmsDatabase = { + "csvlkCount": 6, + "appItemCount": 3, + "kmsItemCount": 29, + "skuItemCount": 202, + "hostBuildCount": 6, + "csvlkData": [ + { + "ePid": "03612-00206-556-123727-03-1033-17763.0000-2972018", + "ePidGroup": "Windows", + "groupId": 206, + "minKeyId": 551000000, + "maxKeyId": 570999999, + "minActiveClients": 0, + "releaseDate": 1538438400 + }, + { + "ePid": "03612-00096-199-799188-03-1033-17763.0000-2972018", + "ePidGroup": "Office2010", + "groupId": 96, + "minKeyId": 199000000, + "maxKeyId": 217999999, + "minActiveClients": 0, + "releaseDate": 1279152000 + }, + { + "ePid": "03612-00206-240-719639-03-1033-17763.0000-2972018", + "ePidGroup": "Office2013", + "groupId": 206, + "minKeyId": 234000000, + "maxKeyId": 255999999, + "minActiveClients": 0, + "releaseDate": 1359417600 + }, + { + "ePid": "03612-00206-438-004532-03-1033-17763.0000-2972018", + "ePidGroup": "Office2016", + "groupId": 206, + "minKeyId": 437000000, + "maxKeyId": 458999999, + "minActiveClients": 0, + "releaseDate": 1442880000 + }, + { + "ePid": "03612-03858-053-089516-03-1033-17763.0000-2972018", + "ePidGroup": "WinChinaGov", + "groupId": 3858, + "minKeyId": 15000000, + "maxKeyId": 999999999, + "minActiveClients": 0, + "releaseDate": 1491350400 + }, + { + "ePid": "03612-00206-684-137669-03-1033-17763.0000-2972018", + "ePidGroup": "Office2019", + "groupId": 206, + "minKeyId": 666000000, + "maxKeyId": 685999999, + "minActiveClients": 0, + "releaseDate": 1537747200 + } + ], + "appItems": [ + { + "guid": "55c92734-d682-4d71-983e-d6ec3f16059f", + "name": "Windows", + "nCountPolicy": 50 + }, + { + "guid": "59a52881-a989-479d-af46-f275c6370663", + "name": "Office2010", + "nCountPolicy": 10 + }, + { + "guid": "0ff1ce15-a989-479d-af46-f275c6370663", + "name": "Office2013+", + "nCountPolicy": 10 + } + ], + "kmsItems": [ + { + "guid": "8449b1fb-f0ea-497a-99ab-66ca96e9a0f5", + "name": "Windows Server 2019", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "11b15659-e603-4cf1-9c1f-f0ec01b81888", + "name": "Windows 10 2019 (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "d27cd636-1962-44e9-8b4f-27b6c23efb85", + "name": "Windows 10 Unknown (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "7ba0bf23-d0f5-4072-91d9-d55af5a481b6", + "name": "Windows 10 China Government", + "appIndex": 0, + "ePidIndex": 4, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "969fe3c0-a3ec-491a-9f25-423605deb365", + "name": "Windows 10 2016 (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "e1c51358-fe3e-4203-a4a2-3b6b20c9734e", + "name": "Windows 10 (Retail)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 1, + "isPreview": 0 + }, + { + "guid": "58e2134f-8e11-4d17-9cb2-91069c151148", + "name": "Windows 10 2015 (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "7fde5219-fbfa-484a-82c9-34d1ad53e856", + "name": "Windows 7", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "bbb97b3b-8ca4-4a28-9717-89fabd42c4ac", + "name": "Windows 8 (Retail)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 5, + "isRetail": 1, + "isPreview": 0 + }, + { + "guid": "3c40b358-5948-45af-923b-53d21fcc7e79", + "name": "Windows 8 (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 5, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "6d646890-3606-461a-86ab-598bb84ace82", + "name": "Windows 8.1 (Retail)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 1, + "isPreview": 0 + }, + { + "guid": "cb8fc780-2c05-495a-9710-85afffc904d7", + "name": "Windows 8.1 (Volume)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "5f94a0bb-d5a0-4081-a685-5819418b2fe0", + "name": "Windows Preview", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 5, + "isRetail": 0, + "isPreview": 1 + }, + { + "guid": "33e156e4-b76f-4a52-9f91-f641dd95ac48", + "name": "Windows Server 2008 A (Web and HPC)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "8fe53387-3087-4447-8985-f75132215ac9", + "name": "Windows Server 2008 B (Standard and Enterprise)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "8a21fdf3-cbc5-44eb-83f3-fe284e6680a7", + "name": "Windows Server 2008 C (Datacenter)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "0fc6ccaf-ff0e-4fae-9d08-4370785bf7ed", + "name": "Windows Server 2008 R2 A (Web and HPC)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "ca87f5b6-cd46-40c0-b06d-8ecd57a4373f", + "name": "Windows Server 2008 R2 B (Standard and Enterprise)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "b2ca2689-a9a8-42d7-938d-cf8e9f201958", + "name": "Windows Server 2008 R2 C (Datacenter)", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "8665cb71-468c-4aa3-a337-cb9bc9d5eaac", + "name": "Windows Server 2012", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 5, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "8456efd3-0c04-4089-8740-5b7238535a65", + "name": "Windows Server 2012 R2", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "6e9fc069-257d-4bc4-b4a7-750514d32743", + "name": "Windows Server 2016", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "6d5f5270-31ac-433e-b90a-39892923c657", + "name": "Windows Server Preview", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 1 + }, + { + "guid": "212a64dc-43b1-4d3d-a30c-2fc69d2095c6", + "name": "Windows Vista", + "appIndex": 0, + "ePidIndex": 0, + "nCountPolicy": 25, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "e85af946-2e25-47b7-83e1-bebcebeac611", + "name": "Office 2010", + "appIndex": 1, + "ePidIndex": 1, + "nCountPolicy": 5, + "protocolVersion": 4, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "e6a6f1bf-9d40-40c3-aa9f-c77ba21578c0", + "name": "Office 2013", + "appIndex": 2, + "ePidIndex": 2, + "nCountPolicy": 5, + "protocolVersion": 5, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "aa4c7968-b9da-4680-92b6-acb25e2f866c", + "name": "Office 2013 (Pre-Release)", + "appIndex": 2, + "ePidIndex": 0, + "nCountPolicy": 5, + "protocolVersion": 5, + "isRetail": 0, + "isPreview": 1 + }, + { + "guid": "85b5f61b-320b-4be3-814a-b76b2bfafc82", + "name": "Office 2016", + "appIndex": 2, + "ePidIndex": 3, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + }, + { + "guid": "617d9eb1-ef36-4f82-86e0-a65ae07b96c6", + "name": "Office 2019", + "appIndex": 2, + "ePidIndex": 5, + "nCountPolicy": 5, + "protocolVersion": 6, + "isRetail": 0, + "isPreview": 0 + } + ], + "skuItems": [ + { + "guid": "8de8eb62-bbe0-40ac-ac17-f75595071ea3", + "name": "Windows Server 2019 ARM64", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a99cc1f0-7719-4306-9645-294102fbff95", + "name": "Windows Server 2019 Azure Core", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "34e1ae55-27f8-4950-8877-7a03be5fb181", + "name": "Windows Server 2019 Datacenter", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "034d3cbb-5d4b-4245-b3f8-f84571314078", + "name": "Windows Server 2019 Essentials", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "de32eafd-aaee-4662-9444-c1befb41bde2", + "name": "Windows Server 2019 Standard", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "90c362e5-0da1-4bfd-b53b-b87d309ade43", + "name": "Windows Server 2019 Datacenter (Semi-Annual Channel)", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "73e3957c-fc0c-400d-9184-5f7b6f2eb409", + "name": "Windows Server 2019 Standard (Semi-Annual Channel)", + "appIndex": 0, + "kmsIndex": 0, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "32d2fab3-e4a8-42c2-923b-4bf4fd13e6ee", + "name": "Windows 10 Enterprise LTSC 2019", + "appIndex": 0, + "kmsIndex": 1, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7103a333-b8c8-49cc-93ce-d37c09687f92", + "name": "Windows 10 Enterprise LTSC 2019 N", + "appIndex": 0, + "kmsIndex": 1, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e0b2d383-d112-413f-8a80-97f373a5820c", + "name": "Windows 10 Enterprise G", + "appIndex": 0, + "kmsIndex": 3, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 4 + }, + { + "guid": "e38454fb-41a4-4f59-a5dc-25080e354730", + "name": "Windows 10 Enterprise GN", + "appIndex": 0, + "kmsIndex": 3, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 4 + }, + { + "guid": "2d5a5a60-3040-48bf-beb0-fcd770c20ce0", + "name": "Windows 10 Enterprise 2016 LTSB", + "appIndex": 0, + "kmsIndex": 4, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "9f776d83-7156-45b2-8a5c-359b9c9f22a3", + "name": "Windows 10 Enterprise 2016 LTSB N", + "appIndex": 0, + "kmsIndex": 4, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "58e97c99-f377-4ef1-81d5-4ad5522b5fd8", + "name": "Windows 10 Home", + "appIndex": 0, + "kmsIndex": 5, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a9107544-f4a0-4053-a96a-1479abdef912", + "name": "Windows 10 Home Country Specific", + "appIndex": 0, + "kmsIndex": 5, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7b9e1751-a8da-4f75-9560-5fadfe3d8e38", + "name": "Windows 10 Home N", + "appIndex": 0, + "kmsIndex": 5, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cd918a57-a41b-4c82-8dce-1a538e221a83", + "name": "Windows 10 Home Single Language", + "appIndex": 0, + "kmsIndex": 5, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e0c42288-980c-4788-a014-c080d2e1926e", + "name": "Windows 10 Education", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "3c102355-d027-42c6-ad23-2e7ef8a02585", + "name": "Windows 10 Education N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "73111121-5638-40f6-bc11-f1d7b0d64300", + "name": "Windows 10 Enterprise", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7b51a46c-0c04-4e8f-9af4-8496cca90d5e", + "name": "Windows 10 Enterprise 2015 LTSB", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "87b838b7-41b6-4590-8318-5797951d8529", + "name": "Windows 10 Enterprise 2015 LTSB N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e272e3e2-732f-4c65-a8f0-484747d0d947", + "name": "Windows 10 Enterprise N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "82bbc092-bc50-4e16-8e18-b74fc486aec3", + "name": "Windows 10 Professional Workstation", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "4b1571d3-bafb-4b40-8087-a961be2caf65", + "name": "Windows 10 Professional Workstation N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "2de67392-b7a7-462a-b1ca-108dd189f588", + "name": "Windows 10 Professional", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "3f1afc82-f8ac-4f6c-8005-1d233e606eee", + "name": "Windows 10 Professional Education", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "5300b18c-2e33-4dc2-8291-47ffcec746dd", + "name": "Windows 10 Professional Education N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a80b5abf-76ad-428b-b05d-a47d2dffeebf", + "name": "Windows 10 Professional N", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ff808201-fec6-4fd4-ae16-abbddade5706", + "name": "Windows 10 Professional Preview", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "43f2ab05-7c87-4d56-b27c-44d0f9a3dabd", + "name": "Windows 10 Enterprise Preview", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ec868e65-fadf-4759-b23e-93fe37f2cc29", + "name": "Windows 10 Enterprise for Virtual Desktops", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e4db50ea-bda1-4566-b047-0ca50abc6f07", + "name": "Windows 10 Remote Server", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "0df4f814-3f57-4b8b-9a9d-fddadcd69fac", + "name": "Windows 10 S (Lean)", + "appIndex": 0, + "kmsIndex": 6, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ae2ee509-1b34-41c0-acb7-6d4650168915", + "name": "Windows 7 Enterprise", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "46bbed08-9c7b-48fc-a614-95250573f4ea", + "name": "Windows 7 Enterprise E", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "1cb6d605-11b3-4e14-bb30-da91c8e3983a", + "name": "Windows 7 Enterprise N", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "b92e9980-b9d5-4821-9c94-140f632f6312", + "name": "Windows 7 Professional", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "5a041529-fef8-4d07-b06f-b59b573b32d2", + "name": "Windows 7 Professional E", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "54a09a0d-d57b-4c10-8b69-a842d6590ad5", + "name": "Windows 7 Professional N", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "db537896-376f-48ae-a492-53d0547773d0", + "name": "Windows 7 Embedded POSReady", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e1a8296a-db37-44d1-8cce-7bc961d59c54", + "name": "Windows 7 Embedded Standard", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "aa6dd3aa-c2b4-40e2-a544-a6bbb3f5c395", + "name": "Windows 7 ThinPC", + "appIndex": 0, + "kmsIndex": 7, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "c04ed6bf-55c8-4b47-9f8e-5a1f31ceee60", + "name": "Windows 8 Core", + "appIndex": 0, + "kmsIndex": 8, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "9d5584a2-2d85-419a-982c-a00888bb9ddf", + "name": "Windows 8 Core Country Specific", + "appIndex": 0, + "kmsIndex": 8, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "197390a0-65f6-4a95-bdc4-55d58a3b0253", + "name": "Windows 8 Core N", + "appIndex": 0, + "kmsIndex": 8, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "8860fcd4-a77b-4a20-9045-a150ff11d609", + "name": "Windows 8 Core Single Language", + "appIndex": 0, + "kmsIndex": 8, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a00018a3-f20f-4632-bf7c-8daa5351c914", + "name": "Windows 8 Professional WMC", + "appIndex": 0, + "kmsIndex": 8, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "10018baf-ce21-4060-80bd-47fe74ed4dab", + "name": "Windows 8 Embedded Industry Professional", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "18db1848-12e0-4167-b9d7-da7fcda507db", + "name": "Windows 8 Embedded Industry Enterprise", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "458e1bec-837a-45f6-b9d5-925ed5d299de", + "name": "Windows 8 Enterprise", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e14997e7-800a-4cf7-ad10-de4b45b578db", + "name": "Windows 8 Enterprise N", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a98bcd6d-5343-4603-8afe-5908e4611112", + "name": "Windows 8 Professional", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ebf245c1-29a8-4daf-9cb1-38dfc608a8c8", + "name": "Windows 8 Professional N", + "appIndex": 0, + "kmsIndex": 9, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "fe1c3238-432a-43a1-8e25-97e7d1ef10f3", + "name": "Windows 8.1 Core", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ffee456a-cd87-4390-8e07-16146c672fd0", + "name": "Windows 8.1 Core ARM", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "db78b74f-ef1c-4892-abfe-1e66b8231df6", + "name": "Windows 8.1 Core Country Specific", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "78558a64-dc19-43fe-a0d0-8075b2a370a3", + "name": "Windows 8.1 Core N", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "c72c6a1d-f252-4e7e-bdd1-3fca342acb35", + "name": "Windows 8.1 Core Single Language", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e58d87b5-8126-4580-80fb-861b22f79296", + "name": "Windows 8.1 Professional Student", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cab491c7-a918-4f60-b502-dab75e334f40", + "name": "Windows 8.1 Professional Student N", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "096ce63d-4fac-48a9-82a9-61ae9e800e5f", + "name": "Windows 8.1 Professional WMC", + "appIndex": 0, + "kmsIndex": 10, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 1, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e9942b32-2e55-4197-b0bd-5ff58cba8860", + "name": "Windows 8.1 Core Connected", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ba998212-460a-44db-bfb5-71bf09d1c68b", + "name": "Windows 8.1 Core Connected Country Specific", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "c6ddecd6-2354-4c19-909b-306a3058484e", + "name": "Windows 8.1 Core Connected N", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "b8f5e3a3-ed33-4608-81e1-37d6c9dcfd9c", + "name": "Windows 8.1 Core Connected Single Language", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "81671aaf-79d1-4eb1-b004-8cbbe173afea", + "name": "Windows 8.1 Enterprise", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "113e705c-fa49-48a4-beea-7dd879b46b14", + "name": "Windows 8.1 Enterprise N", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "c06b6981-d7fd-4a35-b7b4-054742b7af67", + "name": "Windows 8.1 Professional", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7476d79f-8e48-49b4-ab63-4d0b813a16e4", + "name": "Windows 8.1 Professional N", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "0ab82d54-47f4-4acb-818c-cc5bf0ecb649", + "name": "Windows 8.1 Embedded Industry Professional", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "f7e88590-dfc7-4c78-bccb-6f3865b99d1a", + "name": "Windows 8.1 Embedded Industry Automotive", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cd4e2d9f-5059-4a50-a92d-05d5bb1267c7", + "name": "Windows 8.1 Embedded Industry Enterprise", + "appIndex": 0, + "kmsIndex": 11, + "protocolVersion": 6, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cde952c7-2f96-4d9d-8f2b-2d349f64fc51", + "name": "Windows 10 Enterprise Preview", + "appIndex": 0, + "kmsIndex": 12, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "a4383e6b-dada-423d-a43d-f25678429676", + "name": "Windows 10 Professional Preview", + "appIndex": 0, + "kmsIndex": 12, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "cf59a07b-1a2a-4be0-bfe0-423b5823e663", + "name": "Windows 10 Professional WMC Preview", + "appIndex": 0, + "kmsIndex": 12, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "2b9c337f-7a1d-4271-90a3-c6855a2b8a1c", + "name": "Windows 8.x Preview", + "appIndex": 0, + "kmsIndex": 12, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "631ead72-a8ab-4df8-bbdf-372029989bdd", + "name": "Windows 8.x Preview ARM", + "appIndex": 0, + "kmsIndex": 12, + "protocolVersion": 5, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "ddfa9f7c-f09e-40b9-8c1a-be877a9a7f4b", + "name": "Windows Server 2008 Web", + "appIndex": 0, + "kmsIndex": 13, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7afb1156-2c1d-40fc-b260-aab7442b62fe", + "name": "Windows Server 2008 Compute Cluster", + "appIndex": 0, + "kmsIndex": 13, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ad2542d4-9154-4c6d-8a44-30f11ee96989", + "name": "Windows Server 2008 Standard", + "appIndex": 0, + "kmsIndex": 14, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "2401e3d0-c50a-4b58-87b2-7e794b7d2607", + "name": "Windows Server 2008 Standard without Hyper-V", + "appIndex": 0, + "kmsIndex": 14, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "c1af4d90-d1bc-44ca-85d4-003ba33db3b9", + "name": "Windows Server 2008 Enterprise", + "appIndex": 0, + "kmsIndex": 14, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "8198490a-add0-47b2-b3ba-316b12d647b4", + "name": "Windows Server 2008 Enterprise without Hyper-V", + "appIndex": 0, + "kmsIndex": 14, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "68b6e220-cf09-466b-92d3-45cd964b9509", + "name": "Windows Server 2008 Datacenter", + "appIndex": 0, + "kmsIndex": 15, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "fd09ef77-5647-4eff-809c-af2b64659a45", + "name": "Windows Server 2008 Datacenter without Hyper-V", + "appIndex": 0, + "kmsIndex": 15, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "01ef176b-3e0d-422a-b4f8-4ea880035e8f", + "name": "Windows Server 2008 for Itanium", + "appIndex": 0, + "kmsIndex": 15, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "f772515c-0e87-48d5-a676-e6962c3e1195", + "name": "Windows MultiPoint Server 2010", + "appIndex": 0, + "kmsIndex": 16, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "a78b8bd9-8017-4df5-b86a-09f756affa7c", + "name": "Windows Server 2008 R2 Web", + "appIndex": 0, + "kmsIndex": 16, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cda18cf3-c196-46ad-b289-60c072869994", + "name": "Windows Server 2008 R2 HPC Edition", + "appIndex": 0, + "kmsIndex": 16, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "68531fb9-5511-4989-97be-d11a0f55633f", + "name": "Windows Server 2008 R2 Standard", + "appIndex": 0, + "kmsIndex": 17, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "620e2b3d-09e7-42fd-802a-17a13652fe7a", + "name": "Windows Server 2008 R2 Enterprise", + "appIndex": 0, + "kmsIndex": 17, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7482e61b-c589-4b7f-8ecc-46d455ac3b87", + "name": "Windows Server 2008 R2 Datacenter", + "appIndex": 0, + "kmsIndex": 18, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "8a26851c-1c7e-48d3-a687-fbca9b9ac16b", + "name": "Windows Server 2008 R2 for Itanium Enterprise", + "appIndex": 0, + "kmsIndex": 18, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "d3643d60-0c42-412d-a7d6-52e6635327f6", + "name": "Windows Server 2012 Datacenter", + "appIndex": 0, + "kmsIndex": 19, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "95fd1c83-7df5-494a-be8b-1300e1c9d1cd", + "name": "Windows Server 2012 MultiPoint Premium", + "appIndex": 0, + "kmsIndex": 19, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7d5486c7-e120-4771-b7f1-7b56c6d3170c", + "name": "Windows Server 2012 MultiPoint Standard", + "appIndex": 0, + "kmsIndex": 19, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "f0f5ec41-0d55-4732-af02-440a44a3cf0f", + "name": "Windows Server 2012 Standard", + "appIndex": 0, + "kmsIndex": 19, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "b743a2be-68d4-4dd3-af32-92425b7bb623", + "name": "Windows Server 2012 R2 Cloud Storage", + "appIndex": 0, + "kmsIndex": 20, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "00091344-1ea4-4f37-b789-01750ba6988c", + "name": "Windows Server 2012 R2 Datacenter", + "appIndex": 0, + "kmsIndex": 20, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "21db6ba4-9a7b-4a14-9e29-64a60c59301d", + "name": "Windows Server 2012 R2 Essentials", + "appIndex": 0, + "kmsIndex": 20, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "b3ca044e-a358-4d68-9883-aaa2941aca99", + "name": "Windows Server 2012 R2 Standard", + "appIndex": 0, + "kmsIndex": 20, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "3dbf341b-5f6c-4fa7-b936-699dce9e263f", + "name": "Windows Server 2016 Azure Core", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "7b4433f4-b1e7-4788-895a-c45378d38253", + "name": "Windows Server 2016 Cloud Storage", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "21c56779-b449-4d20-adfc-eece0e1ad74b", + "name": "Windows Server 2016 Datacenter", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "2b5a1b0f-a5ab-4c54-ac2f-a6d94824a283", + "name": "Windows Server 2016 Essentials", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "8c1c5410-9f39-4805-8c9d-63a07706358f", + "name": "Windows Server 2016 Standard", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "43d9af6e-5e86-4be8-a797-d072a046896c", + "name": "Windows Server 2016 ARM64", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "e49c08e7-da82-42f8-bde2-b570fbcae76c", + "name": "Windows Server 2016 Datacenter (Semi-Annual Channel)", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "61c5ef22-f14f-4553-a824-c4b31e84b100", + "name": "Windows Server 2016 Standard (Semi-Annual Channel)", + "appIndex": 0, + "kmsIndex": 21, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "ba947c44-d19d-4786-b6ae-22770bc94c54", + "name": "Windows Server 2016 Datacenter Preview", + "appIndex": 0, + "kmsIndex": 22, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "4f3d1606-3fea-4c01-be3c-8d671c401e3b", + "name": "Windows Vista Business", + "appIndex": 0, + "kmsIndex": 23, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "2c682dc2-8b68-4f63-a165-ae291d4cf138", + "name": "Windows Vista Business N", + "appIndex": 0, + "kmsIndex": 23, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "cfd8ff08-c0d7-452b-9f60-ef5c70c32094", + "name": "Windows Vista Enterprise", + "appIndex": 0, + "kmsIndex": 23, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "d4f54950-26f2-4fb4-ba21-ffab16afcade", + "name": "Windows Vista Enterprise N", + "appIndex": 0, + "kmsIndex": 23, + "protocolVersion": 4, + "nCountPolicy": 25, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 0 + }, + { + "guid": "8ce7e872-188c-4b98-9d90-f8f90b7aad02", + "name": "Office Access 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "cee5d470-6e3b-4fcc-8c2b-d17428568a9f", + "name": "Office Excel 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "8947d0b8-c33b-43e1-8c56-9b674c052832", + "name": "Office Groove 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "ca6b6639-4ad6-40ae-a575-14dee07f6430", + "name": "Office InfoPath 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "09ed9640-f020-400a-acd8-d7d867dfd9c2", + "name": "Office Mondo 1 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "ef3d4e49-a53d-4d81-a2b1-2ca6c2556b2c", + "name": "Office Mondo 2 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "ab586f5c-5256-4632-962f-fefd8b49e6f4", + "name": "Office OneNote 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "ecb7c192-73ab-4ded-acf4-2399b095d0cc", + "name": "Office OutLook 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "45593b1d-dfb1-4e91-bbfb-2d5d0ce2227a", + "name": "Office PowerPoint 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "6f327760-8c5c-417c-9b61-836a98287e0c", + "name": "Office Professional Plus 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "df133ff7-bf14-4f95-afe3-7b48e7e331ef", + "name": "Office Project Pro 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "5dc7bf61-5ec9-4996-9ccb-df806a2d0efe", + "name": "Office Project Standard 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "b50c4f75-599b-43e8-8dcd-1081a7967241", + "name": "Office Publisher 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "ea509e87-07a1-4a45-9edc-eba5a39f36af", + "name": "Office Small Business Basics 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "9da2a678-fb6b-4e67-ab84-60dd6a9c819a", + "name": "Office Standard 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "92236105-bb67-494f-94c7-7f7a607929bd", + "name": "Office Visio Premium 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "e558389c-83c3-4b29-adfe-5e4d7f46c358", + "name": "Office Visio Pro 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "9ed833ff-4f92-4f36-b370-8683a4f13275", + "name": "Office Visio Standard 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "2d0882e7-a4e7-423b-8ccc-70d91e0158b1", + "name": "Office Word 2010", + "appIndex": 1, + "kmsIndex": 24, + "protocolVersion": 4, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 1 + }, + { + "guid": "6ee7622c-18d8-4005-9fb7-92db644a279b", + "name": "Office Access 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "f7461d52-7c2b-43b2-8744-ea958e0bd09a", + "name": "Office Excel 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "a30b8040-d68a-423f-b0b5-9ce292ea5a8f", + "name": "Office InfoPath 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "1b9f11e3-c85c-4e1b-bb29-879ad2c909e3", + "name": "Office Lync 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "dc981c6b-fc8e-420f-aa43-f8f33e5c0923", + "name": "Office Mondo 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "efe1f3e6-aea2-4144-a208-32aa872b6545", + "name": "Office OneNote 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "771c3afa-50c5-443f-b151-ff2546d863a0", + "name": "Office OutLook 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "8c762649-97d1-4953-ad27-b7e2c25b972e", + "name": "Office PowerPoint 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "b322da9c-a2e2-4058-9e4e-f59a6970bd69", + "name": "Office Professional Plus 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "4a5d124a-e620-44ba-b6ff-658961b33b9a", + "name": "Office Project Pro 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "427a28d1-d17c-4abf-b717-32c780ba6f07", + "name": "Office Project Standard 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "00c79ff1-6850-443d-bf61-71cde0de305f", + "name": "Office Publisher 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "b13afb38-cd79-4ae5-9f7f-eed058d750ca", + "name": "Office Standard 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "e13ac10e-75d0-4aff-a0cd-764982cf541c", + "name": "Office Visio Pro 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "ac4efaf0-f81f-4f61-bdf7-ea32b02ab117", + "name": "Office Visio Standard 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "d9f5b1c6-5386-495a-88f9-9ad6b41ac9b3", + "name": "Office Word 2013", + "appIndex": 2, + "kmsIndex": 25, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 2 + }, + { + "guid": "44b538e2-fb34-4732-81e4-644c17d2e746", + "name": "Office Access 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "9373bfa0-97b3-4587-ab73-30934461d55c", + "name": "Office Excel 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "aa286eb4-556f-4eeb-967c-c1b771b7673e", + "name": "Office Groove 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "7ccc8256-fbaa-49c6-b2a9-f5afb4257cd2", + "name": "Office InfoPath 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "c53dfe17-cc00-4967-b188-a088a965494d", + "name": "Office Lync 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "2816a87d-e1ed-4097-b311-e2341c57b179", + "name": "Office Mondo 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "67c0f908-184f-4f64-8250-12db797ab3c3", + "name": "Office OneNote 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "7bce4e7a-dd80-4682-98fa-f993725803d2", + "name": "Office Outlook 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "1ec10c0a-54f6-453e-b85a-6fa1bbfea9b7", + "name": "Office PowerPoint 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "87d2b5bf-d47b-41fb-af62-71c382f5cc85", + "name": "Office Professional Plus 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "3cfe50a9-0e03-4b29-9754-9f193f07b71f", + "name": "Office Project Pro 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "39e49e57-ae68-4ee3-b098-26480df3da96", + "name": "Office Project Standard 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "15aa2117-8f79-49a8-8317-753026d6a054", + "name": "Office Publisher 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "cfbfd60e-0b5f-427d-917c-a4df42a80e44", + "name": "Office Visio Pro 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "7012cc81-8887-42e9-b17d-4e5e42760f0d", + "name": "Office Visio Standard 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "de9c7eb6-5a85-420d-9703-fff11bdd4d43", + "name": "Office Word 2013 (Pre-Release)", + "appIndex": 2, + "kmsIndex": 26, + "protocolVersion": 5, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 1, + "ePidIndex": 0 + }, + { + "guid": "67c0fc0c-deba-401b-bf8b-9c8ad8395804", + "name": "Office Access 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "c3e65d36-141f-4d2f-a303-a842ee756a29", + "name": "Office Excel 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "9caabccb-61b1-4b4b-8bec-d10a3c3ac2ce", + "name": "Office Mondo 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "e914ea6e-a5fa-4439-a394-a9bb3293ca09", + "name": "Office Mondo R 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "d8cace59-33d2-4ac7-9b1b-9b72339c51c8", + "name": "Office OneNote 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "ec9d9265-9d1e-4ed0-838a-cdc20f2551a1", + "name": "Office Outlook 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "d70b1bba-b893-4544-96e2-b7a318091c33", + "name": "Office Powerpoint 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "d450596f-894d-49e0-966a-fd39ed4c4c64", + "name": "Office Professional Plus 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "4f414197-0fc2-4c01-b68a-86cbb9ac254c", + "name": "Office Project Pro 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "829b8110-0e6f-4349-bca4-42803577788d", + "name": "Office Project Pro 2016 C2R", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "da7ddabc-3fbe-4447-9e01-6ab7440b4cd4", + "name": "Office Project Standard 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "cbbaca45-556a-4416-ad03-bda598eaa7c8", + "name": "Office Project Standard 2016 C2R", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "041a06cb-c5b8-4772-809f-416d03d16654", + "name": "Office Publisher 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "83e04ee1-fa8d-436d-8994-d31a862cab77", + "name": "Office Skype for Business 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "dedfa23d-6ed1-45a6-85dc-63cae0546de6", + "name": "Office Standard 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "6bf301c1-b94a-43e9-ba31-d494598c47fb", + "name": "Office Visio Pro 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "b234abe3-0857-4f9c-b05a-4dc314f85557", + "name": "Office Visio Pro 2016 C2R", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "aa2a7821-1827-4c2c-8f1d-4513a34dda97", + "name": "Office Visio Standard 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "361fe620-64f4-41b5-ba77-84f8e079b1f7", + "name": "Office Visio Standard 2016 C2R", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "bb11badf-d8aa-470e-9311-20eaf80fe5cc", + "name": "Office Word 2016", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "0bc88885-718c-491d-921f-6f214349e79c", + "name": "Office Professional Plus 2019 C2R Preview", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "fc7c4d0c-2e85-4bb9-afd4-01ed1476b5e9", + "name": "Office Project Pro 2019 C2R Preview", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "500f6619-ef93-4b75-bcb4-82819998a3ca", + "name": "Office Visio Pro 2019 C2R Preview", + "appIndex": 2, + "kmsIndex": 27, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 3 + }, + { + "guid": "9e9bceeb-e736-4f26-88de-763f87dcc485", + "name": "Office Access 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "237854e9-79fc-4497-a0c1-a70969691c6b", + "name": "Office Excel 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "c8f8a301-19f5-4132-96ce-2de9d4adbd33", + "name": "Office Outlook 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "3131fd61-5e4f-4308-8d6d-62be1987c92c", + "name": "Office Powerpoint 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "85dd8b5f-eaa4-4af3-a628-cce9e77c9a03", + "name": "Office Professional Plus 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "2ca2bf3f-949e-446a-82c7-e25a15ec78c4", + "name": "Office Project Pro 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "1777f0e3-7392-4198-97ea-8ae4de6f6381", + "name": "Office Project Standard 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "9d3e4cca-e172-46f1-a2f4-1d2107051444", + "name": "Office Publisher 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "734c6c6e-b0ba-4298-a891-671772b2bd1b", + "name": "Office Skype for Business 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "6912a74b-a5fb-401a-bfdb-2e3ab46f4b02", + "name": "Office Standard 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "5b5cf08f-b81a-431d-b080-3450d8620565", + "name": "Office Visio Pro 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "e06d7df3-aad0-419d-8dfb-0ac37e2bdf39", + "name": "Office Visio Standard 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + }, + { + "guid": "059834fe-a8ea-4bff-b67b-4d006b5447d3", + "name": "Office Word 2019", + "appIndex": 2, + "kmsIndex": 28, + "protocolVersion": 6, + "nCountPolicy": 5, + "isRetail": 0, + "isPreview": 0, + "ePidIndex": 5 + } + ], + "hostBuilds": [ + { + "buildNumber": 17763, + "platformId": 3612, + "displayName": "Windows 10 1809 / Server 2019", + "flags": 7, + "releaseDate": 1538438400 + }, + { + "buildNumber": 14393, + "platformId": 3612, + "displayName": "Windows 10 1607 / Server 2016", + "flags": 7, + "releaseDate": 1470096000 + }, + { + "buildNumber": 9600, + "platformId": 6401, + "displayName": "Windows 8.1 / Server 2012 R2", + "flags": 7, + "releaseDate": 1382054400 + }, + { + "buildNumber": 9200, + "platformId": 5426, + "displayName": "Windows 8 / Server 2012", + "flags": 7, + "releaseDate": 1351209600 + }, + { + "buildNumber": 7601, + "platformId": 55041, + "displayName": "Windows 7 / Server 2008 R2 SP1", + "flags": 6, + "releaseDate": 1298332800 + }, + { + "buildNumber": 6002, + "platformId": 55041, + "displayName": "Windows Vista / Server 2008 SP2", + "flags": 6, + "releaseDate": 1243296000 + } + ] +}; + +// ─── 数据查询工具函数 ────────────────────────────────────────────────────── + +/** + * 按 GUID 查找产品索引(不区分大小写) + * 参考: src/vlmcs.c 中的产品查找逻辑 + * + * @param guid 要查找的 GUID 字符串 + * @param list 产品列表 + * @param count 列表长度 + * @returns 匹配的索引,未找到返回 -1 + */ +export function getProductIndex( + guid: string, + list: ReadonlyArray<{ guid: string }>, + count: number, +): number { + const needle = guid.toLowerCase(); + for (let i = 0; i < count && i < list.length; i++) { + if (list[i].guid.toLowerCase() === needle) return i; + } + return -1; +} + +/** + * 按名称查找许可证包 (SKU),支持部分匹配(不区分大小写) + * 参考: src/vlmcs.c 中 -l 参数的处理逻辑 + * + * @param name 要查找的产品名称(可以是部分名称) + * @returns 匹配的 SkuItem,未找到返回 undefined + */ +export function findLicensePackByName(name: string): SkuItem | undefined { + const needle = name.toLowerCase(); + return KmsData.skuItems.find((sku) => sku.name.toLowerCase().includes(needle)); +} diff --git a/node-vlmcs/src/helpers.ts b/node-vlmcs/src/helpers.ts new file mode 100644 index 00000000..b35ceba4 --- /dev/null +++ b/node-vlmcs/src/helpers.ts @@ -0,0 +1,68 @@ +/** + * 命令行参数解析工具函数 + * + * 参考原版源码: + * - src/helpers.c (parseAddress, getSocketList 等网络地址解析) + * - src/vlmcs.c (命令行参数解析逻辑) + */ + +/** + * 解析地址字符串,支持以下格式: + * - "host:port" — IPv4 地址或主机名加端口 + * - "[ipv6]:port" — IPv6 方括号表示法加端口 + * - "host" — 不带端口,默认 1688 + * - "::1" — 裸 IPv6 地址(不带端口) + * + * 参考: src/network.c 中 getSocketList() 的地址解析逻辑 + */ +export function parseAddress(addr: string): { host: string; port: string } { + const defaultPort = '1688'; + + // IPv6 方括号表示法: [::1]:port 或 [::1] + if (addr.startsWith('[')) { + const closeBracket = addr.indexOf(']'); + if (closeBracket === -1) { + return { host: addr, port: defaultPort }; + } + const host = addr.slice(1, closeBracket); + const rest = addr.slice(closeBracket + 1); + if (rest.startsWith(':') && rest.length > 1) { + return { host, port: rest.slice(1) }; + } + return { host, port: defaultPort }; + } + + // 如果包含多个冒号,说明是裸 IPv6 地址(无端口) + const firstColon = addr.indexOf(':'); + if (firstColon !== -1 && addr.indexOf(':', firstColon + 1) !== -1) { + return { host: addr, port: defaultPort }; + } + + // IPv4 或主机名,可选 :port + if (firstColon !== -1) { + return { host: addr.slice(0, firstColon), port: addr.slice(firstColon + 1) }; + } + + return { host: addr, port: defaultPort }; +} + +/** + * 解析十进制整数字符串,验证范围 [min, max] + * 无效或超出范围返回 null + */ +export function stringToInt(str: string, min: number, max: number): number | null { + if (!/^-?\d+$/.test(str)) return null; + const val = parseInt(str, 10); + if (!Number.isFinite(val) || val < min || val > max) return null; + return val; +} + +/** + * 解析布尔参数: "1" → true, "0" → false, 其他 → null + * 参考: src/vlmcs.c 中 getArgumentBool() 的实现 + */ +export function getArgumentBool(arg: string): boolean | null { + if (arg === '1') return true; + if (arg === '0') return false; + return null; +} diff --git a/node-vlmcs/src/kms.ts b/node-vlmcs/src/kms.ts new file mode 100644 index 00000000..42604c7b --- /dev/null +++ b/node-vlmcs/src/kms.ts @@ -0,0 +1,456 @@ +/** + * KMS 协议实现 — 处理 V4/V5/V6 请求创建和响应解密 + * + * 参考原版源码: + * - src/kms.c (CreateRequestV4, CreateRequestV6, DecryptResponseV4, DecryptResponseV6) + * - src/kms.h (REQUEST, RESPONSE, RESPONSE_RESULT 等结构定义) + */ + +import { + AesCtx, + AES_BLOCK_BYTES, + AES_KEY_BYTES, + AesKeyV5, + AesKeyV6, + xorBlock, + aesCmacV4, + aesEncryptCbc, + aesDecryptCbc, + sha256, + sha256Hmac, + get16RandomBytes, +} from './crypto'; + +import { + PID_BUFFER_SIZE, + ResponseResult, +} from './types'; + +// 重新导出时间工具函数 +export { unixTimeToFileTime as getUnixTimeAsFileTime, fileTimeToUnixTime } from './types'; + +// ─── 协议大小常量 (参考 src/kms.h 中的结构体大小) ────────────────────────── + +// REQUEST 结构: Version(4) + VMInfo(4) + LicenseStatus(4) + BindingExpiration(4) + +// AppID(16) + ActID(16) + KMSID(16) + CMID(16) + N_Policy(4) + +// ClientTime(8) + CMID_prev(16) + WorkstationName(128) = 236 字节 +const REQUEST_SIZE = 236; +/** V4 请求大小: REQUEST(236) + AES-CMAC(16) = 252 字节 */ +const REQUEST_V4_SIZE = 252; +/** V5/V6 请求大小: Version(4) + IV(16) + REQUEST(236) + Pad(4) = 260 字节 */ +const REQUEST_V6_SIZE = 260; + +/** V4 响应中 ePID 前面的字段大小: Version(4) + PIDSize(4) = 8 字节 */ +const V4_PRE_EPID_SIZE = 8; +/** V4 响应中 ePID 后面的字段大小: CMID(16) + ClientTime(8) + Count(4) + VLActivation(4) + VLRenewal(4) = 36 字节 */ +const V4_POST_EPID_SIZE = 36; +/** V6 未加密部分大小: Version(4) + IV(16) = 20 字节 */ +const V6_UNENCRYPTED_SIZE = 20; +/** V6 响应中 ePID 前面的字段大小: V6未加密(20) + ResponseBase.Version(4) + PIDSize(4) = 28 字节 */ +const V6_PRE_EPID_SIZE = 28; +/** V5 响应中 ePID 后面的额外字段: V4后(36) + RandomXoredIVs(16) + Hash(32) = 84 字节 */ +const V5_POST_EPID_SIZE = 84; +/** V6 响应中 ePID 后面的额外字段: V5后(84) + HwId(8) + XoredIVs(16) + HMAC(16) = 124 字节 */ +const V6_POST_EPID_SIZE = 124; +/** V6 解密大小: IV(16) + REQUEST(236) + Pad(4) = 256 字节 */ +const V6_DECRYPT_SIZE = 256; + +const HMAC_SIZE = 16; +const HASH_SIZE = 32; +const HALF_HASH_SIZE = HASH_SIZE >> 1; +/** PID 最大字节数: PID_BUFFER_SIZE * 2 = 128 字节 (UCS-2 编码) */ +const KMS_PID_MAX_BYTES = PID_BUFFER_SIZE * 2; + +// 最大结构体大小(使用完整 PID_BUFFER_SIZE 时) +/** V4 响应最大大小: RESPONSE(172) + MAC(16) = 188 字节 */ +const SIZEOF_RESPONSE_V4 = 188; +/** V5 响应最大大小: Version(4) + IV(16) + RESPONSE(172) + RandomXoredIVs(16) + Hash(32) = 240 字节 */ +const SIZEOF_RESPONSE_V5 = 240; +/** V6 响应最大大小: V5(240) + HwId(8) + XoredIVs(16) + HMAC(16) = 280 字节 */ +const SIZEOF_RESPONSE_V6 = 280; + +// V6 时间槽常量(每个时间槽约 4.11 小时) +// 用于 HMAC 计算,参考 src/kms.c 中的 CreateV6Hmac() +const TIME_C1 = 0x00000022816889BDn; +const TIME_C2 = 0x000000208CBAB5EDn; +const TIME_C3 = 0x3156CD5AC628477An; +const UINT64_MASK = 0xFFFFFFFFFFFFFFFFn; + +// ─── 导出类型 ─────────────────────────────────────────────────────────────── + +/** 解析后的 KMS 响应,对应 src/kms.h 中的 RESPONSE 结构 */ +export interface ParsedResponse { + majorVer: number; // 主版本号 + minorVer: number; // 次版本号 + pidSize: number; // PID 大小(字节) + kmsPID: Buffer; // KMS ePID (UCS-2 编码) + cmid: Buffer; // 客户端机器 ID (16 字节 GUID) + clientTime: Buffer; // 客户端请求时间 (8 字节 FILETIME) + count: number; // 当前活跃客户端数 + vlActivationInterval: number; // 激活间隔(分钟) + vlRenewalInterval: number; // 续期间隔(分钟) +} + +// ─── 内部工具函数 ─────────────────────────────────────────────────────────── + +/** + * 从 ResponseResult 各字段计算位掩码 + * 参考: src/kms.c 中结果掩码的构建方式 + */ +function computeMask(r: ResponseResult): number { + let mask = 0; + if (r.hashOK) mask |= 1 << 0; + if (r.timeStampOK) mask |= 1 << 1; + if (r.clientMachineIDOK) mask |= 1 << 2; + if (r.versionOK) mask |= 1 << 3; + if (r.ivsOK) mask |= 1 << 4; + if (r.decryptSuccess) mask |= 1 << 5; + if (r.hmacSha256OK) mask |= 1 << 6; + if (r.pidLengthOK) mask |= 1 << 7; + if (r.rpcOK) mask |= 1 << 8; + if (r.ivNotSuspicious) mask |= 1 << 9; + mask |= (r.effectiveResponseSize & 0x1FF) << 14; + mask |= (r.correctResponseSize & 0x1FF) << 23; + return mask >>> 0; +} + +/** 创建空的 ParsedResponse(用于解密失败时的早期返回) */ +function emptyParsedResponse(): ParsedResponse { + return { + majorVer: 0, minorVer: 0, pidSize: 0, + kmsPID: Buffer.alloc(0), cmid: Buffer.alloc(16), clientTime: Buffer.alloc(8), + count: 0, vlActivationInterval: 0, vlRenewalInterval: 0, + }; +} + +/** + * 验证 PID 长度 + * 检查: pidSize <= 128,最后一个 WCHAR 为 0,倒数第二个之前的字符都非零 + * + * 参考: src/kms.c 中的 checkPidLength() + */ +function checkPidLength(data: Buffer, pidSizeOffset: number): boolean { + const pidSize = data.readUInt32LE(pidSizeOffset); + if (pidSize > KMS_PID_MAX_BYTES) return false; + + const pidOffset = pidSizeOffset + 4; + const numChars = pidSize >>> 1; + if (numChars < 1) return false; + + // 最后一个 WCHAR 必须是空终止符 + if (data.readUInt16LE(pidOffset + (numChars - 1) * 2) !== 0) return false; + + // 倒数第二个之前的所有 WCHAR 必须非零 + for (let i = 0; i < numChars - 2; i++) { + if (data.readUInt16LE(pidOffset + i * 2) === 0) return false; + } + + return true; +} + +/** + * 从解密后的数据中解析可变大小的响应基本字段 + * 参考: src/kms.c 中的响应解析逻辑 + */ +function parseResponseBase( + data: Buffer, + verOff: number, + pidSize: number, + postEpid: Buffer, +): ParsedResponse { + const clampedPidSize = Math.min(pidSize, KMS_PID_MAX_BYTES); + + return { + minorVer: data.readUInt16LE(verOff), + majorVer: data.readUInt16LE(verOff + 2), + pidSize, + kmsPID: Buffer.from(data.subarray(verOff + 8, verOff + 8 + clampedPidSize)), + cmid: Buffer.from(postEpid.subarray(0, 16)), + clientTime: Buffer.from(postEpid.subarray(16, 24)), + count: postEpid.readUInt32LE(24), + vlActivationInterval: postEpid.readUInt32LE(28), + vlRenewalInterval: postEpid.readUInt32LE(32), + }; +} + +/** + * 计算并写入 V6 HMAC + * 从响应中读取 ClientTime 来派生基于时间槽的密钥, + * 然后将 16 字节 HMAC 写入加密区域的末尾 + * + * 参考: src/kms.c 中的 CreateV6Hmac() + */ +function createV6Hmac( + encryptStart: Buffer, + encryptSize: number, + tolerance: number, +): void { + // ClientTime 位于: 加密区域末尾 - V6_POST_EPID_SIZE + sizeof(CMID) + const ftOffset = encryptSize - V6_POST_EPID_SIZE + 16; + const clientTime = encryptStart.readBigUInt64LE(ftOffset); + + // 时间槽 ≈ 4.11 小时粒度 + const timeSlot = ( + (clientTime / TIME_C1) * TIME_C2 + TIME_C3 + BigInt(tolerance) * TIME_C1 + ) & UINT64_MASK; + + const timeSlotBuf = Buffer.alloc(8); + timeSlotBuf.writeBigUInt64LE(timeSlot); + const hash = sha256(timeSlotBuf); + + // HMAC 密钥 = SHA256(timeSlot) 的后 16 字节 + const hmacKey = hash.subarray(HALF_HASH_SIZE); + const hmacData = encryptStart.subarray(0, encryptSize - HMAC_SIZE); + const hmacResult = sha256Hmac(hmacKey, hmacData); + + // 将 HMAC 结果的后 16 字节写入 HMAC 字段 + hmacResult.copy(encryptStart, encryptSize - HMAC_SIZE, HALF_HASH_SIZE, HASH_SIZE); +} + +// ─── 创建 V4 请求 (参考 src/kms.c 中的 CreateRequestV4) ──────────────────── + +/** + * 构建 252 字节的 V4 KMS 请求: REQUEST(236) + AES-CMAC(16) + */ +export function createRequestV4(requestBase: Buffer): Buffer { + const request = Buffer.alloc(REQUEST_V4_SIZE); + requestBase.copy(request, 0, 0, REQUEST_SIZE); + + // 计算 AES-CMAC 并附加到请求末尾 + const mac = aesCmacV4(request, REQUEST_SIZE); + mac.copy(request, REQUEST_SIZE); + + return request; +} + +// ─── 创建 V5/V6 请求 (参考 src/kms.c 中的 CreateRequestV6) ───────────────── + +/** + * 构建 260 字节的 V5/V6 KMS 请求: Version(4) + IV(16) + 加密的请求(240) + */ +export function createRequestV6(requestBase: Buffer): Buffer { + const request = Buffer.alloc(REQUEST_V6_SIZE); + + // 外层版本号 = requestBase 版本号 + requestBase.copy(request, 0, 0, 4); + + // 随机 16 字节 IV + const iv = get16RandomBytes(); + iv.copy(request, 4); + + // 将基础请求复制到加密区域 + requestBase.copy(request, 20, 0, REQUEST_SIZE); + + // 根据主版本号选择密钥(V6 使用 AesKeyV6,V5 使用 AesKeyV5) + const majorVer = request.readUInt16LE(2); + const isV6 = majorVer > 5; + + const ctx = new AesCtx(); + ctx.initKey(isV6 ? AesKeyV6 : AesKeyV5, isV6, AES_KEY_BYTES); + + // 加密请求(236 字节 → 240 字节,含 PKCS#7 填充) + const plaintext = Buffer.from(request.subarray(20, 20 + REQUEST_SIZE)); + const { encrypted } = aesEncryptCbc(ctx, iv, plaintext); + encrypted.copy(request, 20); + + return request; +} + +// ─── 解密 V4 响应 (参考 src/kms.c 中的 DecryptResponseV4) ────────────────── + +/** + * 解密并验证 V4 KMS 响应 + * 线路格式: Version(4) + PIDSize(4) + PID(变长) + CMID(16) + + * ClientTime(8) + Count(4) + Intervals(8) + MAC(16) + */ +export function decryptResponseV4( + rawResponse: Buffer, + rawRequest: Buffer, +): { result: ResponseResult; response: ParsedResponse } { + const responseSize = rawResponse.length; + + // 确定变长 PID 大小 + const pidSize = rawResponse.readUInt32LE(4); + const clampedPidSize = Math.min(pidSize, KMS_PID_MAX_BYTES); + const copySize = V4_PRE_EPID_SIZE + clampedPidSize; + const messageSize = copySize + V4_POST_EPID_SIZE; + + // 提取 ePID 后面的区域(CMID、ClientTime、Count、Intervals) + const postEpid = Buffer.from(rawResponse.subarray(copySize, copySize + V4_POST_EPID_SIZE)); + const parsed = parseResponseBase(rawResponse, 0, pidSize, postEpid); + + // 计算 CMAC 并与接收到的 MAC 比较 + const computedMac = aesCmacV4(rawResponse, messageSize); + const receivedMac = rawResponse.subarray(messageSize, messageSize + 16); + + // 与请求字段进行验证 + const reqVersion = rawRequest.readUInt32LE(0); + const reqClientTime = rawRequest.subarray(84, 92); // REQUEST.ClientTime + const reqCMID = rawRequest.subarray(64, 80); // REQUEST.CMID + + const result: ResponseResult = { + mask: 0, + hashOK: computedMac.equals(receivedMac), + timeStampOK: parsed.clientTime.equals(reqClientTime), + clientMachineIDOK: parsed.cmid.equals(reqCMID), + versionOK: rawResponse.readUInt32LE(0) === reqVersion, + ivsOK: true, + decryptSuccess: true, + hmacSha256OK: true, + pidLengthOK: checkPidLength(rawResponse, 4), + rpcOK: true, + ivNotSuspicious: true, + effectiveResponseSize: responseSize, + correctResponseSize: SIZEOF_RESPONSE_V4 - KMS_PID_MAX_BYTES + pidSize, + }; + result.mask = computeMask(result); + + return { result, response: parsed }; +} + +// ─── 解密 V5/V6 响应 (参考 src/kms.c 中的 DecryptResponseV6) ─────────────── + +/** + * 解密并验证 V5/V6 KMS 响应 + * + * 线路格式(Version 之后的部分为加密): + * Version(4) + [IV(16) + ResponseBase(变长) + RandomXoredIVs(16) + Hash(32) + * + HwId(8, 仅V6) + XoredIVs(16, 仅V6) + HMAC(16, 仅V6) + * + PKCS填充] + */ +export function decryptResponseV6( + rawResponse: Buffer, + rawRequest: Buffer, +): { result: ResponseResult; response: ParsedResponse; hwid: Buffer } { + const totalSize = rawResponse.length; + const hwid = Buffer.alloc(8); + + // 创建可变工作副本(解密是就地操作) + const response = Buffer.from(rawResponse); + const request = Buffer.from(rawRequest); + + // 初始假设所有验证通过 + const result: ResponseResult = { + mask: 0, + hashOK: true, timeStampOK: true, clientMachineIDOK: true, + versionOK: true, ivsOK: true, decryptSuccess: true, + hmacSha256OK: true, pidLengthOK: true, rpcOK: true, ivNotSuspicious: true, + effectiveResponseSize: totalSize, correctResponseSize: 0, + }; + + // 从外层(未加密)版本字段确定协议版本 + const majorVer = response.readUInt16LE(2); + const isV6 = majorVer > 5; + + // 解密 4 字节 Version 之后的所有内容 + const ctx = new AesCtx(); + ctx.initKey(isV6 ? AesKeyV6 : AesKeyV5, isV6, AES_KEY_BYTES); + + const encryptedLen = totalSize - 4; + aesDecryptCbc(ctx, null, response.subarray(4), encryptedLen); + + // ── 验证 PKCS#7 填充 ── + const lastByte = response[totalSize - 1]; + if (lastByte === 0 || lastByte > AES_BLOCK_BYTES) { + result.decryptSuccess = false; + result.mask = computeMask(result); + return { result, response: emptyParsedResponse(), hwid }; + } + for (let i = totalSize - lastByte; i < totalSize - 1; i++) { + if (response[i] !== lastByte) { + result.decryptSuccess = false; + result.mask = computeMask(result); + return { result, response: emptyParsedResponse(), hwid }; + } + } + + // ── 解析解密后的响应 ── + const pidSize = response.readUInt32LE(V6_PRE_EPID_SIZE - 4); // ResponseBase.PIDSize + const clampedPidSize = Math.min(pidSize, KMS_PID_MAX_BYTES); + const copySize1 = V6_PRE_EPID_SIZE + clampedPidSize; + + const postEpidSize = isV6 ? V6_POST_EPID_SIZE : V5_POST_EPID_SIZE; + const postEpid = Buffer.from(response.subarray(copySize1, copySize1 + postEpidSize)); + + const parsed = parseResponseBase(response, V6_UNENCRYPTED_SIZE, pidSize, postEpid); + + // ── 解密请求的 IV + RequestBase + Pad(用于验证) ── + aesDecryptCbc(ctx, null, request.subarray(4), V6_DECRYPT_SIZE); + + // ── 版本一致性检查 ── + const outerReqVersion = request.readUInt32LE(0); + const innerReqVersion = request.readUInt32LE(20); // RequestBase.Version + const outerRespVersion = response.readUInt32LE(0); + const innerRespVersion = response.readUInt32LE(V6_UNENCRYPTED_SIZE); // ResponseBase.Version + + result.versionOK = + outerReqVersion === innerRespVersion && + outerReqVersion === outerRespVersion && + outerReqVersion === innerReqVersion; + + // ── PID、时间戳、CMID 检查 ── + result.pidLengthOK = checkPidLength(response, V6_PRE_EPID_SIZE - 4); + + const reqClientTime = request.subarray(20 + 84, 20 + 92); // RequestBase.ClientTime + result.timeStampOK = parsed.clientTime.equals(reqClientTime); + + const reqCMID = request.subarray(20 + 64, 20 + 80); // RequestBase.CMID + result.clientMachineIDOK = parsed.cmid.equals(reqCMID); + + // ── 哈希验证 (RandomXoredIVs / Hash) ── + const decryptedReqIV = Buffer.from(request.subarray(4, 20)); + const randomXoredIVs = postEpid.subarray(V4_POST_EPID_SIZE, V4_POST_EPID_SIZE + 16); + const receivedHash = postEpid.subarray(V4_POST_EPID_SIZE + 16, V4_POST_EPID_SIZE + 16 + HASH_SIZE); + + // 恢复原始随机字节: random = decryptedReqIV XOR RandomXoredIVs + const randomKey = Buffer.from(decryptedReqIV); + xorBlock(randomXoredIVs, randomKey); + const hashVerify = sha256(randomKey); + result.hashOK = hashVerify.equals(receivedHash); + + // ── 计算正确的响应大小(不含 PKCS 填充) ── + const sizeofStruct = isV6 ? SIZEOF_RESPONSE_V6 : SIZEOF_RESPONSE_V5; + result.correctResponseSize = sizeofStruct - KMS_PID_MAX_BYTES + pidSize; + + // ── 版本特定验证 ── + if (isV6) { + // 提取 HwId + postEpid.copy(hwid, 0, V5_POST_EPID_SIZE, V5_POST_EPID_SIZE + 8); + + // XoredIVs 必须等于解密后的请求 IV + const xoredIVs = postEpid.subarray(V5_POST_EPID_SIZE + 8, V5_POST_EPID_SIZE + 24); + result.ivsOK = decryptedReqIV.equals(xoredIVs); + + // 请求和响应的 IV 应该不同(相同说明是模拟器) + const responseIV = response.subarray(4, 20); + result.ivNotSuspicious = !decryptedReqIV.equals(responseIV); + + // 使用 ±1 时间槽容差验证 HMAC + const savedHmac = Buffer.from(postEpid.subarray(V5_POST_EPID_SIZE + 24, V5_POST_EPID_SIZE + 40)); + result.hmacSha256OK = false; + + const encryptStart = response.subarray(4); + const encryptSize = result.correctResponseSize - 4; + + for (let tolerance = -1; tolerance <= 1; tolerance++) { + createV6Hmac(encryptStart, encryptSize, tolerance); + const computed = encryptStart.subarray(encryptSize - HMAC_SIZE, encryptSize); + if (savedHmac.equals(computed)) { + result.hmacSha256OK = true; + break; + } + } + } else { + // V5: 请求和响应的 IV 必须匹配 + const responseIV = response.subarray(4, 20); + result.ivsOK = decryptedReqIV.equals(responseIV); + result.hmacSha256OK = true; // V5 没有 HMAC + } + + // 将 PKCS 填充加到 correctResponseSize + const encPartSize = result.correctResponseSize - 4; + result.correctResponseSize += (~encPartSize & 0xF) + 1; + + result.mask = computeMask(result); + return { result, response: parsed, hwid }; +} diff --git a/node-vlmcs/src/network.ts b/node-vlmcs/src/network.ts new file mode 100644 index 00000000..7d98799f --- /dev/null +++ b/node-vlmcs/src/network.ts @@ -0,0 +1,231 @@ +/** + * TCP 网络层 — 处理 TCP 连接、DNS 解析和数据收发 + * + * 参考原版源码: + * - src/network.c (connectToAddress, getSocketList 等连接管理) + * - src/network.h (网络接口定义) + */ + +import * as net from 'net'; +import * as dns from 'dns'; +import { parseAddress } from './helpers'; + +/** 连接超时时间(毫秒),与原版 SO_RCVTIMEO/SO_SNDTIMEO 设置一致 */ +const CONNECT_TIMEOUT_MS = 10_000; + +/** + * 带超时的 TCP 连接 + * 连接成功后暂停 socket,防止在附加数据监听器之前丢失数据 + */ +function connectWithTimeout(host: string, port: number, family?: number): Promise { + return new Promise((resolve, reject) => { + const sock = new net.Socket(); + let settled = false; + + const timer = setTimeout(() => { + if (!settled) { + settled = true; + sock.destroy(); + reject(new Error('Timed out')); + } + }, CONNECT_TIMEOUT_MS); + + sock.once('error', (err: Error) => { + if (!settled) { + settled = true; + clearTimeout(timer); + sock.destroy(); + reject(new Error(err.message)); + } + }); + + sock.connect({ host, port, family }, () => { + if (!settled) { + settled = true; + clearTimeout(timer); + // 暂停 socket,防止在附加数据监听器之前丢失数据 + sock.pause(); + resolve(sock); + } + }); + }); +} + +/** + * 连接到指定地址 + * 支持 "host:port"、"[ipv6]:port" 等格式 + * 会尝试 DNS 解析返回的所有地址(模拟原版 getaddrinfo 遍历行为) + * + * 参考: src/network.c 中的 connectToAddress() + * + * @param addr 地址字符串 + * @param addressFamily 0=自动, 4=仅IPv4, 6=仅IPv6 + * @param showHostName 是否在连接消息中显示主机名(仅影响输出格式) + */ +export async function connectToAddress( + addr: string, + addressFamily: number, + showHostName: boolean +): Promise { + const { host, port } = parseAddress(addr); + const portNum = parseInt(port, 10); + + if (isNaN(portNum) || portNum < 1 || portNum > 65535) { + throw new Error(`Invalid port: ${port}`); + } + + // 判断是否为 IP 字面量地址 + const isIPv4Literal = net.isIPv4(host); + const isIPv6Literal = net.isIPv6(host); + const isLiteral = isIPv4Literal || isIPv6Literal; + + let family: number | undefined; + + if (addressFamily === 4) { + family = 4; + } else if (addressFamily === 6) { + family = 6; + } + + // 地址族兼容性检查 + if (isIPv4Literal && addressFamily === 6) { + throw new Error(`IPv4 address ${host} cannot be used with IPv6-only mode`); + } + if (isIPv6Literal && addressFamily === 4) { + throw new Error(`IPv6 address ${host} cannot be used with IPv4-only mode`); + } + + // 构建要尝试的地址列表 + interface AddrEntry { address: string; family: number } + let targets: AddrEntry[]; + + if (isLiteral) { + // IP 字面量,直接使用 + targets = [{ address: host, family: isIPv4Literal ? 4 : 6 }]; + } else { + // 主机名,通过 DNS 解析(可能返回多个地址) + targets = await new Promise((resolve, reject) => { + dns.lookup(host, { family: family || 0, all: true }, (err, addresses) => { + if (err) { + reject(new Error(`${host}: ${err.message}`)); + } else if ((addresses as dns.LookupAddress[]).length === 0) { + reject(new Error(`${host}: No address associated with hostname`)); + } else { + resolve(addresses as dns.LookupAddress[]); + } + }); + }); + } + + // 按顺序尝试每个地址(模拟原版 getaddrinfo 遍历行为) + for (let i = 0; i < targets.length; i++) { + const connectHost = targets[i].address; + const addrFamily = targets[i].family; + + // 输出连接信息(匹配原版格式) + if (showHostName && !isLiteral) { + process.stdout.write(`Connecting to ${host} (${connectHost}) ... `); + } else { + process.stdout.write(`Connecting to ${connectHost}:${port} ... `); + } + + try { + const sock = await connectWithTimeout(connectHost, portNum, addrFamily); + process.stdout.write('successful\n'); + return sock; + } catch (err: any) { + process.stderr.write(`${connectHost}:${port}: ${err.message}\n`); + if (i === targets.length - 1) { + throw new Error('Could not connect to any KMS server'); + } + } + } + throw new Error('Could not connect to any KMS server'); +} + +/** + * 发送所有字节到 socket,等待写缓冲区排空 + * 参考: src/network.c 中的 _send() + */ +export function sendData(sock: net.Socket, data: Buffer): Promise { + return new Promise((resolve, reject) => { + const ok = sock.write(data, (err) => { + if (err) reject(err); + else if (ok) resolve(); + }); + if (!ok) { + sock.once('drain', () => resolve()); + } + }); +} + +/** + * 从 socket 精确接收 size 字节 + * 缓冲传入数据直到收集到请求的数量,多余的字节会被推回 socket + * + * 参考: src/network.c 中的 _recv() + */ +export function recvData(sock: net.Socket, size: number): Promise { + return new Promise((resolve, reject) => { + if (size === 0) { + resolve(Buffer.alloc(0)); + return; + } + + const chunks: Buffer[] = []; + let received = 0; + + const cleanup = () => { + sock.removeListener('data', onData); + sock.removeListener('error', onError); + sock.removeListener('close', onClose); + sock.removeListener('end', onEnd); + }; + + const onData = (chunk: Buffer) => { + chunks.push(chunk); + received += chunk.length; + if (received >= size) { + cleanup(); + sock.pause(); // 暂停接收,防止数据丢失 + const full = Buffer.concat(chunks); + const result = full.subarray(0, size); + // 将多余的字节推回 socket 的读取缓冲区 + if (full.length > size) { + sock.unshift(full.subarray(size)); + } + resolve(Buffer.from(result)); + } + }; + + const onError = (err: Error) => { + cleanup(); + reject(err); + }; + + const onClose = () => { + cleanup(); + reject(new Error(`Socket closed after receiving ${received} of ${size} bytes`)); + }; + + const onEnd = () => { + cleanup(); + reject(new Error(`Socket ended after receiving ${received} of ${size} bytes`)); + }; + + sock.on('data', onData); + sock.once('error', onError); + sock.once('close', onClose); + sock.once('end', onEnd); + + // 恢复 socket 以开始接收数据 + sock.resume(); + }); +} + +/** + * 检查 socket 是否已断开连接 + */ +export function isDisconnected(sock: net.Socket): boolean { + return sock.destroyed || !sock.writable; +} diff --git a/node-vlmcs/src/output.ts b/node-vlmcs/src/output.ts new file mode 100644 index 00000000..33ed5ad9 --- /dev/null +++ b/node-vlmcs/src/output.ts @@ -0,0 +1,148 @@ +/** + * 详细输出格式化模块 — 处理请求/响应的详细信息显示 + * + * 参考原版源码: + * - src/output.c (logRequest, logResponse, uuid2StringLE 等输出函数) + * - src/output.h (输出接口定义) + */ + +import { KmsData } from './data'; +import { ParsedResponse } from './kms'; +import { ucs2ToUtf8, fileTimeToUnixTime, VERSION } from './types'; + +// ─── 许可证状态映射 (参考 src/output.c 中的 StatusText) ───────────────────── + +const LICENSE_STATUS: Record = { + 0: 'Unlicensed', + 1: 'Licensed (Activated)', + 2: 'OOB grace', + 3: 'OOT grace', + 4: 'NonGenuineGrace', + 5: 'Notification', + 6: 'extended grace', +}; + +/** + * 将小端序 GUID Buffer 格式化为标准字符串 + * 格式: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + * + * 参考: src/output.c 中的 uuid2StringLE() + */ +export function uuid2StringLE(buf: Buffer, offset = 0): string { + const d1 = buf.readUInt32LE(offset); + const d2 = buf.readUInt16LE(offset + 4); + const d3 = buf.readUInt16LE(offset + 6); + const d4 = buf.subarray(offset + 8, offset + 16); + return ( + d1.toString(16).padStart(8, '0') + '-' + + d2.toString(16).padStart(4, '0') + '-' + + d3.toString(16).padStart(4, '0') + '-' + + d4[0].toString(16).padStart(2, '0') + d4[1].toString(16).padStart(2, '0') + '-' + + Array.from(d4.subarray(2)).map(b => b.toString(16).padStart(2, '0')).join('') + ); +} + +/** 在产品列表中按 GUID 查找名称 */ +function findNameByGuid(guidStr: string, list: Array<{ guid: string; name: string }>): string { + const found = list.find(item => item.guid === guidStr); + return found ? found.name : 'Unknown'; +} + +/** + * 将 FILETIME 格式化为 UTC 时间字符串 + * 格式: "YYYY-MM-DD HH:MM:SS" + */ +function formatTimestamp(ft: Buffer): string { + const unixTime = fileTimeToUnixTime(ft); + const d = new Date(unixTime * 1000); + const pad = (n: number) => n.toString().padStart(2, '0'); + return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`; +} + +/** + * 输出 KMS 请求的详细信息(-v 模式) + * + * 参考: src/output.c 中的 logRequest() + */ +export function logRequestVerbose(requestBuf: Buffer): void { + const majorVer = requestBuf.readUInt16LE(2); + const minorVer = requestBuf.readUInt16LE(0); + const vmInfo = requestBuf.readUInt32LE(4); + const licenseStatus = requestBuf.readUInt32LE(8); + const bindingExpiration = requestBuf.readUInt32LE(12); + const appIdStr = uuid2StringLE(requestBuf, 16); + const skuIdStr = uuid2StringLE(requestBuf, 32); + const kmsIdStr = uuid2StringLE(requestBuf, 48); + const cmidStr = uuid2StringLE(requestBuf, 64); + const nPolicy = requestBuf.readUInt32LE(80); + const clientTime = requestBuf.subarray(84, 92); + const cmidPrevStr = uuid2StringLE(requestBuf, 92); + const wsName = ucs2ToUtf8(requestBuf.subarray(108, 236), 64); + + // 查找产品名称 + const appName = findNameByGuid(appIdStr, KmsData.appItems); + const skuName = findNameByGuid(skuIdStr, KmsData.skuItems); + const kmsName = findNameByGuid(kmsIdStr, KmsData.kmsItems); + const statusStr = LICENSE_STATUS[licenseStatus] || licenseStatus.toString(); + + process.stdout.write( + `\nRequest Parameters\n==================\n\n` + + `Protocol version : ${majorVer}.${minorVer}\n` + + `Client is a virtual machine : ${vmInfo ? 'Yes' : 'No'}\n` + + `Licensing status : ${licenseStatus} (${statusStr})\n` + + `Remaining time (0 = forever) : ${bindingExpiration} minutes\n` + + `Application ID : ${appIdStr} (${appName})\n` + + `SKU ID (aka Activation ID) : ${skuIdStr} (${skuName})\n` + + `KMS ID (aka KMS counted ID) : ${kmsIdStr} (${kmsName})\n` + + `Client machine ID : ${cmidStr}\n` + + `Previous client machine ID : ${cmidPrevStr}\n` + + `Client request timestamp (UTC) : ${formatTimestamp(clientTime)}\n` + + `Workstation name : ${wsName}\n` + + `N count policy (minimum clients): ${nPolicy}\n` + ); +} + +/** + * 输出 KMS 响应的详细信息(-v 模式) + * + * 参考: src/output.c 中的 logResponse() + */ +export function logResponseVerbose(ePID: string, hwid: Buffer, response: ParsedResponse, effectiveResponseSize: number): void { + const cmidStr = uuid2StringLE(response.cmid); + const timeStr = formatTimestamp(response.clientTime); + + process.stdout.write( + `\n\nResponse from KMS server\n========================\n\n` + + `Size of KMS Response : ${effectiveResponseSize} (0x${effectiveResponseSize.toString(16)})\n` + + `KMS ePID : ${ePID}\n` + ); + + // V6 协议才有 HwId + if (response.majorVer > 5) { + const hwidHex = Array.from(hwid.subarray(0, 8)).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(''); + process.stdout.write(`KMS HwId : ${hwidHex}\n`); + } + + process.stdout.write( + `Client machine ID : ${cmidStr}\n` + + `Client request timestamp (UTC) : ${timeStr}\n` + + `KMS host current active clients : ${response.count}\n` + + `Renewal interval policy : ${response.vlRenewalInterval}\n` + + `Activation interval policy : ${response.vlActivationInterval}\n\n` + ); +} + +/** 输出运行平台信息 */ +export function printPlatform(): void { + process.stdout.write(`Intended platform: Node.js ${process.version}\n`); +} + +/** 输出通用编译标志 */ +export function printCommonFlags(): void { + process.stdout.write('Common flags:\n'); +} + +/** 输出 vlmcs 客户端特有标志 */ +export function printClientFlags(): void { + process.stdout.write('vlmcs flags: DNS_PARSER=OS\n'); +} diff --git a/node-vlmcs/src/rpc.ts b/node-vlmcs/src/rpc.ts new file mode 100644 index 00000000..ed7ac8e1 --- /dev/null +++ b/node-vlmcs/src/rpc.ts @@ -0,0 +1,571 @@ +/** + * DCE-RPC 协议层 — 实现 KMS 激活所需的 DCE-RPC 客户端协议 + * + * 参考原版源码: + * - src/rpc.c (rpcBindOrAlterClientContext, rpcSendRequest 等) + * - src/rpc.h (RPC_HEADER, RPC_BIND_REQUEST, RPC_RESPONSE 等结构定义) + */ + +import * as net from 'net'; +import { sendData, recvData } from './network'; + +// ─── 数据包类型 (参考 src/rpc.h 中的 RPC_PT_* 常量) ──────────────────────── + +const RPC_PT_REQUEST = 0; // 请求 +const RPC_PT_RESPONSE = 2; // 响应 +const RPC_PT_FAULT = 3; // 错误 +const RPC_PT_BIND_REQ = 11; // 绑定请求 +const RPC_PT_BIND_ACK = 12; // 绑定确认 +const RPC_PT_ALTERCONTEXT_REQ = 14; // 修改上下文请求 +const RPC_PT_ALTERCONTEXT_ACK = 15; // 修改上下文确认 + +// ─── 数据包标志 ───────────────────────────────────────────────────────────── + +const RPC_PF_FIRST = 1; // 分片的第一个包 +const RPC_PF_LAST = 2; // 分片的最后一个包 +const RPC_PF_MULTIPLEX = 16; // 多路复用 + +// ─── 绑定确认结果码 (参考 src/rpc.h) ─────────────────────────────────────── + +const RPC_BIND_ACCEPT = 0; // 接受 +const RPC_BIND_NACK = 2; // 拒绝 +const RPC_BIND_ACK = 3; // 确认(用于 BTFN 绑定时间特性协商) + +// ─── RPC 头部大小 ─────────────────────────────────────────────────────────── + +/** RPC 头部固定大小: 16 字节 */ +const RPC_HEADER_SIZE = 16; + +// ─── GUID 常量 (原始线路字节,无字节序转换) ───────────────────────────────── + +/** NDR32 传输语法 GUID */ +const TransferSyntaxNDR32 = Buffer.from([ + 0x04, 0x5D, 0x88, 0x8A, 0xEB, 0x1C, 0xC9, 0x11, + 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60, +]); + +/** KMS 接口 UUID */ +const InterfaceUuid = Buffer.from([ + 0x75, 0x21, 0xC8, 0x51, 0x4E, 0x84, 0x50, 0x47, + 0xB0, 0xD8, 0xEC, 0x25, 0x55, 0x55, 0xBC, 0x06, +]); + +/** NDR64 传输语法 GUID */ +const TransferSyntaxNDR64 = Buffer.from([ + 0x33, 0x05, 0x71, 0x71, 0xBA, 0xBE, 0x37, 0x49, + 0x83, 0x19, 0xB5, 0xDB, 0xEF, 0x9C, 0xCC, 0x36, +]); + +/** 绑定时间特性协商 GUID (BTFN) */ +const BindTimeFeatureNegotiation = Buffer.from([ + 0x2C, 0x1C, 0xB7, 0x6C, 0x12, 0x98, 0x40, 0x45, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]); + +// ─── 导出接口 ─────────────────────────────────────────────────────────────── + +/** RPC 已协商的能力标志 */ +export interface RpcFlags { + hasNDR32: boolean; // 是否支持 NDR32 传输语法 + hasNDR64: boolean; // 是否支持 NDR64 传输语法 + hasBTFN: boolean; // 是否支持绑定时间特性协商 +} + +/** RPC 诊断信息 */ +export interface RpcDiag { + hasRpcDiag: boolean; // 是否有 RPC 诊断信息 + hasBTFN: boolean; // 服务器是否支持 BTFN + hasNDR64: boolean; // 服务器是否支持 NDR64 +} + +// ─── 状态 ─────────────────────────────────────────────────────────────────── + +/** 调用 ID,从 2 开始(与微软实现一致) */ +let callId = 2; + +// ─── 工具函数: 写入 RPC 头部 ─────────────────────────────────────────────── + +/** + * 写入 RPC 头部 (16 字节) + * 参考: src/rpc.h 中的 RPC_HEADER 结构 + */ +function writeRpcHeader( + buf: Buffer, + packetType: number, + packetFlags: number, + fragLength: number, + currentCallId: number +): void { + buf.writeUInt8(5, 0); // VersionMajor = 5 + buf.writeUInt8(0, 1); // VersionMinor = 0 + buf.writeUInt8(packetType, 2); // PacketType + buf.writeUInt8(packetFlags, 3); // PacketFlags + buf.writeUInt32LE(0x00000010, 4); // DataRepresentation: LE, ASCII, IEEE + buf.writeUInt16LE(fragLength, 8); // FragLength + buf.writeUInt16LE(0, 10); // AuthLength + buf.writeUInt32LE(currentCallId, 12); // CallId +} + +// ─── 工具函数: 解析 RPC 头部 ─────────────────────────────────────────────── + +interface RpcHeader { + versionMajor: number; + versionMinor: number; + packetType: number; + packetFlags: number; + dataRepresentation: number; + fragLength: number; + authLength: number; + callId: number; +} + +/** 从 Buffer 解析 RPC 头部 */ +function parseRpcHeader(buf: Buffer): RpcHeader { + return { + versionMajor: buf.readUInt8(0), + versionMinor: buf.readUInt8(1), + packetType: buf.readUInt8(2), + packetFlags: buf.readUInt8(3), + dataRepresentation: buf.readUInt32LE(4), + fragLength: buf.readUInt16LE(8), + authLength: buf.readUInt16LE(10), + callId: buf.readUInt32LE(12), + }; +} + +/** 获取数据包类型的可读名称(用于调试输出) */ +function packetTypeName(type: number): string { + switch (type) { + case RPC_PT_REQUEST: return 'Request'; + case RPC_PT_RESPONSE: return 'Response'; + case RPC_PT_FAULT: return 'Fault'; + case RPC_PT_BIND_REQ: return 'Bind'; + case RPC_PT_BIND_ACK: return 'Bind Ack'; + case RPC_PT_ALTERCONTEXT_REQ: return 'Alter Context'; + case RPC_PT_ALTERCONTEXT_ACK: return 'Alter Context Ack'; + default: return `Unknown(${type})`; + } +} + +// ─── 构建 Bind/AlterContext 数据包 ────────────────────────────────────────── + +interface CtxItem { + transferSyntax: Buffer; // 传输语法 GUID + syntaxVersion: number; // 语法版本 +} + +/** + * 构建 Bind 或 AlterContext 数据包 + * + * 参考: src/rpc.c 中的 rpcBindOrAlterClientContext() 的数据包构建部分 + * 每个上下文项: ContextId(2) + NumTransItems(2) + InterfaceUUID(16) + + * InterfaceVerMajor(2) + InterfaceVerMinor(2) + TransferSyntax(16) + SyntaxVersion(4) = 44 字节 + */ +function buildBindPacket( + packetType: number, + packetFlags: number, + currentCallId: number, + ctxItems: CtxItem[] +): Buffer { + const ctxItemSize = 44; + // 绑定体: MaxXmitFrag(2) + MaxRecvFrag(2) + AssocGroup(4) + NumCtxItems(4) + 上下文项数组 + const bindBodySize = 2 + 2 + 4 + 4 + ctxItems.length * ctxItemSize; + const totalSize = RPC_HEADER_SIZE + bindBodySize; + const buf = Buffer.alloc(totalSize); + + writeRpcHeader(buf, packetType, packetFlags, totalSize, currentCallId); + + let offset = RPC_HEADER_SIZE; + buf.writeUInt16LE(5840, offset); // MaxXmitFrag + buf.writeUInt16LE(5840, offset + 2); // MaxRecvFrag + buf.writeUInt32LE(0, offset + 4); // AssocGroup + buf.writeUInt32LE(ctxItems.length, offset + 8); // NumCtxItems + offset += 12; + + for (let i = 0; i < ctxItems.length; i++) { + buf.writeUInt16LE(i, offset); // ContextId + buf.writeUInt16LE(1, offset + 2); // NumTransItems + InterfaceUuid.copy(buf, offset + 4); // InterfaceUUID + buf.writeUInt16LE(1, offset + 20); // InterfaceVerMajor + buf.writeUInt16LE(0, offset + 22); // InterfaceVerMinor + ctxItems[i].transferSyntax.copy(buf, offset + 24); // TransferSyntax + buf.writeUInt32LE(ctxItems[i].syntaxVersion, offset + 40); // SyntaxVersion + offset += ctxItemSize; + } + + return buf; +} + +// ─── 解析绑定响应 ─────────────────────────────────────────────────────────── + +interface BindResult { + ackResult: number; // 确认结果码 + ackReason: number; // 确认原因 + transferSyntax: Buffer; // 传输语法 + syntaxVersion: number; // 语法版本 +} + +/** + * 解析 Bind/AlterContext 响应体 + * 参考: src/rpc.h 中的 RPC_BIND_RESPONSE 结构 + */ +function parseBindResponse( + body: Buffer, + verbose: boolean +): { results: BindResult[]; maxRecvFrag: number } { + let offset = 0; + + const maxXmitFrag = body.readUInt16LE(offset); + const maxRecvFrag = body.readUInt16LE(offset + 2); + const assocGroup = body.readUInt32LE(offset + 4); + offset += 8; + + const secondaryAddressLength = body.readUInt16LE(offset); + offset += 2; + + if (verbose) { + console.log(` Max Xmit/Recv Frag: ${maxXmitFrag}/${maxRecvFrag}, AssocGroup: 0x${assocGroup.toString(16).padStart(8, '0')}`); + console.log(` Secondary Address Length: ${secondaryAddressLength}`); + } + + // 跳过二级地址和对齐填充到 4 字节边界 + offset += secondaryAddressLength; + const totalOff = offset; + offset += ((4 - (totalOff % 4)) % 4); + + const numResults = body.readUInt32LE(offset); + offset += 4; + + if (verbose) { + console.log(` Num Results: ${numResults}`); + } + + const results: BindResult[] = []; + for (let i = 0; i < numResults; i++) { + const ackResult = body.readUInt16LE(offset); + const ackReason = body.readUInt16LE(offset + 2); + const transferSyntax = Buffer.from(body.subarray(offset + 4, offset + 20)); + const syntaxVersion = body.readUInt32LE(offset + 20); + offset += 24; + + if (verbose) { + const resultStr = ackResult === RPC_BIND_ACCEPT ? 'Accept' : + ackResult === RPC_BIND_NACK ? 'Nack' : + ackResult === RPC_BIND_ACK ? 'Ack' : `Unknown(0x${ackResult.toString(16)})`; + console.log(` Result[${i}]: ${resultStr} (reason: 0x${ackReason.toString(16)})`); + } + + results.push({ ackResult, ackReason, transferSyntax, syntaxVersion }); + } + + return { results, maxRecvFrag }; +} + +// ─── 绑定/修改上下文 ─────────────────────────────────────────────────────── + +/** + * 执行单次 Bind 或 AlterContext 交换并处理响应 + * + * 参考: src/rpc.c 中的 rpcBindOrAlterClientContext() + */ +async function rpcBindOrAlterContext( + sock: net.Socket, + packetType: number, + verbose: boolean, + useClientRpcNDR64: boolean, + useClientRpcBTFN: boolean, + useMultiplexedRpc: boolean, + rpcFlags: RpcFlags, + rpcDiag: RpcDiag, +): Promise { + // AlterContext 只发送 NDR32;Bind 发送 NDR32 + 可选 NDR64 + BTFN + const isBind = packetType === RPC_PT_BIND_REQ; + const ctxItems: CtxItem[] = [ + { transferSyntax: TransferSyntaxNDR32, syntaxVersion: 2 }, + ]; + + let ctxNDR64 = -1; + let ctxBTFN = -1; + + if (isBind && useClientRpcNDR64) { + ctxNDR64 = ctxItems.length; + ctxItems.push({ transferSyntax: TransferSyntaxNDR64, syntaxVersion: 1 }); + } + + if (isBind && useClientRpcBTFN) { + ctxBTFN = ctxItems.length; + ctxItems.push({ transferSyntax: BindTimeFeatureNegotiation, syntaxVersion: 1 }); + } + + const packetFlags = RPC_PF_FIRST | RPC_PF_LAST | (useMultiplexedRpc ? RPC_PF_MULTIPLEX : 0); + const currentCallId = callId++; + const bindPacket = buildBindPacket(packetType, packetFlags, currentCallId, ctxItems); + + await sendData(sock, bindPacket); + + // 接收响应头 + const headerBuf = await recvData(sock, RPC_HEADER_SIZE); + const header = parseRpcHeader(headerBuf); + + if (verbose) { + console.log(`Received RPC ${packetTypeName(header.packetType)} (FragLength=${header.fragLength}, CallId=${header.callId})`); + } + + const expectedAckType = isBind ? RPC_PT_BIND_ACK : RPC_PT_ALTERCONTEXT_ACK; + + if (header.packetType === RPC_PT_FAULT || header.packetType !== expectedAckType) { + const bodySize = header.fragLength - RPC_HEADER_SIZE; + if (bodySize > 0) await recvData(sock, bodySize); + return 1; + } + + // 读取响应体 + const bodySize = header.fragLength - RPC_HEADER_SIZE; + const body = await recvData(sock, bodySize); + const { results } = parseBindResponse(body, verbose); + + // 处理每个上下文项的结果 + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + if (i === ctxBTFN) { + // BTFN 上下文:结果码为 RPC_BIND_ACK(3) 表示支持 + if (result.ackResult === RPC_BIND_ACK) { + rpcFlags.hasBTFN = true; + rpcDiag.hasBTFN = true; + if (verbose) process.stdout.write('... BTFN '); + } + continue; + } + + if (result.ackResult === RPC_BIND_NACK) { + continue; // 被拒绝,跳过 + } + + if (result.ackResult === RPC_BIND_ACCEPT) { + if (i === ctxNDR64) { + rpcFlags.hasNDR64 = true; + rpcDiag.hasNDR64 = true; + if (verbose) process.stdout.write('... NDR64 '); + } else if (i === 0) { + rpcFlags.hasNDR32 = true; + if (verbose) process.stdout.write('... NDR32 '); + } + } + } + + return 0; +} + +/** + * 执行 RPC 绑定协商 + * + * 始终请求 NDR32 传输语法。根据参数可选请求 NDR64 和绑定时间特性协商 (BTFN)。 + * 如果 NDR32 未被接受,发送 AlterContext 单独协商 NDR32。 + * + * 参考: src/rpc.c 中的 rpcBindClient() + */ +export async function rpcBindClient( + sock: net.Socket, + verbose: boolean, + useClientRpcNDR64: boolean, + useClientRpcBTFN: boolean, + useMultiplexedRpc: boolean +): Promise<{ status: number; rpcDiag: RpcDiag; rpcFlags: RpcFlags }> { + const rpcFlags: RpcFlags = { hasNDR32: false, hasNDR64: false, hasBTFN: false }; + const rpcDiag: RpcDiag = { hasRpcDiag: false, hasBTFN: false, hasNDR64: false }; + + // 第一步: 发送 Bind 请求 + let status = await rpcBindOrAlterContext( + sock, RPC_PT_BIND_REQ, verbose, + useClientRpcNDR64, useClientRpcBTFN, useMultiplexedRpc, + rpcFlags, rpcDiag + ); + + if (status) return { status, rpcDiag, rpcFlags }; + + // 第二步: 如果 NDR32 未被接受,发送 AlterContext 单独协商 + if (!rpcFlags.hasNDR32) { + status = await rpcBindOrAlterContext( + sock, RPC_PT_ALTERCONTEXT_REQ, verbose, + false, false, useMultiplexedRpc, + rpcFlags, rpcDiag + ); + if (status) return { status, rpcDiag, rpcFlags }; + } + + if (!rpcFlags.hasNDR32 && !rpcFlags.hasNDR64) { + process.stderr.write('\nFatal: Could neither negotiate NDR32 nor NDR64 with the RPC server\n'); + return { status: 1, rpcDiag, rpcFlags }; + } + + rpcDiag.hasRpcDiag = true; + + if (verbose) { + process.stdout.write('\n'); + } + + return { status: 0, rpcDiag, rpcFlags }; +} + +// ─── 发送 RPC 请求 (参考 src/rpc.c 中的 rpcSendRequest) ──────────────────── + +/** + * 通过已建立的 RPC 连接发送 KMS 请求并接收响应 + * + * 当服务器支持 NDR64 且不是第一个包时使用 NDR64 编码,否则回退到 NDR32。 + * + * 参考: src/rpc.c 中的 rpcSendRequest() + */ +export async function rpcSendRequest( + sock: net.Socket, + kmsRequest: Buffer, + rpcFlags: RpcFlags, + useClientRpcNDR64: boolean, + firstPacketSent: boolean +): Promise<{ status: number; kmsResponse: Buffer | null; responseSize: number }> { + const requestSize = kmsRequest.length; + const useNDR64 = useClientRpcNDR64 && rpcFlags.hasNDR64 && firstPacketSent; + + let requestBody: Buffer; + if (useNDR64) { + // NDR64 格式: AllocHint(4) + ContextId(2) + Opnum(2) + DataLength(8) + DataSizeIs(8) + Data + const bodySize = 4 + 2 + 2 + 8 + 8 + requestSize; + requestBody = Buffer.alloc(bodySize); + let offset = 0; + requestBody.writeUInt32LE(requestSize + 16, offset); // AllocHint + offset += 4; + requestBody.writeUInt16LE(1, offset); // ContextId (NDR64 = 1) + offset += 2; + requestBody.writeUInt16LE(0, offset); // Opnum + offset += 2; + requestBody.writeBigUInt64LE(BigInt(requestSize), offset); // Ndr64.DataLength + offset += 8; + requestBody.writeBigUInt64LE(BigInt(requestSize), offset); // Ndr64.DataSizeIs + offset += 8; + kmsRequest.copy(requestBody, offset); + } else { + // NDR32 格式: AllocHint(4) + ContextId(2) + Opnum(2) + DataLength(4) + DataSizeIs(4) + Data + const bodySize = 4 + 2 + 2 + 4 + 4 + requestSize; + requestBody = Buffer.alloc(bodySize); + let offset = 0; + requestBody.writeUInt32LE(requestSize + 8, offset); // AllocHint + offset += 4; + requestBody.writeUInt16LE(0, offset); // ContextId (NDR32 = 0) + offset += 2; + requestBody.writeUInt16LE(0, offset); // Opnum + offset += 2; + requestBody.writeUInt32LE(requestSize, offset); // Ndr.DataLength + offset += 4; + requestBody.writeUInt32LE(requestSize, offset); // Ndr.DataSizeIs + offset += 4; + kmsRequest.copy(requestBody, offset); + } + + const totalSize = RPC_HEADER_SIZE + requestBody.length; + const packet = Buffer.alloc(totalSize); + const currentCallId = callId++; + writeRpcHeader( + packet, + RPC_PT_REQUEST, + RPC_PF_FIRST | RPC_PF_LAST, + totalSize, + currentCallId + ); + requestBody.copy(packet, RPC_HEADER_SIZE); + + await sendData(sock, packet); + + // ── 接收响应 ────────────────────────────────────────────────────────── + + const headerBuf = await recvData(sock, RPC_HEADER_SIZE); + const header = parseRpcHeader(headerBuf); + + // 处理 Fault 响应 + if (header.packetType === RPC_PT_FAULT) { + const bodySize = header.fragLength - RPC_HEADER_SIZE; + if (bodySize >= 4) { + const faultBody = await recvData(sock, bodySize); + const faultStatus = faultBody.readUInt32LE(0); + return { status: faultStatus || 1, kmsResponse: null, responseSize: 0 }; + } + if (bodySize > 0) await recvData(sock, bodySize); + return { status: 1, kmsResponse: null, responseSize: 0 }; + } + + // 验证响应类型 + if (header.packetType !== RPC_PT_RESPONSE) { + const bodySize = header.fragLength - RPC_HEADER_SIZE; + if (bodySize > 0) await recvData(sock, bodySize); + return { status: 1, kmsResponse: null, responseSize: 0 }; + } + + // 读取完整响应体 + const bodySize = header.fragLength - RPC_HEADER_SIZE; + const body = await recvData(sock, bodySize); + + // 解析 NDR 响应头 + // 参考: src/rpc.h 中的 RPC_RESPONSE / RPC_RESPONSE64 结构 + let offset = 0; + const allocHint = body.readUInt32LE(offset); // AllocHint + offset += 4; + const contextId = body.readUInt16LE(offset); // ContextId + offset += 2; + const cancelCount = body.readUInt8(offset); // CancelCount + offset += 1; + offset += 1; // Pad1 + + const responseUsesNDR64 = contextId === 1; + + let dataLength: number; + let dataSizeMax: bigint | number; + let dataSizeIs: bigint | number; + + if (responseUsesNDR64) { + // NDR64 响应: DataLength(8) + DataSizeMax(8) + DataSizeIs(8) + dataLength = Number(body.readBigUInt64LE(offset)); + offset += 8; + dataSizeMax = body.readBigUInt64LE(offset); + offset += 8; + dataSizeIs = body.readBigUInt64LE(offset); + offset += 8; + } else { + // NDR32 响应: DataLength(4) + DataSizeMax(4) + DataSizeIs(4) + dataLength = body.readUInt32LE(offset); + offset += 4; + dataSizeMax = body.readUInt32LE(offset); + offset += 4; + dataSizeIs = body.readUInt32LE(offset); + offset += 4; + } + + // 如果 dataSizeMax 为 0,说明 RPC 调用返回了错误状态 + if (dataSizeMax === 0 || dataSizeMax === 0n) { + const errorStatus = Number(dataSizeIs); + return { status: errorStatus || 1, kmsResponse: null, responseSize: 0 }; + } + + if (dataLength <= 0 || offset + dataLength > body.length) { + return { status: 1, kmsResponse: null, responseSize: 0 }; + } + + // 提取 KMS 响应数据 + const kmsResponse = Buffer.from(body.subarray(offset, offset + dataLength)); + offset += dataLength; + + // 4 字节对齐填充 + const padBytes = (4 - (dataLength % 4)) % 4; + offset += padBytes; + + // 读取 ReturnCode (HRESULT) + let returnCode = 0; + if (offset + 4 <= body.length) { + returnCode = body.readUInt32LE(offset); + } + + if (returnCode !== 0) { + return { status: returnCode, kmsResponse, responseSize: dataLength }; + } + + return { status: 0, kmsResponse, responseSize: dataLength }; +} diff --git a/node-vlmcs/src/types.ts b/node-vlmcs/src/types.ts new file mode 100644 index 00000000..f06457ec --- /dev/null +++ b/node-vlmcs/src/types.ts @@ -0,0 +1,150 @@ +/** + * 基础类型定义 — GUID、FILETIME、UCS-2 编码转换 + * + * 参考原版源码: + * - src/types.h (GUID、FILETIME 等基本类型定义) + * - src/helpers.c (GUID 字符串解析、时间转换) + * - src/output.c (GUID 格式化输出) + */ + +// ─── GUID 结构 ────────────────────────────────────────────────────────────── +// 与 C 代码中的 GUID 结构对应: Data1(DWORD) + Data2(WORD) + Data3(WORD) + Data4[8](BYTE) +// 内存中以小端序 (little-endian) 存储,与网络传输格式一致 + +/** GUID 结构体,对应 src/types.h 中的 GUID 定义 */ +export interface GUID { + data1: number; // uint32,小端序 + data2: number; // uint16,小端序 + data3: number; // uint16,小端序 + data4: Buffer; // 8 字节,直接存储 +} + +/** PID 缓冲区大小(WCHAR 数量),对应 src/types.h 中的 PID_BUFFER_SIZE */ +export const PID_BUFFER_SIZE = 64; +/** 工作站名缓冲区大小(WCHAR 数量) */ +export const WORKSTATION_NAME_BUFFER = 64; + +// ─── FILETIME 常量 ────────────────────────────────────────────────────────── +// Windows FILETIME: 从 1601-01-01 起的 100 纳秒间隔数(64 位无符号整数) +// Unix 时间戳: 从 1970-01-01 起的秒数 + +/** FILETIME 纪元 (1601-01-01) 与 Unix 纪元 (1970-01-01) 的差值,单位为 100 纳秒 */ +const FILETIME_UNIX_EPOCH_DIFF = 116444736000000000n; +/** 每毫秒的 100 纳秒数 */ +const HUNDRED_NS_PER_MS = 10000n; + +// ─── GUID 序列化 ──────────────────────────────────────────────────────────── + +/** 将 GUID 结构序列化为 16 字节小端序 Buffer,用于网络传输 */ +export function guidToBuffer(guid: GUID): Buffer { + const buf = Buffer.alloc(16); + buf.writeUInt32LE(guid.data1, 0); + buf.writeUInt16LE(guid.data2, 4); + buf.writeUInt16LE(guid.data3, 6); + guid.data4.copy(buf, 8, 0, 8); + return buf; +} + +/** + * 解析 GUID 字符串为小端序结构 + * 格式: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + * Data1/Data2/Data3 作为十六进制整数解析(内存中小端存储) + * Data4 直接从十六进制字符串取字节(不做字节序转换) + * + * 参考: src/helpers.c 中的 string2UuidLE() + */ +export function stringToGuidLE(str: string): GUID | null { + const re = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i; + const m = str.match(re); + if (!m) return null; + + const data1 = parseInt(m[1], 16) >>> 0; + const data2 = parseInt(m[2], 16); + const data3 = parseInt(m[3], 16); + const data4 = Buffer.from(m[4] + m[5], 'hex'); + + return { data1, data2, data3, data4 }; +} + +// ─── FILETIME 转换 ────────────────────────────────────────────────────────── + +/** + * 获取当前时间的 FILETIME 表示(8 字节小端序 Buffer) + * 参考: src/helpers.c 中的 getFileTime() + */ +export function unixTimeToFileTime(): Buffer { + const nowMs = BigInt(Date.now()); + const ft = nowMs * HUNDRED_NS_PER_MS + FILETIME_UNIX_EPOCH_DIFF; + const buf = Buffer.alloc(8); + buf.writeBigUInt64LE(ft); + return buf; +} + +/** + * 将 8 字节小端序 FILETIME Buffer 转换为 Unix 时间戳(秒) + * 参考: src/output.c 中的时间格式化逻辑 + */ +export function fileTimeToUnixTime(ft: Buffer): number { + const val = ft.readBigUInt64LE(0); + const unixMs = (val - FILETIME_UNIX_EPOCH_DIFF) / HUNDRED_NS_PER_MS; + return Number(unixMs) / 1000; +} + +// ─── UCS-2 编码转换 ───────────────────────────────────────────────────────── +// KMS 协议中的字符串使用 UCS-2 LE 编码(每个字符 2 字节,小端序) + +/** + * 将 UTF-8 字符串转换为 UCS-2 LE Buffer,固定长度 maxChars * 2 字节 + * 超出部分截断,不足部分填零 + */ +export function utf8ToUcs2(str: string, maxChars: number): Buffer { + const buf = Buffer.alloc(maxChars * 2); + const truncated = str.slice(0, maxChars); + const encoded = Buffer.from(truncated, 'utf16le'); + encoded.copy(buf, 0, 0, Math.min(encoded.length, maxChars * 2)); + return buf; +} + +/** + * 将 UCS-2 LE Buffer 转换为 UTF-8 字符串,遇到空终止符 (0x0000) 停止 + * 参考: src/output.c 中 ePID 的解码逻辑 + */ +export function ucs2ToUtf8(buf: Buffer, maxChars: number): string { + const limit = Math.min(buf.length, maxChars * 2); + // 查找空终止符(偶数边界上的两个零字节) + let end = limit; + for (let i = 0; i < limit; i += 2) { + if (buf[i] === 0 && buf[i + 1] === 0) { + end = i; + break; + } + } + return buf.subarray(0, end).toString('utf16le'); +} + +// ─── 版本信息 ─────────────────────────────────────────────────────────────── + +/** 版本标识字符串,对应原版的 VERSION 宏 */ +export const VERSION = 'private build'; + +// ─── 响应验证结果 ─────────────────────────────────────────────────────────── + +/** + * KMS 响应验证结果 — 对应 src/kms.h 中的 RESPONSE_RESULT + * 每个字段表示一项验证检查是否通过 + */ +export interface ResponseResult { + mask: number; // 所有检查结果的位掩码 + hashOK: boolean; // 哈希验证通过 + timeStampOK: boolean; // 时间戳匹配 + clientMachineIDOK: boolean; // 客户端机器 ID 匹配 + versionOK: boolean; // 协议版本一致 + ivsOK: boolean; // IV(初始化向量)匹配 + decryptSuccess: boolean; // 解密成功 + hmacSha256OK: boolean; // HMAC-SHA256 验证通过(仅 V6) + pidLengthOK: boolean; // PID 长度有效 + rpcOK: boolean; // RPC 返回码为零 + ivNotSuspicious: boolean; // IV 非可疑(V6 中请求和响应 IV 应不同) + effectiveResponseSize: number; // 实际响应大小 + correctResponseSize: number; // 预期响应大小 +} diff --git a/node-vlmcs/tsconfig.json b/node-vlmcs/tsconfig.json new file mode 100644 index 00000000..68c9371b --- /dev/null +++ b/node-vlmcs/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "esModuleInterop": true, + "declaration": true, + "sourceMap": false, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79362bed..9818ca4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,15 @@ importers: specifier: ^3.5.30 version: 3.5.30(typescript@5.9.3) + node-vlmcs: + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.37 + typescript: + specifier: ^5.4.0 + version: 5.9.3 + packages: '@alloc/quick-lru@5.2.0': @@ -1947,6 +1956,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/node@22.15.19': resolution: {integrity: sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw==} @@ -6780,6 +6792,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@20.19.37': + dependencies: + undici-types: 6.21.0 + '@types/node@22.15.19': dependencies: undici-types: 6.21.0 @@ -9927,8 +9943,7 @@ snapshots: magic-string: 0.30.21 unplugin: 2.3.11 - undici-types@6.21.0: - optional: true + undici-types@6.21.0: {} unenv@2.0.0-rc.24: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..331d3d5c --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - node-vlmcs