简介ntpwedit 0.7 的 C 源码包面向需要研究 Windows 本地账户密码修改原理、或希望二次开发系统维护工具的程序员与运维人员。它通过直接读写 SAM 文件实现密码清空与重置需在 WinPE 或挂载硬盘的离线环境下运行支持从 Windows 2000 到 Windows 10 的 32/64 位系统。压缩包共 85 个文件约 236KB以 35 个 .h 头文件与 26 个 .c 源文件为主体另有 11 个 txt 说明、3 个 cmd 与 2 个 sh 构建脚本、2 个 manifest、2 个 rc 资源脚本及 makefile、license、ico 等完整覆盖 libntpw、libsam、ntreg、libtomcrypt 等模块与对话框控件实现。已有 440 人学习下载。读者可借此理清 SAM 解析、注册表操作与密码重置的底层逻辑参考 MinGW/MSVC 构建脚本完成编译并基于对话框与控件代码扩展界面是研究离线密码恢复与系统安全机制的实用源码素材。1. 拿到 ntpwedit 0.7 源码包它到底解决什么问题手上有一台离线 Windows 机器本地管理员密码没人记得域账号也进不去唯一能用的入口是 PE 或者另一套可启动环境。这种场景下图形化的密码编辑工具往往比命令行更省事而 ntpwedit 就是这类工具里被反复提起的一个。它做的事情很聚焦直接读写 Windows 的 SAM 数据库把本地账户的密码清空或重设不依赖原密码也不需要登录系统。这次拿到的是ntpwedit-0.7-src.zip也就是 0.7 版本的完整 C 源码包不是编译好的 exe。对一线运维来说源码包的价值在于可审计、可自行编译、可针对特定架构调整而不是拿来即用。它适合需要处理离线密码恢复、做应急盘工具链、或者想研究 SAM 结构的人如果你只是想双击运行那这个包不是给你准备的你得先过编译这一关。2. 源码结构与 SAM 读写原理先看懂再动手2.1 目录里有什么哪些文件是核心把 zip 解开后目录结构不复杂典型布局大致是这样文件/目录作用ntpwedit.cpp主程序入口界面与流程控制sam.cpp/sam.hSAM 数据库解析与写回的核心逻辑ntpwedit.rc资源脚本对话框、菜单定义*.h结构体、常量、函数声明Makefile/.vcxproj构建脚本或工程文件真正决定功能边界的是sam.cpp这一层。它要完成的事情包括定位 SAM 文件、解析注册表 hive 格式、找到SAM\Domains\Account\Users下的各个 RID 项、读取V和F两个关键值、再对密码哈希区域做清零或替换。0.7 这个版本号说明它不是最新但核心逻辑已经稳定常见做法是直接拿它当学习注册表 hive 解析的样本。2.2 SAM 里密码到底存在哪Windows 本地账户的密码不是明文也不是简单 MD5。SAM 中每个用户对应一个 RID用户信息存在V值里其中偏移 0x9C 附近是 LM 哈希区0xA8 附近是 NT 哈希区。ntpwedit 的做法不是去破解哈希而是把这两段区域直接写零或者写入一个新的哈希。写零之后该账户在登录时会被视为空密码这是离线恢复里最常用的手段。这里有个容易混淆的点清空密码和重设密码在 SAM 层面是两种操作。清空是把哈希区置零重设是计算新密码的 NT 哈希再写入。ntpwedit 的界面通常提供“清空”和“修改”两个入口源码里对应不同的写回分支。看源码时重点跟WritePassword或类似函数能看清它到底改了哪几个字节。2.3 编译前必须确认的工具链这是 C 源码Windows 平台构建。常见做法是用 Visual Studio 打开工程文件或者用 MinGW 配合 Makefile。如果你机器上只有较新的 VS打开老工程可能会提示工具集不匹配需要手动改 Platform Toolset。另一个高频问题是运行库编译出来的 exe 依赖 MSVC 运行库目标机器上如果没有对应的Microsoft Visual C Redistributable程序会直接报缺少 DLL。离线环境里这一点尤其要注意要么静态链接要么把运行库一起带上。# 以 MinGW 为例先确认编译器可用 g --version # 如果工程带 Makefile通常直接 mingw32-make # 若报找不到 windres说明资源编译工具没装全上面命令的含义很直接先验证g存在再用mingw32-make走 Makefile。windres是处理.rc资源文件的缺了它会在链接阶段报错。参数上没有什么可调的关键是工具链完整。如果你用 VS步骤换成“打开 .vcxproj → 改工具集 → 生成”但同样要确认 Windows SDK 版本别差太远。3. 从源码到可运行程序编译、链接与首次验证3.1 用 Visual Studio 构建的完整路径多数人手上是 VS那就走这条线。打开工程后先别急着点生成按顺序检查三处平台是 x64 还是 Win32、字符集是 Unicode 还是多字节、运行库是 /MT 还是 /MD。ntpwedit 这类老代码常见的是多字节字符集如果你强行改成 Unicode字符串处理部分可能编译不过。运行库建议选 /MT静态链接生成的 exe 不依赖外部 redistributable放到 PE 环境里更省心。// 典型的老式 Windows 入口源码里大概率长这样 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 初始化对话框、加载资源 // 后续调用 SAM 解析逻辑 return DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, MainDlgProc); }这段代码说明程序是对话框驱动的DialogBox阻塞运行直到窗口关闭。参数IDD_MAIN来自资源脚本如果你改了.rc里的对话框 ID这里也要同步改否则运行起来是空白或者直接失败。逻辑上它没有复杂初始化重点全在对话框过程函数里对按钮事件的响应。3.2 链接阶段最常见的两类报错第一类是unresolved external symbol通常指向RegOpenKeyEx、RegQueryValueEx这类注册表 API。原因是没有链接Advapi32.lib。解决方式是在项目属性里把Advapi32.lib加进附加依赖项或者在代码里用#pragma comment(lib, Advapi32.lib)。第二类是资源编译报错提示找不到windows.h或winres.h这是 SDK 路径没配好重装或修复 Windows SDK 即可。# 如果走命令行链接显式带上库 g ntpwedit.cpp sam.cpp ntpwedit.o -o ntpwedit.exe -ladvapi32 -lcomctl32 -mwindows-ladvapi32对应注册表相关 API-lcomctl32对应通用控件-mwindows告诉链接器生成 GUI 子系统程序而不是控制台程序。少了-mwindows会多弹一个黑框功能不受影响但观感不对。参数顺序上库要放在源文件之后否则某些链接器会报未定义。3.3 首次运行怎么验证它真的能读 SAM编译成功后不要直接往生产机器上招呼。先在一台测试虚拟机上验证读取功能把程序放到 PE 里或者用管理员权限在正常系统里运行指向一个复制出来的 SAM 副本。注意正常运行时 SAM 是被系统锁定的直接读会失败所以要么在离线环境操作要么先备份再挂载。验证读取成功的标志是程序能列出本地用户和 RID这一步不涉及写入风险最低。# 离线环境下先备份 SAM 和 SYSTEM后悔药先备好 copy C:\Windows\System32\config\SAM SAM.bak copy C:\Windows\System32\config\SYSTEM SYSTEM.bak这两条命令是血泪经验SAM 写坏了系统就进不去备份是唯一后悔药。SYSTEM也要备因为 SAM 的部分解析依赖 SYSTEM 里的 boot key。只备 SAM 不备 SYSTEM某些情况下解出来的哈希是错的。4. 避坑与排查SAM 操作里那些翻车点4.1 现象程序能打开但用户列表为空原因通常有两个。一是 SAM 文件路径不对程序默认读系统目录离线环境下系统目录并不是你挂载的那个二是权限不足即使离线文件 ACL 也可能阻止读取。解决方式是显式指定 SAM 路径或者用管理员权限运行。如果是在 PE 里确认 PE 本身是以 SYSTEM 权限启动的。4.2 现象清空密码后仍然提示密码错误这是最典型的翻车场景。原因往往是只清了 LM 哈希没清 NT 哈希或者写回时偏移算错。现代 Windows 默认不用 LM 哈希登录校验走 NT 哈希只清前者等于没清。另一个可能是写回后没有正确刷新SAM 有校验和与事务日志直接改文件可能导致系统忽略修改。解决方式是确认源码里两个哈希区都置零并且在离线状态下操作避免系统缓存覆盖。4.3 现象编译出的 exe 在目标机器上报缺少 DLL原因就是动态链接了 MSVC 运行库而目标机器没装对应的Microsoft Visual C Redistributable。解决方式有两种改成 /MT 静态链接重新编译或者把对应版本的运行库安装包一起带到目标机器。离线应急盘建议一律静态链接少一个依赖少一个变量。4.4 现象写入后系统无法启动或账户异常这是最严重的情况通常是因为写入了非法哈希或者破坏了 SAM 结构。SAM 是注册表 hive有固定的头部和校验随意改字节会导致整个 hive 无法加载。解决方式只有一个用之前备份的 SAM 和 SYSTEM 还原。这也是为什么每次操作前强制备份不是可选项而是必须项。如果没备份恢复难度会陡增。4.5 现象在 64 位系统上编译 32 位程序后行为异常ntpwedit 操作的是文件层面的 SAM理论上 32 位程序在 64 位系统上也能读写但涉及注册表重定向时可能读错位置。解决方式是尽量编译成与目标系统一致的架构64 位系统就出 x64 版本。如果只能用 32 位确认程序访问的是真实系统路径而不是 WOW64 重定向后的路径。5. 进阶用法把 ntpwedit 逻辑接进自己的应急工具链源码包最大的价值不是那个 exe而是sam.cpp里那套解析逻辑可以拆出来复用。我一般会把它整理成一个独立的静态库去掉界面部分只保留OpenSam、EnumUsers、ClearPassword这几个函数然后接到自己的 PE 工具或者批处理流程里。这样做的直接好处是可以在清空密码后自动执行后续动作比如启用某个账户、写入登录脚本、或者记录操作日志。// 把核心逻辑抽出来的大致形态 BOOL ClearUserPassword(LPCWSTR samPath, DWORD rid) { // 1. 加载 hive // 2. 定位 SAM\Domains\Account\Users\00000XXX // 3. 找到 V 值中的哈希偏移 // 4. 将 NT 哈希区写零 // 5. 回写并刷新 return TRUE; }参数samPath指向离线挂载的 SAM 文件rid是目标用户的 RID通常 500 是 Administrator。逻辑说明先加载 hive 到内存再按路径逐级定位写零后必须回写整个 hive 而不是只改文件某一段否则校验会失败。这一步的坑在于 hive 加载需要RegLoadKey权限普通用户做不到必须以 SYSTEM 或管理员身份运行。验证方法上我习惯在虚拟机里做闭环测试先建一个带密码的测试账户用工具清空重启尝试空密码登录成功后再恢复快照。这样每改一次代码都能快速验证不会把测试机搞成一次性消耗品。另一个技巧是保留一份已知良好的 SAM 样本每次编译出新版本先拿样本跑读取确认解析没退化再上真实环境。从那以后我每次动 SAM 之前都强制走一遍“备份 SAM SYSTEM → 虚拟机验证 → 再操作目标机”的流程哪怕再急也不跳过。希望帮到你。本文还有配套的精品资源点击获取