热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:羽夏逆向破解日记簿——逆向迅捷录屏的思考

篇首语:本文由编程笔记#小编为大家整理,主要介绍了羽夏逆向破解日记簿——逆向迅捷录屏的思考相关的知识,希望对你有一定的参考价值。 羽夏逆向破解日记簿之逆向迅捷录屏的思考

篇首语:本文由编程笔记#小编为大家整理,主要介绍了羽夏逆向破解日记簿——逆向迅捷录屏的思考相关的知识,希望对你有一定的参考价值。





羽夏逆向破解日记簿之逆向迅捷录屏的思考


看前必读

  “迅捷录屏”是免费录屏软件,但有VIP功能,而其中的录屏组件是商业软件,本篇文章仅仅介绍 逆向分析过程关于开发软件防止逆向的思考 ,不会提供任何成品破解补丁,仅限用于学习和研究目的,否则,一切后果自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述侵害合法权益的内容。如果您喜欢其包含的录屏组件,请支持正版软件,购买注册,得到更好的正版服务如有侵权到您的权益,请联系本人整改。


主角和工具



  • Detect it easy 1.01

  • PE Explorer

  • IDA 7.5

  • X32DBG

  • 迅捷录屏

  • ZDSoft SDK


浅析迅捷录屏

  迅捷屏幕录像工具支持游戏录制、直播录屏,教学视频录制等,保存本地后随时学习。支持多种视频录制和多种格式输出,且声画同步多种使用场景,是一个无论是上班族还是学生党都适用的录屏软件。当然,前面的只是官方的措辞,功能比较强,但如果免费,限制多多,广告多多。我们先看看这个软件:
  打开这个软件后,迎面而来的就是推广会员的广告,毕竟要恰钱嘛,无可厚非:



  关掉这个广告,整体来说界面挺好的,UI设计挺合理,然后看看设置里的东西:



  设置的功能挺多的,但是要修改某些设置,会让你登录。我们看看它的录屏功能:



  上面明码标价,普通用户有诸多限制,吧啦吧啦。先不管,我们来看看录屏效果:



  一个倒计时,挺合理:



  然后就进入录屏阶段,自己感兴趣测试一下,结束录屏后我们看看视频:



  视频挺清晰的,但是右下角有一个万恶的水印。


深入迅捷录屏

  接下来就深入看看它到底是怎样实现功能的,可以看到它的安装内容有大量不属于迅捷录屏的东西,比如下面图片所示的东西:



  DuiLib肯定不是迅捷的,这个是一个DirectUI库,在GithubMIT协议开源,不过有坑,或多或少有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 {
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
#include
#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 <<"请输入注册用户名:" < cin >> name;
cout < cin >> email;
str.append(email);
cout < cin >> f;
if (f <300)
{
f = 300;
cout < }
if (f > 999)
{
f = 999;
cout < }
for (int i = 0; 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 < <<"注册用户名:" < <<"注册邮箱:" < < < <<"=========================" < system("pause");
return 0;
}

  注意:上面的代码仅限用于学习和研究目的,否则,一切后果自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述侵害合法权益的内容。如果您喜欢其包含的录屏组件,请支持正版软件,购买注册,得到更好的正版服务


小结

  这个或许是我写的最长的一篇分析文,通过这篇文章,我们认识到DuiLib在项目当中的应用,并了解了MD5算法在注册码的应用。这仅仅是一个算法应用的缩影。我们可以看到迅捷录屏没能保护好自己的License,使自己的Key被泄露。迅捷录屏的强大并不是因为自身的强大,而是由于录屏SDK的强大,本篇分析总结至此。







本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15680444.html



推荐阅读
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
author-avatar
駱宏艷_230
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有