密码控件安全技术浅析及攻击实例

密码控件安全技术浅析及攻击实例
密码控件是一个很常见的控件,这篇文章主要介绍一下安全密码控件的一些实现思路和攻击思路。
 
1 物理密码键盘和密码控件的对比
 
先说一下程序中的密码控件和现实中的密码键盘的对比。几乎每个人都在银行或者ATM机器上输入过密码,那在输入密码的过程中你接触到的那个键盘就是一个金融密码键盘。一般柜台的密码键盘都像鼠标一样带一个数据线,这个数据线就是连接密码键盘和银行处处理终端的。结构图如下:
 
密码控件安全技术浅析及攻击实例_第1张图片
 
上图显而易见,如果一个坏人想要去窃取密码信息,由两个方式:
A. 在终端记录用户的输入按键
B. 从数据线中截获用户密码输入
 
对于第一种方法,就靠用户在输入密码时候的自我保护了,而第二种方法也不是轻易能实现的。不要小瞧了这个密码键盘,这个密码键盘的制造标准都是有国标规定的,国标规定的一点是:从键盘流出的数据一定要是用户输入加密后的数据,所以即使截获到键盘流出的数据想要破解出密码也需要先得到该密码键盘在加密过程中使用的密钥(这里使用的是对称加密算法)。
 
其实密码控件就是密码键盘的软件实现,在一个使用密码控件的软件系统中(多是网络校验系统),密码控件的整个软件实现就相当于整个物理的密码键盘,对于物理的数据线路和银行终端这样的角色,在密码控件系统中没有绝对的对应。因为密码控件可能只是简单的把用户输入的数据加密后发送到网络上,这时候网络就是物理的数据线路;而另一种情况是用密码控件产生的数据流向整个本地程序的另一个模块,这时候计算机内存就成为了物理的数据线路的角色。
 
无论如何,上述的物理密码键盘肯定要比各种密码控件安全,因为他们使用的场合有限,能够接触到密码键盘内部的人很少,所以就无法从密码键盘的内部来做一些恶意攻击行为。而软件实现的密码控件就现对显得捉襟见肘了,因为要部署到用户终端,而用户终端的环境十分的复杂,并且权限十分的宽松。
 
2 软键盘密码控件原理和攻击方法
 
Windows系统的内置的控件中没有专门的密码控件这一说,而只是对一个SingleLine的Edit控件设置一个属性password,这样就实现了这个Edit的输入内容只会显示成一种不可识别的字符。但是这种原生的密码控件从安全的角度来说就只有一点:从视觉上看不到用户输入的密码,但是整个密码控件仍然遵循Win32 消息派遣处理流程,也就是说,写一个标准的程序就可以达到先于密码空间获得用户输入从而记录密码的效果。
 
因此很多软件厂商出于安全的考虑就做出了各式各样的“安全密码控件”,常见的密码控件类型:
1. 低级键盘钩子,改变消息参数以及派遣路径
2. 软键盘,完全绕过键盘输入消息
3. 暂时还没想到……
 
下面简单介绍一下以上两种控件的原理
 
2.1 软键盘式密码控件
 
由于第2种方案使用的不是很多,所以简单介绍,这种方法使用不多的原因之一就是用户体验不如键盘输入。这种密码控件的方法就是把键盘输入转为鼠标按键输入,而鼠标按键消息中并没有携带原始的密码字符,而是按键的坐标,然后程序把坐标映射到相应的字符,而映射过程中遵循的F(x,y)是程序开发者自行设计并且可以实时动态更新(变序)。
 
先不考虑其他的保护措施,如果要攻击这种安全控件,从设计这种控件的程序员的角度来考虑,如果要获取用户输入密码,至少需要两个条件
A. 鼠标点击坐标(x,y)
B. 内存中映射算法F(x,y)
攻击者可以截获鼠标点击坐标(x,y)然后根据破解出来的F(x,y)来计算出用户输入,这种方法对于一个没有做到全方位保护的软键盘密码控件可能有效,但是还是太麻烦了,其实有个捷径,攻击者只要识别出用户的鼠标点击操作发生在软键盘上,然后在每次用户点击鼠标时就来一个屏幕截图,全屏or局部都可以,然后把截取的图片排序,就可以得到用户输入的密码数据了,完全绕过程序的映射算法。
 
