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

更新,修改了一下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都打包放到这里了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

W5500-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W5500-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试&#xff0c;那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一种面向连…

MYSQL进阶-查询优化- 实战 STATUS

回城传送–》《100天精通MYSQL从入门到就业》 文末有送书活动&#xff0c;可以参加&#xff01; 文章目录 一、练习题目二、SQL思路SQL进阶-查询优化- SHOW STATUS初始化数据解法SHOW STATUS是什么实战经验&#xff1a;常用的mysql状态查询1、QPS(每秒处理的请求数量)计算思路…

flutter相关URL schemes

先看效果 使用 url_launcher库 做唤起其他app操作 url_launcher | Flutter Package 配置 安卓 flutter 项目目录下的 android\app\src\main\AndroidManifest.xml 如果不配置的话 有些手机就打不开app <queries><!-- If your app checks for SMS support --><…

Web前端之NodeJS、Vue

文章目录 一、Babel转码器1.1 Babel安装流程1.2 Babel命令行转码 二、Promise对象三、测试方式四、Vue&#xff08;渐进式JS框架&#xff09;4.1 准备4.2 创建一个项目4.3 运行一个项目 五、模板语法5.1 文本5.2 原始html5.3 属性Attribute5.4 使用JavaScript表达式 六、条件渲…

禁止别人调试自己的前端页面代码

为啥要禁止&#xff1f; 由于前端页面会调用很多接口&#xff0c;有些接口会被别人爬虫分析&#xff0c;破解后获取数据 为了 杜绝 这种情况&#xff0c;最简单的方法就是禁止人家调试自己的前端代码 无限 debugger 前端页面防止调试的方法主要是通过不断 debugger 来疯狂输…

chatGPT应用于房地产行业

作为 2023 年的房地产专业人士&#xff0c;您无疑认识到技术对行业的重大影响。近年来&#xff0c;一项技术进步席卷了世界——人工智能。人工智能彻底改变了房地产业务的各个方面&#xff0c;从简化管理任务到增强客户互动。 在本文中&#xff0c;我们将探讨几种巧妙的人工智…

CEC2013(MATLAB):能量谷优化算法EVO求解CEC2013的28个函数

一、能量谷优化算法EVO 能量谷优化算法&#xff08;Energy valley optimizer&#xff0c;EVO&#xff09;是MahdiAzizi等人于2023年提出的一种新颖的元启发式算法&#xff0c;其灵感来自关于稳定性和不同粒子衰变模式的物理原理。能量谷优化算法&#xff08;Energy valley opt…

spss什么是描述性分析,以及如何去处理。

描述性分析是数据分析的第一步&#xff0c;是了解和认识数据基本特征和结构的方法&#xff0c;只有在完成了描述性统计分析&#xff0c;充分的了解和认识数据特征后&#xff0c;才能更好地开展后续更复杂的数据分析。因此&#xff0c;描述性分析是开展数据分析过程中最基础且必…

Idea添加mybatis的mapper文件模版

针对Java开发人员&#xff0c;各种框架的配置模版的确是需要随时保留一份&#xff0c;在使用的时候&#xff0c;方便复制粘贴&#xff0c;但是也依然不方便&#xff0c;我们可以给开发工具&#xff08;IDE&#xff09;中添加配置模版&#xff0c;这里我介绍下使用idea开发工具&…

高忆管理:今年来尚未有公司递表,香港SPAC市场为何“熄火”?

香港SPAC上市准则敞开之后&#xff0c;从第一家公司上市到现在已经有1年多的时刻。&#xff08;【深度】王石、李宁、卫哲争相发起建立SPAC&#xff0c;香港版“上市盲盒”会火吗&#xff1f; 界面新闻了解到&#xff0c;今年以来&#xff0c;香港SPAC并没有新动态&#xff0c;…

数据链路层 -帧结构

帧结构 802.3 有线网卡的帧格式802.11 无线网卡的帧格式 802是指一个国际标准委员会 IEEE 802 帧头主要包括三个部分&#xff1a; 目标MAC&#xff08;6个字节&#xff09;源MAC &#xff08;6个字节&#xff09;类型 &#xff08;包含两种类型&#xff1a;0x0800【IP协议】 …

麻省理工学院利用水泥和炭黑制造出超级电容器

麻省理工学院的研究人员展示了一种使用低成本水泥和炭黑的制造的超级电容器&#xff0c;这一发明可能会彻底颠覆可再生能源的存储。 ​为了解决太阳能、风能和潮汐能等间歇性和非周期性电源的普及问题&#xff0c;实用并且廉价的电力储存技术一直是重要的环节。MIT的研究人员最…

vue中全局状态存储 pinia和vuex对比 pinia比vuex更香 Pinia数据持久化及数据加密

前言 毕竟尤大佬都推荐使用pinia&#xff0c;支持vue2和vue3&#xff01; 如果熟悉vuex&#xff0c;花个把小时把pinia看一下&#xff0c;就不想用vuex了 支持选项式api和组合式api写法pinia没有mutations&#xff0c;只有&#xff1a;state、getters、actionspinia分模块不…

TartanVO: A Generalizable Learning-based VO 论文阅读

论文信息 题目:TartanVO: A Generalizable Learning-based VO 作者&#xff1a;Wenshan Wang&#xff0c; Yaoyu Hu 来源&#xff1a;ICRL 时间&#xff1a;2021 代码地址&#xff1a;https://github.com/castacks/tartanvo Abstract 我们提出了第一个基于学习的视觉里程计&…

妙盈面试(部分)

算法题1&#xff0c;二分查找即可&#xff1a; git rebase算法题2&#xff0c;求二叉树两结点的最小公共祖先 搞笑的是&#xff0c;第2道算法题我刚开始做&#xff0c;黑屏了两秒钟&#xff0c;当时其实腾讯会议软件已经崩溃了&#xff0c;但是我没注意到而是继续做题。等到做…

Stable Diffusion - 常用的负向提示 Embeddings 解析与 坐姿 (Sitting) 提示词

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132145248 负向 Embeddings 是用于提高 StableDiffusion 生成图像质量的技术&#xff0c;可以避免生成一些不符合预期的图像特征&#xff0c;比如…

推出稳定代码:人工智能辅助编码的新视野

推荐&#xff1a;使用 NSDT场景编辑器 快速助你搭建可二次编辑的3D应用场景 在不断发展的软件开发环境中&#xff0c;对效率和可访问性的追求导致了各种工具和平台的创建。最新的创新之一是StableCode&#xff0c;这是Stability AI的大型语言模型&#xff08;LLM&#xff09;生…

整理mongodb文档:改

个人博客 整理mongodb文档:改 求关注&#xff0c;求批评&#xff0c;求进步 文章概叙 本文主要讲的是mongodb的updateOne以及updateMany&#xff0c;主要还是在shell下进行操作&#xff0c;也讲解下主要的参数upsert以及更新的参数。 数据准备 本次需要准备的数据不是很多…

怎样做接口测试:从入门到精通的指南

怎样做接口测试&#xff1a;从入门到精通的指南 为什么接口测试如此重要&#xff1f; 接口测试在软件开发过程中扮演着关键的角色。它确保不同系统、组件或服务之间的无缝集成和通信。 接口测试的基本概念 接口测试是一种验证软件系统组件之间相互连接的过程。它主要关注数据…

SpringBoot 整合Druid

集成Druid Druid简介 Java程序很大一部分要操作数据库&#xff0c;为了提高性能操作数据库的时候&#xff0c;又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现&#xff0c;结合了 C3P0、DBCP 等 DB 池的优点&#xff0c;同时加入了日志监控。 D…