本帖最后由 云在天 于 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」窗口。
[C#] 纯文本查看 复制代码 [STAThread]
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 初始化授权」:
[C#] 纯文本查看 复制代码 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();
// 代码省略 ...
看到代码后,可以发现我们没有找错,许可授权在这里读取:
[C#] 纯文本查看 复制代码 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 这个方法:
[C#] 纯文本查看 复制代码 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";
// 代码省略 ...
这段代码,表示程序会优先检查加密狗内部的授权。若是加密狗不存在,则进行序列号的校验。
把狗相关的函数提取出来,分析一下调用:
[C#] 纯文本查看 复制代码
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[7];
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 即可:
[Asm] 纯文本查看 复制代码 xor eax, eax
retn 04h
后面一个有点麻烦,因为需要读取数据到内存进行验证,而数据又不能凭空出现。
把「NTReadIv」这个方法的功能翻译成白话大概就是:
1. 申请 7 个字节的数据块。
2. 读取狗的数据:从 0 开始读取 7 个字节,读到的内容写到刚才申请的数据块里面。
3. 如果数据块的内容与「NTIv」这个变量的内容一致则验证成功,否则失败。
而在此之前,我们需要知道所谓的密文是什么内容;回到构建函数 (SN -> .cctor)
[C#] 纯文本查看 复制代码
static SN()
{
Class3.lQUOYnkznSKeP(); // 不用管,空函数,删掉即可
NTCode = EncryptString.ThisIsLegalString("h9puNortcwP/Y2Kl6MDz4A0obLNWW4uD");
string_0 = EncryptString.ThisIsLegalString("kWzdAyI6OhVUwAE69SdTMW4skDH2i+uEM2mrsvDGcdiv2wWi2xU8/A==");
NTIv = EncryptString.ThisIsLegalString("jztU+R63Ke4=");
// 代码省略 ...
到这里就比较简单了,进去 EncryptString 这个类全部拷贝到 IDE,然后简单做一个界面 [自行美化吧~]:
解密按钮的事件代码按照程序里的写法糊进去就行:
[C#] 纯文本查看 复制代码
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 读取狗上面记录的内容;
[C#] 纯文本查看 复制代码 private static LicenseType NTReadLicense()
{
LicenseType free = LicenseType.Free;
try
{
byte[] pData = new byte[2];
if (NT88API.NTRead(0x10, 0x12, pData) != 0)
{
return free;
}
// 代码省略 ...
又看到了熟悉的 NTRead 函数。此时,读取的是 0x10(16) 开始的两个字节一直到 0x12 的位置。
接着往下看,一系列的判断分支,根据分支内容判断授权版本。在此之前,调查一下程序定义的授权类型:
[C#] 纯文本查看 复制代码 public enum LicenseType
{
Enterprise, // 企业版
Professional, // 专业版
Standerd, // 标准版
Trial, // 试用版
Free, // 免费版
Given // 到处发广告后挺通知作者获取的推广版授权
}
目标很清晰,就是我们功能最多、最全的「企业版」。
因此,把代码优化、精简一下:
[C#] 纯文本查看 复制代码 private static LicenseType NTReadLicense()
{
LicenseType license = LicenseType.Free;
// 读入加密狗数据
byte[] pData = new byte[2];
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:
[C#] 纯文本查看 复制代码 // 取得版本
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」看看怎么处理:
[C#] 纯文本查看 复制代码 // 代码省略 ..
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++
[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);
[C++] 纯文本查看 复制代码 // 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 编译器会加一些奇怪的导出函数不会改.. 太不美观了!
[Asm] 纯文本查看 复制代码 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, [endAddress]
mov esi, [address]
sub ecx, esi
push ecx
mov edi, [pDataBuffer]
lea eax, [oldProtect]
push eax ; OldProtect
push PAGE_READWRITE ; New Protect
push ecx ; Size
push edi ; Source Address
call [VirtualProtect]
; 回写数据
mov ecx, [esp] ; pop ecx & push ecx
mov esi, _dogData ; 指向模拟狗的数据
add esi, [address]
cld
rep movsb
pop ecx
lea eax, [oldProtect]
push eax ; OldProtect
push [oldProtect] ; New Protect
push ecx ; Size
sub edi, ecx ; edi 会增加, 所以改回去
push edi ; Source Address
call [VirtualProtect]
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 还可以直接写数据到狗里面。
太乐逆向辅助工具[C# 源码-VS2015].7z
(414.43 KB, 下载次数: 651)
fake_NT88_fix1.7z
(1.63 KB, 下载次数: 967)
Fix1:
修正 Windows 7+ 系统的崩溃问题,感谢 my1229 的测试。
--- 存档用,有错误;修正版请看上面
fake_NT88.7z
(1.55 KB, 下载次数: 416)
|