做网站开发 甲方提供资料,wordpress 有广告插件下载,长沙网站排名优化,网站见建设更新#xff0c;修改了一下typora的上传脚本#xff0c;把图片全部上传到看雪上了
本文已于2023-08-02首发于个人博客
图片加载不出来#xff0c;放了一个PDF版本在附件里
文中有几张图片是动图#xff0c;如果不会动#xff0c;可以去我的个人博客看
最近破解了一个M…更新修改了一下typora的上传脚本把图片全部上传到看雪上了
本文已于2023-08-02首发于个人博客
图片加载不出来放了一个PDF版本在附件里
文中有几张图片是动图如果不会动可以去我的个人博客看
最近破解了一个MFC程序OriginLab Pro这里记录一下相关过程以及学到的一些东西
软件下载地址下载整个仓库然后和并解压即可
定位激活代码
程序安装完毕之后打开就会弹出激活界面 我们选择使用license进行离线激活 对于这种输入框最后肯定是要使用USER32!GetWindowText*来获取用户输入的在输入框中随便输个字符串然后在windbg中使用bm下个通配符断点 1 2 3 4 5 6 7 0:005 bm user32!getwindowtext* 1: 00007ffba7f06f50 !USER32!GetWindowTextW$filt$0 2: 00007ffba7f06e95 !USER32!GetWindowTextA$filt$0 3: 00007ffba7ed9490 !USER32!GetWindowTextA 4: 00007ffba7eda200 !USER32!GetWindowTextLengthW 5: 00007ffba7edc2f0 !USER32!GetWindowTextW 6: 00007ffba7f59ce0 !USER32!GetWindowTextLengthA
之后点击OK
实际测试发现在我们点击OK之前这些断点就会被反复触发这样会干扰我们找到真正的license处理逻辑因此我们需要改进一下先在COMCTL32!Button_WndProc0x7fb下断点这个断点只有在点击按钮的时候才会被触发然后点击OK该断点触发之后再下上面的通配符断点即可定位到真正的license处理逻辑 我们使用IDA来看一下调用栈中编号为02的位置首先这个代码位于C:\Program Files\OriginLab\Origin2023b\ou.dll
计算出偏移量 1 2 3 4 5 6 7 8 9 10 11 12 0:000 lm m *ou* Browse full module list start end module name 0000000010000000 000000001046d000 Resource (deferred) 00007ffb53b40000 00007ffb54452000 SogouPy (deferred) 00007ffb54460000 00007ffb546a3000 sogoutsf (deferred) 00007ffb54d90000 00007ffb54de1000 OUIM (deferred) 00007ffb76fb0000 00007ffb76feb000 OCcontour (deferred) 00007ffb79400000 00007ffb794a4000 Outl (deferred) 00007ffb794b0000 00007ffb79be7000 ou (export symbols) C:\Program Files\OriginLab\Origin2023b\ou.dll 0:000 ? ou!COUxlView::xlWorksheetToNativeOrigin0x23489-00007ffb794b0000 Evaluate expression: 1271337 0000000000136629
根据该偏移量在IDA中跳转到对应的位置 1 2 3 4 .text:000000018013661C lea rdx, [rsp38harg_18] .text:0000000180136621 mov rcx, rbx .text:0000000180136624 call ?GetWindowTextWCWndQEBAXAEAV?$CStringT_WV?$StrTraitMFC_DLL_WV?$ChTraitsCRT_WATLATLZ ; CWnd::GetWindowTextW(ATL::CStringTwchar_t,StrTraitMFC_DLLwchar_t,ATL::ChTraitsCRTwchar_t ) .text:0000000180136629 lea rcx, [rsp38harg_18]
根据我们的经验一眼就能看出rdx是传出参数也就是说[rsp38harg_18]会保存我们输入的license 分析汇编代码
把函数sub_180136600整体给看了一下并没有找到激活相关的代码那我们就接着看调用栈中编号03的代码也就是函数sub_180134090
ou!sub_180134090
该函数中的关键代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .text:00000001801340AA lea rcx, [rsp28harg_8] .text:00000001801340AF call cs:??0CStringUTF8QEAAXZ ; CStringUTF8::CStringUTF8(void) .text:00000001801340B5 nop .text:00000001801340B6 lea rdx, [rdi490h] .text:00000001801340BD lea r8, [rsp28harg_8] .text:00000001801340C2 mov rcx, rdi .text:00000001801340C5 call sub_180136600 .text:00000001801340CA mov rcx, [rsp28harg_8] .text:00000001801340CF call sub_18012EF30 .text:00000001801340D4 mov ebx, eax .text:00000001801340D6 lea rcx, [rsp28harg_0] .text:00000001801340DB call cs:??0CStringUTF8QEAAXZ ; CStringUTF8::CStringUTF8(void) .text:00000001801340E1 nop .text:00000001801340E2 mov r9d, ebx .text:00000001801340E5 lea r8, aBc04 ; BC04: .text:00000001801340EC lea rdx, aSD_2 ; %s%d .text:00000001801340F3 lea rcx, [rsp28harg_0] .text:00000001801340F8 call cs:?FormatCStringUTF8QEAAXPEBDZZ ; CStringUTF8::Format(char const *,...) .text:00000001801340FE mov r8d, 1 .text:0000000180134104 mov rdx, [rsp28harg_0] .text:0000000180134109 lea ecx, [r814h] .text:000000018013410D call LABUTIL_diagnostics .text:0000000180134112 nop .text:0000000180134113 lea rcx, [rsp28harg_0] .text:0000000180134118 call cs:__imp_??1CStringUTF8QEAAXZ ; CStringUTF8::~CStringUTF8(void) .text:000000018013411E mov eax, cs:dword_1803D4898 .text:0000000180134124 test ebx, ebx .text:0000000180134126 cmovnz eax, ebx .text:0000000180134129 mov cs:dword_1803D4898, eax .text:000000018013412F lea rcx, [rsp28harg_8] .text:0000000180134134 call cs:__imp_??1CStringUTF8QEAAXZ ; CStringUTF8::~CStringUTF8(void)
我们在ou1340C5下断点观察函数sub_180136600调用完成后第二个参数和第三个参数的情况 很明显第3个参数rsp28harg_8是传出参数调用完成后会保存我们的license字符串
继续观察这段代码我们可以发现license text作为第1个参数传给了函数sub_18012EF30返回值放到ebx中并最终作为函数CStringUTF8::Format的第4个参数然后这个函数就结束了
那现在我们就进入函数sub_18012EF30中一探究竟
ou!sub_18012EF30
首先我们通过windbg的调试可以确定下面这个函数的两次调用分别返回了字符串REGID和FSN 后面函数sub_180136A90也被调用了两次我们先看第一次调用的参数传入情况 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 0:000 dc poi(rcx) 0000015feea4c9e8 73696874 20736920 6563696c 2065736e this is license 0000015feea4c9f8 74786574 72003300 6e696769 5c62614c text.3.riginLab\ 0000015feea4ca08 6563694c 0065736e 00756a70 00adf00d License.pju..... 0000015feea4ca18 eea4cb58 0000015f 0000002f baadf00d X..._.../....... 0000015feea4ca28 65780035 646f4d64 6e496c65 2e626968 5.xedModelInhib. 0000015feea4ca38 00666466 61745320 64656b63 73694820 fdf. Stacked His 0000015feea4ca48 72676f00 6f2e6d61 00756a70 baadf00d .ogram.opju..... 0000015feea4ca58 eea4ca18 0000015f 0000002f baadf00d ...._.../....... 0:000 dc poi(rdx) 0000015ffa7c2a08 49474552 20730044 6563696c 0065736e REGID.s license. 0000015ffa7c2a18 fa7c2978 0000015f 0000000f baadf00d x)|._........... 0000015ffa7c2a28 003d4c43 33323632 62302e00 005c6100 CL.2623..0b.a\. 0000015ffa7c2a38 fa7c2a18 0000015f 0000000f baadf00d .*|._........... 0000015ffa7c2a48 72747845 76650061 6c2e6c61 00006369 Extra.eval.lic.. 0000015ffa7c2a58 00000001 00000001 0000000f baadf00d ................ 0000015ffa7c2a68 44500003 2e324345 00464446 baadf00d ..PDEC2.FDF..... 0000015ffa7c2a78 00000001 00000002 0000000f baadf00d ................ 0:000 dc poi(r8) 00007ffb8a77f050 00000000 00000000 00000000 00000000 ................ 00007ffb8a77f060 8a77a8f8 00007ffb 00000020 00000fff ..w..... ....... 00007ffb8a77f070 00015262 00000000 fd2fff10 0000015f bR......../._... 00007ffb8a77f080 fa7c3e78 0000015f 8a7721b0 00007ffb x|._....!w..... 00007ffb8a77f090 8a7721c0 00007ffb ffffffff ffffffff .!w............. 00007ffb8a77f0a0 ffffffff 00000000 00000000 00000000 ................ 00007ffb8a77f0b0 00000000 00000000 020007d0 00000000 ................ 00007ffb8a77f0c0 8a77a8f8 00007ffb 00000040 00000fff ..w............ 0:000 r r9 r9000000000000000b
1p我们输入的license text2pREGID3p传出参数4p硬编码的值0xB
ou!sub_180136A90
现在我们进入函数sub_180136A90这个函数中调用的都是CString类的一些函数因此很容易理清逻辑这个函数会对我们的输入的license字符串作如下处理
license text转为大写获取REGID在license text中的index如果index为-1license text中不存在REGID或者0REGID位于license text的开头则返回1同时传出参数为空否则从license text的indexlen(REGID)1的位置开始截取长度为0xB的字符串填充到传出参数另外第一个参数也就是我们输入的license text也发生了变化index及之后的字符都被丢弃了
然后再次调用 可以看到该函数第二次调用的返回值会直接影响到最终的返回值0x191及401也就是license text验证失败的错误代码 很显然我们不希望最后返回401因此我们要控制al不为0那我们就可以构造出如下的license text来控制al为1 1 0FSN0123456789abcdefghijkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ
然后函数sub_180136A90两次调用的传出参数将会分别作为函数okuCountTotalSeries的参数被调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0:000 dc rcx 0000015ffa7c2a28 45444342 49484746 004c4b4a 005c6174 BCDEFGHIJKL.ta\. 0000015ffa7c2a38 fa7c2958 0000015f 0000000f baadf00d X)|._........... 0000015ffa7c2a48 003d4c43 33323632 62302e00 005c6100 CL.2623..0b.a\. 0000015ffa7c2a58 00000001 00000001 0000000f baadf00d ................ 0000015ffa7c2a68 44500003 2e324345 00464446 baadf00d ..PDEC2.FDF..... 0000015ffa7c2a78 00000001 00000002 0000000f baadf00d ................ 0000015ffa7c2a88 44004303 2e334345 00464446 baadf00d .C.DEC3.FDF..... 0000015ffa7c2a98 00000001 00000001 0000000f baadf00d ................ 0:000 dc rdx 0000015feea4c868 34333231 38373635 63626139 67666564 123456789abcdefg 0000015feea4c878 69006968 62614c6e 6369005c 65736e65 hi.inLab\.icense 0000015feea4c888 51000000 55545352 59585756 0000005a ...QRSTUVWXYZ... 0000015feea4c898 eea4cb98 0000015f 0000002f baadf00d ...._.../....... 0000015feea4c8a8 4e534630 33323130 37363534 62613938 0FSN0123456789ab 0000015feea4c8b8 66656463 6a696867 6369006b 65736e65 cdefghijk.icense 0000015feea4c8c8 65630000 0065736e 36323532 00003332 ..cense.252623.. 0000015feea4c8d8 00000001 00000013 0000002f baadf00d ......../.......
ok!okuCountTotalSeries
该函数位于C:\Program Files\OriginLab\Origin2023b\ok.dll 从上图中可以看出我们需要控制函数okuCountTotalSeries的返回值不为0
那么我们就需要控制该函数内部的这两个分支不能跳到loc_180E0C1D7 ok!sub_180E0A430
该函数代码不多逻辑也是相当的简单 就是把我们传给函数okuCountTotalSeries的第2个参数也就是字符串 1 2 123456789abcdefghi 012345678901234567
的0xB处开始拷贝出来就是cdefghi返回值就是atol函数的返回值很显然当前的注册码是没有办法转换成long的因此我们需要把cdefghi改成1234567
现在我们的注册码就变成了 1 0FSN0123456789ab1234567jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ
ok!sub_180DF8F70
这个函数的内容有一点长其传入的参数如下 1 2 3 4 5 6 7 8 9 10 11 12 13 0:000 dc rcx 0000015ffa7c3e88 45444342 49484746 004c4b4a 00006369 BCDEFGHIJKL.ic.. 0000015ffa7c3e98 00000001 00000001 0000000f baadf00d ................ 0000015ffa7c3ea8 6d650030 005c7365 00332e32 00007300 0.emes\.2.3..s.. 0000015ffa7c3eb8 00000001 00000009 0000000f baadf00d ................ 0000015ffa7c3ec8 57746547 6449646e 746e0078 006c6f72 GetWndIdx.ntrol. 0000015ffa7c3ed8 00000001 0000000f 0000000f baadf00d ................ 0000015ffa7c3ee8 4e746547 65657254 6e616843 00736567 GetNTreeChanges. 0000015ffa7c3ef8 00000001 0000000d 0000000f baadf00d ................ 0:000 r rdx rdx0000000000000000 0:000 r r8 r80000000000000000
我不想进去看了直接看一下返回值返回值是一个整型数然后和函数sub_180E0A430的返回值进行了比较如果两者不相等最终就会返回0
那么很简单我们只需要控制sub_180E0A430的返回值和该函数的返回值一样即可当前函数的返回值是0x42dded即4382189再次更新我们的注册码 1 0FSN0123456789ab4382189jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ
好了现在我们重新回到ou.dll的函数sub_18012EF30
ou!loc_18012EFDC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mov dl, 22h ; lea rcx, [rbparg_10] call cs:?ReverseFindCStringUTF8QEBAHDZ ; CStringUTF8::ReverseFind(char) lea r8d, [rax1] lea rdx, [rbpvar_18] lea rcx, [rbparg_10] call cs:?LeftCStringUTF8QEBA?AV1HZ ; CStringUTF8::Left(int) nop mov rax, [rbpvar_20] mov [rsp50hvar_30], rax mov r9, [rbparg_18] xor r8d, r8d lea rdx, [rbparg_8] lea rcx, [rbpvar_18] call sub_1801308E0 mov ecx, 191h test eax, eax cmovz edi, ecx lea rcx, [rbpvar_18] call cs:__imp_??1CStringUTF8QEAAXZ ; CStringUTF8::~CStringUTF8(void) jmp short loc_18012F033
上面汇编代码中的[rbparg_10]就是函数sub_180136A90的第1个参数我们前面已经知道了该函数会改变自己第一个参数的内容就是会把REGID和FSN及后面的字符都丢掉那么到这里[rbparg_10]的值就是 1 0
这段代码做的事就是在[rbparg_10]找到最后一次出现的位置然后丢弃该位置之后的字符
最后调用函数sub_1801308E0
ou!sub_1801308E0
我们先看一下参数传入情况
1p前面使用截取出来的字符串2p传出参数3p04p123456789ab43821895pBCDEFGHIJKL
该函数会检查第一个参数是否是空字符串因为我们没有所以第一道检查就挂了
另外就是该函数的返回值也需要进行控制根据loc_18012EFDC中的内容 1 2 3 mov ecx, 191h test eax, eax cmovz edi, ecx
只有eax不为0edi才不会变成0x191
因此我们需要经过该函数的重重检查到达loc_180130A1F 前面的检查都由CString类的函数完成分析起来很简单这里不再赘述最终形成的注册码为 1 INCREMENTFEATUREorglabSIGN123456789FSN09876543278L5824771TUVWXYZREGIDFSNABCDEFGHIJKLMNOPQRSTUVWXYZ
但是输入该注册码之后并没有提示我们注册成功虽然没有401报错了但是我们的软件仍未被激活
仔细审查函数sub_1801308E0的代码可以在函数尾部发现有一个叫做COKAccess::GetTempViewportLimits的函数被调用其第1个参数为 1 INCREMENTFEATUREorglabSIGN123456789
进入该函数一路跟到了ok.dll的sub_18012DDA0
ok!sub_18012DDA0 在该函数下断点我们可以观察到注册码中的下面三部分被存储到了内存的特定位置中 1 2 3 INCREMENTFEATUREorglabSIGN123456789 9876543278L5824771 SNABCDEFGHI
我们记录下这三个字符串存储的内存地址然后下内存读的条件断点
通过内存访问断点定位检测代码
我直接在这三个内存地址上下内存读断点 1 2 3 ba r1 00000218db5dab88 ba r1 00000218db5dac08 ba r1 00000218dc168aa8
最后在第二个断点被触发时得到如下调用栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Breakpoint 1 hit ok!OSetNumericSettings::operator0x403b: 00007ffc178eec1b 0f95c0 setne al 0:000 k # Child-SP RetAddr Call Site 00 000000adbfdff558 00007ffc178ec37b ok!OSetNumericSettings::operator0x403b 01 000000adbfdff560 00007ffc17f4eae3 ok!OSetNumericSettings::operator0x179b 02 000000adbfdff5a0 00007ffc17f54649 ok!okfxDoNewLegendEntries0x21b3 03 000000adbfdff5e0 00007ffc17f54f6d ok!GetObjectPlotCategory::ObjectSeriesSetType0x1229 04 000000adbfdff630 00007ffc17f50673 ok!GetObjectPlotCategory::ObjectSeriesSetType0x1b4d 05 000000adbfdffcb0 00007ffc17f535c1 ok!GetObjectPlotCategory::DataSeriesGetProcessedData0xe93 06 000000adbfdffce0 00007ffc17955ded ok!GetObjectPlotCategory::ObjectSeriesSetType0x1a1 07 000000adbfdffd30 00007ffc1a962731 ok!COKAccess::OkOnItemReDraw0x10d 08 000000adbfdffdb0 00007ffc33c70a7c ou!COriginApp::OnIdle0xd1 09 000000adbfdffe00 00007ffc33ca3c20 mfc140u!CWinThread::Run0x5c [D:\a\_work\1\s\src\vctools\VC7Libs\Ship\ATLMFC\Src\MFC\thrdcore.cpp 621] 0a 000000adbfdffe40 00007ff6625ea44e mfc140u!AfxWinMain0xc0 [D:\a\_work\1\s\src\vctools\VC7Libs\Ship\ATLMFC\Src\MFC\winmain.cpp 61] 0b 000000adbfdffe80 00007ffc651a7614 Origin640xa44e 0c 000000adbfdffec0 00007ffc654a26b1 KERNEL32!BaseThreadInitThunk0x14 0d 000000adbfdffef0 0000000000000000 ntdll!RtlUserThreadStart0x21
稍加整理即可得到函数的调用顺序我们只取出调用栈中的前几个即可整理出来如下的调用栈 1 2 3 4 5 6 7 sub_180793590 - sub_180790520 调用点0x18079066E - sub_180794B10 - sub_180794600 - sub_18078EAC0 - sub_18012C360 - sub_18012EC10
这里面的函数代码一个比一个长简单放一个拓扑图感受一下 所以这些函数我都是大致浏览了一下其中函数sub_180790520吸引到了我的注意
在下面这个地方0x18079066E函数调用了sub_180794B10而这个函数的返回值决定了分支的走向 继续往下浏览我发现在右边的分支最终会调用函数COKAccess::UpdateMainWinTitle这个看起来很不错因为如果是激活失败的话也没必要更新主窗口的标题可以看到我当前的测试软件的窗口标题中有一个Expired同时左边的分支并没有什么有趣的东西因此我们尝试把函数sub_180794B10的返回值强制修改为1 patch程序
这里之所以选择修改返回值而不是分析函数sub_180794B10的代码是因为这个函数实在是太复杂了不如赌一把直接修改返回值
看一下缩略图就知道这个函数有多复杂了比我上面贴的那个还复杂 想要把这个函数分析明白需要很多时间所以我选择直接patch
我们最终会从左边那个分支返回所以我们需要把mov eax, ebx修改掉保证最后的返回值非0 这个指令对应的机器码如下 1 2 3 0:001 u ok7954CB ok!GetObjectPlotCategory::ObjectSeriesSetType0x20ab: 00007ffc17f554cb 8bc3 mov eax,ebx
只占用2个字节因此我们需要搞一个只占用2字节而且还能保证eax非0的指令 如上图所示mov al, 1就正好符合我们的需求我们只需要使用管理员权限打开IDA将ok.dll的0x1807954CB修改为b001即可 使用API HOOK技术定位消息发送代码
我当时以为这把肯定能搞定了结果输入注册码之后弹出了如下消息框真的是太难了 现在我要找一下这个窗是怎么弹出来的在user32!messageboxw下断点看一下调用栈 1 2 3 4 5 6 7 Breakpoint 0 hit USER32!MessageBoxW: 00007ffc64609750 4883ec38 sub rsp,38h 0:000 k # Child-SP RetAddr Call Site 00 00000046963fe638 00007ffc1a939814 USER32!MessageBoxW 01 00000046963fe640 00007ffc33c8782e ou!CMainFrame::WindowProc0x704
这个消息框是在CMainFrame::WindowProc函数的0x18003980F位置被调用的
这个函数看名字再加上IDA分析出来的参数情况基本上能猜出来是通过消息触发的后两个参数应该就是lParam和wParam和SendMessage的参数对应 我们可以使用IDA的分支图追溯一下函数MessageBoxW的第2和第3个参数就是消息框的标题和内容是哪里来的 最终可以定位到这两个参数是在0x18003940F 和0x180039422被初始化的从这个地方的函数名称和参数传入情况就能看出来第一个参数是传出参数将会保存一个字符串并且这个函数两次调用其第2个参数分别是CMainFrame::WindowProc的第4和第3个参数
因此我们可以下下面这样的断点来观察该函数调用前后的参数情况 1 2 3 ba e1 ou3940F r rdx;g ba e1 ou39422 dc poi(rsp48);r rdx;g ba e1 ou39428 dc poi(rsp40);g
得到如下结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 rdx0000000000003f0e 000001d9d2b91dc8 00740041 00650074 0074006e 006f0069 A.t.t.e.n.t.i.o. 000001d9d2b91dd8 0021006e abab0000 abababab abababab n.!............. 000001d9d2b91de8 abababab feeeabab 00000000 00000000 ................ 000001d9d2b91df8 00000000 00000000 feeefeee feeefeee ................ 000001d9d2b91e08 bd768892 00fbed99 d2a8e380 000001d9 ..v............. 000001d9d2b91e18 c305e490 000001d9 feeefeee feeefeee ................ 000001d9d2b91e28 feeefeee feeefeee feeefeee feeefeee ................ 000001d9d2b91e38 feeefeee feeefeee feeefeee feeefeee ................ rdx0000000000003fb6 000001d9d2a8e398 006f0059 00720075 00460020 0045004c Y.o.u.r. .F.L.E. 000001d9d2a8e3a8 006c0058 0020006d 0069006c 00650063 X.l.m. .l.i.c.e. 000001d9d2a8e3b8 0073006e 00200065 00690066 0065006c n.s.e. .f.i.l.e. 000001d9d2a8e3c8 00690020 00200073 006e0069 00610076 .i.s. .i.n.v.a. 000001d9d2a8e3d8 0069006c 002e0064 abab0000 abababab l.i.d........... 000001d9d2a8e3e8 abababab abababab feeeabab feeefeee ................ 000001d9d2a8e3f8 feeefeee feeefeee 00000000 00000000 ................ 000001d9d2a8e408 00000000 00000000 feeefeee feeefeee ................
可以看到两次调用的第2个参数分别为0x3f0e和0x3fb6我们选择0x3fb6作为过滤条件因为标题区分度不够高可能别的窗口也会使用同样的标题而内容重复的概率不高那么我们就可以HOOK住消息发送函数检测传进来的wParam是否为0x3fb6消息发送函数我知道的一共有两个一个是SendMessage另一个是PostMessage我当时先HOOK的SendMessageW但是并没有拦截到wParam值为0x3fb6的调用后面我又HOOK了PostMessageW成功拦截下面我记录一下我HOOK这两个API的过程
api hook的技术细节
我在网上搜了搜给出的方案是把原始函数的前面几个字节替换成跳转到我们的hook函数的指令然后在hook函数中对参数进行过滤之后恢复原始函数的前面几个字节再去调用原始函数并返回
但是这个并不满足我的需求由于在hook函数内修复了原始函数所以他只能hook一次
我后来想的办法是在hook函数内修复原始函数调用原始函数后保存返回值然后再修改原始函数的前几个字节重新hook住这个函数再返回前面保存的返回值但是这样在多线程中会出现问题最后在汪哥的提醒下找到了下面的hook方式 最终导致的结果就是当PostMessageW被调用的时候指令的走向就变成了下面这样 原理已经清楚代码就很容易写了如下图所示我们顺利定位到了发送该消息的调用栈 这条消息实际上是由sub_180793590发送的调用位置在0x180793625 而这个函数又是在OkOnItemReDraw的0x180195DE8 位置调用的观察该函数的分支走向并结合里面调用的各个函数名分析即可定位到关键判断语句的位置0x180195D3E 1 2 test eax, eax jnz loc_18019624F
故技重施把jnz修改为jz即可前者的机器码为0F 85后者的机器码为0F 84修改之后重新打开OriginPro没有任何弹窗也不会出现过一段时间就自动退出的情况虽然查看注册信息仍然是未激活状态但是已经不影响正常使用了
OK这次的破解就到这儿吧不足之处还望各位师傅指点
本篇文章中用到的工具以及patch之后的DLL都打包放到这里了