所以从以上攻击方法可以看出,软件盘密码控件如果要做到更安全那就必须对两个事件进行处理,
A. 用户点击鼠标的消息不被其他程序截获
B. 发生截屏操作时对本程序的所有窗口DC做黑屏处理
关于软键盘密码控件就介绍这么多,只是原理性的介绍,并没有实践代码,但是原理和攻击方法已经十分清晰了,所以有兴趣的同学可以帮助一些采用软键盘密码控件的厂商做下安全测试。
 
2.2 低级键盘钩子式密码控件
 
这个就是现如今流行并且典型的密码控件实现方式了,这种方式下键盘还是那个键盘,输入框还是那个输入框,唯一的不同是这个输入框的消息派遣路径,贱了不少,各个厂商都想方设法的在Windows消息处理机制中寻找解决安全问题的途径。
 
正常的输入框,在接收用户的键盘输入时会相应WM_CHAR消息,这个消息是如何来的呢?当按下一个键,键盘驱动产生硬件中断IRQ,然后经过HAL映射中断请求级别(IRQL)如果这个级别比CPU的允许的级别高,那就发生中断,CPU取出键盘中断号,然后用该中断号作为索引取出IDT中的对应的描述符,然后执行该描述符所指向的一个键盘处理程序,键盘中断处理程序的作用就是处理原始的键盘扫描码生成数据结构,然后缓存数据。
然后就是键盘驱动的工作,键盘驱动的工作就是为了完成设备栈顶部的一个功能号为IRP_MJ_READ的IRP,这个IRP是win32子系统进程csrss.exe中的一个线程RIT(Raw input thread)产生的,当这个IRP被完成后,RIT负责用键盘事件产生的数据生成一个按键消息并且放入对应的GUI线程的输入队列中。
而后对应的GUI线程在消息循环中使用GetMessage获取消息,就会取出一个WM_KEYDOWN消息,然后如果该GUI线程中使用了TranslateMessage处理这个WM_KEYDOWN消息,则会生成一个WM_CHAR消息存入自身的POST消息队列,然后下次再去GetMessage的时候就会取出WM_CHAR消息,并且把该消息携带的char传入相应的Window Procedure,如果没有使用TranslateMessage处理这个消息就不会产生WM_CHAR消息。
 
完整的流程图如下:
 
密码控件安全技术浅析及攻击实例_第2张图片
 
从上面的描述以及流程图来看,如果作为一个攻击者想要获取用户输入的密码,很简单的做法就是截获WM_CHAR消息,的确是这样的,用Spy++就可以轻而易于的获得一个原生密码输入框的所有WM_CHAR消息,所以安全密码控件的目标就是让攻击者无法截获WM_CHAR消息,或者截获到WM_CHAR消息的时候这个VirtualKeyCode是假的,而真正的VirtualKeyCode已经被处理。因为在从用户按键到密码框多出一个显示字符的过程中只有WM_CHAR这个消息是明文记录了用户键入的数据。
对于保护WM_CHAR消息的做法,必然是要在得到WM_KEYDOWN这个消息的时候就进行,因为只要有了WM_KEYDWON这个消息就可以绕过系统后续的处理先于系统计算出用户按下的Virtual Key,然后保存这个案件对应的Char,然后再返回一个修改过的WM_KEYDOWN消息,后面的数据都不用再管了,WM_KEYDOWN被修改,Virtual Key被修改,导致后续调用TranselateMessage消息时产生的WM_CHAR也不正确,所以攻击者就算截获到WM_CHAR也是徒劳的。
 
