返回
顶部

本文中所有涉及到的压缩包密码均为1

最近破解了一个MFC程序,OriginLab Pro,这里记录一下相关过程以及学到的一些东西

软件下载地址下载整个仓库然后和并解压即可

定位激活代码

程序安装完毕之后,打开就会弹出激活界面

image-20230731133459135

我们选择使用license进行离线激活

image-20230731133626522

对于这种输入框,最后肯定是要使用USER32!GetWindowText*来获取用户输入的,在输入框中随便输个字符串,然后在windbg中使用bm下个通配符断点

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处理逻辑

image-20230731142s12806

我们使用IDA来看一下调用栈中编号为02的位置,首先这个代码位于C:\Program Files\OriginLab\Origin2023b\ou.dll

计算出偏移量

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中跳转到对应的位置

.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

image-20230731142312806

分析汇编代码

把函数sub_180136600整体给看了一下,并没有找到激活相关的代码,那我们就接着看调用栈中编号03的代码,也就是函数sub_180134090

ou!sub_180134090

该函数中的关键代码如下:

.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调用完成后第二个参数和第三个参数的情况

image-20230731145750931

很明显,第3个参数rsp+28h+arg_8是传出参数,调用完成后会保存我们的license字符串

继续观察这段代码我们可以发现,license text作为第1个参数传给了函数sub_18012EF30,返回值放到ebx中并最终作为函数CStringUTF8::Format的第4个参数,然后这个函数就结束了

那现在我们就进入函数sub_18012EF30中一探究竟

ou!sub_18012EF30

首先我们通过windbg的调试可以确定下面这个函数的两次调用分别返回了字符串REGIDFSN

image-20230731150429309

后面函数sub_180136A90也被调用了两次,我们先看第一次调用的参数传入情况

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

ou!sub_180136A90

现在我们进入函数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及之后的字符都被丢弃了

然后再次调用

image-20230731152625752

可以看到该函数第二次调用的返回值会直接影响到最终的返回值,0x191即401,也就是license text验证失败的错误代码

image-20230731152809407

很显然我们不希望最后返回401,因此我们要控制al不为0,那我们就可以构造出如下的license text来控制al为1

0FSN0123456789abcdefghijkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ

然后函数sub_180136A90两次调用的传出参数将会分别作为函数okuCountTotalSeries的参数被调用

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  ......../.......

ok!okuCountTotalSeries

该函数位于C:\Program Files\OriginLab\Origin2023b\ok.dll

image-20230731162804040

从上图中可以看出,我们需要控制函数okuCountTotalSeries的返回值不为0

那么我们就需要控制该函数内部的这两个分支,不能跳到loc_180E0C1D7

image-20230731162949620

ok!sub_180E0A430

该函数代码不多,逻辑也是相当的简单

image-20230731163332758

就是把我们传给函数okuCountTotalSeries的第2个参数,也就是字符串

123456789abcdefghi
012345678901234567

的0xB处开始拷贝出来,就是cdefghi,返回值就是atol函数的返回值,很显然,当前的注册码是没有办法转换成long的,因此我们需要把cdefghi改成1234567

现在我们的注册码就变成了

0FSN0123456789ab1234567jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ

ok!sub_180DF8F70

这个函数的内容有一点长,其传入的参数如下:

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,再次更新我们的注册码

0FSN0123456789ab4382189jkREGIDABCDEFGHIJKLMNOPQRSTUVWXYZ

好了,现在我们重新回到ou.dll的函数sub_18012EF30

ou!loc_18012EFDC

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]的值就是

0

这段代码做的事就是在[rbp+arg_10]找到"最后一次出现的位置,然后丢弃该位置之后的字符

最后调用函数sub_1801308E0

ou!sub_1801308E0

我们先看一下参数传入情况

  • 1p:前面使用"截取出来的字符串
  • 2p:传出参数
  • 3p:0
  • 4p:123456789ab4382189
  • 5p:BCDEFGHIJKL

该函数会检查第一个参数是否是空字符串,因为我们没有",所以第一道检查就挂了

另外就是该函数的返回值也需要进行控制,根据loc_18012EFDC中的内容

mov     ecx, 191h
test    eax, eax
cmovz   edi, ecx

只有eax不为0,edi才不会变成0x191

因此我们需要经过该函数的重重检查,到达loc_180130A1F

image-20230731171634145

前面的检查都由CString类的函数完成,分析起来很简单,这里不再赘述,最终形成的注册码为:

INCREMENTFEATUREorglabSIGN123456789""FSN09876543278L5824771TUVWXYZREGIDFSNABCDEFGHIJKLMNOPQRSTUVWXYZ

但是输入该注册码之后,并没有提示我们注册成功,虽然没有401报错了,但是我们的软件仍未被激活

仔细审查函数sub_1801308E0的代码,可以在函数尾部发现有一个叫做COKAccess::GetTempViewportLimits的函数被调用,其第1个参数为

INCREMENTFEATUREorglabSIGN123456789""

进入该函数,一路跟到了ok.dllsub_18012DDA0

ok!sub_18012DDA0

image-20230731174604691

在该函数下断点,我们可以观察到,注册码中的下面三部分被存储到了内存的特定位置中

INCREMENTFEATUREorglabSIGN123456789""
9876543278L5824771
SNABCDEFGHI

我们记录下这三个字符串存储的内存地址,然后下内存读的条件断点

