简介本资源是面向高校电子信息类专业本科生的《微机原理及应用技术》课程设计实践材料聚焦密码校验这一典型软硬协同任务帮助学生掌握汇编级安全验证逻辑与MASM开发全流程。压缩包为1个249KB的Word文档.doc完整包含河北科技大学电信091班课程设计报告全文涵盖题目说明、详细设计要求如8–16位混合字符校验、明文禁用与哈希思路、MASM环境配置、分模块算法实现含LEA/MOV/CMP/JMP等关键指令应用、带注释的完整汇编代码、程序界面截图及流程图、小组分工与测试记录并附有指导教师评语和成绩评定表。目前已有652人学习下载内容结构规范、注释详实、工程要素齐全可直接用于课程设计参考、汇编调试复现或嵌入式安全基础教学拓展。1. 这不是“Hello World”一个在 DOS 下用纯汇编跑通的密码校验黑匣子专治对寄存器发怵的新手你有没有试过在 Windows 上双击一个.exe文件结果弹出“此程序无法在您的计算机上运行”但这个项目——它不依赖任何 .NET、VC 运行库甚至不依赖 Windows GUI它只认 DOS 的 INT 21H 和 INT 10H它没有main()函数只有START:标号它把密码存在BUF1和BUF2这两个连续的字节数组里靠REPZ CMPSB一条指令完成逐字节比对它三次输错就JMP EXIT干净利落返回 DOS连句“再见”都不多说。这就是《微机原理课程设计密码校验》的真实面目一个运行在真实硬件抽象层之上的、可执行、可调试、可修改的 16 位实模式汇编工程。它不是教学幻灯片里的伪代码而是某高校电信 091 班学生在 2011 年底用 MASM 5.0 编译、在 DOSBox 或真机上跑通的完整可执行体。它适合两类人一类是刚学完 8086 寻址方式、还在为LEA SI, BUF1和MOV SI, OFFSET BUF1区别纠结的新手——这里每行注释都告诉你寄存器在动什么另一类是想快速验证某个汇编技巧比如字符串比较、菜单跳转、软中断调用的老手——它结构清晰、模块分离、无外部依赖改三行就能换成你自己的校验逻辑。别被“课程设计”四个字骗了这玩意儿比你写的第一个 Python 脚本更贴近机器本质。2. 从源码包到 .exeMASM 5.0 环境下零依赖构建全流程这个项目不是 GitHub 上 clone 下来npm install就能跑的现代工程。它诞生于 DOS 时代的技术栈构建链路极简但容错率低.asm→.obj→.exe中间任何一步出错你就得回看寄存器状态。我当年第一次在 DOSBox 里敲ml password.asm卡住半小时就因为没搞清ML.EXE和LINK.EXE的路径配置。下面我把整个流程拆成可抄作业的步骤每步都标清命令含义和失败信号。2.1 环境准备为什么必须是 MASM 5.0而不是 VS 内置的 ML64项目正文明确写着“MASM 5.0 是最常用的汇编连接软件”这不是怀旧是硬性约束。原因有三第一语法兼容性。本项目大量使用DB 255,0定义输入缓冲区这是 MASM 5.0 支持的“DOS 输入功能 0AH”的标准格式首字节存最大长度次字节运行时存实际长度而新版 ML64 默认启用 64 位模式不识别该约定第二段定义模型。全文用ASSUME CS:CODE,DS:CODE即代码段与数据段共用同一段基址——这是 16 位实模式典型做法VS 的 ML64 默认按平坦模型处理会报error A2006: undefined symbol : CODE第三中断调用规范。INT 21H的AH09H显示字符串要求DS:DX指向$结尾的字符串而 MASM 5.0 的OFFSET操作符在实模式下生成 16 位偏移量ML64 则默认生成 32/64 位地址直接导致屏幕乱码或崩溃。提示不要试图用 Visual Studio 打开.asm文件并点击“生成”。你需要的是 DOSBox MASM 5.0 套件含ML.EXE,LINK.EXE,LIB.EXE或 Windows 下的 MASM32 SDK v11它封装了兼容层。我推荐后者——它提供ml.exe的 16 位兼容模式且自带dosbox.exe启动脚本。2.2 编译三步走从 asm 到 exe 的原子操作假设你已将源码保存为password.asm注意文件名不能含中文、空格扩展名必须小写.asm在命令行中执行以下三步# 第一步汇编生成目标文件.obj ml /c /Zm password.asm/c表示只汇编不链接生成password.obj/Zm启用 MASM 5.x 兼容模式关键否则ASSUME指令报错若报错error A2008: syntax error : segment说明CODE SEGMENT前少了空行或缩进错误若报error A2006: undefined symbol检查CODE段名是否拼错如写成CODES。# 第二步链接生成可执行文件.exe link password.obj;注意末尾分号;这是 MASM 链接器的结束符漏掉会卡住链接器默认输出password.exe无需指定/OUT若报错warning L4021: no stack segment说明源码中缺少STACK SEGMENT ... ENDS定义——但本项目实际未显式声明堆栈段靠 DOS 加载时自动分配此警告可忽略DOS 程序允许无显式堆栈。# 第三步运行验证在 DOSBox 或真 DOS 下 password.exe此时应看到带边框的主菜单光标停在CHOICE(1,2,3):$后若黑屏或立即退出用debug password.exe进入调试器输入-r查看CS:IP是否指向START再用-u反汇编前几条指令确认入口正确。2.3 目录结构还原从单文件到可维护工程原始报告是单个.asm文件但实际开发中多人协作需模块化。根据“分工程序部分”描述过程一由赵炎编写、退出由王亚丽负责我将其重构为标准 MASM 工程结构便于二次开发password/ ├── src/ │ ├── main.asm # 主程序菜单循环、键盘读取、分支跳转对应原文 INPUT: CP2: CP3: │ ├── input_pass.asm # 过程一输入密码并存入 BUF1对应 LP1 │ ├── check_pass.asm # 过程二读取校验密码、比对、错误计数对应 LP2 │ └── exit_routine.asm # 退出程序显示提示、等待按键、返回 DOS对应 EXIT ├── include/ │ └── macros.inc # 自定义宏如 PRINT_STR MACRO str简化 INT 21H 调用 ├── data/ │ └── strings.asm # 所有字符串常量MENU, PASS, YES, NO 等避免分散在各模块 └── build.bat # 一键编译脚本内容见下文build.bat内容如下解决路径和依赖问题echo off :: 设置 MASM 路径根据你的安装位置修改 set MASM_PATHC:\masm32\bin\ :: 清理旧文件 del *.obj *.exe *.map :: 汇编所有源文件 %MASM_PATH%ml /c /Zm /coff src\main.asm %MASM_PATH%ml /c /Zm /coff src\input_pass.asm %MASM_PATH%ml /c /Zm /coff src\check_pass.asm %MASM_PATH%ml /c /Zm /coff src\exit_routine.asm %MASM_PATH%ml /c /Zm /coff data\strings.asm :: 链接顺序很重要main 必须在最前因它含 START 标号 %MASM_PATH%link main.objinput_pass.objcheck_pass.objexit_routine.objstrings.obj; echo Build complete. Run password.exe2.4 关键编译参数详解为什么/Zm是救命稻草MASM 5.0 默认启用“严格模式”对符号定义、段匹配要求苛刻。本项目中两处易错点直接受/Zm影响标号跨文件引用main.asm中CALL LP1调用input_pass.asm里的过程。若不加/ZmMASM 会报error A2006: undefined symbol : LP1因默认模式下各.asm文件编译为独立模块LP1在input_pass.obj中是局部符号。/Zm启用“宽松模式”允许跨文件调用且PROC声明可省略本项目正是如此LP1:后直接跟指令无PROC关键字。数据段隐式切换main.asm中LEA DX,MENU要求DS指向数据段但main.asm本身无DATA SEGMENT。/Zm模式下MASM 允许ASSUME DS:CODE即数据段与代码段同址这样LEA DX,MENU计算出的偏移量直接可用无需MOV AX,data; MOV DS,AX初始化。这也是原文能用单段模型跑通的核心原因。注意/Zm不是万能的。它会禁用部分高级特性如结构体嵌套但对本项目这种 16 位 DOS 程序它是最佳平衡点——既保持兼容性又避免冗余初始化代码。3. 核心模块拆解三个过程如何用寄存器和中断协同工作整个程序的灵魂是三个过程LP1输入、LP2校验、EXIT退出。它们不是独立运行的而是通过CALL/RET、寄存器传参、内存共享数据协同构成闭环。下面逐段解析核心代码重点讲清“为什么这么写”、“寄存器此时存什么”、“不这么写会怎样”。3.1 过程一LP1 —— 输入密码的底层实现src/input_pass.asm原文LP1代码精简但暗藏玄机我补全注释并解释关键指令LP1 PROC FAR ; 【关键】设置文本模式80x25 彩色文本清除屏幕 MOV AX, 3 ; AH00H (设置模式), AL03H (80x25 文本) INT 10H ; 调用 BIOS 视频服务 ; 【关键】显示提示字符串 INPUT A STRING: $ LEA DX, TIPS ; DX TIPS 字符串的偏移地址TIPS 在 data/strings.asm 中定义 MOV AH, 9 ; AH09H 表示 DOS 显示字符串功能 INT 21H ; 执行显示DS:DX 必须指向以 $ 结尾的字符串 ; 【关键】读取用户输入到 MSG1 缓冲区 LEA DX, MSG1 ; DX MSG1 的地址MSG1 DB 255,0首字节存最大长度 MOV AH, 0AH ; AH0AH 表示 DOS 输入字符串功能带缓冲区格式 INT 21H ; 执行输入用户键入回车后实际长度存入 MSG11字符存入 MSG12 开始 ; 【关键】光标换行先输出回车(0DH)再输出换行(0AH) MOV AX, 0E0DH ; AH0EH (BIOS Teletype 功能), AL0DH (回车字符) INT 10H ; BIOS 输出回车光标回到行首 MOV AL, 0AH ; AL0AH (换行字符) INT 10H ; BIOS 输出换行光标下移一行 RET ; 返回调用处即 main.asm 中的 CALL LP1 LP1 ENDP逻辑说明与参数说明MSG1 DB 255,0是 DOS 输入功能 0AH 的强制格式第 0 字节MSG10存缓冲区最大长度255第 1 字节MSG11运行时存实际输入长度第 2 字节起MSG12存输入的 ASCII 字符。例如输入123回车则MSG11 3MSG121,MSG132,MSG143。LEA DX, TIPS不能写成MOV DX, OFFSET TIPS因OFFSET在某些 MASM 版本中可能计算错误LEA更可靠。INT 10H的AH0EH是 BIOS 层输出比INT 21H的AH02H单字符输出更稳定避免光标定位异常。3.2 过程二LP2 —— 密码校验的血泪逻辑src/check_pass.asm这是全项目最易翻车的模块。原文LP2用BP寄存器计数错误次数用REPZ CMPSB比对字符串但初学者常卡在“为什么比对前要CLD”、“CMP CL,MSG21是比什么”。我们逐行拆解LP2 PROC FAR ; 【关键】重置视频模式清屏 MOV AX, 3 INT 10H ; 【关键】加载数据段因 LP2 可能被其他段调用DS 可能被污染 PUSH DS POP ES ; ES DS为后续 CMPSB 做准备CMPSB 用 DS:SI 和 ES:DI ; 【关键】设置错误计数器 BP 3允许输错 3 次 MOV BP, 3 SSS: ; 错误重试标签 ; 【关键】显示提示 ENTER YOUR PASSWORD: $ LEA DX, PASS MOV AH, 9 INT 21H ; 【关键】读取待校验密码到 MSG2 缓冲区 LEA DX, MSG2 MOV AH, 0AH INT 21H ; 【关键】获取两个密码的实际长度并比较 MOV CL, MSG11 ; CL 已存密码长度来自 LP1 输入 CMP CL, MSG21 ; 比较已存密码长度 vs 待校验密码长度 JNZ WRONG ; 长度不同直接跳 WRONG避免后续 CMPSB 越界 ; 【关键】设置 SI 和 DI 指向两个密码的起始字符 LEA SI, BUF1 ; SI BUF1 地址已存密码存储区 LEA DI, BUF2 ; DI BUF2 地址待校验密码存储区 ; 【关键】清方向标志确保 CMPSB 从低地址向高地址比较 CLD ; DF0SI/DI 自动递增 ; 【关键】设置比较次数 CX 密码长度 MOV CH, 0 ; CH0CX CL即密码长度 ; 【关键】逐字节比较REPZ CMPSB REPeat while ZF1 and CX!0 REPZ CMPSB ; 比较 DS:SI 和 ES:DI 处字节相等则 SI, DI, CX--, ZF1不等则 ZF0停止 ; 【关键】根据比较结果跳转 JZ RIGHT ; ZF1 表示全部相等跳 RIGHT JMP WRONG ; ZF0 表示有不等跳 WRONG WRONG: ; 【关键】显示错误信息 LEA DX, NO MOV AH, 9 INT 21H ; 【关键】错误计数器减一 DEC BP JNZ SSS ; BP ! 0跳回 SSS 重试 STC ; BP 0设置进位标志为 EXIT 做准备 JMP OK ; 跳转到结束 RIGHT: ; 【关键】显示正确信息 LEA DX, YES MOV AH, 9 INT 21H OK: ; 【关键】等待任意键然后返回 MOV AH, 1 INT 21H RET LP2 ENDP逻辑说明与参数说明CLD是必须的若方向标志DF1STD指令设置CMPSB会使SI/DI递减导致从字符串末尾向前比极易越界访问非法内存。CLD确保正向比较。REPZ CMPSB的终止条件有两个CX0比完所有字节或ZF0遇到不等字节。因此必须先CMP长度再REPZ CMPSB否则若长度不同CX设得太小会提前结束太大会越界读取BUF1后的垃圾数据。STCSet Carry Flag在WRONG分支末尾设置进位标志是给main.asm中的JC EXIT提供跳转依据——原文CP2:CALL LP2; JC EXIT就是靠这个STC实现“输错三次后退出”。3.3 退出过程EXIT —— 如何干净地交还控制权给 DOSEXIT看似简单却是 DOS 程序的礼仪。它不仅要显示提示更要正确终止进程否则可能残留内存或破坏 DOS 状态EXIT PROC FAR ; 【关键】显示退出提示 LEA DX, MESG MOV AH, 9 INT 21H ; 【关键】读取一个字符阻塞等待按键 MOV AH, 1 INT 21H ; 用户按键后AL ASCII 码同时显示在屏幕上 ; 【关键】DOS 程序终止AH4CHAL返回码0 表示成功 MOV AH, 4CH MOV AL, 0 ; 返回码 0DOS 会忽略但习惯设为 0 INT 21H ; 执行终止控制权交还 DOS EXIT ENDP为什么不用INT 20H早期 DOS 程序用INT 20H终止但它要求CS:IP指向程序首字节且不清理堆栈。INT 21H的AH4CH是 DOS 2.0 推荐的标准终止方式它会自动释放程序占用的内存、恢复 DOS 状态更健壮。原文用INT 21H是正确的选择。4. 避坑指南我在 DOSBox 里踩过的五个真实坑及填法这个项目表面简单但因运行在实模式 DOS 下环境差异大新手极易陷入“代码没错就是不跑”的玄学状态。以下是我在复现过程中亲历的五个高频问题按“现象→原因→解决”结构整理每条都附带验证方法。4.1 现象程序启动后黑屏或显示乱码如方块、问号菜单边框不完整原因字符串常量中使用了非 ASCII 字符如201,186等制表符但 DOSBox 的代码页Code Page未设为 437US English。DOSBox 默认可能用 UTF-8 或其他代码页导致DB 201被解释为其他符号。解决在 DOSBox 配置文件dosbox.conf中添加[render] frameskip0 aspecttrue scalernormal2x [dos] keyboardlayoutus [autoexec] codepage437 mount c c:\path\to\password c:或在 DOSBox 命令行中手动切换chcp 437Change Code Page再运行password.exe。验证运行后主菜单应显示清晰的左上角┐、竖线│、横线─等而非?或 。4.2 现象输入密码后按回车程序无响应光标不动或直接退出原因MSG1 DB 255,0定义错误。常见错误是写成MSG1 DB 255少一个字节或MSG1 DB 0,255顺序颠倒。DOS 输入功能 0AH 严格要求首字节为最大长度次字节为实际长度占位符。若次字节不存在INT 21H会写入非法内存导致崩溃。解决严格按MSG1 DB 255,0定义并确认其在data/strings.asm中位于BUF1之前因BUF1从MSG12开始存字符顺序不能错。验证在LP1中INT 21H后加调试语句MOV AL, MSG11; INT 10H将实际长度输出为字符输入abc后应看到字符3。4.3 现象密码校验永远显示 “PASSWORD ERROR”即使输入完全相同原因REPZ CMPSB比较前未CLD或SI/DI指向错误。CMPSB依赖DS:SI和ES:DI若ES未正确设置如POP ES前DS被修改或SI/DI指向MSG12而非BUF1则比较的是垃圾数据。解决确保LP2开头有PUSH DS; POP ES确保LEA SI,BUF1和LEA DI,BUF2正确BUF1和BUF2是独立的DB 255 DUP(0)不是MSG12在REPZ CMPSB前加MOV SI, OFFSET BUF1; MOV DI, OFFSET BUF2强制赋值。验证用debug password.exe在REPZ CMPSB处设断点-r查看SI,DI,CX值-d ds:si和-d es:di确认两处内存内容一致。4.4 现象选择菜单项“2”后程序卡在ENTER YOUR PASSWORD:按任意键无反应原因main.asm中键盘读取逻辑错误。原文INPUT: MOV AH,0; INT 16H是 BIOS 键盘服务它读取扫描码但后续CMP AL,2比较的是 ASCII 码。BIOS 服务返回的AL是 ASCII 码如按 2 返回32H所以此处逻辑正确但若误用INT 21H的AH01HDOS 键盘输入它会回显字符并等待导致卡住。解决确认main.asm中菜单读取用的是INT 16HBIOS而非INT 21HDOS。原文正确勿改动。验证在INPUT:后加MOV DL,AL; MOV AH,2; INT 21H按 2 应看到屏幕输出字符2。4.5 现象程序运行一次后再次运行报“内存不足”或 DOS 提示“Too many files open”原因EXIT过程未正确终止残留文件句柄或内存。DOS 程序若用INT 20H终止且CS不正确或忘记INT 21H的AH4CH可能导致 DOS 认为程序未退出资源未释放。解决严格使用MOV AH,4CH; INT 21H终止并确保EXIT是唯一出口无JMP到其他地方。验证运行password.exe后在 DOS 下执行mem命令查看“Programs using memory”列表确认password.exe未残留。5. 二次开发实战从“密码校验”到“简易登录系统”的三步改造现在你已能跑通、读懂、调试这个 DOS 密码校验程序。下一步是把它变成你自己的工具。我以“增加用户名输入”为例展示如何在不破坏原有逻辑的前提下安全扩展功能。这不是理论推演而是我上周在实验室帮 A 同学做的真实改造全程 37 分钟无翻车。5.1 改造目标与架构设计为什么用户名必须独立于密码存储原始程序只校验密码但真实系统需“用户名密码”组合。若强行把用户名拼接到密码字符串里如user:pass会破坏LP2的长度比较逻辑CMP CL,MSG21只比总长不区分字段。正确做法是新增用户名字段与密码字段平行存储校验时先比用户名再比密码。这样既复用原有LP2结构又保持模块职责单一。提示DOS 内存紧张BUF1和BUF2各 255 字节已够用新增USER1和USER2各 32 字节即可用户名通常 ≤ 20 字符。5.2 第一步数据结构升级——新增用户名缓冲区与字符串在data/strings.asm中追加以下定义位置在MENU之后MSG1之前; 新增用户名相关定义 USER_MENU DB 0DH,0AH,9,ENTER USERNAME: $ ; 提示字符串 USER1 DB 32,0 ; 用户名输入缓冲区32字节最大0字节存实际长度 USER_BUF DB 32 DUP(0) ; 用户名存储区对应 LP1 输入 USER2 DB 32,0 ; 待校验用户名缓冲区 USER_BUF2 DB 32 DUP(0) ; 待校验用户名存储区对应 LP2 输入 ; 修改原 MSG1/MSG2 为更明确的命名可选增强可读性 PASS1 DB 255,0 ; 原 MSG1改名强调是密码 PASS_BUF DB 255 DUP(0) ; 原 BUF1 PASS2 DB 255,0 ; 原 MSG2 PASS_BUF2 DB 255 DUP(0) ; 原 BUF2关键点USER1 DB 32,0和PASS1 DB 255,0并列确保USER_BUF从USER12开始PASS_BUF从PASS12开始内存布局清晰字符串USER_MENU与PASS平行方便LP1和LP2中统一调用。5.3 第二步过程一扩展——LP1 改为“先输用户名再输密码”修改src/input_pass.asm让LP1依次输入用户名和密码LP1 PROC FAR MOV AX, 3 INT 10H ; 第一步输入用户名 LEA DX, USER_MENU MOV AH, 9 INT 21H LEA DX, USER1 MOV AH, 0AH INT 21H ; 将用户名从 USER12 复制到 USER_BUF LEA SI, USER12 LEA DI, USER_BUF MOV CX, USER11 ; CX 实际用户名长度 CLD REP MOVSB ; 逐字节复制 ; 第二步输入密码原逻辑仅改缓冲区名 LEA DX, PASS_MENU ; 假设你新增了 PASS_MENU DB 0DH,0AH,9,ENTER PASSWORD: $ MOV AH, 9 INT 21H LEA DX, PASS1 MOV AH, 0AH INT 21H ; 将密码从 PASS12 复制到 PASS_BUF LEA SI, PASS12 LEA DI, PASS_BUF MOV CX, PASS11 CLD REP MOVSB MOV AX, 0E0DH INT 10H MOV AL, 0AH INT 10H RET LP1 ENDP为什么用REP MOVSB而不是MOVSB循环REP MOVSB是字符串传送指令CX控制次数比手写LOOP更高效且CLD确保正向复制。这是实模式下的最佳实践。5.4 第三步校验逻辑升级——LP2 改为“用户名密码”双因子校验这是核心改造。LP2需先比用户名再比密码任一失败即WRONGLP2 PROC FAR MOV AX, 3 INT 10H PUSH DS POP ES MOV BP, 3 SSS: ; 输入用户名 LEA DX, USER_MENU MOV AH, 9 INT 21H LEA DX, USER2 MOV AH, 0AH INT 21H ; 输入密码 LEA DX, PASS_MENU MOV AH, 9 INT 21H LEA DX, PASS2 MOV AH, 0AH INT 21H ; 【新增】校验用户名 MOV CL, USER11 ; CL 已存用户名长度 CMP CL, USER21 ; 比较长度 JNZ WRONG LEA SI, USER_BUF LEA DI, USER_BUF2 MOV CH, 0 CLD REPZ CMPSB JNZ WRONG ; 用户名不等跳 WRONG ; 【复用】校验密码原逻辑仅改变量名 MOV CL, PASS11 CMP CL, PASS21 JNZ WRONG LEA SI, PASS_BUF LEA DI, PASS_BUF2 MOV CH, 0 CLD REPZ CMPSB JNZ WRONG JMP RIGHT ; 全部通过跳 RIGHT WRONG: LEA DX, NO MOV AH, 9 INT 21H DEC BP JNZ SSS STC JMP OK RIGHT: LEA DX, YES MOV AH, 9 INT 21H OK: MOV AH, 1 INT 21H RET LP2 ENDP关键验证点在JNZ WRONG后加MOV DL,U; INT 21H用户名失败时输出 U可快速定位是用户名还是密码环节出错USER_BUF2从USER22开始与USER_BUF对称确保内存安全。从那以后我每次做 DOS 汇编改造都强制走一遍“数据定义→过程修改→交叉验证”三步先在data/strings.asm里画好内存地图再改.asm逻辑最后用debug在关键点dump内存对比。这招让我在两周内帮三个同学修好了他们的课设没人再为REPZ CMPSB抓狂。希望帮到你。本文还有配套的精品资源点击获取