API HOOK技术在MFC程序破解过程中的应用

news2024/11/24 12:28:44

更新,修改了一下typora的上传脚本,把图片全部上传到看雪上了

本文已于2023-08-02首发于个人博客

图片加载不出来,放了一个PDF版本在附件里

文中有几张图片是动图,如果不会动,可以去我的个人博客看

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

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

定位激活代码

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

image-20230731133459135

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

image-20230731133626522

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

1

2

3

4

5

6

7

0:005> bm user32!getwindowtext*

  100007ffb`a7f06f50 @!"USER32!GetWindowTextW$filt$0"

  200007ffb`a7f06e95 @!"USER32!GetWindowTextA$filt$0"

  300007ffb`a7ed9490 @!"USER32!GetWindowTextA"

  400007ffb`a7eda200 @!"USER32!GetWindowTextLengthW"

  500007ffb`a7edc2f0 @!"USER32!GetWindowTextW"

  600007ffb`a7f59ce0 @!"USER32!GetWindowTextLengthA"

之后点击OK

实际测试发现,在我们点击OK之前,这些断点就会被反复触发,这样会干扰我们找到真正的license处理逻辑,因此我们需要改进一下,先在COMCTL32!Button_WndProc+0x7fb下断点,这个断点只有在点击按钮的时候才会被触发,然后点击OK,该断点触发之后,再下上面的通配符断点,即可定位到真正的license处理逻辑

image-20230731142s12806

我们使用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

image-20230731142312806

分析汇编代码