产生假WM_CHAR的的方法各个厂商迥异,比如QQ的就是(此处过滤5000字)。再比如支x宝,如果一个用户的密码是mypasswordisaccess,那么按照这个键序列输入后,控件返回的char序列会是1234556789054qqw55,也就是说在支x宝安全控件每次初始化之后,当用户输入一个键,控件就会为这个键设置一个映射char,如果下次用户再输入这个键,那就不用设置直接返回上次设置的char,策略就是用户输入键之后控件负责生成映射关系,这个映射的值的产生方式很简单:
 
密码控件安全技术浅析及攻击实例_第3张图片
 
如上图的四行键位,从上到下从左到右排列。
 
上面说了原理,要实现的话,必然用到Windows的一种技术,那就是基于消息的Hook机制了(此Hook跟inline,IAT,SSDT等Hook完全不是一回事),因为要处理在系统生成WM_CHAR之前就处理WM_KEYDWON并且修改,就必须要使用低级键盘钩子,WH_KEYBOARD_LL。
 
2.3 支x宝安全密码控件原理分析实践
 
好了,不纸上谈兵了,找个靶子分析分析吧,为了避免律师函,还是选择支x宝吧宝,支x宝的密码控件用在了登陆和支付密码的输入过程中,两处使用的都是同一个密码控件,先来调试下吧。
 
按照前面的讲到的,该密码控件使用了windows hook机制,那就先从SetWindowsHookEx这个函数入手吧。
 
代码:
 
