小弟新手报到,提交一篇拙文一篇,供前辈们批评
使用OllyDbg 分析 USB HID 设备接口协议
作者: Tase tase@163.com
关键字: OllyDbg,USB ,HID,Bus hound,
分析工具: OllyDbg,Bus hound
分析对象: 一个USB 接口飞行模拟器
目的:通过分析使用USB HID设备的软件,从中其主程序中“扣”HID 设备数据协议,再根据这些协议自己实现USB HID的设备,达到仿制原装 HID数据采集设备的目的。
我有幸拿到了一款原装的飞行模拟软件和接口设备,这个模拟器是通过一个USB接口把遥控设备上的数据采集到PC,再通过软件模拟现实飞行,因为原装设备昂贵,因此打算DIY一套接口,便下手分析了一把!
这个模拟器软件有两部分组成,其一是模拟软件,其二是一个USB的数据采集设备,在拿到设备前,我就猜想,它可能是HID(人体学设备)DEVICE,现在很多USB数据采集设备都是采用的HID协议的方法设计,这样的话就可以实现在WINDOWS下免驱动安装,并且可以通过windows自带的接口以客户态的方式访问这些硬件接口。
经过测试,果然是HID设备,使用Bushound5 总线观察器,用它来捕捉总线上的USB设备数据,HID设备很简单,花了两个小时就基本弄清楚了它大部分的数据含义,因此很快就按照我理解的协议把设备在单片机开发板上实现了,插上我的设备,模拟器软件很快就识别了我的接口,但显示各通道数据时,都是0,也就是说,它的系统检测到了一个接口设备,但这个设备采样的数据是错误的。
于是我又回到总线数据分析,找到我遗漏的2个bit,这个2个bit 是我没有理解的,我猜想它是校验位,经过观察,这个2个bit只和几个字节有关,但就是这2个bit花了我4天的时间,这几天让我学会Excel 中很多大多数人不知道的功能,学会了使用VBScript 写宏,我甚至用到了MATLAB,我一直是用统计的方法寻找2bit的规律,但最终我失败了。
山穷水尽,想到了Ollydbg 分析它主程序中的USB接口的协议,没有想到,一个晚上,就解决了我的问题。
在Windows 下访问HID 设备可以有两种方法,一是核态,也就是用驱动程序的方法访问,这个方法非常复杂,我知道原理,但自己还没有写过这样的驱动。二是客户态,即应用程序通过调用Windows提供的Hid 访问函数就可以实现对HID设备的访问。如果我们截获了这些函数的调用,那么,距离我们要解决的目标就不远了。
一般情况下,主程序识别USB HID外设的方法:首先调用HidD_GetHidGuid函数获取HID设备的类标识,调用SetupDiGetClassDevs函数查询所有已安装的HID设备,得到一个指向该HID设备集合的句柄,调用SetupDiEnumDeviceInterface函数查询HID设备集中每一个设备的接口信息,对每一个接口,调用SetupDiGetDeviceInterfaceDetail函数获取其详细的信息,包括设备名称(头四个字节),CreateFile用此设备名打开设备,调用SetupDiDestroyDeviceInfoList函数释放设备信息集合;第二步,打开设备,获取设备的属性值以及设备能力描述,调用CreaterFile函数打开本设备。调用HidD_GetAttributes函数,获取USB设备的有关属性。它包含了设备的厂商ID、产品ID及产品的版本号等。可以根据这些信息判断该设备是否为目标设备,但我们关心的并不是这些,我们关心的是这个设备打开以后的 ReadFile 操作,我们知道,Windows下的设备都是当作文件来读写的。
启动OllyDbg 装入模拟器软件,下断点 HidD_GetAttributes,CreateFile,这个函数是能把HID 的关键信息获取,例如厂商ID,产品ID,版本?等,在调试前,拔掉机器上所有不必要的USB设备,这样枚举到的设备会少些,你断点的次数也会少很多。
因为前面用到了Bus Hound,很容易知道我们要关心的设备的 VID 和 PID,这样在断点在HidD_GetAttributes, 之后,很容易得知哪个设备是我们所关心的,在堆栈中记下设备的字符串路径。在CreateFile函数断点时,确认要打开的设备是否是我们关心的设备?
我们关心的HID设备打开以后,就要关心 ReadFile 和 WriteFile了,下断点 ReadFile ,不幸的是,这样的断点太多了! 想到一个问题,HID虽然是低速设备,但刷新时间也是10ms/帧,那么一般的WINDOWS的定时器最多只能精确到50ms,要想不丢帧读取HID的设备数据,一定会用到高精度的定时器――多媒体定时器。
用到了多媒体定时器,就肯定会用到下面的几个函数 ,timeGetDevCaps,timeBeginPeriod,timeSetEvent ,遂下断点在这函数,经过一番努力,果然找到了关键的ReadFile,读完了HID的设备数据后,干什么? 按照常理,就该分析和校验了,果然,我分析的正确,正是如此,下面是破解当时获取的关键汇编代码,因为这篇文章是几个月后写的,所以有些内容只能靠回忆了。
读懂了这些代码就完全弄明白了它的协议。
004417B2 . 8B0D D8CD9500 MOV ECX,DWORD PTR DS:[95CDD8] //取出CH1-CH4 的高位字节
004417B8 . A3 00CE9500 MOV DWORD PTR DS:[95CE00],EAX
004417BD . 33D2 XOR EDX,EDX
004417BF . 8BC1 MOV EAX,ECX
004417C1 . 8A15 D7CD9500 MOV DL,BYTE PTR DS:[95CDD7] //取出CH1通道数据
004417C7 . 25 FF000000 AND EAX,0FF
004417CC . 8BF2 MOV ESI,EDX // 这里要注意,保存CH1的低8位数据,将来用来算校验位
004417CE . 8BD0 MOV EDX,EAX
004417D0 . 83E2 03 AND EDX,3 //用0x3 来掩码 高位字节,取出CH1的高2位
004417D3 . 33DB XOR EBX,EBX
004417D5 . C1E2 08 SHL EDX,8 //将CH1 的高2位左移 8位
004417D8 . 8A1D D6CD9500 MOV BL,BYTE PTR DS:[95CDD6] //取出CH2 的通道数据
004417DE . 8D9432 DC03000>LEA EDX,DWORD PTR DS:[EDX+ESI+3DC] //计算出 CH1 的最终数据,高2位+CH1的数据 + 常数3DC
004417E5 . 8915 E0CD9500 MOV DWORD PTR DS:[95CDE0],EDX //保存CH1的数据
004417EB . 8BD0 MOV EDX,EAX
004417ED . 83E2 0C AND EDX,0C //取出CH2的高2位掩码
004417F0 . C1E2 06 SHL EDX,6 //计算出 CH2 的高2位
004417F3 . 8D941A DC03000>LEA EDX,DWORD PTR DS:[EDX+EBX+3DC] //计算出 CH2 的完整数据
004417FA . 33DB XOR EBX,EBX
004417FC . 8915 E4CD9500 MOV DWORD PTR DS:[95CDE4],EDX //保存CH2 的数据
00441802 . 8BD0 MOV EDX,EAX
00441804 . 83E2 30 AND EDX,30 //取CH3 高位 的掩码
00441807 . 8ADD MOV BL,CH //取CH3 低8位数据
00441809 . C1E2 04 SHL EDX,4 //高2位移位
0044180C . 25 C0000000 AND EAX,0C0 //取CH4 的高位掩码
00441811 . 8D8C1A DC03000>LEA ECX,DWORD PTR DS:[EDX+EBX+3DC] //计算出CH3 的完整数据
00441818 . 33D2 XOR EDX,EDX
0044181A . 8A15 D5CD9500 MOV DL,BYTE PTR DS:[95CDD5] //取CH4 低8位数据
00441820 . 890D E8CD9500 MOV DWORD PTR DS:[95CDE8],ECX //保存CH3 的数据
00441826 . 8D8482 DC03000>LEA EAX,DWORD PTR DS:[EDX+EAX*4+3DC] //计算CH4 D 数据
0044182D . A3 ECCD9500 MOV DWORD PTR DS:[95CDEC],EAX //保存CH4 的数据
00441832 . A1 DCCD9500 MOV EAX,DWORD PTR DS:[95CDDC] //取出CH5-CH8 的高位数据和标志位数据
00441837 . A8 40 TEST AL,40 // 这个标志的第6bit 是表示奇偶帧的
00441839 . 74 41 JE SHORT REFLEX.0044187C //如果是0 表示是偶数帧,那么CH5,CH6 有效,GOTO 到0044187C
0044183B . 25 FF000000 AND EAX,0FF //否则 是奇数帧
00441840 . 33D2 XOR EDX,EDX
00441842 . 8A15 DBCD9500 MOV DL,BYTE PTR DS:[95CDDB] //取CH5的低8位数据
00441848 . 8BC8 MOV ECX,EAX
0044184A . 83E1 0C AND ECX,0C
0044184D . C1E1 06 SHL ECX,6
00441850 . 8D8C11 DC03000>LEA ECX,DWORD PTR DS:[ECX+EDX+3DC] //同上,计算出CH5 的完整数据
00441857 . 8BD0 MOV EDX,EAX
00441859 . 83E2 03 AND EDX,3 //取CH6 的高2位
0044185C . 890D F8CD9500 MOV DWORD PTR DS:[95CDF8],ECX //保存CH5
00441862 . C1E2 08 SHL EDX,8
00441865 . 33C9 XOR ECX,ECX
00441867 . 8A0D DACD9500 MOV CL,BYTE PTR DS:[95CDDA] //取CH6 的低8位
0044186D . 8D940A DC03000>LEA EDX,DWORD PTR DS:[EDX+ECX+3DC] //计算出CH6 数据
00441874 . 8915 FCCD9500 MOV DWORD PTR DS:[95CDFC],EDX // 保存CH6 数据
0044187A . EB 4A JMP SHORT REFLEX.004418C6
0044187C > 25 FF000000 AND EAX,0FF
00441881 . 33D2 XOR EDX,EDX
00441883 . 8A15 DBCD9500 MOV DL,BYTE PTR DS:[95CDDB]
00441889 . 8BC8 MOV ECX,EAX
0044188B . 83E1 0C AND ECX,0C
0044188E . C1E1 06 SHL ECX,6
00441891 . 8D8C11 DC03000>LEA ECX,DWORD PTR DS:[ECX+EDX+3DC]
00441898 . 8BD0 MOV EDX,EAX
0044189A . 890D F0CD9500 MOV DWORD PTR DS:[95CDF0],ECX
004418A0 . 83E2 03 AND EDX,3
004418A3 . 33C9 XOR ECX,ECX
004418A5 . 8A0D DACD9500 MOV CL,BYTE PTR DS:[95CDDA]
004418AB . C1E2 08 SHL EDX,8
004418AE . 8D940A DC03000>LEA EDX,DWORD PTR DS:[EDX+ECX+3DC]
004418B5 . 8BC8 MOV ECX,EAX
004418B7 . C1E9 07 SHR ECX,7 // 这里也需要注意,这里是取出标志位的最高位,做判断用的
004418BA . 8915 F4CD9500 MOV DWORD PTR DS:[95CDF4],EDX // 上面的部分是算出 7- 8 通道的数据并保存
004418C0 . 890D 04CE9500 MOV DWORD PTR DS:[95CE04],ECX
004418C6 > 8BD0 MOV EDX,EAX //公共算2BIT 校验位的 部分 FLAG
004418C8 . 83F6 07 XOR ESI,7 //CH1 和 0X7 XOR ==> A
004418CB . 83E2 0F AND EDX,0F //标志位取低4位 ==>B
004418CE . 03D6 ADD EDX,ESI //标志位的低4位和A + B==>C
004418D0 . D1E2 SHL EDX,1 //C << 1 ==> D
004418D2 . 33D0 XOR EDX,EAX //D XOR FLAG ==> E
004418D4 . F6C2 30 TEST DL,30 //E & 0x30 ==>F 如果是 0 就成功了!
004418D7 . 74 3A JE SHORT REFLEX.00441913
004418D9 . A1 C4CD9500 MOV EAX,DWORD PTR DS:[95CDC4]
004418DE . 85C0 TEST EAX,EAX
004418E0 . 75 40 JNZ SHORT REFLEX.00441922
004418E2 . A1 08CE9500 MOV EAX,DWORD PTR DS:[95CE08]
004418E7 . C705 C4CD9500 >MOV DWORD PTR DS:[95CDC4],1
004418F1 . 50 PUSH EAX
004418F2 . EB 1D JMP SHORT REFLEX.00441911
004418F4 > 8B0D 08CE9500 MOV ECX,DWORD PTR DS:[95CE08]
004418FA . C705 C4CD9500 >MOV DWORD PTR DS:[95CDC4],1
00441904 . 8935 B8CD9500 MOV DWORD PTR DS:[95CDB8],ESI
0044190A . 8935 BCCD9500 MOV DWORD PTR DS:[95CDBC],ESI
00441910 . 51 PUSH ECX
00441911 > FFD5 CALL EBP
00441913 > A1 C4CD9500 MOV EAX,DWORD PTR DS:[95CDC4]
00441918 . 33F6 XOR ESI,ESI
0044191A . 3BC6 CMP EAX,ESI
0044191C .^0F84 61FEFFFF JE REFLEX.00441783
00441922 > 5F POP EDI
00441923 . 5E POP ESI
00441924 . 5D POP EBP
00441925 . 33C0 XOR EAX,EAX
00441927 . 5B POP EBX
00441928 . 59 POP ECX
00441929 . C2 0400 RETN 4
2Bit= ((((pPacket->byteCh1 ^ 0x07) + (CH_HID_REPORT[7] & 0x0F))<<1)&0x30)>>4;
当时困扰我4天的2BIT校验数据,其实用C语言一行就写完了,兴奋啊! 立刻将这个算法写进单片机的固件,插上我自己DIY的HID模拟器设备,OK,系统已经能正确的从我的设备中采集数据了。
USB 协议破解成功!
这是我第一次使用OllyDbg 解决实际问题,愚昧之处还请各位前辈批评。
总结: 有时我们在解决一个难题的时候,往往在正面不能获取进展时,不如从侧面,后面下手,所谓杀猪捅屁股,各有各的杀法!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课