通过内存访问断点定位检测代码

我直接在这三个内存地址上下内存读断点

ba r1 00000218`db5dab88 
ba r1 00000218`db5dac08
ba r1 00000218`dc168aa8 

最后在第二个断点被触发时得到如下调用栈

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

稍加整理,即可得到函数的调用顺序,我们只取出调用栈中的前几个即可,整理出来如下的调用栈

sub_180793590
    -> sub_180790520   调用点:0x18079066E
        -> sub_180794B10
            -> sub_180794600
                -> sub_18078EAC0 
                    -> sub_18012C360
                        -> sub_18012EC10

这里面的函数代码一个比一个长,简单放一个拓扑图感受一下

image-20230801012233407

所以这些函数我都是大致浏览了一下,其中函数sub_180790520吸引到了我的注意

在下面这个地方(0x18079066E),函数调用了sub_180794B10,而这个函数的返回值决定了分支的走向

image-20230801012617114

继续往下浏览,我发现在右边的分支最终会调用函数COKAccess::UpdateMainWinTitle,这个看起来很不错,因为如果是激活失败的话也没必要更新主窗口的标题,可以看到我当前的测试软件的窗口标题中有一个Expired,同时左边的分支并没有什么有趣的东西,因此我们尝试把函数sub_180794B10的返回值强制修改为1

image-20230801012947437

patch程序

这里之所以选择修改返回值而不是分析函数sub_180794B10的代码,是因为这个函数实在是太复杂了,不如赌一把直接修改返回值

看一下缩略图就知道这个函数有多复杂了,比我上面贴的那个还复杂

image-20230801015555174

想要把这个函数分析明白需要很多时间,所以我选择直接patch

我们最终会从左边那个分支返回,所以我们需要把mov eax, ebx修改掉,保证最后的返回值非0

image-20230801015820452

这个指令对应的机器码如下

0:001> u ok+7954CB 
ok!GetObjectPlotCategory::ObjectSeriesSetType+0x20ab:
00007ffc`17f554cb 8bc3            mov     eax,ebx

只占用2个字节,因此我们需要搞一个只占用2字节,而且还能保证eax非0的指令

image-20230801020330001

如上图所示,mov al, 1就正好符合我们的需求,我们只需要使用管理员权限打开IDA,将ok.dll的0x1807954CB修改为b001即可

image-202308010228518a71

使用API HOOK技术定位消息发送代码

我当时以为这把肯定能搞定了,结果输入注册码之后弹出了如下消息框,真的是太难了

image-20230801022851871

现在我要找一下这个窗是怎么弹出来的,在user32!messageboxw下断点,看一下调用栈

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的参数对应

image-20230801025717941

我们可以使用IDA的分支图追溯一下函数MessageBoxW的第2和第3个参数(就是消息框的标题和内容)是哪里来的

imasge-20230801025717941

最终可以定位到这两个参数是在0x18003940F0x180039422被初始化的,从这个地方的函数名称和参数传入情况就能看出来,第一个参数是传出参数,将会保存一个字符串,并且这个函数两次调用,其第2个参数分别是CMainFrame::WindowProc的第4和第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"

得到如下结果:

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个参数分别为0x3f0e0x3fb6,我们选择0x3fb6作为过滤条件,因为标题区分度不够高,可能别的窗口也会使用同样的标题,而内容重复的概率不高,那么我们就可以HOOK住消息发送函数,检测传进来的wParam是否为0x3fb6,消息发送函数我知道的一共有两个,一个是SendMessage,另一个是PostMessage,我当时先HOOK的SendMessageW,但是并没有拦截到wParam值为0x3fb6的调用,后面我又HOOK了PostMessageW,成功拦截,下面我记录一下我HOOK这两个API的过程

api hook的技术细节

我在网上搜了搜,给出的方案是把原始函数的前面几个字节替换成跳转到我们的hook函数的指令,然后在hook函数中对参数进行过滤,之后恢复原始函数的前面几个字节,再去调用原始函数并返回

但是这个并不满足我的需求,由于在hook函数内修复了原始函数,所以他只能hook一次

我后来想的办法是在hook函数内修复原始函数,调用原始函数后保存返回值,然后再修改原始函数的前几个字节,重新hook住这个函数,再返回前面保存的返回值,但是这样在多线程中会出现问题,最后在汪哥的提醒下,找到了下面的hook方式

image-20230801200025544

最终导致的结果就是,当PostMessageW被调用的时候,指令的走向就变成了下面这样

image-20230801200204285

原理已经清楚,代码就很容易写了,如下图所示,我们顺利定位到了发送该消息的调用栈

image-20230801213826730

这条消息实际上是由sub_180793590发送的,调用位置在0x180793625

image-20230801204150489

而这个函数又是在OkOnItemReDraw0x180195DE8位置调用的,观察该函数的分支走向并结合里面调用的各个函数名分析即可定位到关键判断语句的位置:0x180195D3E

test    eax, eax
jnz     loc_18019624F

故技重施,把jnz修改为jz即可,前者的机器码为0F 85,后者的机器码为0F 84,修改之后,重新打开OriginPro,没有任何弹窗,也不会出现过一段时间就自动退出的情况,虽然查看注册信息仍然是未激活状态,但是已经不影响正常使用了

OK,这次的破解就到这儿吧,不足之处还望各位师傅指点

本篇文章中用到的工具以及patch之后的DLL都打包放到这里