[md]申请会员id superasd
申请邮箱 250581290@qq.com
技术文章: VA插件破解
环境:
Win10 x64
VS2019
VisualAssist 插件最新版 2399
cheate engine
IDA pro
x32dbg
先上图为证,是本人作品,并且已经破解成功.
下面描述破解过程
之前用老版本的时候是下的别人的破解版,大家都知道是一个VA_X.dll 替换即可, 最近我的VA突然自动更新了,然后失效了,就尝试自己解决一下.
我的现在打开VS就提示一个过期的弹窗, 凭直觉先调试.
1用IDA启动VS, 发现不行,全是异常,由于IDA不能设置全部忽略异常,等VS启动后停在选择项目的界面时,用IDA附加调试,没有问题. 我们观察,进入vs主界面后只有一个VA对话框, 要么取消,要么选择注册. 如果选择取消的话, 这个对话框似乎再也没有地方查看了,只能重新启动vs, 这就很麻烦了, 因为vs非常庞大的软件,启动的话调试器要卡很久, 而且相关状态全没了; 而如果选择注册的话, 我浪费了一些时间去追踪她是如何解析字符串的,这里比较麻烦; 没有什么有用的信息
2 由于我也就是想尽快把va变成能用的状态即可, 并不是追求什么完美破解, 也没打算把注册机都给弄出来, 那么就换个思路, 我们看看这个对话框是怎么弹出来的, 如果你购买了正版, 那么应该是不会弹出对话框的, 所以我有一个大胆的想法, 把对话框直接给干掉是不是行了? 于是把对话框的相关API全部断点, 然后停在了CreateDialogIndirectParamA, 不过这里参数好像看不出什么线索; 创建对话框只需要传入资源id就行, 这些全是二进制, 这个思路又行不通了, 而且我搜索对话框的字符串,也没有得到什么信息.
Address Module Function
76711FF7 user32.dll user32_CreateDialogIndirectParamA+7
1F33694B VA_X.dll jpt_1F331B39+0x4896
1F336020 VA_X.dll jpt_1F331B39+0x3F6B
1F336178 VA_X.dll jpt_1F331B39+0x40C3
1F3362DE VA_X.dll jpt_1F331B39+0x4229
1F2D88EA VA_X.dll jpt_1F2D35A8+0x51D6
1F2DF04B VA_X.dll jpt_1F2DE20B+0xCAB
1EFD00B0 VA_X.dll jpt_1EFC81FD+0x7E9C
1F33EBFE VA_X.dll jpt_1F331B39+0xCB49
1F51AF3D VA_X.dll jpt_1F51991B+0x1602
749D6357 kernel32.dll kernel32_BaseThreadInitThunk+17
771B8962 ntdll.dll ntdll_RtlGetAppContainerNamedObjectPath+E2
771B892F ntdll.dll ntdll_RtlGetAppContainerNamedObjectPath+AF
4 于是我就只能硬上看代码了, 从这里我就开始了很繁琐的调试体力活,每次改动都要重启一次VS, 我不得不怀疑VA的开发组肯定是故意的, 想破解也要很有耐心才行...
先看第一个函数 也就是 1F33694B 这里, 只有一个很简单的if跳转, 屏蔽之, 再次启动还是弹框, 失败....
这里省略N次调试过程.
void __usercall sub_1F3361C5(int a1@<ebp>)
{
_DWORD *v1; // ecx
_DWORD *v2; // edi
int v3; // ebx
int v4; // esi
int v5; // eax
bool v6; // zf
int v7; // eax
int v8; // esi
int v9; // eax
int v10; // ebx
int v11; // esi
int v12; // eax
int (__thiscall *v13)(_DWORD); // esi
int v14; // eax
int v15; // eax
void (__thiscall *v16)(_DWORD *); // esi
int v17; // [esp-20h] [ebp-20h]
int v18; // [esp-Ch] [ebp-Ch]
sub_1F47823B(36);
v2 = v1;
*(_DWORD *)(a1 - 36) = v1;
v3 = v1[35];
v4 = v1[34];
*(_DWORD *)(a1 - 28) = v3;
v5 = sub_1F3378ED();
v6 = v2[33] == 0;
*(_DWORD *)(a1 - 32) = *(_DWORD *)(v5 + 12);
if ( !v6 )
{
v7 = sub_1F3378ED();
v18 = v2[33];
v8 = *(_DWORD *)(v7 + 12);
*(_DWORD *)(a1 - 32) = v8;
v9 = ((int (__cdecl *)(int, int, int))unk_1C0533B0)(v8, v18, 5);
v4 = ((int (__cdecl *)(int, int))kernel32_LoadResource)(v8, v9);
}
if ( v4 )
{
直到我分析到这里, 终于发现了一个有用的函数LoadResource, 我猜应该是用来加载对话框的吧, 我先做一个猜测, 这里函数如果进来的话 说明已经被判定为expire状态了, 所以这里往下全都不用看了.
[C] 纯文本查看 复制代码 int __stdcall sub_1F2D84A0(int a1, char a2)
{
int (***v2)(); // edi
int v4; // ebx
int v5; // ecx
int v6; // esi
int v7; // ecx
void **v8; // eax
int v9; // eax
void *v10; // esi
int v11; // ebx
int v12; // ecx
int v13; // eax
int v14; // esi
int *v15; // eax
int v16; // ecx
int v17; // edx
int v18; // eax
省略大段代码........
v44 = 10;
if ( !v45 )
{
v37 = ((int (*)(void))kernel32_GetLastError)();
if ( dword_1F894000 )
sub_1EF8B880("ERROR running dialog %d(%08x)", v37, v37);
sub_1EE05370(dword_1F894490, &off_1F6E1158, &off_1F66419C, 16);
}
}
if ( v44 == 10 )
{
if ( a2 )
{
sub_1ED9AF70(&v57);
LOBYTE(v61) = 22;
sub_1EDA0130(&v57, &off_1F6E1C40, v58);
sub_1EDD7850(dword_1F894490, &v57, "Good-thru Date Expired", 0);
省略.........
return v14;
}
看到了又一个非常有用的字符串"ERROR running dialog", 好家伙, 所以之前的尝试全是错的. 说明开始就钻牛角尖了, 浪费大把时间. 我也是没想到VA弹个框掉了如此多层函数, 真的怕不是故意的混淆视线..
5 既然如此, 我们反过来看, 从系统dll第一次进入va模块开始找
int sub_1EFD0070()
{
_DWORD v1[17]; // [esp-2Ch] [ebp-44h] BYREF
((void (__stdcall *)(_DWORD, int))combase_CoInitializeEx)(0, 2);
if ( off_1F84BD48 && ((int (__thiscall *)(int (***)(), void ****))(*off_1F84BD48)[7])(off_1F84BD48, &off_1F84A9B4) )
{
v1[10] = 1;
v1[13] = v1;
v1[12] = v1;
v1[9] = v1;
v1[16] = -1;
((void (__cdecl *)(char, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))sub_1F0737E0)(
(char)&std::_Func_impl_no_alloc<_lambda_d57e0b89b7985f042def112d34043b4f_,void,>::`vftable',
v1[1],
v1[2],
v1[3],
v1[4],
v1[5],
v1[6],
v1[7],
v1[8]);
}
else
{
((void (__thiscall *)(void ****))loc_1EFB5590)(&off_1F84A9B4);
}
((void (*)(void))combase_CoUninitialize)();
return 0;
}
这里有两个明显的api, 之前做过com编程,记得这是个初始化入口, 我们来尝试一下, 如果把这里整个bypass的话, 效果如何? 果然启动vs后没有任何提示, va菜单也是黑色; 果然是压根没加载
最终我们发现了一个超长函数,
int __thiscall sub_1F2DED30(int *this, int a2)
{
void *v3; // ecx
int (*v5)(void); // eax
int v6; // esi
int v7; // eax
bool v8; // zf
int v9; // eax
int v10; // eax
int v11; // eax
int v12; // esi
_DWORD *v13; // eax
int v14; // edx
int *v15; // eax
int *v16; // ecx
int *v17; // eax
_DWORD *v18; // eax
int v19; // esi
_DWORD *v20; // eax
int v21; // esi
_DWORD *v22; // eax
int v23; // esi
int v24; // eax
int v25; // [esp+10h] [ebp-60h] BYREF
int v26; // [esp+14h] [ebp-5Ch] BYREF
int v27; // [esp+18h] [ebp-58h] BYREF
int v28; // [esp+1Ch] [ebp-54h]
int v29; // [esp+20h] [ebp-50h] BYREF
int v30; // [esp+24h] [ebp-4Ch]
int v31; // [esp+28h] [ebp-48h] BYREF
bool v32; // [esp+2Eh] [ebp-42h]
bool v33; // [esp+2Fh] [ebp-41h]
int v34[5]; // [esp+30h] [ebp-40h] BYREF
unsigned int v35; // [esp+44h] [ebp-2Ch]
int v36[5]; // [esp+48h] [ebp-28h] BYREF
unsigned int v37; // [esp+5Ch] [ebp-14h]
int v38; // [esp+6Ch] [ebp-4h]
sub_1F2E0D90();
if ( !off_1F8DFA28 )
{
if ( dword_1F894000 )
sub_1EF8B880("ERROR: vaaux failed to load 1");
goto LABEL_4;
}
v5 = (int (*)(void))sub_1EF14540("GetAuxDll");
if ( !v5 )
{
LABEL_4:
v3 = off_1F8DFA04;
goto LABEL_5;
}
v3 = (void *)v5();
off_1F8DFA04 = v3;
LABEL_5:
if ( !v3 )
{
if ( dword_1F894000 )
sub_1EF8B880("ERROR: vaaux failed to load 2");
return 0;
}
(*(void (__thiscall **)(void *, int (***)()))(*(_DWORD *)v3 + 4))(v3, &off_1F84BD50);
sub_1F2E0C40(&v29, (int)"DAYSINSTALLED");
v38 = 0;
if ( *(_DWORD *)(v29 - 12) )
{
if ( *(_DWORD *)(sub_1F3378ED() + 12) )
v7 = *(_DWORD *)(sub_1F3378ED() + 12);
else
v7 = *(_DWORD *)(sub_1F3378ED() + 8);
sub_1F2EB310(v7);
(*(void (__thiscall **)(int, int *, int *, int *))(*(_DWORD *)a2 + 12))(a2, &v25, &v26, &v27);
sub_1F2EB2E0(v25, v26, v27);
v8 = (*(int (__thiscall **)(int *))(*this + 56))(this) == 0;
v9 = *this;
LOBYTE(v30) = !v8;
v10 = (*(int (__thiscall **)(int *, _DWORD))(v9 + 24))(this, 0);
v32 = v10 != 0;
if ( v10 )
v11 = (*(int (__thiscall **)(int *))(*this + 36))(this);
else
v11 = sub_1F2EB800();
v12 = v11;
v13 = sub_1F2E0C40(&v31, (int)"EXPIRED");
LOBYTE(v38) = 1;
v28 = *(_DWORD *)(*v13 - 12);
v33 = v28 > 0;
LOBYTE(v38) = 0;
v14 = v31 - 16;
if ( _InterlockedDecrement((volatile signed __int32 *)(v31 - 16 + 12)) <= 0 )
(*(void (__stdcall **)(int))(**(_DWORD **)v14 + 4))(v14);
if ( v12 != 1 )
{
LABEL_37:
if ( v12 == 6 || v12 == 4 )
goto LABEL_48;
if ( v12 == 2 )
{
LABEL_42:
if ( !(_BYTE)v30 && (sub_1F51734E(v29) > 29 || v12 == 2) )
{
sub_1F2E6610("TrialLicense", "BD7D-A4CE-A835-1421-73DB-9D7F-603C-A3FB-91D2-A64F-BB7D-B26B");
if ( v12 == 2 && v32 )
{
LABEL_49:
if ( sub_1F2E1D40() || sub_1F2E1A20() )
{
byte_1F84A964 = 1;
((void (__thiscall *)(int (***)()))(*off_1F84BD48)[18])(off_1F84BD48);
if ( (*(int (__thiscall **)(int *, int))(*this + 24))(this, 1) )
goto LABEL_68;
v12 = sub_1F2EB800();
}
if ( v12 )
{
if ( !(*(int (__thiscall **)(int, int, int))(*(_DWORD *)a2 + 4))(a2, v12, v30) )
{
if ( v12 == 2 )
{
sub_1F0104F0();
v6 = 0;
goto LABEL_70;
}
if ( v33 )
{
sub_1F010580();
v6 = 0;
goto LABEL_70;
}
LABEL_67:
v6 = 0;
goto LABEL_70;
}
if ( !(*(int (__thiscall **)(int *, int))(*this + 24))(this, 1) && sub_1F2EB800() )
{
v18 = sub_1F2E0C40(&v31, (int)"CLOCKBACK");
LOBYTE(v38) = 5;
v19 = *(_DWORD *)(*v18 - 12);
LOBYTE(v38) = 0;
sub_1ED984C0(&v31);
if ( v19 )
{
sub_1F0102B0();
(*(void (__thiscall **)(int, void ****, _DWORD))(*(_DWORD *)a2 + 8))(a2, &off_1F6E3A68, 0);
v6 = 0;
goto LABEL_70;
}
v20 = sub_1F2E0C40(&v31, (int)"CLOCKFORWARD");
LOBYTE(v38) = 6;
v21 = *(_DWORD *)(*v20 - 12);
LOBYTE(v38) = 0;
sub_1ED984C0(&v31);
if ( v21 )
{
sub_1F010340();
(*(void (__thiscall **)(int, void ****, _DWORD))(*(_DWORD *)a2 + 8))(a2, &off_1F6E3B28, 0);
v6 = 0;
goto LABEL_70;
}
v22 = sub_1F2E0C40(&v31, (int)"EXPIRED");
LOBYTE(v38) = 7;
v23 = *(_DWORD *)(*v22 - 12);
LOBYTE(v38) = 0;
sub_1ED984C0(&v31);
if ( v23 )
{
sub_1F0103D0();
(*(void (__thiscall **)(int, int (__stdcall ***)(int, int, int, int, int), _DWORD))(*(_DWORD *)a2 + 8))(
a2,
&off_1F6E3BE8,
0);
goto LABEL_67;
}
LABEL_69:
v6 = 1;
goto LABEL_70;
}
}
LABEL_68:
(*(void (__thiscall **)(int))(*(_DWORD *)a2 + 16))(a2);
goto LABEL_69;
}
v12 = sub_1F2EB800();
}
LABEL_48:
if ( !v12 )
goto LABEL_68;
goto LABEL_49;
}
LABEL_40:
if ( !v33 )
goto LABEL_48;
if ( !v12 )
goto LABEL_68;
goto LABEL_42;
}
if ( (_BYTE)v30 )
goto LABEL_40;
v36[4] = 0;
v37 = 15;
LOBYTE(v36[0]) = 0;
v34[4] = 0;
v35 = 15;
LOBYTE(v34[0]) = 0;
LOBYTE(v38) = 4;
if ( sub_1F2EB110((int)v36, (int)v34) )
{
if ( v28 > 0 )
{
LABEL_30:
v16 = v34;
v17 = v36;
if ( v35 >= 0x10 )
v16 = (int *)v34[0];
if ( v37 >= 0x10 )
v17 = (int *)v36[0];
if ( !sub_1F2EA460(v17, v16) )
v12 = sub_1F2EB800();
goto LABEL_36;
}
v15 = v36;
if ( v37 >= 0x10 )
v15 = (int *)v36[0];
if ( (unsigned __int8)sub_1F2EAF50(v15) )
{
v33 = 1;
goto LABEL_30;
}
if ( v28 > 0 )
goto LABEL_30;
}
LABEL_36:
LOBYTE(v38) = 3;
sub_1ED87DE0(v34);
LOBYTE(v38) = 0;
sub_1ED87DE0(v36);
goto LABEL_37;
}
sub_1F010460();
v6 = 0;
LABEL_70:
v38 = -1;
v24 = v29 - 16;
if ( _InterlockedDecrement((volatile signed __int32 *)(v29 - 16 + 12)) <= 0 )
(*(void (__stdcall **)(int))(**(_DWORD **)v24 + 4))(v24);
return v6;
}
先吐槽一下ida, 虽然是输出了伪c代码, 这跳转满天飞看的实在是糟糕....
7 在85行看到了明显的expire, trial license , 然后是clockback clockforward, 不太清楚具体目的,在这里开始做体力活了,找到关键跳转;
8 由于代码很长,需要排除一些错误的地方,由于我们现在就是过期状态; 很好办,让ida在这里单步走一遍,剩下的没走到的函数那就是重点了;
9 直接说结论; 修改92行的v12变量为0就行了;
10 验证过程:
vs2019启动, 停在选择界面; 打开任意调试器或者cheate engine, 定位 1f2dee79, 在这里我们把esi置0, 为了字节对齐, 修改为xor esi,esi; 选择项目打开主界面,果然成功!
总结部分:
va 肯定是用了什么壳, 有一定反调试, 检测api是否被hook, 启动时自校验 等等功能, 只不过没有vmp那些很明显.
结合开头说的,
ida , 不能直接启动,否则全是异常, 只能附加调试, 但是ida分析又慢的要死, 如果先分析保存文件下次复用又会莫名其妙无响应或者崩溃. 而且有时候断点久了 vs自动退出, 还不知道怎么回事
x32dbg, 可以直接启动调试, 比较快, 可惜没有反编译插件
OllyDbg, 直接启动可以... 不过卡的要死, 放弃
cheate engine ,虽然原本是个游戏修改器, 不过作为小工具非常好用, 附加超快, 改内存查数据比调试器好用, 自带简易调试器功能
本着能用就行的原则, 就自行编写一个dll 来启动的时候自动修改字节;
dll 不上传了, 放代码
void* addr = (void*)(va + 0x5dee79);
DWORD old;
if (!VirtualProtect(addr, 2, PAGE_EXECUTE_READWRITE, &old))
{
err("vp fail");
return;
}
uint16_t* p = (uint16_t*)addr;
if (*p != 0xf08b)
{
return;
}
*p = 0xf631;
if (!VirtualProtect(addr, 2, old, &old))
{
err("vp fail");
return;
}
|