把函数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, [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也被调用了两次,我们先看第一次调用的参数传入情况

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

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

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

ok!okuCountTotalSeries

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

image-20230731162804040

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

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

image-20230731162949620

ok!sub_180E0A430

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

image-20230731163332758

就是把我们传给函数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

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

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

1

0

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

最后调用函数sub_1801308E0

ou!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

image-20230731171634145

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

1

INCREMENTFEATUREorglabSIGN123456789""FSN09876543278L5824771TUVWXYZREGIDFSNABCDEFGHIJKLMNOPQRSTUVWXYZ

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

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

1

INCREMENTFEATUREorglabSIGN123456789""

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

ok!sub_18012DDA0

image-20230731174604691

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

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

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

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

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

1

2

3

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下断点,看一下调用栈

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

image-20230801025717941

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

imasge-20230801025717941

最终可以定位到这两个参数是在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个参数分别为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

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

1

2

test    eax, eax

jnz     loc_18019624F

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/860596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kettle lookup 流查询组件关键词匹配应用案例

Kettle 流查询组件lookup应用案例详解 需求说明 通过对初始的文本文件按照引用表匹配&#xff0c;过滤后的记录输入到表中。 解决方案 Step1&#xff1a;拖动表输入&#xff0c;配置要查询字段及表 Step2&#xff1a;文件文件输入&#xff0c;指定文本文件路径及字段名称、字…

DB-Engines 排名调查

目录 一、理论 1.DB-Engines排名 一、理论 1.DB-Engines排名 &#xff08;1&#xff09;概念 DB-Engines排名是数据库领域的流行度榜单&#xff0c;它对全球范围内的419款数据库&#xff08;截至2023年8月&#xff09;进行排名&#xff0c;每月更新一次&#xff0c;排名越靠…

简单程度与自负是否相关?探索STM32的学习价值

事实上&#xff0c;无论STM32是否简单并不重要&#xff0c;更重要的是我们能通过学习STM32获得什么。通过STM32&#xff0c;我们可以学习到许多知识&#xff1a;如果我们制作一个键盘或鼠标&#xff0c;我们可以学习USB协议。如果我们制作一个联网设备&#xff0c;我们需要学习…

想知道有关再营销活动的一切?看这一篇文章就够了

有没有想过怎样充分利用过去的流量&#xff1f;假设您开展了一场精彩的营销活动&#xff0c;并吸引了大量用户访问您的网站。但他们大部分都没有像你期望的那样完成交易。推出再营销活动&#xff0c;这就是您向那些已经接触过您的营销活动但尚未采取任何具体行动的人进行营销的…

代码随想录算法训练营第十四天|对树的初步认识

二叉树种类 在我们解题过程中二叉树有两种主要的形式&#xff1a;满二叉树和完全二叉树。 满二叉树 满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 这棵二叉树为满二叉树&…

性能测评:腾讯云轻量应用服务器_CPU内存带宽流量

腾讯云轻量应用服务器性能如何&#xff1f;轻量服务器CPU内存带宽配置高&#xff0c;CPU采用什么型号主频多少&#xff1f;轻量应用服务器会不会比云服务器CVM性能差&#xff1f;腾讯云服务器网详解CPU型号主频、内存、公网带宽和系统盘存储多维对比&#xff0c;相对于CVM云服务…

机器人CPP编程基础-03变量类型Variables Types

机器人CPP编程基础-02变量Variables 全文AI生成。 C #include<iostream>using namespace std;main() {int a10,b35; // 4 bytescout<<"Value of a : "<<a<<" Address of a : "<<&a <<endl;cout<<"Val…

排序的介绍

排序算法介绍 排序是计算机内经常进行的一种操作&#xff0c;其目的是将一组“无序”的记录序列调整为“有序”的记录序列 粗暴理解 将杂乱无章的数据元素&#xff0c;通过一定的方法按照关键字顺序排列的过程叫做排序 排序分内部排序和外部排序&#xff0c;若整个排序过程不需…

4路光栅尺磁栅尺编码器5MHz高速差分信号转Modbus TCP网络模块 YL97

特点&#xff1a; ● 光栅尺磁栅尺解码转换成标准Modbus TCP协议 ● 光栅尺5V差分信号直接输入&#xff0c;4倍频计数 ● 模块可以输出5V的电源给光栅尺供电 ● 高速光栅尺磁栅尺计数&#xff0c;频率可达5MHz ● 支持4个光栅尺同时计数&#xff0c;可识别正反转 ● 可网…

JD商品详情页面+关键词搜索商品列表API接口数据,详情页面数据返回值说明

作为一个工作助手&#xff0c;我无法直接为您提供实时的京东商品详情。您可以通过访问京东官方网站或使用API接口来查看商品的详细信息。每个商品的详情页面通常会包括以下内容&#xff1a; 1. 商品标题&#xff1a;商品的名称和简要描述。 2. 价格信息&#xff1a;包括原价、…

常见问题03:SpringBoot项目启动初始化数据(执行sql文件)

1、类ResourceDatabasePopulator介绍 使用外部资源中定义的 SQL 脚本填充、初始化或清理数据库。 调用addScript(org.springframework.core.io.Resource)以添加单个 SQL 脚本位置。调用addScripts(org.springframework.core.io.Resource…)以添加多个 SQL 脚本位置。请参阅此类…

通讯协议035——全网独有的OPC HDA知识一之聚合(四)平均值

本文简单介绍OPC HDA规范的基本概念&#xff0c;更多通信资源请登录网信智汇(wangxinzhihui.com)。 本节旨在详细说明HDA聚合的要求和性能。其目的是使HDA聚合标准化&#xff0c;以便HDA客户端能够可靠地预测聚合计算的结果并理解其含义。如果用户需要聚合中的自定义功能&…

Spring之事务管理

文章目录 前言一、事务及其参数含义1.事务的四个特性2.事务的传播行为&#xff08;propagation&#xff09;3.事务隔离性4.事务的隔离级别&#xff08;ioslation&#xff09;5.timeout&#xff08;超时&#xff09;6.readOnly&#xff08;是否只读&#xff09;7.rollbackFor&am…

Apache Maven:从构建到部署,一站式解决方案

目录 一、Maven介绍 1. Maven是什么&#xff1f; 2.Maven的作用&#xff1f; 二、Maven仓库介绍 2.1 库的分类 三、Maven安装与配置 3.1 Maven安装 3.2 Maven环境配置 3.3 仓库配置 四、Eclipse与Maven配置 五、Maven项目测试 5.1 新建Maven项目步骤及注意事项 5.…

单调递增的数字——力扣738

文章目录 题目描述解法题目描述 解法 #include<iostream> #include<string>using namespace std;int monotoneIncreasingDigits

WhatsApp 实时聊天小插件:快速触达客户的秘密

当您进入商店时&#xff0c;您希望销售人员会向您打招呼&#xff0c;或者至少在您需要时可以找到人提供帮助。对于电子商务商店&#xff0c;客户的期望不会降低。但谁应该担任 24-7的商店经理&#xff1f;实时聊天可以成为您的电子商务商店经理。 什么是 WhatsApp 实时聊天小插…

python中字符串的内建函数之expandtabs的用法

一、str.expandtabs() 进入这个函数之前&#xff0c;我们来看看: str_1 abc\tdef print(str_1) # abc defstr_2 \t print(len(str_2)) # 1根据打印结果来看&#xff0c;abc距离def一个字符的间距。在字符串中\t的长度为1&#xff08;tab键也就是\t&#xff0c;通常说的是…

07-1_Qt 5.9 C++开发指南_文件系统及文件读写_文本文件读写(使用 QTextStream 进行文件读写更为方便)

文章目录 1. 实例功能概述2. QFile 读写文本文件3. QFile 和QTextStream 结合读写文本文件4. 解决中文乱码的问题5. 框架及源码5.1 可视化UI设计5.2 mainwindow.h5.3 mainwindow.cpp5.4 main.cpp 1. 实例功能概述 文本文件是指以纯文本格式存储的文件&#xff0c;例如用 Qt Cr…

个人网站使用又拍云实现CDN加速

前言&#xff1a;前几篇文章完成了将个人博客部署到阿里云服务器上并实现了https加密访问&#xff0c;但是由于图片以及js文件较大&#xff0c;访问速度较慢并且不太稳定&#xff0c;就想着使用CDN加速一下网站访问速度&#xff0c;于是就有了这篇文章。 前置条件&#xff1a; …

类和对象的学习

类和对象说明 类的属性和方法 类的入门案例 //类名 public class school {//属性String name; //名称int jsNumber; //教室数目int jfNumber;//机房数目//方法public void show(){System.out.println("名称: " name "教室数目" jsNumber " , 机房数…