更新,修改了一下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 : 00007ffb `a7f06f50 @! "USER32!GetWindowTextW$filt$0" 2 : 00007ffb `a7f06e95 @! "USER32!GetWindowTextA$filt$0" 3 : 00007ffb `a7ed9490 @! "USER32!GetWindowTextA" 4 : 00007ffb `a7eda200 @! "USER32!GetWindowTextLengthW" 5 : 00007ffb `a7edc2f0 @! "USER32!GetWindowTextW" 6 : 00007ffb `a7f59ce0 @! "USER32!GetWindowTextLengthA" |
之后点击OK
实际测试发现,在我们点击OK之前,这些断点就会被反复触发,这样会干扰我们找到真正的license处理逻辑,因此我们需要改进一下,先在COMCTL32!Button_WndProc+0x7fb
下断点,这个断点只有在点击按钮的时候才会被触发,然后点击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 00000000 ` 10000000 00000000 ` 1046d000 Resource (deferred) 00007ffb ` 53b40000 00007ffb ` 54452000 SogouPy (deferred) 00007ffb ` 54460000 00007ffb ` 546a3000 sogoutsf (deferred) 00007ffb ` 54d90000 00007ffb ` 54de1000 OUIM (deferred) 00007ffb ` 76fb0000 00007ffb ` 76feb000 OCcontour (deferred) 00007ffb ` 79400000 00007ffb ` 794a4000 Outl (deferred) 00007ffb ` 794b0000 00007ffb ` 79be7000 ou (export symbols) C:\Program Files\OriginLab\Origin2023b\ou.dll 0 : 000 > ? ou!COUxlView::xlWorksheetToNativeOrigin + 0x23489 - 00007ffb ` 794b0000 Evaluate expression: 1271337 = 00000000 ` 00136629 |
根据该偏移量在IDA中跳转到对应的位置
1 2 3 4 | .text: 000000018013661C lea rdx, [rsp + 38h + arg_18] .text: 0000000180136621 mov rcx, rbx .text: 0000000180136624 call ?GetWindowTextW@CWnd@@QEBAXAEAV?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@@Z ; CWnd::GetWindowTextW(ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> &) .text: 0000000180136629 lea rcx, [rsp + 38h + arg_18] |
根据我们的经验,一眼就能看出rdx是传出参数,也就是说[rsp+38h+arg_18]
会保存我们输入的license
把函数sub_180136600
整体给看了一下,并没有找到激活相关的代码,那我们就接着看调用栈中编号03的代码,也就是函数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, [rsp + 28h + arg_8] .text: 00000001801340AF call cs:?? 0CStringUTF8 @@QEAA@XZ ; CStringUTF8::CStringUTF8(void) .text: 00000001801340B5 nop .text: 00000001801340B6 lea rdx, [rdi + 490h ] .text: 00000001801340BD lea r8, [rsp + 28h + arg_8] .text: 00000001801340C2 mov rcx, rdi .text: 00000001801340C5 call sub_180136600 .text: 00000001801340CA mov rcx, [rsp + 28h + arg_8] .text: 00000001801340CF call sub_18012EF30 .text: 00000001801340D4 mov ebx, eax .text: 00000001801340D6 lea rcx, [rsp + 28h + arg_0] .text: 00000001801340DB call cs:?? 0CStringUTF8 @@QEAA@XZ ; 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, [rsp + 28h + arg_0] .text: 00000001801340F8 call cs:? Format @CStringUTF8@@QEAAXPEBDZZ ; CStringUTF8:: Format (char const * ,...) .text: 00000001801340FE mov r8d, 1 .text: 0000000180134104 mov rdx, [rsp + 28h + arg_0] .text: 0000000180134109 lea ecx, [r8 + 14h ] .text: 000000018013410D call LABUTIL_diagnostics .text: 0000000180134112 nop .text: 0000000180134113 lea rcx, [rsp + 28h + arg_0] .text: 0000000180134118 call cs:__imp_?? 1CStringUTF8 @@QEAA@XZ ; 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, [rsp + 28h + arg_8] .text: 0000000180134134 call cs:__imp_?? 1CStringUTF8 @@QEAA@XZ ; CStringUTF8::~CStringUTF8(void) |
我们在ou+1340C5
下断点,观察函数sub_180136600
调用完成后第二个参数和第三个参数的情况
很明显,第3个参数rsp+28h+arg_8
是传出参数,调用完成后会保存我们的license字符串
继续观察这段代码我们可以发现,license text作为第1个参数传给了函数sub_18012EF30
,返回值放到ebx中并最终作为函数CStringUTF8::Format
的第4个参数,然后这个函数就结束了
那现在我们就进入函数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) 0000015f `eea4c9e8 73696874 20736920 6563696c 2065736e this is license 0000015f `eea4c9f8 74786574 72003300 6e696769 5c62614c text. 3.riginLab \ 0000015f `eea4ca08 6563694c 0065736e 00756a70 00adf00d License.pju..... 0000015f `eea4ca18 eea4cb58 0000015f 0000002f baadf00d X..._... / ....... 0000015f `eea4ca28 65780035 646f4d64 6e496c65 2e626968 5.xedModelInhib . 0000015f `eea4ca38 00666466 61745320 64656b63 73694820 fdf. Stacked His 0000015f `eea4ca48 72676f00 6f2e6d61 00756a70 baadf00d .ogram.opju..... 0000015f `eea4ca58 eea4ca18 0000015f 0000002f baadf00d ...._... / ....... 0 : 000 > dc poi(rdx) 0000015f `fa7c2a08 49474552 20730044 6563696c 0065736e REGID.s license. 0000015f `fa7c2a18 fa7c2978 0000015f 0000000f baadf00d x)|._........... 0000015f `fa7c2a28 003d4c43 33323632 62302e00 005c6100 CL = . 2623. . 0b .a\. 0000015f `fa7c2a38 fa7c2a18 0000015f 0000000f baadf00d . * |._........... 0000015f `fa7c2a48 72747845 76650061 6c2e6c61 00006369 Extra. eval .lic.. 0000015f `fa7c2a58 00000001 00000001 0000000f baadf00d ................ 0000015f `fa7c2a68 44500003 2e324345 00464446 baadf00d ..PDEC2.FDF..... 0000015f `fa7c2a78 00000001 00000002 0000000f baadf00d ................ 0 : 000 > dc poi(r8) 00007ffb ` 8a77f050 00000000 00000000 00000000 00000000 ................ 00007ffb ` 8a77f060 8a77a8f8 00007ffb 00000020 00000fff ..w..... ....... 00007ffb ` 8a77f070 00015262 00000000 fd2fff10 0000015f bR........ / ._... 00007ffb ` 8a77f080 fa7c3e78 0000015f 8a7721b0 00007ffb x>|._....!w..... 00007ffb ` 8a77f090 8a7721c0 00007ffb ffffffff ffffffff .!w............. 00007ffb ` 8a77f0a0 ffffffff 00000000 00000000 00000000 ................ 00007ffb ` 8a77f0b0 00000000 00000000 020007d0 00000000 ................ 00007ffb ` 8a77f0c0 8a77a8f8 00007ffb 00000040 00000fff ..w.....@....... 0 : 000 > r r9 r9 = 000000000000000b |
- 1p:我们输入的license text
- 2p:REGID
- 3p:传出参数
- 4p:硬编码的值,0xB
现在我们进入函数sub_180136A90
,这个函数中调用的都是CString类的一些函数,因此很容易理清逻辑,这个函数会对我们的输入的license字符串作如下处理
- license text转为大写
- 获取REGID在license text中的index
- 如果index为-1(license text中不存在REGID)或者0(REGID位于license text的开头),则返回1,同时传出参数为空
- 否则从license text的
index+len('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 0000015f `fa7c2a28 45444342 49484746 004c4b4a 005c6174 BCDEFGHIJKL.ta\. 0000015f `fa7c2a38 fa7c2958 0000015f 0000000f baadf00d X)|._........... 0000015f `fa7c2a48 003d4c43 33323632 62302e00 005c6100 CL = . 2623. . 0b .a\. 0000015f `fa7c2a58 00000001 00000001 0000000f baadf00d ................ 0000015f `fa7c2a68 44500003 2e324345 00464446 baadf00d ..PDEC2.FDF..... 0000015f `fa7c2a78 00000001 00000002 0000000f baadf00d ................ 0000015f `fa7c2a88 44004303 2e334345 00464446 baadf00d .C.DEC3.FDF..... 0000015f `fa7c2a98 00000001 00000001 0000000f baadf00d ................ 0 : 000 > dc rdx 0000015f `eea4c868 34333231 38373635 63626139 67666564 123456789abcdefg 0000015f `eea4c878 69006968 62614c6e 6369005c 65736e65 hi.inLab\.icense 0000015f `eea4c888 51000000 55545352 59585756 0000005a ...QRSTUVWXYZ... 0000015f `eea4c898 eea4cb98 0000015f 0000002f baadf00d ...._... / ....... 0000015f `eea4c8a8 4e534630 33323130 37363534 62613938 0FSN0123456789ab 0000015f `eea4c8b8 66656463 6a696867 6369006b 65736e65 cdefghijk.icense 0000015f `eea4c8c8 65630000 0065736e 36323532 00003332 ..cense. 252623. . 0000015f `eea4c8d8 00000001 00000013 0000002f baadf00d ........ / ....... |
该函数位于C:\Program Files\OriginLab\Origin2023b\ok.dll
从上图中可以看出,我们需要控制函数okuCountTotalSeries
的返回值不为0
那么我们就需要控制该函数内部的这两个分支,不能跳到loc_180E0C1D7
该函数代码不多,逻辑也是相当的简单
就是把我们传给函数okuCountTotalSeries
的第2个参数,也就是字符串
1 2 | 123456789abcdefghi 012345678901234567 |
的0xB处开始拷贝出来,就是cdefghi
,返回值就是atol
函数的返回值,很显然,当前的注册码是没有办法转换成long的,因此我们需要把cdefghi
改成1234567
现在我们的注册码就变成了
1 | 0FSN0123456789ab1234567jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ |
这个函数的内容有一点长,其传入的参数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 0 : 000 > dc rcx 0000015f `fa7c3e88 45444342 49484746 004c4b4a 00006369 BCDEFGHIJKL.ic.. 0000015f `fa7c3e98 00000001 00000001 0000000f baadf00d ................ 0000015f `fa7c3ea8 6d650030 005c7365 00332e32 00007300 0.emes \. 2.3 ..s.. 0000015f `fa7c3eb8 00000001 00000009 0000000f baadf00d ................ 0000015f `fa7c3ec8 57746547 6449646e 746e0078 006c6f72 GetWndIdx.ntrol. 0000015f `fa7c3ed8 00000001 0000000f 0000000f baadf00d ................ 0000015f `fa7c3ee8 4e746547 65657254 6e616843 00736567 GetNTreeChanges. 0000015f `fa7c3ef8 00000001 0000000d 0000000f baadf00d ................ 0 : 000 > r rdx rdx = 0000000000000000 0 : 000 > r r8 r8 = 0000000000000000 |
我不想进去看了,直接看一下返回值,返回值是一个整型数,然后和函数sub_180E0A430
的返回值进行了比较,如果两者不相等,最终就会返回0
那么很简单,我们只需要控制sub_180E0A430
的返回值和该函数的返回值一样即可,当前函数的返回值是0x42dded
,即4382189
,再次更新我们的注册码
1 | 0FSN0123456789ab4382189jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ |
好了,现在我们重新回到ou.dll
的函数sub_18012EF30
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, [rbp + arg_10] call cs:?ReverseFind@CStringUTF8@@QEBAHD@Z ; CStringUTF8::ReverseFind(char) lea r8d, [rax + 1 ] lea rdx, [rbp + var_18] lea rcx, [rbp + arg_10] call cs:?Left@CStringUTF8@@QEBA?AV1@H@Z ; CStringUTF8::Left( int ) nop mov rax, [rbp + var_20] mov [rsp + 50h + var_30], rax mov r9, [rbp + arg_18] xor r8d, r8d lea rdx, [rbp + arg_8] lea rcx, [rbp + var_18] call sub_1801308E0 mov ecx, 191h test eax, eax cmovz edi, ecx lea rcx, [rbp + var_18] call cs:__imp_?? 1CStringUTF8 @@QEAA@XZ ; CStringUTF8::~CStringUTF8(void) jmp short loc_18012F033 |
上面汇编代码中的[rbp+arg_10]
就是函数sub_180136A90
的第1个参数,我们前面已经知道了该函数会改变自己第一个参数的内容,就是会把REGID和FSN及后面的字符都丢掉,那么到这里[rbp+arg_10]
的值就是
这段代码做的事就是在[rbp+arg_10]
找到"
最后一次出现的位置,然后丢弃该位置之后的字符
最后调用函数sub_1801308E0
我们先看一下参数传入情况
- 1p:前面使用
"
截取出来的字符串 - 2p:传出参数
- 3p:0
- 4p:123456789ab4382189
- 5p:BCDEFGHIJKL
该函数会检查第一个参数是否是空字符串,因为我们没有"
,所以第一道检查就挂了
另外就是该函数的返回值也需要进行控制,根据loc_18012EFDC
中的内容
1 2 3 | mov ecx, 191h test eax, eax cmovz edi, ecx |
只有eax不为0,edi才不会变成0x191
因此我们需要经过该函数的重重检查,到达loc_180130A1F
前面的检查都由CString类的函数完成,分析起来很简单,这里不再赘述,最终形成的注册码为:
1 | INCREMENTFEATUREorglabSIGN123456789""FSN09876543278L5824771TUVWXYZREGIDFSNABCDEFGHIJKLMNOPQRSTUVWXYZ |
但是输入该注册码之后,并没有提示我们注册成功,虽然没有401报错了,但是我们的软件仍未被激活
仔细审查函数sub_1801308E0
的代码,可以在函数尾部发现有一个叫做COKAccess::GetTempViewportLimits
的函数被调用,其第1个参数为
1 | INCREMENTFEATUREorglabSIGN123456789"" |
进入该函数,一路跟到了ok.dll
的sub_18012DDA0
在该函数下断点,我们可以观察到,注册码中的下面三部分被存储到了内存的特定位置中
1 2 3 | INCREMENTFEATUREorglabSIGN123456789"" 9876543278L5824771 SNABCDEFGHI |
我们记录下这三个字符串存储的内存地址,然后下内存读的条件断点
我直接在这三个内存地址上下内存读断点
1 2 3 | ba r1 00000218 `db5dab88 ba r1 00000218 `db5dac08 ba r1 00000218 `dc168aa8 |
最后在第二个断点被触发时得到如下调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Breakpoint 1 hit ok!OSetNumericSettings::operator = + 0x403b : 00007ffc ` 178eec1b 0f95c0 setne al 0 : 000 > k # Child-SP RetAddr Call Site 00 000000ad `bfdff558 00007ffc ` 178ec37b ok!OSetNumericSettings::operator = + 0x403b 01 000000ad `bfdff560 00007ffc ` 17f4eae3 ok!OSetNumericSettings::operator = + 0x179b 02 000000ad `bfdff5a0 00007ffc ` 17f54649 ok!okfxDoNewLegendEntries + 0x21b3 03 000000ad `bfdff5e0 00007ffc ` 17f54f6d ok!GetObjectPlotCategory::ObjectSeriesSetType + 0x1229 04 000000ad `bfdff630 00007ffc ` 17f50673 ok!GetObjectPlotCategory::ObjectSeriesSetType + 0x1b4d 05 000000ad `bfdffcb0 00007ffc ` 17f535c1 ok!GetObjectPlotCategory::DataSeriesGetProcessedData + 0xe93 06 000000ad `bfdffce0 00007ffc ` 17955ded ok!GetObjectPlotCategory::ObjectSeriesSetType + 0x1a1 07 000000ad `bfdffd30 00007ffc ` 1a962731 ok!COKAccess::OkOnItemReDraw + 0x10d 08 000000ad `bfdffdb0 00007ffc ` 33c70a7c ou!COriginApp::OnIdle + 0xd1 09 000000ad `bfdffe00 00007ffc ` 33ca3c20 mfc140u!CWinThread::Run + 0x5c [D:\a\_work\ 1 \s\src\vctools\VC7Libs\Ship\ATLMFC\Src\MFC\thrdcore.cpp @ 621 ] 0a 000000ad `bfdffe40 00007ff6 ` 625ea44e mfc140u!AfxWinMain + 0xc0 [D:\a\_work\ 1 \s\src\vctools\VC7Libs\Ship\ATLMFC\Src\MFC\winmain.cpp @ 61 ] 0b 000000ad `bfdffe80 00007ffc ` 651a7614 Origin64 + 0xa44e 0c 000000ad `bfdffec0 00007ffc ` 654a26b1 KERNEL32!BaseThreadInitThunk + 0x14 0d 000000ad `bfdffef0 00000000 ` 00000000 ntdll!RtlUserThreadStart + 0x21 |
稍加整理,即可得到函数的调用顺序,我们只取出调用栈中的前几个即可,整理出来如下的调用栈
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
这里之所以选择修改返回值而不是分析函数sub_180794B10
的代码,是因为这个函数实在是太复杂了,不如赌一把直接修改返回值
看一下缩略图就知道这个函数有多复杂了,比我上面贴的那个还复杂
想要把这个函数分析明白需要很多时间,所以我选择直接patch
我们最终会从左边那个分支返回,所以我们需要把mov eax, ebx
修改掉,保证最后的返回值非0
这个指令对应的机器码如下
1 2 3 | 0 : 001 > u ok + 7954CB ok!GetObjectPlotCategory::ObjectSeriesSetType + 0x20ab : 00007ffc ` 17f554cb 8bc3 mov eax,ebx |
只占用2个字节,因此我们需要搞一个只占用2字节,而且还能保证eax非0的指令
如上图所示,mov al, 1
就正好符合我们的需求,我们只需要使用管理员权限打开IDA,将ok.dll的0x1807954CB
修改为b001
即可
我当时以为这把肯定能搞定了,结果输入注册码之后弹出了如下消息框,真的是太难了
现在我要找一下这个窗是怎么弹出来的,在user32!messageboxw
下断点,看一下调用栈
1 2 3 4 5 6 7 | Breakpoint 0 hit USER32!MessageBoxW: 00007ffc ` 64609750 4883ec38 sub rsp, 38h 0 : 000 > k # Child-SP RetAddr Call Site 00 00000046 ` 963fe638 00007ffc ` 1a939814 USER32!MessageBoxW 01 00000046 ` 963fe640 00007ffc ` 33c8782e ou!CMainFrame::WindowProc + 0x704 |
这个消息框是在CMainFrame::WindowProc
函数的0x18003980F
位置被调用的
这个函数看名字再加上IDA分析出来的参数情况,基本上能猜出来是通过消息触发的,后两个参数应该就是lParam和wParam,和SendMessage
的参数对应
我们可以使用IDA的分支图追溯一下函数MessageBoxW
的第2和第3个参数(就是消息框的标题和内容)是哪里来的
最终可以定位到这两个参数是在0x18003940F
和0x180039422
被初始化的,从这个地方的函数名称和参数传入情况就能看出来,第一个参数是传出参数,将会保存一个字符串,并且这个函数两次调用,其第2个参数分别是CMainFrame::WindowProc
的第4和第3个参数
因此我们可以下下面这样的断点来观察该函数调用前后的参数情况
1 2 3 | ba e1 ou + 3940F "r rdx;g" ba e1 ou + 39422 "dc poi(rsp+48);r rdx;g" ba e1 ou + 39428 "dc poi(rsp+40);g" |
得到如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | rdx = 0000000000003f0e 000001d9 `d2b91dc8 00740041 00650074 0074006e 006f0069 A.t.t.e.n.t.i.o. 000001d9 `d2b91dd8 0021006e abab0000 abababab abababab n.!............. 000001d9 `d2b91de8 abababab feeeabab 00000000 00000000 ................ 000001d9 `d2b91df8 00000000 00000000 feeefeee feeefeee ................ 000001d9 `d2b91e08 bd768892 00fbed99 d2a8e380 000001d9 ..v............. 000001d9 `d2b91e18 c305e490 000001d9 feeefeee feeefeee ................ 000001d9 `d2b91e28 feeefeee feeefeee feeefeee feeefeee ................ 000001d9 `d2b91e38 feeefeee feeefeee feeefeee feeefeee ................ rdx = 0000000000003fb6 000001d9 `d2a8e398 006f0059 00720075 00460020 0045004c Y.o.u.r. .F.L.E. 000001d9 `d2a8e3a8 006c0058 0020006d 0069006c 00650063 X.l.m. .l.i.c.e. 000001d9 `d2a8e3b8 0073006e 00200065 00690066 0065006c n.s.e. .f.i.l.e. 000001d9 `d2a8e3c8 00690020 00200073 006e0069 00610076 .i.s. .i.n.v.a. 000001d9 `d2a8e3d8 0069006c 002e0064 abab0000 abababab l.i.d........... 000001d9 `d2a8e3e8 abababab abababab feeeabab feeefeee ................ 000001d9 `d2a8e3f8 feeefeee feeefeee 00000000 00000000 ................ 000001d9 `d2a8e408 00000000 00000000 feeefeee feeefeee ................ |
可以看到两次调用的第2个参数分别为0x3f0e
和0x3fb6
,我们选择0x3fb6
作为过滤条件,因为标题区分度不够高,可能别的窗口也会使用同样的标题,而内容重复的概率不高,那么我们就可以HOOK住消息发送函数,检测传进来的wParam
是否为0x3fb6
,消息发送函数我知道的一共有两个,一个是SendMessage,另一个是PostMessage,我当时先HOOK的SendMessageW
,但是并没有拦截到wParam
值为0x3fb6
的调用,后面我又HOOK了PostMessageW
,成功拦截,下面我记录一下我HOOK这两个API的过程
我在网上搜了搜,给出的方案是把原始函数的前面几个字节替换成跳转到我们的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都打包放到这里了