二、问题的分析及对策
1、关于视窗操作系统“用户登录”对话框问题
我们将视窗操作系统内“用户登录”密码研判的核心代码列出来分析如下。
以上进行了多次单向HasH运算并得到长度为128位的HasH串(具体算法分析略)然后与保存在对应PWL文件内的正确值比对,详见以下代码。
: :
7FA71D97 B910000000 mov ecx, 00000010 ;HasH串为128位即16个字节
7FA71D9C 2BC0 sub eax, eax ;初始化EAX寄存器
7FA71D9E F3 repz
7FA71D9F A6 cmpsb ;进行128位HasH串比对
7FA71DA0 7405 je 7FA71DA7 ;相等,系统认为用户密码正确
7FA71DA2 1BC0 sbb eax, eax ;否则置密码比对错误标志
7FA71DA4 83D8FF sbb eax, FFFFFFFF
7FA71DA7 85C0 test eax, eax ;测试密码比对标志位
7FA71DA9 B8261C0000 mov eax, 00001C26 ;预置密码错误消息
7FA71DAE 7502 jne 7FA71DB2 ;密码比对错误,则转
7FA71DB0 33C0 xor eax, eax ;否则置密码比对正确位
7FA71DB2 5F pop edi ;恢复现场
7FA71DB3 5E pop esi ;恢复现场
7FA71DB4 5B pop ebx ;恢复现场
7FA71DB5 8BE5 mov esp, ebp ;恢复现场
7FA71DB7 5D pop ebp ;恢复现场
7FA71DB8 C20400 ret 0004 ;返回
: :
从上述分析可见,当我们将密码比对的长度由“0x10H” 改为“0”——也就是将上述
将 mov ecx, 00000010
改为 → mov ecx, 00000000
仅仅改动“一个”字节,“用户登录”密码的比对结果就将“永远”正确。
仅仅依赖一条“repz cmpsb”指令对系统级的密码进行决定性研判是相当危险的,而仅仅通过一条件语句来选择视窗操作系统的密码安全类指令的决定性流向,其实是很“传统”的低级做法,保护效果相当脆弱。因为不论操作系统采用的单向HasH结果长度多么“惊人”也不论操作系统在执行“repz cmpsb”这条研判指令之前进行了多少轮大负荷的迭代编码,我们只要在这一条“关键路径”上实施“点穴”——这个层次的安全保护即告失效。
对策:
视窗操作系统在这里的最大“疏忽”就是对“苦心积虑”最后获得的128位HasH串仅仅参与简单的比对,并没有进行更有效的利用,建议:
将上述最后运算出来的128位HasH串不论其对错都作为新一轮解码迭代运算的输入算子,对后续的部分关键代码或数据实施解码,这样用户输入正确的密码后自然能正确地继续装载视窗操作系统,否则只能导致错误提示而无法继续正确地装载视窗操作系统。
事实上这是一个明显的安全漏洞。不要认为存储设备上的系统级文件设置了“只读”、“隐藏”等权限,就可以高枕无忧。事实上,只要系统的存储设备可以被“物理”地读写,那么对其上敏感的系统级文件进行“篡改”就不会是一件“相当”困难的事情。而在大多数情况下,这件事还是比较容易做到的。有关这个安全漏洞的更进一步讨论,我们将在《脆弱的软件著作权保护方式及对策》一文里进行,并相应地给出一个有效的解决方案。
2、关于视窗操作系统“标识登录”对话框问题
我们将视窗操作系统内“标识登录”密码研判的核心代码列出来分析如下。
: : ┏ ECX、EAX分别指向用户输入密码及正确密码
797972C3 8D4DE8 lea ecx, dword ptr [ebp-18]
797972C6 8D85E8FDFFFF lea eax, dword ptr [ebp+FFFFFDE8]
797972CC 8A18 mov bl, byte ptr [eax] ;获取第一个密码字符
797972CE 8AD3 mov dl, bl ;保存起副本
797972D0 3A19 cmp bl, byte ptr [ecx] ;与正确的密码比对
797972D2 751A jne 797972EE ;第一个密码字符不正确
797972D4 84D2 test dl, dl ;为最后一个密码字符?
797972D6 7412 je 797972EA ;是则转
797972D8 8A5801 mov bl, byte ptr [eax+01] ;获取下一密码字符
797972DB 8AD3 mov dl, bl ;保存其副本
797972DD 3A5901 cmp bl, byte ptr [ecx+01] ;与正确的密码比对
797972E0 750C jne 797972EE ;比对不成功则转
797972E2 40 inc eax ;调整当前密码字符指针
797972E3 40 inc eax ;调整当前密码字符指针
797972E4 41 inc ecx ;调整正确密码字符指针
797972E5 41 inc ecx ;调整正确密码字符指针
797972E6 84D2 test dl, dl ;为最后一个密码字符?
797972E8 75E2 jne 797972CC ;尚未比对完则转
797972EA 33C0 xor eax, eax ;比对结束并且全部正确
797972EC EB05 jmp 797972F3 ;转后续测试标志位
797972EE 1BC0 sbb eax, eax ;比对结束但是不正确
797972F0 83D8FF sbb eax, FFFFFFFF ;置错误标志位
797972F3 85C0 test eax, eax ;测试标志位
797972F5 7434 je 7979732B ;密码比对正确则转
: :
从上述分析可见,当我们将密码比对的二个指针EAX、ECX均指向同一个地址——也就是将上述
将 mov bl, byte ptr [eax]
改为 → mov bl, byte ptr [ecx]
将 mov bl, byte ptr [eax+01]
改为 → mov bl, byte ptr [ecx+01]
仅仅改动“二个”字节,“标识登录”密码的比对结果就将“永远”正确。这同样是一个明显的安全漏洞。这个漏洞源自明文密码比较的指令过于接近,很容易通过对有关的指令代码进行“篡改”达到“冒名”攻击的目的。
对策:
首先我们要尽可能避免明文密码的比较,并尽可能地将明码密码“暗码”化,即将明文密码通过稳健的摘要密码算法“撕碎”后“淹没”在长度至少在128位的高强度单向散列串里(具体参考我们发表的《大型Web应用软件系统的安全登录隐患及对策》一文),如果实在没有这个“耐性”做这项工作,那至少要考虑将密码研判的有关指令“离散”化而不象现在这般前后“紧密”相连,更为安全的做法请参考我们完成的《脆弱的软件著作权保护方式及对策》一文,里头相应地给出一个有效的解决方案。
