羽夏逆向破解日记簿——逆向迅捷录屏的思考
Posted 羽夏的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了羽夏逆向破解日记簿——逆向迅捷录屏的思考相关的知识,希望对你有一定的参考价值。
看前必读
“迅捷录屏”是免费录屏软件,但有VIP
功能,而其中的录屏组件是商业软件,本篇文章仅仅介绍 逆向分析过程 和 关于开发软件防止逆向的思考 ,不会提供任何成品破解补丁,仅限用于学习和研究目的,否则,一切后果自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述侵害合法权益的内容。如果您喜欢其包含的录屏组件,请支持正版软件,购买注册,得到更好的正版服务。如有侵权到您的权益,请联系本人整改。
主角和工具
- Detect it easy 1.01
- PE Explorer
- IDA 7.5
- X32DBG
- 迅捷录屏
- ZDSoft SDK
浅析迅捷录屏
迅捷屏幕录像工具支持游戏录制、直播录屏,教学视频录制等,保存本地后随时学习。支持多种视频录制和多种格式输出,且声画同步多种使用场景,是一个无论是上班族还是学生党都适用的录屏软件。当然,前面的只是官方的措辞,功能比较强,但如果免费,限制多多,广告多多。我们先看看这个软件:
打开这个软件后,迎面而来的就是推广会员的广告,毕竟要恰钱嘛,无可厚非:
关掉这个广告,整体来说界面挺好的,UI
设计挺合理,然后看看设置里的东西:
设置的功能挺多的,但是要修改某些设置,会让你登录。我们看看它的录屏功能:
上面明码标价,普通用户有诸多限制,吧啦吧啦。先不管,我们来看看录屏效果:
一个倒计时,挺合理:
然后就进入录屏阶段,自己感兴趣测试一下,结束录屏后我们看看视频:
视频挺清晰的,但是右下角有一个万恶的水印。
深入迅捷录屏
接下来就深入看看它到底是怎样实现功能的,可以看到它的安装内容有大量不属于迅捷录屏的东西,比如下面图片所示的东西:
DuiLib
肯定不是迅捷的,这个是一个DirectUI
库,在Github
以MIT
协议开源,不过有坑,或多或少有Bug
。应该是互盾公司基于该库进行二次开发修补得到。
ffmpeg
是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。是开源的,遵守LGPL
/GPL
协议的,如果使用这个东西就必须带上它的License
,如果你的项目用到它的代码就必须开源。对于该款录屏程序,最起码应该带上它的License
以表示尊重开源成果,然而翻遍整个文件夹,都没找到。
最可疑的就是下面的两个文件夹:
根据名称命名,似乎不属于互盾公司开发的产品,点击去看看任意一个Dll
,都不是互盾公司的数字签名,如下是其中一个:
看样子是某深圳公司的,我们从网上查查看这个组件是何方神圣。
点进去看看,看到logo
正是数字签名中的ZD SOFT
,对这套组件的开发者和来源一定程度上的验证,我们还看到了下面的表格:
并且这个SDK
价格不菲,属于终身制购买商业使用权:
然后我们下载它的SDK
,下载是免费的,解压后有三个版本,我们随便看一个,发现和那个录屏软件的东西是一模一样的:
根据上面的价格我们也明白,互盾公司为什么卖这个略高的价钱,为什么也有终身制会员这个玩意,毕竟这个SDK
也挺贵的:
至此,我们对这款录屏软件有了一个猜测:互盾公司向紫迪公司购买了该SDK
,然后基于开源项目进行构建,使用DuiLib
作为界面开发库,使用C++
语言进行开发,如果是正确的,所有的技术细节基本都暴露了出来。
好,我们老套路分析,用Detect it easy
查一查:
根据上图结果证实了我的“使用C++
语言进行开发”猜想,由于没加壳,直接拖到IDA
看一看,等待分析完毕。
查一查导入表,发现有大量的DuiLib
相关的函数,坐实了界面库就是它:
然后继续检查是不是用的上述分析的录屏SDK
:
根据上面的搜索,我们基本证实了所有的猜想,甚至扒出了它的初始化录屏SDK
的相关代码和它购买的Key
,为了保护人家的合法权益,故打码处理:
既然是基于DuiLib
开发的,找它的所属文件也没找到它的UI
界面皮肤文件,我们用PE Explorer
查看一下资源,发现可疑部分:
因为DuiLib
的皮肤文件是可以使用压缩包嵌入程序资源使用,并且看到里面有PK
字样,十分有可能是界面资源包,另存用十六进制查看器查看一下:
可以看出,这个文件很符合zip
格式,然后改后缀尝试打开:
我们成功的把它的皮肤资源提取出来,使用Github
上的皮肤编辑器,能够正常打开:
分析录屏 SDK
既然人家是使用录屏SDK
,继续分析那个软件就没意思了,基本分析到这里自己就能重写一个和它一模一样的软件。如果使用录屏SDK
,就必须得到其Key
,当然你可以购买,尊重他人的开发成果。对于学习软件逆向的初学者,自然想看看里面的验证过程。如果能够你想出来,想想为什么能够被他人破解,对自己以后的软件开发成果保护提供反制思路。注意,不得将本篇文章的逆向结果用于非法用途,仅供学习思考,请尊重他人的劳动成果,如造成的损失,本人概不负责。
既然函数都是ScnLib.dll
导出的,作为破解者的身份,我肯定想要搞一个功能最全的,那就选ScnLibMax
版本的,老套路,先用Detect it easy
查一下:
既然是逆向分析了,得到Key
的算法,就必须从注册函数入手,为了方便观看。其伪代码如下:
int __stdcall ScnLib_SetLicenseW(LPCSTR pcszName, LPCSTR pcszEmail, LPCSTR pcszKey)
{
struct AFX_MODULE_STATE *v3; // eax
int result; // eax
int v5[2]; // [esp+Ch] [ebp-8h] BYREF
v3 = sub_100351ED();
AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(v5, v3);
AfxGetModuleState();
result = sub_10014A30(pcszName, pcszEmail, pcszKey);
*(v5[1] + 4) = v5[0];
return result;
}
可以看出,它调用了sub_10014A30
这个子函数,然后根据返回结果来提示是否注册成功,这个是这个SDK
的说明:
If your license key is validated and enabled successfully, the return value is TRUE. Otherwise, the return value is FALSE.
好,我们点击去看看这个函数是什么样子:
int __stdcall sub_10014A30(LPCSTR pcszName, LPCSTR pcszEmail, LPCSTR pcszKey)
{
int res; // edi
int v4; // eax
wchar_t *v6; // [esp+10h] [ebp-14h] BYREF
wchar_t *Source; // [esp+14h] [ebp-10h] BYREF
int v8; // [esp+20h] [ebp-4h]
res = 0;
CString::CString(&Source, pcszName);
v8 = 0;
CString::CString(&v6, pcszEmail);
LOBYTE(v8) = 1;
CString::CString(&pcszName, pcszKey);
LOBYTE(v8) = 2;
CString::TrimLeft(&Source);
CString::TrimRight(&Source);
CString::TrimLeft(&v6);
CString::TrimRight(&v6);
CString::MakeLower(&v6);
CString::TrimLeft(&pcszName);
CString::TrimRight(&pcszName);
CString::MakeUpper(&pcszName);
wcscpy(&Destination, Source);
wcscpy(&String1, v6);
sub_1002C8D0(pcszName);
v4 = sub_1002CF30(&word_1004E890);
LOBYTE(v8) = 1;
if ( v4 && dword_10051508 >= 300 )
{
res = 1;
CString::~CString(&pcszName);
LOBYTE(v8) = 0;
CString::~CString(&v6);
v8 = -1;
CString::~CString(&Source);
}
else
{
CString::~CString(&pcszName);
LOBYTE(v8) = 0;
CString::~CString(&v6);
v8 = -1;
CString::~CString(&Source);
sub_1002C8D0(0);
}
sub_10017C30(6u, 0);
return res;
}
然后我们注意到最关心的语句:
if ( v4 && dword_10051508 >= 300 )
注册是否成功,完全由这两个变量决定,我们先看看dword_10051508
的引用,看看有没有修改它的:
不幸的是,压根没有。肯定有一个时刻,进行校验的时候,修改了这个值。
我们可以看出,v4
的结果跟word_1004E890
这个全局变量有关系,为了方便观看,命个名:
wcscpy(&UserName, Source);
wcscpy(&UserEmail, v6);
sub_1002C8D0(info, pcszName);
isreg = CheckReg(info);
LOBYTE(v8) = 1;
if ( isreg && flag >= 300 )
{
res = 1;
CString::~CString(&pcszName);
LOBYTE(v8) = 0;
CString::~CString(&v6);
v8 = -1;
CString::~CString(&Source);
}
细心的你可能会发现sub_1002C8D0
怎么这次有两个参数了吗,怎么成了两个?是因为我点进去了CheckReg
,然后返回重新生成伪代码,注意不要过于依赖F5
。
根据猜测,sub_1002C8D0
这个函数应该是生成与注册信息相关的东西,然后调用CheckReg
进行检查,我们看看这个函数到底干了啥:
wchar_t *__thiscall sub_1002C8D0(int info, LPCSTR inputkey)
{
wchar_t *v3; // ebp
wchar_t *result; // eax
int v5; // edi
wint_t v6; // si
bool v7; // zf
int v8; // esi
int v9; // [esp+10h] [ebp-4h]
v3 = (info + 10852);
result = 0;
*(info + 11384) = 0;
memset((info + 10852), 0, 0x208u);
if ( inputkey )
{
v9 = 0;
LABEL_3:
v5 = 0;
while ( 1 )
{
v6 = *inputkey;
result = inputkey + 1;
v7 = *inputkey == 0;
inputkey = (inputkey + 2);
if ( v7 )
break;
if ( iswdigit(v6) || iswalpha(v6) )
*(info + 2 * wcslen(v3) + 10852) = v6;
else
--v5;
if ( ++v5 >= 5 )
{
v8 = v9 + 1;
if ( v9 + 1 < 5 )
*(info + 2 * wcslen(v3) + 10852) = 45;
++v9;
if ( v8 >= 5 )
return wcsupr(v3);
goto LABEL_3;
}
}
}
return result;
}
注意,上面的结果是经过我函数命名处理的,为什么在第二个参数命名为inputkey
而不是注册名相关的吗?因为如下代码:
CString::CString(&pcszName, pcszKey);
在调用该函数之前pcszName
已不再是所谓的名字了,而是你输入的注册码了,故使用该名称,可以说,这个所谓的注册名压根没用到。
由于用到的是全局变量,这个全局变量肯定是个结构体,我们先看看在哪里:
.data:1004E890 ; WCHAR info[4906]
.data:1004E890 info dw ? ; DATA XREF: sub_10013E70+11↑o
.data:1004E890 ; unknown_libname_1↑o ...
.data:1004E892 db ? ;
.data:1004E893 db ? ;
.data:1004E894 db ? ;
.data:1004E895 db ? ;
为了保持良好的可读性,先把它变成个数组,这肯定不是数组那么简单:
.data:1004E890 ; WCHAR info[4906]
.data:1004E890 info dw 132Ah dup(?) ; DATA XREF: sub_10013E70+11↑o
接上续,可以看出这个函数是把输入的注册码进行整理,成XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
的只包含数字和字母的大写形式,放到info + 10852
这个地址中,我们看看这个对应的是什么东西,经过计算在下面的位置:
.data:100512F4 unk_100512F4 db ? ; ; DATA XREF: sub_10002CE0+6E↑o
.data:100512F4 ; sub_10014940+39↑o
.data:100512F5 db ? ;
.data:100512F6 db ? ;
这个地方正是我们放经过整理的注册码,给个名字,然后边车数组,最终看到下面的结果:
.data:1004E890 ; WCHAR info[4906]
.data:1004E890 info dw 132Ah dup(?) ; DATA XREF: sub_10013E70+11↑o
.data:1004E890 ; unknown_libname_1↑o ...
.data:10050EE4 ; wchar_t UserName
.data:10050EE4 UserName dw 104h dup(?) ; DATA XREF: sub_10002CE0+78↑o
.data:10050EE4 ; sub_10014940+43↑o ...
.data:100510EC ; wchar_t UserEmail
.data:100510EC UserEmail dw 104h dup(?) ; DATA XREF: sub_10002CE0+73↑o
.data:100510EC ; sub_10014940+3E↑o ...
.data:100512F4 RegCode db 208h dup(?) ; DATA XREF: sub_10002CE0+6E↑o
.data:100512F4 ; sub_10014940+39↑o
.data:100514FC dword_100514FC dd ? ; DATA XREF: sub_10017DF0+5A↑r
.data:10051500 db ? ;
.data:10051501 db ? ;
.data:10051502 db ? ;
.data:10051503 db ? ;
.data:10051504 dword_10051504 dd ? ; DATA XREF: sub_10017DF0+93↑r
.data:10051504 ; sub_10017DF0+144↑r
.data:10051508 flag dd ? ; DATA XREF: sub_1000F410+F7↑r
.data:10051508 ; sub_10014940+21↑r ...
.data:1005150C dword_1005150C dd ? ; DATA XREF: sub_10015390+5C↑w
.data:1005150C ; sub_10015400+3↑w ...
信息的你可能会发现我们之前命好名字的flag
在这里,也就是info + 0x2C78
的位置,在之后的分析要十分注意。
然后看看最终Boss
函数:
BOOL __thiscall CheckReg(int this)
{
bool v2; // zf
const wchar_t *v3; // ebx
wchar_t *v4; // eax
wchar_t *v5; // eax
int v6; // eax
int v7; // esi
wchar_t Destination; // [esp+8h] [ebp-2000h] BYREF
char v10[8188]; // [esp+Ah] [ebp-1FFEh] BYREF
__int16 v11; // [esp+2006h] [ebp-2h]
v2 = *(this + 0x285C) == 0;
v3 = (this + 0x285C);
*(this + 11384) = 0;
if ( !v2 && *(this + 0x2A64) )
{
Destination = 0;
memset(v10, 0, sizeof(v10));
v11 = 0;
v4 = wcscpy(&Destination, this);
v5 = wcsrchr(v4, 0x5Cu);
wcscpy(v5 + 1, v3);
*(this + 0x2C78) = sub_1002CD80(1000, &Destination);
}
v6 = *(this + 0x2C78);
if ( v6 <= 0 || v6 > 1000 )
sub_1002C8D0(this, 0);
if ( *(this + 0x2C70) )
sub_1002CAE0(this);
v7 = *(this + 0x2C78);
return v7 > 0 && v7 <= 1000;
}
如果眼尖,你就会发现一个华点:
*(this + 0x2C78) = sub_1002CD80(1000, &Destination);
点进去看看,经过轻微的重命名得到如下结果:
int __thiscall sub_1002CD80(int this, int a2, wchar_t *String)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
result = 0;
if ( a2 > 0 )
{
MultiByteStr = 0;
memset(v10, 0, sizeof(v10));
if ( sub_1002CBC0(&MultiByteStr, String) )
{
LOWORD(WideCharStr) = 0;
memset(&WideCharStr + 2, 0, 0x40u);
LOWORD(Destination) = 0;
memset(&Destination + 2, 0, 0x204u);
v12 = 0;
wcscpy(&Destination, (this + 0x2A64));
for ( i = 0; i < a2; ++i )
{
MultiByteToWideChar(0, 0, &MultiByteStr, -1, &WideCharStr, 33);
sub_1002CBC0(&MultiByteStr, &WideCharStr);
for ( j = 0; j < 32; ++j )
{
if ( !iswalpha(*(&WideCharStr + j))
&& (!(j % 2) || (*(&WideCharStr + j) & 0x80000001) == 0)
&& (j % 2 || (*(&WideCharStr + j) & 0x80000001) != 0) )
{
v7 = (i + j + *(&WideCharStr + j)) % 20;
*(&WideCharStr + j) = v7 + 71;
if ( v7 == 8 )
*(&WideCharStr + j) = 48;
if ( *(&WideCharStr + j) == 73 )
*(&WideCharStr + j) = 49;
if ( *(&WideCharStr + j) == 90 )
*(&WideCharStr + j) = 50;
}
}
sub_1002C8D0(this, &WideCharStr);
if ( !wcscmp((this + 0x2A64), &Destination) )
break;
}
result = i + 1;
}
else
{
result = -1;
}
}
return result;
}
从上面的伪代码可以看出,sub_1002CBC0
函数是十分重要的函数,点击去看看,由于伪代码有些错误,经过调整:
int __stdcall sub_1002CBC0(char *Buffer, wchar_t *String)
{
size_t v2; // edi
CHAR *v3; // eax
CHAR *v4; // esi
char v6[16]; // [esp+4h] [ebp-B8h] BYREF
char v7[152]; // [esp+18h] [ebp-A4h] BYREF
int v8; // [esp+B8h] [ebp-4h]
if ( !String || !*String )
return 0;
v2 = wcslen(String);
v3 = operator new(v2 + 1);
v4 = v3;
if ( v3 )
{
WideCharToMultiByte(0, 0, String, -1, v3, v2 + 1, 0, 0);
sub_1002D010(v7);
*&v6[1] = 0;
*&v6[5] = 0;
*&v6[9] = 0;
v8 = 0;
*&v6[13] = 0;
v6[0] = 0;
v6[15] = 0;
sub_1002D080(v4, v2);
sub_1002D140(v6);
sprintf(
Buffer,
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
v6[0],
v6[1],
v6[2],
v6[3],
v6[4],
v6[5],
v6[6],
v6[7],
v6[8],
v6[9],
v6[10],
v6[11],
v6[12],
v6[13],
v6[14],
v6[15]);
v8 = -1;
nullsub_2(v7);
operator delete(v4);
}
return 1;
}
可以看出,这个函数肯定返回为1
。但是生成的Buffer
是十分重要的东西,看样子是长度为32的普通字符串,对于以后的解密十分重要,这些东西说明了v6
的重要性,而它与sub_1002D140
有重要关系,点击去看看:
int __thiscall sub_1002D140(_DWORD *this, int a2)
{
_DWORD *v3; // edi
int v4; // ecx
unsigned int v5; // eax
char v7; // [esp+8h] [ebp-8h] BYREF
int v8; // [esp+9h] [ebp-7h]
__int16 v9; // [esp+Dh] [ebp-3h]
char v10; // [esp+Fh] [ebp-1h]
v3 = this + 4;
v8 = 0;
v9 = 0;
v7 = 0;
v10 = 0;
sub_1002DB20(&v7, this + 4, 8);
v4 = 56;
v5 = (*v3 >> 3) & 0x3F;
if ( v5 >= 0x38 )
v4 = 120;
sub_1002D080(this + 22, v4 - v5);
sub_1002D080(&v7, 8);
sub_1002DB20(a2, this, 16);
return sub_1002D030(this);
}
一看是两个函数,不明觉厉,返回去,重新反编译一下:
int __stdcall sub_1002CBC0(char *Buffer, wchar_t *String)
{
size_t v2; // edi
CHAR *v3; // eax
CHAR *v4; // esi
char v6[16]; // [esp+4h] [ebp-B8h] BYREF
_DWORD v7[38]; // [esp+18h] [ebp-A4h] BYREF
int v8; // [esp+B8h] [ebp-4h]
if ( !String || !*String )
return 0;
v2 = wcslen(String);
v3 = operator new(v2 + 1);
v4 = v3;
if ( v3 )
{
WideCharToMultiByte(0, 0, String, -1, v3, v2 + 1, 0, 0);
sub_1002D010(v7);
*&v6[1] = 0;
*&v6[5] = 0;
*&v6[9] = 0;
v8 = 0;
*&v6[13] = 0;
v6[0] = 0;
v6[15] = 0;
sub_1002D080(v4, v2);
sub_1002D140(v7, v6);
sprintf(
Buffer,
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
v6[0],
v6[1],
v6[2],
v6[3],
v6[4],
v6[5],
v6[6],
v6[7],
v6[8],
v6[9],
v6[10],
v6[11],
v6[12],
v6[13],
v6[14],
v6[15]);
v8 = -1;
nullsub_2(v7);
operator delete(v4);
}
return 1;
}
突然发现这个v6
太花心了,竟然和v7
又扯上关系了,点击去看看:
void *__thiscall sub_1002D010(void *this)
{
sub_1002D030(this);
return this;
}
还没看到底裤,继续跟进去:
int __thiscall sub_1002D030(_DWORD *this)
{
int result; // eax
result = 0;
this[5] = 0;
this[4] = 0;
*this = 1732584193;
this[1] = -271733879;
this[2] = -1732584194;
this[3] = 271733878;
memset(this + 6, 0, 0x40u);
memset(this + 22, 0, 0x40u);
*(this + 88) = 0x80;
return result;
}
看到这里,是不是蒙圈了,这是啥玩意,弄成16进制看看:
int __thiscall sub_1002D030(_DWORD *this)
{
int result; // eax
result = 0;
this[5] = 0;
this[4] = 0;
*this = 0x67452301;
this[1] = 0xEFCDAB89;
this[2] = 0x98BADCFE;
this[3] = 0x10325476;
memset(this + 6, 0, 0x40u);
memset(this + 22, 0, 0x40u);
*(this + 88) = 0x80;
return result;
}
别说这个挺有规律的,this
搞完后在内存中就是如下形式:
0x01 0x23 0x45 0x67 0x89 0xAB 0xCD 0xEF
0xFE 0xDC 0xBA 0x98 0x76 0x54 0x32 0x10
有一说一,挺有规律,但不知道这串数字是干啥的,然后sub_1002D140
这里面的函数吧,看也看不懂,蒙圈了,继续不了了。
学过或接触算法的人,很快能够意识到这个是魔数,我们用IDA
的插件Findcrypt
查一查结果:
这个插件指明是MD5
算法,什么是MD5
算法请自行百度,它指向的地址正好是我们的函数。也就是说,sub_1002CBC0
这个函数会将输入的String
参数进行MD5
运算,取出运算的字节数组的前16个进行格式化成32长度的大写字符串导出给Buffer
,那么我们可以将其命名为GetMD5Code
,剩下的就好好搞了,总结一下sub_1002CD80
函数的具体流程:
如果flag >= 300 && flag < 1000
,就说明注册成功。为什么还要小于1000
呢?是由于我们调用这个函数a2
就是1000
。这个SDK
还有显示注册信息的函数ScnLib_About
,里面的sub_10014940
函数也佐证了这点:
void sub_10014940()
{
int v0; // [esp+0h] [ebp-14h] BYREF
unsigned __int16 *v1; // [esp+4h] [ebp-10h] BYREF
int v2; // [esp+10h] [ebp-4h]
CString::CString(&v0);
v2 = 0;
if ( flag <= 0 || flag > 1000 )
CString::Format(&v0, 0x3EAu);
else
CString::Format(&v0, 0x3E9u, &UserName, &UserEmail, RegCode);
CString::CString(&v1);
LOBYTE(v2) = 1;
CString::Format(&v1, aSDD0DSSS, aZdSdk, 1, 1, 6, asc_10046F30, aHrcgBG, v0);
AfxMessageBox(v1, 0x40u, 0);
LOBYTE(v2) = 0;
CString::~CString(&v1);
v2 = -1;
CString::~CString(&v0);
}
至此,此完整的验证流程就这些了。其中有一个东西还没有具体分析,就是v4 = wcscpy(&Destination, this);
当中的this
地址存储的是啥,为了方便分析,直接用动态分析的方式进行,怎么动态调试获得我就不说了,需要使用这个SDK
的进程进行调试,而迅捷录屏就可以当作小白鼠,你会得到下面的字符串:Software\\ZD Soft\\Screen Recorder SDK\\
。综上,我们的分析就结束了。
注册机编写
既然都分析到这里了,就C++
写个注册机吧。由于其中使用了MD5
算法,我们不可能自己写,直接Github
大法,找到了一个项目:
然后创建一个控制台工程,把里面的文件包含进去,就可以愉快的编码了:
#include <iostream>
#include <algorithm>
#include "md5.h"
using namespace std;
int main()
{
string str = "Software\\\\ZD Soft\\\\Screen Recorder SDK\\\\";
string name;
string email;
string regcode;
MD5* md5;
int f;
cout << "请输入注册用户名:" << endl;
cin >> name;
cout << endl << "请输入注册邮箱:" << endl;
cin >> email;
str.append(email);
cout << endl << "请输入密钥计算递归深度(整数:300 - 999): " << endl;
cin >> f;
if (f < 300)
{
f = 300;
cout << endl << "【警告】由于输入的数字小于合法区间,故选择最小深度为 300 !" << endl;
}
if (f > 999)
{
f = 999;
cout << endl << "【警告】由于输入的数字大于合法区间,故选择最大深度为 999 !" << endl;
}
for (int i = 0; i < f; i++)
{
md5 = new MD5(str);
str = md5->toStr();
delete md5;
str.erase(32);
transform(str.begin(), str.end(), str.begin(), toupper);
regcode = str;
for (int j = 0; j < 32; j++)
{
auto ch = str[j];
if (!isalpha(ch) && (!(j & 1) || !(ch & 1)) && ((j & 1) || (ch & 1)))
{
auto c = (i + j + ch) % 20 + 71;
switch (c)
{
case \'O\':
regcode[j] = \'0\';
break;
case \'I\':
regcode[j] = \'1\';
break;
case \'Z\':
regcode[j] = \'2\';
break;
default:
regcode[j] = c;
break;
}
}
}
cout << endl << "==========注册信息==========" << endl
<< "注册用户名:" << name << endl
<< "注册邮箱:" << email << endl << "注册码:"
<< regcode.substr(0, 5) << \'-\' << regcode.substr(5, 5) << \'-\'
<< regcode.substr(10, 5) << \'-\' << regcode.substr(15, 5) << \'-\' << regcode.substr(20, 5) << endl
<< "=========================" << endl << endl;
system("pause");
return 0;
}
注意:上面的代码仅限用于学习和研究目的,否则,一切后果自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述侵害合法权益的内容。如果您喜欢其包含的录屏组件,请支持正版软件,购买注册,得到更好的正版服务。
小结
这个或许是我写的最长的一篇分析文,通过这篇文章,我们认识到DuiLib
在项目当中的应用,并了解了MD5
算法在注册码的应用。这仅仅是一个算法应用的缩影。我们可以看到迅捷录屏没能保护好自己的License
,使自己的Key
被泄露。迅捷录屏的强大并不是因为自身的强大,而是由于录屏SDK
的强大,本篇分析总结至此。
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15680444.html
以上是关于羽夏逆向破解日记簿——逆向迅捷录屏的思考的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript(JS)逆向工具Tampermonkey用法 (JS Hook代码逆向破解)