codelive 发表于 2014-10-19 00:49

加密狗破解之硬件复制和软件模拟_Rockey4ND

加密狗逆向之硬件复制和软件模拟_Rockey4ND
CodeLive @ 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来举例说明加密狗的完美分析方法,同样适用于其他加密狗,只要程序仅对狗的数据进行访问,狗内没有算法函数的情况就可以用差不多的方法实现。

本文完,因为时间关系,没有仔细校验错误,有不对的地方还希望大家指正, 谢谢大家。







codelive 发表于 2014-10-19 00:52

扫地僧 发表于 2014-10-19 00:51
谢谢分享,不过大大可以把代码弄的清楚些吗,根本看不明白- -

刚才用代码风格有问题。
话说你这么晚也没睡啊!

Cari 发表于 2014-10-19 00:55

code老师不放试炼品咩

codelive 发表于 2014-10-19 00:57

Cari 发表于 2014-10-19 00:55
code老师不放试炼品咩

软件比较敏感,主要是谈方法,关键代码都帖出来了。

Cari 发表于 2014-10-19 00:58

codelive 发表于 2014-10-19 00:57
软件比较敏感,主要是谈方法,关键代码都帖出来了。

soga。期待后续精彩文章。

Shark恒 发表于 2014-10-19 00:58

codelive 发表于 2014-10-19 00:57
软件比较敏感,主要是谈方法,关键代码都帖出来了。

简单帮你编辑了一下源码,你看这种格式行不行?

codelive 发表于 2014-10-19 00:59

Shark恒 发表于 2014-10-19 00:58
简单帮你编辑了一下源码,你看这种格式行不行?

这样好多了,谢谢老大,我刚才试过有问题。

Cari 发表于 2014-10-19 01:01

Shark恒 发表于 2014-10-19 00:58
简单帮你编辑了一下源码,你看这种格式行不行?

这排版好

Shark恒 发表于 2014-10-19 01:01

codelive 发表于 2014-10-19 00:59
这样好多了,谢谢老大,我刚才试过有问题。

感谢你的作品,已加入精华!{:6_197:}

Shark恒 发表于 2014-10-19 01:02

Cari 发表于 2014-10-19 01:01
这排版好

只是把代码仍进去了,也没做详细的处理。。。对了,你修复的那个OD有毒。。你查一下。好像感染exe
页: [1] 2 3 4 5 6 7
查看完整版本: 加密狗逆向之硬件复制和软件模拟_Rockey4ND