ntdll!DbgBreakPoint:
00000000`77410530 cc int 3
0:209> bp USER32!SetWindowsHookExW
0:209> g
 
然后操作网页,让控件获取焦点,触发断点,看下堆栈:
 
代码:
 
(271c.4d7c): Break instruction exception - code 80000003 (first chance)
Breakpoint 0 hit
USER32!SetWindowsHookExW:
76d77603 8bff mov edi,edi
0:173:x86> kvn L10
# ChildEBP RetAddr Args to Child 
00 4000ba84 73e94267 0000000d 5c1cca08 5c1c0000 USER32!SetWindowsHookExW (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for npAliSecCtrl.DLL
*** ERROR: Symbol file could not be found. Defaulted to export symbols for npAliSecCtrl.DLL - 
01 4000bab4 5c1ccaf5 0000000d 5c1cca08 5c1c0000 IEShims!NS_SetWindowsHookEx::APIHook_SetWindowsHookExW+0x6b (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
02 4000bb04 5c1c62d2 00140154 00000007 0014050a npAliSecCtrl!NP_Initialize+0x21c7
03 4000bb50 76d662fa 4e1c3070 00000007 0014050a npAliSecCtrl!NP_Shutdown+0x269a
04 4000bb7c 76d66d3a 05940920 00140154 00000007 USER32!InternalCallWinProc+0x23
05 4000bbf4 76d66de8 00000000 05940920 00140154 USER32!UserCallWinProcCheckWow+0x109 (FPO: [Non-Fpo])
06 4000bc50 76d66e44 010e22c0 00000000 00000007 USER32!DispatchClientMessage+0xe0 (FPO: [Non-Fpo])
07 4000bc8c 775b010a 4000bca4 00000000 4000bde0 USER32!__fnDWORD+0x2b (FPO: [Non-Fpo])
08 4000bcc4 5c1c9054 00140154 4e1c2f1c 00000001 ntdll_775a0000!KiUserCallbackDispatcher+0x2e (FPO: [0,0,0])
09 4000bcfc 5c1c5d79 0014050a 00000007 000c0822 npAliSecCtrl!NP_Shutdown+0x541c
0a 4000bd4c 76d662fa 4e1c2f1c 4000c01c 000c0822 npAliSecCtrl!NP_Shutdown+0x2141
0b 4000bd78 76d66d3a 05940d80 0014050a 00000007 USER32!InternalCallWinProc+0x23
0c 4000bdf0 76d66de8 00000000 05940d80 0014050a USER32!UserCallWinProcCheckWow+0x109 (FPO: [Non-Fpo])
0d 4000be4c 76d66e44 00e9fdf0 00000000 00000007 USER32!DispatchClientMessage+0xe0 (FPO: [Non-Fpo])
0e 4000be88 775b010a 4000bea0 00000000 4000bf38 USER32!__fnDWORD+0x2b (FPO: [Non-Fpo])
0f 4000bec0 5c1c6df4 0014050a 00000000 4e1c2ed4 ntdll_775a0000!KiUserCallbackDispatcher+0x2e (FPO: [0,0,0])
 
然后看一下动态的设置两个断点:
 
代码:
 
0:170:x86> bp USER32!SetWindowsHookExW ".printf \"Hit USER32!SetWindowsHookExW\\r\\n\"; g"
0:170:x86> bp USER32!NtUserUnhookWindowsHookEx ".printf \"Hit USER32!UnhookWindowsHookEx\\r\\n\"; g"
0:170:x86> bl
0 e x86 76d8f52b 0001 (0001) 0:**** USER32!NtUserUnhookWindowsHookEx ".printf \"Hit USER32!UnhookWindowsHookEx\\r\\n\"; g"
1 e x86 76d77603 0001 (0001) 0:**** USER32!SetWindowsHookExW ".printf \"Hit USER32!SetWindowsHookExW\\r\\n\"; g"
0:170:x86> g
 
然后就来来回回操作让控件获取和失去焦点:
 
代码:
 
(271c.1618): Unknown exception - code 800706ba (first chance)
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
Hit USER32!SetWindowsHookExW
Hit USER32!UnhookWindowsHookEx
 
然后看一下调用SetWindowsHookExW的时候,设置的Hook处理函数所在模块以及偏移
 
代码:
 
0:173:x86> lm a 5c1c0000 
start end module name
5c1c0000 5c27d000 npAliSecCtrl C (export symbols) npAliSecCtrl.DLL
 
 
下面就不调试了,直接去分析这个处理函数了,找到模块npAliSecCtrl,先脱壳,然后找到偏移ca008
 
密码控件安全技术浅析及攻击实例_第4张图片
 
然后就逆吧……我没有把这些代码逆完,只是一个大致框架出来了。
 
密码控件安全技术浅析及攻击实例_第5张图片
 
其实只要知道他的HOOK实现机制就Ok了,这个控件的原理就是当控件获取焦点的时候就设置一个低级键盘钩子,这个键盘钩子的作用就是截获WM_KEYDOWN然后处理消息,修改消息。在调试中还发现,在其设置了一个低级键盘钩子之后并没有再设置一个调试钩子,这样就很容易突破这种保护机制了,只要照准时机在他的低级键盘钩子设置后再设置一个低级键盘钩子,那么就可以先于控件自身的钩子获取到键盘输入消息了,不知道这个是否属于考虑欠缺的一个设计,不过即使加一个调试钩子,也并不是毫无破绽的,仍让有方法可以攻击。
 
2.4 支x宝安全密码控件攻击实例
 
上面说了即使加了调试钩子,也还是不能阻止攻击的,原因是Windows 消息钩子的层次太高了,在如此高的系统层次上面去做一些防护,基本没有办法做到绝对安全。对于支x宝的密码控件,只要一个Inline Hook就可以完全破除了。
 
原理:进入到浏览器的进程内存空间,Inline Hook住User32!SetWindowsHookExW/A函数,分析该函数的参数,判断如果传入的模块是属于目标模块并且钩子类型是低级键盘钩子,则调用原函数,等待原函数返回成功之后,再调用原函数设置一个低级键盘钩子,然后返回。这样就相当于在控件本身的钩子之上加了一个钩子,而这个钩子是我们的钩子,我们可以先于控件钩子得到键盘输入,从而计算出用户输入的字符,达到键盘记录的目的。
 
xxxx种原因,我不能在这里提供源码,只有一图和一个bin供各位研究了。
 

密码控件安全技术浅析及攻击实例_第6张图片


指出一个小小错误,qq不是修改WM_KEYDOWN,而是使用WH_KEYBOARD_LL钩子截获所有的键盘消息并且使用NtSendInput随机模拟按键

你可能感兴趣的:(密码控件安全技术浅析及攻击实例)