爱飞的猫 发表于 2015-9-1 21:30

加密狗模拟破解「XX地图下载器」

本帖最后由 云在天 于 2019-6-7 13:13 编辑

参考帖子:.NET破解之太乐地图下载器【非暴破】 <- 软件介绍相关请参考这篇
第一次写破解过程,如有错误还请指出。
★★ 仅供学习交流之用 请勿用于商业用途 ★★

单词表/工具
IDE: 开发环境,比如商用的 Visual Studio,免费开源的 Sharp Develop。
Encrypt: 加密
String: 字符串
Dog: 狗,在此处可理解为加密狗
.NET: 微软开发的变成框架,可使用 .NET Reflector 进行反编译。

使用到的逆向工具可以在爱盘找到:http://down.52pojie.cn/Tools/NET/
IDE 请自行搜索 :3

一、拆解文件,寻找验证
脱壳这个很简单 扔到 de4dot 神器自动脱掉了。

把处理后的文件扔到 .NET Reflector 里面,右键模组 -> Go To Entry Point 进入入口点


观察代码,程序在入口点通过建立互斥体检测程序是否已经运行,然后进入「MainForm」窗口。

private static void Main()
{
    try
    {
      bool flag;
      Mutex mutex = new Mutex(true, Application.ProductName, out flag);
      if (flag)
      {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Class2.np6LKJCzZAthI();                                 // 空的函数,不用管 (可点进去看到)
            Application.Run(new MainForm());                        // 主窗口为 MainForm, 我们点进去查看内容
            mutex.ReleaseMutex();
      }
      else
      {
            MessageBox.Show(null, "软件已开启,请确认AZMap.exe进程关闭后再启动软件。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            Application.Exit();
      }
    }
    catch (Exception exception)
    {
      LogUtil.Error(exception.Message, exception);
    }
}


在主窗口初始化的代码内,「初始化运行环境」的下面有一行很可疑,「InitLicense 初始化授权」:
public MainForm()
{
    Class2.np6LKJCzZAthI();
    this._useCache = true;
    this._useHistory = true;
    this.lastPoint = new PointLatLng();
    Control.CheckForIllegalCrossThreadCalls = false;
    this._loginWindow = new Login();
    new Thread(new ThreadStart(this.startLoginWindow)).Start();
    this.setLoginWindowsStatus("初始化运行环境...");
    this.InitializeComponent();
    this.InitLicense();                                          // 初始化授权,点进去看看
    this._commands = new MetroCommands();
    // 代码省略 ...


看到代码后,可以发现我们没有找错,许可授权在这里读取:
private void InitLicense()
{
    try
    {
      SN.RegisterProductCode = "wfg783X8Joo=";                     // 后面写了个工具解密这类文字,我们的目标是加密狗因此这行并没有什么卵用
      LicenseType lic = SN.Registered(RegisterProduct.Desktop);    // 取得授权状态。
      this.RefreshFormTitle(lic);                                  // 启用、禁用各类功能 (根据授权)
      this.StartDogListener();                                     // 开始监听加密狗事件,真直白的名字。
      // 代码省略 ...

SN 全名 Serial Number[序列号],这个类名也很直白,就是我们研究的目标了。

二、拆解验证,糊点代码
点进去 SN 这个类,然后单击 Reflector 界面最底部的「Expand Methods」展开这个类所有代码,方便分析:


把全部文件拷贝到 Visual Studio 或文字编辑器备份,定位到 Registered 这个方法:
    public static LicenseType Registered(RegisterProduct regProduct = 1)
    {
      if (NTFind() == 0)
      {
            if (NTLogin() && NTReadIv())
            {
                return NTReadLicense();
            }
            return LicenseType.Trial;
      }
      string path = GetLocalAZMapPath(regProduct) + "azmap_4";
      string str2 = GetLocalPath(regProduct) + "azmap_check_4";
      // 代码省略 ...

这段代码,表示程序会优先检查加密狗内部的授权。若是加密狗不存在,则进行序列号的校验。

把狗相关的函数提取出来,分析一下调用:

    private static int NTFind()
    {
      return NT88API.NTFindFirst(NTCode);
    }

    private static bool NTLogin()
    {
      bool flag = false;
      try
      {
            if (NT88API.NTLogin(string_0) == 0)
            {
                flag = true;
            }
      }
      catch (Exception)
      {
      }
      return flag;
    }

    private static bool NTReadIv()
    {
      bool flag = false;
      try
      {
            byte[] pData = new byte;
            if ((NT88API.NTRead(0, 7, pData) == 0) && (Encoding.Default.GetString(pData) == NTIv))
            {
                flag = true;
            }
      }
      catch (Exception)
      {
      }
      return flag;
    }

NTFind:   调用 API 「NTFindFirst」 查询第一个加密狗的存在,返回 0 表示成功。
NTLogin:调用 API 「NTLogin」 尝试登录,返回 0 表示成功 (加了容错处理,所以代码看起来比较怪),方法返回 true 表示成功。
NTReadIv: 调用 API 「NTRead」 读入加密狗数据,并与内置的密文进行比对验证,方法返回 true 表示获取成功并验证通过。
NTReadLicense: 读取狗上面的数据,根据数据返回相应的授权信息。

前两个,直接在 DLL 修改为返回 0 即可:
xor eax, eax
retn 04h

后面一个有点麻烦,因为需要读取数据到内存进行验证,而数据又不能凭空出现。
把「NTReadIv」这个方法的功能翻译成白话大概就是:
1. 申请 7 个字节的数据块。
2. 读取狗的数据:从 0 开始读取 7 个字节,读到的内容写到刚才申请的数据块里面。
3. 如果数据块的内容与「NTIv」这个变量的内容一致则验证成功,否则失败。

而在此之前,我们需要知道所谓的密文是什么内容;回到构建函数 (SN -> .cctor)

static SN()
{
    Class3.lQUOYnkznSKeP();   // 不用管,空函数,删掉即可
    NTCode = EncryptString.ThisIsLegalString("h9puNortcwP/Y2Kl6MDz4A0obLNWW4uD");
    string_0 = EncryptString.ThisIsLegalString("kWzdAyI6OhVUwAE69SdTMW4skDH2i+uEM2mrsvDGcdiv2wWi2xU8/A==");
    NTIv = EncryptString.ThisIsLegalString("jztU+R63Ke4=");
    // 代码省略 ...


到这里就比较简单了,进去 EncryptString 这个类全部拷贝到 IDE,然后简单做一个界面 [自行美化吧~]:


解密按钮的事件代码按照程序里的写法糊进去就行:

      private void btnDecrypt_Click(object sender, EventArgs e)
      {
            txtPlain.Text = EncryptString.ThisIsLegalString(txtSource.Text);
      }

解密后,可以看到这样的内容:
NTCode: 1234567890arctiler
string_0: 3eee0d60fbb583e1bf33c6990d5f9e0d
NTIv: azmap09
顺便: SN.RegisterProductCode 的文字解密后是「desktop」,可理解为桌面版。

此时,可以知道加密狗从 0 到 7 的内容是 azmap09 这一串字符了,记录下来:


当这三个验证都通过后,程序就会调用 NTReadLicense 读取狗上面记录的内容;
private static LicenseType NTReadLicense()
{
    LicenseType free = LicenseType.Free;
    try
    {
      byte[] pData = new byte;
      if (NT88API.NTRead(0x10, 0x12, pData) != 0)
      {
            return free;
      }

    // 代码省略 ...


又看到了熟悉的 NTRead 函数。此时,读取的是 0x10(16) 开始的两个字节一直到 0x12 的位置。
接着往下看,一系列的判断分支,根据分支内容判断授权版本。在此之前,调查一下程序定义的授权类型:
public enum LicenseType
{
    Enterprise,    // 企业版
    Professional,// 专业版
    Standerd,      // 标准版
    Trial,         // 试用版
    Free,          // 免费版
    Given          // 到处发广告后挺通知作者获取的推广版授权
}
目标很清晰,就是我们功能最多、最全的「企业版」。

因此,把代码优化、精简一下:
private static LicenseType NTReadLicense()
{
    LicenseType license = LicenseType.Free;
   
    // 读入加密狗数据
    byte[] pData = new byte;
    if (NT88API.NTRead(0x10, 0x12, pData) != 0)
    {
      return LicenseType.Free;
    }
   
    string str = Encoding.Default.GetString(pData);
    if (str != null)
    {
      // 根据应用版本判断授权版本
      switch (ApplicationConfig.Version) {
            case 0:
                switch(str) {
                  case "-1":
                        license = LicenseType.Enterprise;   // 企业版
                        break;
                  case "-3":
                        license = LicenseType.Professional;
                        break;
                  case "-5":
                        license = LicenseType.Standerd;
                        break;
                  
                  default:
                        license = LicenseType.Free;
                }
                break;
            case 1:
                switch(str) {
                  case "-2":
                        license = LicenseType.Professional;
                        break;
                  case "-4":
                        license = LicenseType.Standerd;
                        break;
                  
                  default:
                        license = LicenseType.Free;
                }
                break;
            case 2:
                if (str == "-9") {
                  license = LicenseType.Enterprise;         // 企业版
                } else {
                  license = LicenseType.Free;
                }
                break;
      }
    }
    R.Instance.SetLicenseType(license);
    R.Instance.SetDogLicenseType(license);
    return license;
}


得出结论,加密狗授权信息是根据加密狗里面的数据以及 ApplicationConfig.Version 里面的数据判断的。而 Version 这个数据需要调查一下是 0、1 还是 2:
// 取得版本
public static int get_Version()
{
    int result = 1;
    if (getValue("Version") != null)
    {
      int.TryParse(getValue("Version"), out result);// 读取 Version 属性
    }
    return result;
}

// 取得属性值
private static string getValue(object key)
{
    try
    {
      // 竟然是一个 SQLite 数据库
      object obj2 = SQLiteHelper.ExecuteScalar(conn, "select value from Config where key = @key", new SQLiteParameter[] { new SQLiteParameter("@key", key) });
      if (obj2 == null)
      {
            return null;
      }
      return obj2.ToString();
    }
    catch
    {
      return null;
    }
}

// 回到类初始化查看初始化代码 ..
static ApplicationConfig()
{
    Class3.lQUOYnkznSKeP();
    conn = null;
    connLock = new object();
    inited = false;
    initedObject = new object();
    if (conn == null)
    {
      lock (connLock)
      {
            if (conn == null)
            {
                // 数据库文件是 Config 文件,里面又一个 Config 表
                conn = SQLiteHelper.GetSQLiteConn("Config");
                if (!SQLiteHelper.ContainTable(conn, "Config"))
                {
                  SQLiteHelper.ExcuteNoneQuery(conn, "create table Config(key nvarchar(256) primary key,value nvarchar(256))", null, null);
                }
            }
      }
    }
}


而当我把数据库用工具打开时,却提示我数据库被加密或无效.. 走进去「GetSQLiteConn」看看怎么处理:

      // 代码省略 ..
      connection = new SQLiteConnection {
            ConnectionString = builder.ToString()
      };
      connection.SetPassword(DB_PASSWORD);      // 有密码
      connection.Open();
      // 代码省略 ..

// 回到类的构造函数看初始化
static SQLiteHelper()
{
    Class3.lQUOYnkznSKeP();      // 空的函数,无视它
    DB_PASSWORD = EncryptString.ThisIsLegalString("jztU+R63Ke4=");
    SQLITE_PAGE_SIZE = 0x8000;
}

熟悉的加密文字,扔到工具里解密看看,还真是这个「azmap09」。

于是糊了几行代码,取得数据库储存的代码号是 0。因此如果想让版本为企业版,加密狗的内容必须是 -1 了。


三、戏弄程序,编写狗狗
首先摸清目标狗的导出函数:


然后编写个相同的导出函数;我这里首先用的是 C++
// 文件: FakeLib.h

// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the FAKELIB_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// FAKELIB_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef FAKELIB_EXPORTS
#define FAKELIB_API extern "C" __declspec(dllexport) int __stdcall
#else
#define FAKELIB_API __declspec(dllimport)
#endif

FAKELIB_API _NT3DESCBCDecrypt(char* vi, char* pDataBuffer, int length);
FAKELIB_API _NT3DESCBCEncrypt(char* vi, char* pDataBuffer, int length);
FAKELIB_API _NTCheckLicense(int licenseCode);

FAKELIB_API _NTFindFirst(char* NTCode);
FAKELIB_API _NTGetHardwareID(char* hardwareID);

FAKELIB_API _NTLogin(char* LoginCode);
FAKELIB_API _NTLogout();
FAKELIB_API _NTRead(int address, int Length, char* pDataBuffer);
FAKELIB_API _NTWrite(int address, int Length, char* pDataBuffer);


// FakeLib.cpp : Defines the exported functions for the DLL application.
//

#include <windows.h>
#include "FakeLib.h"

int __stdcall _NT3DESCBCDecrypt(char* vi, char* pDataBuffer, int length)
{
    return 0;
}

int __stdcall _NT3DESCBCEncrypt(char* vi, char* pDataBuffer, int length)
{
    return 0;
}

int __stdcall _NTCheckLicense(int licenseCode)
{
    return 0;
}

int __stdcall _NTFindFirst(char* NTCode)
{
    return 0;
}

int __stdcall _NTGetHardwareID(char* hardwareID)
{
    return 0;
}

int __stdcall _NTLogin(char* LoginCode)
{
    return 0;
}

int __stdcall _NTLogout()
{
    return 0;
}

char* _dogData = "azmap09.........-1";
int __stdcall _NTRead(int address, int endAddress, char* pDataBuffer)
{
    int size = endAddress - address;
    DWORD oldProtect;
    VirtualProtect(pDataBuffer, size, PAGE_READWRITE, &oldProtect);
    memcpy(pDataBuffer, _dogData + address, size);
    VirtualProtect(pDataBuffer, size, oldProtect, &oldProtect);
    return 0;
}

int __stdcall _NTWrite(int address, int Length, char* pDataBuffer)
{
    return 0;
}

最后带上修正导出表的 def 文件:
LIBRARY FakeLib
EXPORTS
    NT3DESCBCDecrypt=_NT3DESCBCDecrypt
    NT3DESCBCEncrypt=_NT3DESCBCEncrypt
    NTCheckLicense=_NTCheckLicense
    NTFindFirst=_NTFindFirst
    NTGetHardwareID=_NTGetHardwareID
    NTLogin=_NTLogin
    NTLogout=_NTLogout
    NTRead=_NTRead
    NTWrite=_NTWrite

编译后更名 NT88.dll,扔进去看看:


企业版。嘛,就这样了 :3

附、汇编重写
使用 FASM 制作,生成的文件超级小。
还有个原因就是 MSVC 编译器会加一些奇怪的导出函数不会改.. 太不美观了!

format PE GUI 4.0 DLL
entry DllMain
include 'win32ax.inc'

section '.text' code readable executable

; 入口函数
proc DllMain hinstDLL,fdwReason,lpvReserved
mov al, 1
ret
endp

; 读取狗数据
; int NTRead(int address, int endAddress, char* pDataBuffer);
proc NTRead address,endAddress,pDataBuffer
local oldProtect:DWORD
push esi
push edi

mov ecx,
mov esi,
sub ecx, esi
push ecx
mov edi,

lea eax,
push eax                      ; OldProtect
push PAGE_READWRITE            ; New Protect
push ecx                      ; Size
push edi                      ; Source Address
call

; 回写数据
mov ecx,             ; pop ecx&push ecx
mov esi, _dogData            ; 指向模拟狗的数据
add esi,
cld
rep movsb

pop ecx
lea eax,
push eax                      ; OldProtect
push             ; New Protect
push ecx                      ; Size
sub edi, ecx                      ; edi 会增加, 所以改回去
push edi                      ; Source Address
call
pop edi
pop esi

xor eax, eax
ret
endp

; 填充用函数 - 3 参数
proc NT3Arg a1, a2, a3
xor eax, eax
ret
endp

; 填充用函数 - 1 参数
proc NT1Arg a1
xor eax, eax
ret
endp

; 填充用函数 - 0 参数
proc NT0Arg
xor eax, eax
retn
endp

; 狗数据
section '.data' data readable writeable
_dogData db 'azmap09.........-1'

; 导入表
section '.idata' import data readable writeable
library kernel,'KERNEL32.DLL'
import kernel,\
    VirtualProtect, 'VirtualProtect'

; 导出表
section '.edata' export data readable
export 'NT88.DLL',\
    NT3Arg,'NT3DESCBCDecrypt',\
    NT3Arg,'NT3DESCBCEncrypt',\
    NT1Arg,'NTCheckLicense',\
    NT1Arg,'NTFindFirst',\
    NT1Arg,'NTGetHardwareID',\
    NT1Arg,'NTLogin',\
    NT0Arg,'NTLogout',\
    NTRead,'NTRead',\
    NT3Arg,'NTWrite'

section '.reloc' fixups data readable discardable


★★ 仅供学习交流之用 请勿用于商业用途 ★★
P.s. 没看错的话 NT88.dll 还可以直接写数据到狗里面。




Fix1:
修正 Windows 7+ 系统的崩溃问题,感谢 my1229 的测试。

--- 存档用,有错误;修正版请看上面

爱飞的猫 发表于 2015-9-10 05:24

cinesejin 发表于 2015-9-10 03:48
请问各位破解加密狗的软件必须先自己买个 加密狗的吗

不一定。这篇帖子通过反编译程序代码逆向出了应该在加密狗上的数据。

但是,新版本可能会针对模拟狗的不完善之处进行防破解措施,例如随机读取其它位置的内容进行验证。

xzdzbj 发表于 2015-11-24 13:32

技术贴,虽然我是个搬砖的,但是对于这种高科技人才,我是佩服的五体投地。请收下我的膝盖在地上砸的坑吧。    出了太乐地图5.0.2版本了以后还会出破解吗?

狼牙 发表于 2015-9-1 21:34

吸吸人气

FantasyOwl 发表于 2015-9-1 21:37

分析的好详细,感谢分享

来自星星的我 发表于 2015-9-1 21:38

沙发么??太激动了,撸主的文章让在下受益匪浅!!!

hyj5719 发表于 2015-9-1 21:48

前来学习技术贴。

xyzxf 发表于 2015-9-1 21:58

汇编很流弊。。。。

segasonyn64 发表于 2015-9-1 23:00

看样子是龙脉的加密狗啊,这个加密狗貌似挺简单的,可以直接在读写狗的DLL里面修改返回信息

my1229 发表于 2015-9-1 23:10

本帖最后由 my1229 于 2015-9-1 23:29 编辑

支持楼主的详细教程,学习了!将NT88.dll扔进去安装目录复盖原文件,软件运行出错?

zailush 发表于 2015-9-1 23:17

谢谢分享,学习学习

爱飞的猫 发表于 2015-9-2 00:16

my1229 发表于 2015-9-1 23:10
支持楼主的详细教程,学习了!将NT88.dll扔进去安装目录复盖原文件,软件运行出错?

我只在 xp 下测试过,待我扔 7 里面看看
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 加密狗模拟破解「XX地图下载器」