基于时间函数Hook的MATLAB绘图美化工具:破解版实战指南

Abstract

FigureBest作为一款科研绘图美化工具,它可以帮助科研人员快速美化Matlab绘图,提高绘图效率,使图像生产更适合科研论文。该工具在试用版本中会有过期问题,针对这一问题,可即通过更改系统时间来绕过试用限制。然而直接更改系统时间,可能导致浏览器等其他软件出现问题,这是因为许多应用程序依赖于客户端的时间戳验证。针对上述问题,本文提出了一个时间函数hook方案,可以仅更改Matlab获取的系统时间,而不改变其他软件获取的时间戳,解决试用限制问题的同时,能够保证系统的正常运行。这种方法同样也适用于破解其他的软件的证书有效期验证。

keywords:科研绘图,时间函数Hook,时间戳,有效期验证

Instruction

绘图风格在学术论文中的地位重中之重,一个清晰高端的图像,能够极大提高论文的观感,使你的论文更加专业、更易阅读。

单论数据可视化工具,Python、Matlab、origin、echarts、Adobe AI等工具都可以帮我们快速绘制出图像,然而这些工具输出的原始图像都达不到顶刊论文中的视觉效果,因此后续的美化工作就是非常重要的了,上述软件中Python对图像的美化能力较低,因此图像的美化上限不高,不建议使用。origin和Adobe AI虽然图像定制能力很强,但是不支持使用代码进行数据处理,在绘制一些复杂数据集的时候,我们通常会涉及到对原始数据的二次处理,使得我们观测的实验数据能够直接导出图像,而无需反复运行预处理脚本,如:用python或java进行预处理的步骤可以直接使用Matlab进行数据→绘图的一条龙服务来代替。因此综合考虑可扩展性和美化上限,Matlab和echarts,都是不错的选择。这其中,Matlab的数据处理更强,而echarts外观定制能力更强,可根据需要进行选择。

