加密狗破解之硬件复制和软件模拟_Rockey4ND
加密狗逆向之硬件复制和软件模拟_Rockey4NDCodeLive @ 2014-10-18
收到风声说再不发原创的帖子就要被清理了,赶紧熬夜写一篇,呵呵~~
本文主要通过对Rockey4ND狗的分析来说明如何逆向加密狗,包括硬件复制和软件模拟,不谈爆破方法。
首先说明可以硬件复制或者软件模拟的狗要具备下面几种条件:
1.有真狗的运行环境,没狗逆向一般只能爆破;
2.硬件复制要知道狗的用户密码和开发密码,软件模拟密码可以不需要;
3.程序只从狗中读取或写入数据;
4.如果程序在狗中写入了算法函数,这个一般会比较难,但如果逆向出函数算法,也可以模拟;
下面就介绍一款使用了Rockey4ND狗的软件的分析过程,软件名字当然不会说了。
Rockey4ND是无驱HID类型狗,这种截获数据方法有多种,可以用USBTrace,hid.dll替换,HOOK CreateFileA等方法, 但直接有效的方法是直接替换狗的API调用.
1.首先下载一份Rockey4ND的开发SDK,通过查看文件很容易知道,这个狗只有一个API,API定义为:
WORD WINAPI Rockey(WORD function, WORD* handle, DWORD* lp1,DWORD* lp2, WORD* p1, WORD* p2, WORD* p3, WORD* p4, BYTE* buffer);
主要功能定义(Rockey4_ND_32.h文件):
#defineRY_FIND 1 // Find Dongle
#defineRY_FIND_NEXT 2 // Find Next Dongle
#defineRY_OPEN 3 // Open Dongle
#defineRY_CLOSE 4 // Close Dongle
#defineRY_READ 5 // Read Dongle
#defineRY_WRITE 6 // Write Dongle
#defineRY_RANDOM 7 // Generate Random Number
#defineRY_SEED 8 // Generate Seed Code
#defineRY_WRITE_USERID 9 // Write User ID
#defineRY_READ_USERID 10 // Read User ID
#defineRY_SET_MOUDLE 11 // Set Module
#defineRY_CHECK_MOUDLE 12 // Check Module
#defineRY_WRITE_ARITHMETIC 13 // Write Arithmetic
#defineRY_CALCULATE1 14 // Calculate 1
#defineRY_CALCULATE2 15 // Calculate 2
#defineRY_CALCULATE3 16 // Calculate 3
#defineRY_DECREASE 17 // Decrease Module Unit
#defineRY_CALLNET 18 // NetRockey4ND arithmetic
(1) Find Dongle
Input:
function = 1
*p1 = pass1
*p2 = pass2
*p3 = pass3
*p4 = pass4
Return:
*lp1 = Rockey4ND HID
return 0 = Success, else is error code
(2) Find Next Dongle
Input:
function = 2
*p1 = pass1
*p2 = pass2
*p3 = pass3
*p4 = pass4
Return:
*lp1 = Rockey4ND HID
return 0 = Success, else is error code
(3) Open Dongle
Input:
function = 3
*p1 = pass1
*p2 = pass2
*p3 = pass3
*p4 = pass4
*lp1 = Rockey4ND HID
Return:
*handle = Opened dongle handle
return 0 = Success, else is error code
(4) Close Dongle
Input:
function = 4
*handle = dongle handle
Return:
return 0 = Success, else is error code
(5) Read Dongle
Input:
function = 5
*handle = dongle handle
*p1 = pos
*p2 = length
buffer = pointer of buffer
Return:
Fill buffer with read contents
return 0 = Success, else is error code
(6) Write Dongle
Input:
function = 6
*handle = dongle handle
*p1 = pos
*p2 = length
buffer = pointer of buffer
Return:
return 0 = Success, else is error code
(7) Generate Random Number
Input:
function = 7
*handle = dongle handle
Return:
*p1 = random number
return 0 = Success, else is error code
(8) Generate Seed Code
Input:
function = 8
*handle = dongle handle
*lp2 = seed code
Return:
*p1 = seed return code 1
*p2 = seed return code 2
*p3 = seed return code 3
*p4 = seed return code 4
return 0 = Success, else is error code
(9) Write User ID
Input:
function = 9
*handle = dongle handle
*lp1 = User ID
Return:
return 0 = Success, else is error code
(10) Read User ID
Input:
function = 10
*handle = dongle handle
Return:
*lp1 = User ID
return 0 = Success, else is error code
(11) Set Module
Input:
function = 11
*handle = dongle handle
*p1 = module number
*p2 = module content
*p3 = set whether allow decrease (1 = allow, 0 = no allow)
Return:
return 0 = Success, else is error code
(12) Check Module
Input:
function = 12
*handle = dongle handle
*p1 = module number
Return:
*p2 = 1 means the module is valid, 0 means the module is invalid
*p3 = 1 means the module can't decrease, 0 means the module can decrease
return 0 = Success, else is error code
(13) Write Arithmetic
Input:
function = 13
*handle = dongle handle
*p1 = pos
buffer = arithmetic instruction string
Return:
return 0 = Success, else is error code
(14) Calculate 1 (Hide Unit Init Content = HID high 16bit, HID low 16bit, module content, random number)
Input:
function = 14
*handle = dongle handle
*lp1 = calculate begin pos
*lp2 = module number
*p1 = input value 1
*p2 = input value 2
*p3 = input value 3
*p4 = input value 4
Return:
*p1 = return code 1
*p2 = return code 2
*p3 = return code 3
*p4 = return code 4
return 0 = Success, else is error code
(15) Calculate 2 (Hide Unit Init Content = seed return code 1, seed return code 2, seed return code 3, seed return code 4)
Input:
function = 15
*handle = dongle handle
*lp1 = calculate begin pos
*lp2 = seed code
*p1 = input value 1
*p2 = input value 2
*p3 = input value 3
*p4 = input value 4
Return:
*p1 = return code 1
*p2 = return code 2
*p3 = return code 3
*p4 = return code 4
return 0 = Success, else is error code
(16) Calculate 3 (Hide Unit Init Content = module content, module+1 content, module+2 content, module+3 content)
Input:
function = 16
*handle = dongle handle
*lp1 = calculate begin pos
*lp2 = module begin pos
*p1 = input value 1
*p2 = input value 2
*p3 = input value 3
*p4 = input value 4
Return:
*p1 = return code 1
*p2 = return code 2
*p3 = return code 3
*p4 = return code 4
return 0 = Success, else is error code
(17) Decrease Module Unit
Input:
function = 17
*handle = dongle handle
*p1 = module number
Return:
return 0 = Success, else is error code 最好先看看SDK中自带的示例程序,对狗的API一般是如何调用的。
2.分析软件,发现软件是直接使用DLL来对狗进行访问的,Rockey4ND的DLL名字是: Rockey4ND.dll,对这种直接使用DLL来调用的程序真是无语,连分析API函数特征都不需要了(如果是静态库方式调用就需要分析API地址)。
3.建立一个DLL工程,实现和狗API一样的函数: Rockey,定义如下:
WORD WINAPI Rockey(WORD function, WORD* handle, DWORD* lp1,DWORD* lp2, WORD* p1, WORD* p2, WORD* p3, WORD* p4, BYTE* buffer)
{
WORD ret = ERR_SUCCESS;
// ......后面再说
return ret;
}
Makefile内容:
LIBRARY ApiRockey4
EXPORTS
Rockey @ 1
这样DLL就导出了和加密狗一样的API, 同时也要把自己的DLL改为Rockey4ND.dll,原Rockey4ND.dll就要换一个名字,比如改为:Rockey4ND.org.dll
4.获取原狗DLL的API地址:
1)定义API函数,然后声明一个全局变量:
typedef WORD (WINAPI * api_Rockey)(WORD function, WORD* handle, DWORD* lp1,DWORD* lp2,
WORD* p1, WORD* p2, WORD* p3, WORD* p4, BYTE* buffer);
// 这个就是为了保存原API的地址
api_Rockey g_Rockey = NULL;
2)获取原API地址:
HMODULE hModule = LoadLibraryW(L"Rockey4ND.org.dll");
if(hModule != NULL)
{
// 获取原API函数地址
g_Rockey = (api_Rockey)GetProcAddress(hModule, "Rockey");
}
在DLL加载的时候运行上面的代码
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
// 加载原DLL模块
HMODULE hModule = LoadLibraryW(L"Rockey4ND.org.dll");
if(hModule != NULL)
{
// 获取原API函数地址
g_Rockey = (api_Rockey)GetProcAddress(hModule, "Rockey");
}
}
break ;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
5.数据截获
WORD WINAPI Rockey(WORD function, WORD* handle, DWORD* lp1,DWORD* lp2, WORD* p1, WORD* p2, WORD* p3, WORD* p4, BYTE* buffer)
{
WORD ret = ERR_SUCCESS;
// 这里是执行原API调用
ret = g_Rockey(function, handle, lp1, lp2, p1, p2, p3, p4, buffer);
// 根据定义,我把对狗进行数据读写的数据转化为字符串,便于记录
string strBuffer;
if((function == RY_READ) || (function == RY_WRITE))
{
if((p2 != NULL) && ((*p2) > 0))
{
// 数据转换成文本
strBuffer = data2HexString((const char *)buffer, (*p2));
}
}
// 格式所有参数
string str = formatString("Rockey(function(%d), handle(0x%04X), "
"lp1(0x%08X), lp2(0x%08X), "
"0x%04X, 0x%04X, 0x%04X, 0x%04X, "
"%s), ret = %d\r\n",
(int)(function), (handle != NULL) ? (int)(*handle) : 0,
(lp1 != NULL) ? (int)(*lp1) : 0, (lp2 != NULL) ? (int)(*lp2) : 0,
(p1 != NULL) ? (int)(*p1) : 0, (p2 != NULL) ? (int)(*p2) : 0,
(p3 != NULL) ? (int)(*p3) : 0, (p4 != NULL) ? (int)(*p4) : 0,
strBuffer.c_str(), (int)(ret));
//打印输出或者记录到文件
logOutput(str);
return ret;
}
6.把自己的DLL替换后,运行程序,如果程序没问题,所有数据都会被记录:
// 查找狗,ret = 0,表示找到狗,成功.
Rockey(function(1), handle(0x0000), lp1(0x66666666), lp2(0x77777777), 0x1111, 0x2222, 0x3333, 0x4444, ), ret = 0
// 打开狗,ret = 0,打开成功
Rockey(function(3), handle(0x0000), lp1(0x66666666), lp2(0x77777777), 0x1111, 0x2222, 0x3333, 0x4444, ), ret = 0
// 查找下一个,ret = 17, 没有找到更多的狗,失败
Rockey(function(2), handle(0x0000), lp1(0x66666666), lp2(0x77777777), 0x1111, 0x2222, 0x3333, 0x4444, ), ret = 17
// 从位置0x0008,读取0x0010长度的数据,读取到的内容是:A869BA3E70096F3C0D578FE34E5F8FD4
Rockey(function(5), handle(0x0000), lp1(0x66666666), lp2(0x77777777), 0x0008, 0x0010, 0x3333, 0x4444, A869BA3E70096F3C0D578FE34E5F8FD4), ret = 0
通过参数定义可以知道,开发商ID是0x66666666, 加密狗的4个密码也获取到了,基本密码为:0x1111, 0x2222,高级密码:0x3333, 0x4444 (我截获的密码当然不是这个啦)。
然后多运行几次程序,看看每次读取的数据是否都是相同的,如果每次都一样,那真的是要恭喜你了,但如果有调用狗内函数的功能,那就复杂了,这里暂时不讨论算法这种情况。
7.硬件复制方案:
1)既然4个密码都有了,最完美的方案就是硬件复制,从TB上面买空狗;
2)用官方工具可以直接复制,Rockey4ND_Editor.exe,这个在Rockey4ND的SDK中有,就是使用上面稍有复杂;
3)如果觉得工具有点麻烦的话,可以写一个程序,先从原狗中把数据都读出来,然后再把数据写入到新狗中;
8.软件模拟方案:
就是把我们之前记录数据的部分反过来,下面是大概的代码,一些代码不具体解释了:
WORD WINAPI Rockey(WORD function, WORD* handle, DWORD* lp1,DWORD* lp2, WORD* p1, WORD* p2, WORD* p3, WORD* p4, BYTE* buffer)
{
WORD ret = ERR_SUCCESS;
*handle = NULL;
switch(function)
{
case RY_FIND:
{
// 返回开发商ID
*lp1 = 0x66666666;
}
break ;
case RY_OPEN:
{
// 返回句柄
*handle = 0x8888;
}
break ;
case RY_FIND_NEXT:
{
// 没有更多加密狗
ret = ERR_NOMORE;
}
break ;
case RY_READ:
{
// gRockey4Data是原狗的所有数据
memcpy(buffer, gRockey4Data+(*p1), (*p2));
}
break ;
case RY_WRITE:
{
memcpy(gRockey4Data+(*p1), buffer, (*p2));
}
break ;
case RY_RANDOM:
{
*p1 = (WORD)(rand()%(0x10000));
}
break ;
case RY_SEED:
{
*p1 = (WORD)(rand()%(0x10000));
*p2 = (WORD)(rand()%(0x10000));
*p3 = (WORD)(rand()%(0x10000));
*p4 = (WORD)(rand()%(0x10000));
}
break ;
}
}
硬件复制和软件模拟不需要对原程序进行逆向,软件升级一般也不需要做任何改动,算是完美的方案,本文是通过Rockey4ND来举例说明加密狗的完美分析方法,同样适用于其他加密狗,只要程序仅对狗的数据进行访问,狗内没有算法函数的情况就可以用差不多的方法实现。
本文完,因为时间关系,没有仔细校验错误,有不对的地方还希望大家指正, 谢谢大家。
扫地僧 发表于 2014-10-19 00:51
谢谢分享,不过大大可以把代码弄的清楚些吗,根本看不明白- -
刚才用代码风格有问题。
话说你这么晚也没睡啊!
code老师不放试炼品咩
Cari 发表于 2014-10-19 00:55
code老师不放试炼品咩
软件比较敏感,主要是谈方法,关键代码都帖出来了。
codelive 发表于 2014-10-19 00:57
软件比较敏感,主要是谈方法,关键代码都帖出来了。
soga。期待后续精彩文章。
codelive 发表于 2014-10-19 00:57
软件比较敏感,主要是谈方法,关键代码都帖出来了。
简单帮你编辑了一下源码,你看这种格式行不行?
Shark恒 发表于 2014-10-19 00:58
简单帮你编辑了一下源码,你看这种格式行不行?
这样好多了,谢谢老大,我刚才试过有问题。
Shark恒 发表于 2014-10-19 00:58
简单帮你编辑了一下源码,你看这种格式行不行?
这排版好
codelive 发表于 2014-10-19 00:59
这样好多了,谢谢老大,我刚才试过有问题。
感谢你的作品,已加入精华!{:6_197:}
Cari 发表于 2014-10-19 01:01
这排版好
只是把代码仍进去了,也没做详细的处理。。。对了,你修复的那个OD有毒。。你查一下。好像感染exe