本文中主要是使用Matlab进行科研绘图美化工作,对于传统的美化工作,我们总需要运行各种代码,来调整图像边框、颜色、线条粗细等,来自代码的调整无法做到所见即所得,虽然我们可以借助Matlab官网画廊(https://www.mathworks.com/products/matlab/plot-gallery.html#)进行风格参考,但直接使用代码进行美化工作依然会花费巨大的时间成本。

针对这个痛点,b站up主【图通道】在2021年4月19日推出了一个成熟的科研绘图美化工具 FigureBest 4.0  虽然支持的功能较少,但可以直接下载进行FigureBest 4.0功能体验  。同时作者也在GitHub上开源了FigureBest-2.0 项目(https://github.com/tutusjtu/FigureBest-2.0)以供大家参考学习。最近(2023年10月14日)作者进一步推出了功能更加强大的 FigureBest 4.6版本,并在11月份开放了了FigureBest 4.6 试用版下载 。FigureBest主要是通过自动识别Matlab绘图窗口,并调用各类颜色、边框、线条调整函数,对图像进行实时调整,并内置了适合科研论文的绘图风格极大提高了绘图效率,通过该工具美化后的图像,可以轻松达到科研论文标注,只需要花费几秒钟的点击时间,真正做到了点击就送,并且软件仅占用3MB的空间,体积非常小,作为Matlab插件,即插即用。

然而该试用版本将会在11月1日过期(本人用的是10月内测版,最新公测版是11月30日过期)。而正版软件需要收费,且费用不低。

安全老油子们对这种试用过期的软件已经见怪不怪了,早在15年左右,《火哥内核VT》系列课程中6.10小节【SIDT 拦截2 反VT检测讲解】视频中17:07秒处,就提到了:使用过期证书签名的驱动,如何在非测试模式下,通过更改系统时间来启动。同理对于很多证书相关的密码学算法,不使用联网通信的情况下,只能通过获取系统时间的方式进行验证。也就是说通过更改系统时间我们就可以轻松启动此试用版本,无论当前真实的时间如何。

然而这样更改时间的方式虽然可以破解试用软件的时间限制,却同样造成了浏览器无法正常工作的情况,由于网络应用一致是安全工程师重点保护的对象,服务器经常需要校验客户端的时间戳以确定客户端的合法性,很多密码学方案的底层方案也对时间戳的有效性强依赖。这就导致,更改系统时间后,我们访问互联网的很多网站(如:chatgpt)都会发生未知错误,且浏览器只是已知的可能发生错误的重灾区,更多其他软件笔者虽然没有逐一测试,但产生错误的概率也是非常大的,比如类似【百度网盘】【英雄联盟】【原神】【MobaXterm】等这些对于网络通信强依赖的应用,都可能发生错误。

那么能否有一种方式,仅仅更改Matlab获取到的系统时间,而不改变其他软件获取到的时间戳呢?答案是,能。这也就是本文提出的解决方案:时间函数hook。这个方案广泛适用于任何时间前测类的软件,比如当年有名的ARK工具PC-Hunter其试用证书存在有效期,我们同样可以通过这种方式进行破解。

Contribution

  • 提出了一种基于时间函数HOOK的软件试用期延长方案
  • 使用C语言开发实现了【windows平台所有时间函数的infinite Hook通用注入dll】
  • 提出了从Figure Best入口函数处动态注入dll的实现思路

Find WIN32 Time API Function

API Penetration Testing Process

根据我们的猜想,软件中应当是调用了Matlab时间戳函数now作为license.txt凭证的校验比对时戳,因此我们只需要找到如何劫持matlab的内置函数now,让他始终返回一个小于11月1日的固定值,比如始终返回2023年8月1日,即可使得license的校验结果始终处在有效期内。

我们首先尝试在百度上,查找C语言中如何获取系统时间,网上给出的代码为:

time_t timer0;
timer0 = time(NULL);
printf("%lld\n", timer0);

下一步我们就需要看一下,time函数是调用了哪一个系统的通用API来实现的,因为考虑到复用性问题,一般可供用户层调用的功能类似的API函数一般在5个以内,否则微软后期的维护将难以进行,用户层调用的方式越多,windows操作系统所需维护的函数越多,我估计他们应该不会想加班维护一些p用没有的东西。更何况是获取系统时间这个简单的功能。

我们搜索一下C语言的time函数是调用了哪个WIN32 API,不出意外的没有找到结果,毕竟这个问题过于底层,而且小众。接下来我们只能通过自己实验验证,来确定time函数的调用方式。

我们使用CE的Open Process → File → Create Process功能创建一个进程,CE调试器会自动在Entry Point处对程序进行断点

接下我们在PE文件的【增量链接】汇编区,找到测试程序的main入口(注意要编译成debug模式才有这些注释,否则全是地址编号,可读性很低,这应该不用多说),按空格键跳转到程序main入口处

在入口函数首行,没多远的位置,找到了我们代码的关键字符串,往前推一个函数,就是我们自己调用的 time(NULL); 按空格跳转进去。

跟进去后发现,time函数所调用的是ucrtbased.time64 动态链接库函数。熟悉C++开发的人应该很熟悉这个ucrtbased.dll,因为换到没有装Microsoft Visual Studio 2015的电脑上运行程序的时候,总是会报错找不到ucrtbased.dll。这个dll是一个服务于c++程序的二次封装dll。既然windows出产的系统文件里没有它,说明它还不够底层。也就是ucrtbased.time64应该还会调用更底层的WIN32 API,那个API才是我们的目标。

跟到time64函数中可以发现,time64进一步调用了KERNELBASE.GetSystemTimeAsFileTime,而KERNELBASE.dll是系统出场时自带的,满足我们的要求,因此我们首先尝试对KERNELBASE.GetSystemTimeAsFileTime进行修改。

我们首先使用断点hook来测试一下对GetSystemTimeAsFileTime 能否更改Matlab时间函数 now的返回值。我们首先断点该函数,并获取到时间响应值为01DA0D44C709EB77 记录下该值后

通过阅读该函数汇编可知,rax作为时间戳,将会将低位分别赋值到,rcx所指向的地址处,和+4字节的偏移地址处,我们在运行mov [rcx],eax之前对rax值进行修改,将其改为固定值,即可实现时间函数hook

此时切换到Matlab运行两侧时间戳函数,可以发现两次获取到的时间不同,这说明 GetSystemTimeAsFileTime 函数并不在 now函数的调用分支上,我们还需要收集其他可能的API函数进行测试。

Hook Code Revision

当我们还是初学者时,有时无法确定自己修改的汇编是否正确,此时可以使用chat-gpt帮我们做订正,以确定我们的写法是否正确,提示词模板如下:

经过调试发现KERNELBASE.GetSystemTime 并不在matlab now的调用路径上,KERNELBASE.GetSystemTimeAsFileTime确实调用了,但是hook之后时间没有改变,你看我hook的对吗KERNELBASE.GetSystemTimeAsFileTimemov eax,7FFE0014mov rax,[rax] mov rax,0x01DA0CAD76950693  #我的hook点返回一个固定时间戳mov [rcx],eax shr rax,20mov [rcx+04],eax 你的Hook代码似乎已经正确地目标到了KERNELBASE.GetSystemTimeAsFileTime,这是一个Windows API函数,Matlab可能会使用它来获取系统时间。你在Hook中将返回值更改为一个固定时间戳,这应该会使Matlab的now函数返回固定时间。

Collect And Test More Available APIs

当我们熟悉上述测试流程之后,就可以对每个潜在的系统API进行Hook后再测试其能否改变Matlab时间函数的行为,直到Hook后的函数可以使得两次调用 now的结果返回同一个时间戳为止。

那么如何收集API呢,以往我们只能靠看黑客相关的论坛,或者微软官方文档,来找寻可能调用的API,如今我们已经定位到了KERNELBASE中存在时间函数,那么根据软件架构的合理性,相似的功能应该会被划分到同一个dll中,由此我们可以让GPT根据我们已经找到的API去推测其他可能的API函数。提示词模板如下:

我觉得问题在于,matlab 的now没有调用GetSystemTimeAsFileTime这个函数,你还知道什么系统函数可以获取时间吗,我换一个hook函数试试 你是对的,Matlab的now函数可能并不直接调用GetSystemTimeAsFileTime,因此Hooking该函数可能不会影响Matlab的now行为。Matlab可能使用其他系统调用或机制来获取时间。 另一个常用的获取系统时间的Windows API函数是GetSystemTime,但你已经尝试过并发现它不在now的调用路径上。 你可以尝试Hook以下其他可能与时间获取相关的函数,但请注意,这仅仅是一种尝试,因为Matlab的内部行为可能因版本和配置而异。以下是一些可能与时间获取有关的Windows API函数: 请注意,Hooking这些函数仍然可能不会影响Matlab的now函数,因为Matlab可能使用自己的内部实现来获取时间。此外,修改Matlab的内部行为可能会违反MathWorks的许可协议,因此请谨慎行事,并确保你有合法的使用方式。

经过GPT的提示,我们又分别测试了GetSystemTimeGetLocalTime函数,通过分析汇编代码,我们分析对于GetLocalTime函数的代码,可以在如下位置Hook Rax寄存器实现固定时间戳的功能

对函数进行hook后我们再次运行 now函数测试修改效果:

可以看到不管我运行多少次 now 函数,其返回的时间戳均固定不变,支持我们已经能确定需要Hook的系统时间函数。

Auto Assemble DLL Development

Time Function Reconstruction

通过之前的分析不难看出,GetLocalTime函数的汇编行数较多,虽然修改起来并不复杂,但是由于对于rax的修改是在汇编中部进行的,且由于系统函数的infinite hook通常涉及到 16字节左右的代码空间,这回覆盖大量现有的汇编代码,导致通过汇编Hook重构该函数的工作量非常大。

相比之下,根据微软对于函数的官方说明我们不难看出,如果只是返回固定时间戳的话,通过C代码重写该函数是非常简单的,只需填充一下LPSYSTEMTIME 结构就行了。

void GetLocalTime(  [out] LPSYSTEMTIME lpSystemTime);[out] lpSystemTime A pointer to a SYSTEMTIME structure to receive the current local date and time. typedef struct _SYSTEMTIME {    WORD wYear;    WORD wMonth;    WORD wDayOfWeek;    WORD wDay;    WORD wHour;    WORD wMinute;    WORD wSecond;    WORD wMilliseconds;} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;

因此综合考虑时间成本,本文选择使用API infinite hook进行实现。

我们首先通过如下代码填充了SYSTEMTIME结构体,重写了自定义时间函数:

void myGetLocalTime(_Out_ LPSYSTEMTIME lpSystemTime) {
    lpSystemTime->wYear = 2023;
    lpSystemTime->wMonth = 8;
    lpSystemTime->wDayOfWeek = 2;
    lpSystemTime->wDay = 1;
    lpSystemTime->wHour = 21;
    lpSystemTime->wMinute = 13;
    lpSystemTime->wSecond = 20;
    lpSystemTime->wMilliseconds = 895;

}

接下来我们使用之前编写的自动化注入类进行API 无限hook操作:

void mySystemTimeHook()
{

    HANDLE handle = GetCurrentProcess();
    ULONG_PTR myGetLocalTimeAddr = (ULONG_PTR)myGetLocalTime;
    injectHelper::apiHook(handle, "KERNELBASE", "GetLocalTime", myGetLocalTimeAddr);

}

Far Jump Hardcode

这里面的重点是升级我们的generateJmpCode方法,该方法在之前破解AxMath的时候用过,但是只适配了x86汇编,本次更新将对该函数进一步升级以适应x64的远跳转:

SmartBytes generateJmpCode(LONG_PTR from, LONG_PTR to)
   {
       LONG_PTR offset = to - (from + 2);
       if (abs(offset) <= MAXINT8)
       {
           byte tempCode[2] = { 0xEB,0x00 };//0xEA
           tempCode[1] = (byte)offset;
           SIZE_T length = sizeof(tempCode) / sizeof(tempCode[0]);
           return getDynamicBytes(tempCode, length);;
       }
       offset = to - (from + 3);
       if (abs(offset) <= MAXINT16) {
           byte tempCode[3] = { 0xE9,0x00,0x00 };//0xEA
           bit_converter::i16_to_bytes(offset, false, tempCode + 1);
           SIZE_T length = sizeof(tempCode) / sizeof(tempCode[0]);
           return getDynamicBytes(tempCode, length);;
       }
       offset = to - (from + 5);
       if(abs(offset) <= MAXINT32) {

           byte tempCode[5] = { 0xE9,0x00,0x00,0x00,0x00 };//0xEA
           bit_converter::i32_to_bytes(offset, false, tempCode + 1);
           SIZE_T length = sizeof(tempCode) / sizeof(tempCode[0]);
           return getDynamicBytes(tempCode, length);;
       }
       else {
           offset = to;
           //jmp 0x7FF6C4580000
           byte tempCode[14] = { 0xFF,0x25,0x00,0x00,0x00,0x00,  0x00,0x00,0x58,0xC4,0xF6,0x7F,0x00,0x00 };
           bit_converter::i64_to_bytes(offset, false, tempCode + 6);
           SIZE_T length = sizeof(tempCode) / sizeof(tempCode[0]);
           return getDynamicBytes(tempCode, length);;
       }


   }

这里需要注意的是,远跳转硬编码 FF 25的编码规则为:

FF 25 + 4个字节00 + 8个字节绝对地址 (也就是int64)

相比之下,元函数调用的硬编码 FF 15 的编码更怪了:

FF 15 + 02 00 00 00 +EB 08 + 8个字节绝对地址

我找GPT问了一下编码规则,但是它给出的答复给我绕晕了。可以暂时理解为 02 00 00 00EB 08 都是固定值。

Auto Assemble DLL Injection

最后就是如何将编写好的Hook DLL 赶在 FigureBest运行之前,注入到Matlab进程里,如果像之前Axmath破解那样直接修改Matlab的导入表来注入dll,可能会造成外来某些需要正常调用时间函数的代码运行不正常,为了避免这种情况,最好在常规状态下不对matlab进行注入,只有运行Figure Best的时候才运行注入程序。

经过观察发现,FigureBest存在一个入口脚本 FB.m,这个脚本是未被加密的可以随意编辑,因此我们在脚本首行添加一段dll加载代码,即可赶在FigureBest之前进行注入:

loadlibrary('userplugins/dllpoc_64.dll');
%% show msg
disp('FB is starting...')

%% encoding check
% ---------------------------
% PASS: UTF8 OR GBK
% WARNING: 'OTHERS'
DefaultCharacterSet = feature('DefaultCharacterSet');
locale = feature('locale');
encoding = locale.encoding;
if ~strcmp(DefaultCharacterSet,'GBK') | ~strcmp(encoding,'GBK')
    disp(['[DefaultCharacterSet]' DefaultCharacterSet '; ' '[encoding]' encoding])
    disp('Chaos character MAY occur if were NOT GBK, while main functions wonnot be affected!')
    disp('Please change the encoding IF possible')
end

%% set path
% ---------------------------
folderOfThis = fileparts(mfilename('fullpath')); % get the folder of current .m
addpath(genpath(folderOfThis)); % add path and subpath (temporary)
savepath % add path and subpath (permanent)
cd(folderOfThis) % change current folder
clear folderOfThis

%% open
FigureBest_v4

最后需要注意的是,Matlab加载dll需要配一个.h文件才能正常加载,这个文件只需要和dll名称一样即可(如:dllpoc_64.h),文件内容可以是空的!

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空