-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsearch.xml
1048 lines (501 loc) · 570 KB
/
search.xml
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>堆栈结构、溢出分析 读书笔记0-05</title>
<link href="/2019/04/01/2019-04-01-Note0-%E5%A0%86%E6%A0%88%E7%BB%93%E6%9E%84%20&%20%E6%BA%A2%E5%87%BA%E7%AC%94%E8%AE%B0/"/>
<url>/2019/04/01/2019-04-01-Note0-%E5%A0%86%E6%A0%88%E7%BB%93%E6%9E%84%20&%20%E6%BA%A2%E5%87%BA%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<blockquote><p>操作系统版本: Windows Xp</p></blockquote><blockquote><p>编译器:Vc6.0</p></blockquote><blockquote><p>编译模式:Release</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在Windows中的申请堆内存的方式有很多,例如:LocalAloc、malloc等</p><p>在这些函数实际上内部都是调用RtlAllocateHeap函数进行申请的,在分析对堆的数据结构时,只分析这一个函数就够了</p><h1 id="堆表概念"><a href="#堆表概念" class="headerlink" title="堆表概念"></a>堆表概念</h1><p>堆表位于堆区的起始位置,用于索引堆区的重要信息,堆表设计时考虑了使用平衡二叉树结构,以提高检索效率,而现代操作系统的对标往往不止一种数据结构</p><p>在堆表中有两个重要的数据结构,快表和空表,用于分配、释放、回收堆空间</p><h2 id="空表"><a href="#空表" class="headerlink" title="空表"></a>空表</h2><p>空表中保存了申请栈空间的地址,以链表(双向链表)的方式保存</p><p>空表中保存了128个项,每项都是一个单独的链表,每项包含了8个字节的空间,这8个字节的空间保存了两个地址,分别是链表的起始地址和结束地址</p><p>空表不同项保存的空间大小 = 空表项序号 * 8 ,例如:空表[3] = 3 * 8 = 24 字节,在空表[3]链表中的每一项都保存了24字节空间,除0项外,其余同理</p><p>0号项除外,0号项保存了所有大于等于1024字节的堆块,小于512KB,按大小升序的方式链接在0项的链表中</p><p>当HeapCreate结束后,空表除了0项以外,其余项保存的两个地址均指向自身,代表此项尚未初始化</p><p>当在HeapCreate后,使用其返回值(句柄)分配8字节空间后释放,这个8字节空间将从空表的0项切割出8字节空间,释放后将其地址链入空表1项,在释放后空表1项的地址也就不会指向自身了,在次申请8字节空间时将在空表中优先查找</p><p><img src="https://tcs.teambition.net/storage/3122390710945b443003571218248b569e8e?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjIzOTA3MTA5NDViNDQzMDAzNTcxMjE4MjQ4YjU2OWU4ZSJ9.WXlaR1Y-efvxQIuz5TGe9EP6O1N97uuskGDN3qSQqj8&download=image.png"></p><blockquote><p>空表链表图式</p></blockquote><h2 id="快表"><a href="#快表" class="headerlink" title="快表"></a>快表</h2><p>快表中同样包含128项,每项以单链表方式链接,在初始化时也与空表不同,初始化时的快表每一项都为空,且每项最多包含4个节点,故快表相比空表更容易填满</p><p><img src="https://tcs.teambition.net/storage/3122ad524405ed171c40bfc0d8c7b8b600c9?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjJhZDUyNDQwNWVkMTcxYzQwYmZjMGQ4YzdiOGI2MDBjOSJ9.ZUWsLQH9FOX6ObIfMfWFOSlwr0TkU5g6K2GwLPmAsZM&download=image.png"></p><blockquote><p>快表链表图示</p></blockquote><h1 id="堆块操作"><a href="#堆块操作" class="headerlink" title="堆块操作"></a>堆块操作</h1><p>堆块操作分为,释放、合并、分配三种,其合并是由堆管理系统完成的,其余都是程序中代码执行的</p><h2 id="分配"><a href="#分配" class="headerlink" title="分配"></a>分配</h2><p>当分配事件发生后,存在三种分配方式,快表分配、空表分配、0号空表分配</p><p>在快表分配时,寻找大小匹配的空闲块,将状态修改为占用态、把它从堆表中“卸下”,最后返回一个指向堆块块身的指针给程序使用</p><p>空表分配时,会优先寻找最优的空闲块,如找不到,会寻找最小能满足要求的空闲块</p><p>零号空表按大小升序的排列方式,保存了大小不同的堆块,在分配时会在此链表中反向查找最后一个块,也就是最大块,如果能满足需求,则重新正向搜素最小能满足空闲堆块分配</p><p>”找零钱”,当空表中无法找到适当的堆块时,会找一个稍大些的堆块进行分配,在分配时将此堆块精确切割为所需大小,将大块堆块剩下的部分重新写入堆块的块首,并链入0号空表</p><p>在快表中,只有精确匹配时才会分配,也就不存在“找零钱”的现象</p><h2 id="释放"><a href="#释放" class="headerlink" title="释放"></a>释放</h2><p>释放堆块时,首先将堆块的状态设置为空闲,后将其链入相应的对表中,释放后的块会被链入对应项中链表的末尾,分配时同样也从链表末尾中取出来</p><p>快表中最多保存4项内容</p><h2 id="合并"><a href="#合并" class="headerlink" title="合并"></a>合并</h2><p>在反复申请释放堆内存后,避免产生内存碎片会出现堆块合并</p><p>当堆管理系统发现两个空闲堆块彼此相邻的时候,会将两个堆块合并</p><p>堆块合并包括,将两个块从空闲链表中卸下,合并堆块,调整合并后大块的块首信息,将新块重现链入空闲链表</p><p>在具体分配和释放时,根据操作内存的大小不同,Windows采取的策略也不同,可以把内存分为3类:</p><p>小块:SIZE < 1KB</p><p>大块:1KB <= SIZE < 512KB</p><p>巨块:SIZE >= 512KB</p><p><img src="https://tcs.teambition.net/storage/312293d87194cefb0bf8cb3a1ccb4890cc63?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjI5M2Q4NzE5NGNlZmIwYmY4Y2IzYTFjY2I0ODkwY2M2MyJ9.ejggQYi83o1JM3tiqkjUUNA7iTMZNB6mhqwTLMG9ONw&download=image.png"></p><table><thead><tr><th></th><th>分配</th><th>释放</th></tr></thead><tbody><tr><td>小块</td><td>首先进行快表分配快表分配失败,使用空表分配空表分配失败,使用堆缓存分配堆缓存分配失败,0号空表分配0号空表分配失败,进行内存紧缩后再次尝试仍无法分配返回NULL</td><td>优先链入快表(只能链入4个空闲块)若快表填满,链入空表</td></tr><tr><td>大块</td><td>首先使用堆缓存分配若堆缓存分配失败,使用零号空表的大块进行分配</td><td>优先将其放入堆缓存若堆缓存满,链入零号空表</td></tr><tr><td>巨块</td><td>巨块申请很罕见,用虚方法分配,实际不是从堆区分配的</td><td>直接释放没有堆表操作</td></tr></tbody></table><h2 id="Windows-堆管理特点"><a href="#Windows-堆管理特点" class="headerlink" title="Windows 堆管理特点"></a>Windows 堆管理特点</h2><ol><li><p>快表中的空闲块被设置为占用态,故不会发生堆块合并操作</p></li><li><p>快表只有精确匹配时才会分配,不存在”次优解”和“找零钱”</p></li><li><p>快表是单链表,操作会比双链表简单,插入删除都少用很多指令</p></li><li><p>在分配与释放时总是优先使用快表,失败后才用空表</p></li><li><p>快表只有4项,很容易被填满,所以空表也是频繁使用的</p></li></ol><h1 id="堆调试"><a href="#堆调试" class="headerlink" title="堆调试"></a>堆调试</h1><p>在调试堆时,不可以直接使用调试器加载exe进行调试,当直接加载时,exe被标记为调试模式,在调试状态下将会启用调试态对管理策略,主要差异如下:</p><ol><li><p>调试堆不使用快表,只使用空表分配</p></li><li><p>所有堆块都被加上了多余的16字节尾部,用来防止溢出(防止程序溢出而不是堆溢出攻击),这包括8字节的0xAB和8字节的0x00</p></li><li><p>块首的标识位不同</p></li></ol><p>当代码在调试状态下正常运行,在Release模式下崩溃,很有可能就是这个原因</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdio.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdlib.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><windows.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">HLOCAL h1,h2,h3,h4,h5,h6;</span><br><span class="line">HANDLE hp;</span><br><span class="line">hp = HeapCreate(<span class="number">0</span>, <span class="number">0x1000</span>, <span class="number">0x10000</span>);</span><br><span class="line"></span><br><span class="line">_asm <span class="keyword">int</span> <span class="number">3</span></span><br><span class="line"></span><br><span class="line">h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">3</span>);</span><br><span class="line">h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">5</span>);</span><br><span class="line">h3 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">6</span>);</span><br><span class="line">h4 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">8</span>);</span><br><span class="line">h5 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">19</span>);</span><br><span class="line">h6 = HeapAlloc(hp, HEAP_ZERO_MEMORY, <span class="number">24</span>);</span><br><span class="line"></span><br><span class="line">HeapFree(hp, <span class="number">0</span>, h1);</span><br><span class="line">HeapFree(hp, <span class="number">0</span>, h3);</span><br><span class="line">HeapFree(hp, <span class="number">0</span>, h5);</span><br><span class="line"></span><br><span class="line">HeapFree(hp, <span class="number">0</span>, h4);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将代码编译为Release模式后并运行,当代码运行到第11行会出现异常,这时Windows会提示你是否需要附加到调试器,在此附加到调试器即可进行调试</p><h2 id="堆结构"><a href="#堆结构" class="headerlink" title="堆结构"></a>堆结构</h2><p>运行上述代码并附加到调试器后,EAX中存储的就是HeapCreate返回的内存地址(句柄)</p><p>以此地址为基地址,+0x178的偏移位置就是空表的位置</p><p><img src="https://tcs.teambition.net/storage/3122a2469faebce21c3d9270818b9aeaef7a?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjJhMjQ2OWZhZWJjZTIxYzNkOTI3MDgxOGI5YWVhZWY3YSJ9.L89DzIjfHmN9_j9f0QUAsNdY81e_yNZLr0hGDK22AD0&download=image.png"></p><p>每8个字节为空表的一项,在这里注意到除了0项外的所有空表项都是指向自身</p><p>跳转到0号空表地址处</p><p><img src="https://tcs.teambition.net/storage/31229b1c4df8ec4931bbe81fe468e64da38c?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjI5YjFjNGRmOGVjNDkzMWJiZTgxZmU0NjhlNjRkYTM4YyJ9.I3eOZBf9WsxKEbnvphzFAlzhJBICkVaNOF9RER8Rc2Y&download=image.png"></p><p>此地址(3A0688)保存的第一个地址为链表的上一个节点,(3A068C)保存的第二个地址为下一个节点</p><p><img src="https://tcs.teambition.net/storage/312269e739678eaf3c3a5773ae40e7c73563?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjI2OWU3Mzk2NzhlYWYzYzNhNTc3M2FlNDBlN2M3MzU2MyJ9.WG8cs4hfUuSXsZeCbyXw2LJWTXt4vUaQOrweOvhyvtA&download=image.png"></p><p>此地址-8 *<em>(</em>(DWORD*)3A688 - 8) **中的4个字节,保存了这个堆块的大小</p><p>这4个字节分为两部分,每两个字节为一部分,转换成数值为0x130 和 0x8,0x130代表当前堆块的大小为 0x130 * 8,0x8代表上一个堆块占用的空间为0x8 * 0x8</p><p>此地址-4 *<em>(</em>(DWORD*)3A688 - 4) **中的4个字节,保存了这个堆块的一些标志位,详见下图</p><p><img src="https://tcs.teambition.net/storage/3122cbd6ccb2c1f24858a0d7f35c6ff08447?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IiIsImV4cCI6MTYxMzEzNDM1NywiaWF0IjoxNjEyNTI5NTU3LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMjJjYmQ2Y2NiMmMxZjI0ODU4YTBkN2YzNWM2ZmYwODQ0NyJ9.Rt-Yl9VjlUVeHL7b1FR2_rgyMV-_cJjAWURk0Fk7284&download=image.png"></p><blockquote><p>在3A0680开始后的8个字节为块头,而链表中保存的地址跳过了这8个字节,但是在块大小中是包含这8个字节的,此堆块的大小为0x130 * 8,在这个大小中是包含块头的8个字节大小的</p></blockquote><blockquote><p>其他非0项空表的结构与其相同</p></blockquote><h2 id="运行流程"><a href="#运行流程" class="headerlink" title="运行流程"></a>运行流程</h2><p>单步执行过第一个分配操作之后,此次申请将会在0项空表中进行分割,分割后重新修改两个块的块头信息,结束之后将标志位设置Busy,并将地址返回</p><p>在第一次释放时,根据释放的大小 / 8 决定将这个堆块放入空表的哪一项中,并修改其标志位</p><p>出现多次释放时,若两个块空闲块相邻,将合并这几个堆块,后修改其块首,使用新的堆块大小 / 8,来放入对应空表的位置</p><p>当多次释放后,再次申请时优先在空表中查找符合大小的堆块,并在链表尾端摘出此堆块,如果存在,返回给程序使用,如不存在…(详见堆块操作中的分配部分)</p><h2 id="快表问题"><a href="#快表问题" class="headerlink" title="快表问题"></a>快表问题</h2><p>在上面的代码中是没有用到快表的,若想使用快表,需要将上面代码的第9行替换为</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hp = HeapCreate(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>这时空快表就会被启用了</p><p>快表被启用后,HeapCreate返回的地址 + 0x688 处将不会是0号空表指向的位置了,这个地址被快表占领了</p><p>在上面运行流程的释放过程也会发生变化,释放时优先释放到快表结构中,释放到快表时,不会将堆块的块头标志位修改为空闲状态,也是因为这个原因快表中不会出现合并的情况</p><p>快表中的每项只保存4个堆块,如快表[4]对应的链表中,已经有了4个堆块,那么又产生了一次释放,对应的快表项还是快表[4],在这种情况下会将其保存到空表中</p><p>在快表启用的状态下,多次释放后,再次申请时优先在快表中查找符合大小的堆块,并在链表尾端摘出此堆块,如没有则在空表中查找,如果存在,返回给程序使用,如不存在…(详见堆块操作中的分配部分)</p><h1 id="溢出利用点分析-DWORD-SHOOT"><a href="#溢出利用点分析-DWORD-SHOOT" class="headerlink" title="溢出利用点分析 DWORD SHOOT"></a>溢出利用点分析 DWORD SHOOT</h1><p>在上述中,如果出现申请堆空间的情况,如空表中出现大小匹配的块,那么将在空表[*]的位置中取出链表指针,并将链表中的最后一项摘下来返回程序使用</p><p>这时就产生了一次链表摘除操作,链表摘除时代码模拟:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">remove</span><span class="params">(ListNode *node)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">node->blink->flink = node->flink;</span><br><span class="line">node->flink->blink = node->blink;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们通过溢出的方式修改node的两个节点的内容来进行对某一内存的修改</p><p>首先我们需要申请堆块,大小随意如0x200,假定地址为0x401000,那么0x401200处为下一个堆块的块头,保存下一个堆块的大小和标志,则0x401208处则保存了下一个堆块的链表数据,</p><p>我们通过溢出的方式将下一个堆块的链表中的两个数据进行修改,这样当堆管理器对这个堆块进行操作的时候(合并、分配、摘除),将会出发Dword Shoot</p><p>Good Job</p>]]></content>
<categories>
<category> Note </category>
</categories>
<tags>
<tag> Note </tag>
</tags>
</entry>
<entry>
<title>网易游戏的四种解密&解包,附工具</title>
<link href="/2019/03/01/2019-03-01-%E7%BD%91%E6%98%93%E6%B8%B8%E6%88%8F%E7%9A%84%E5%9B%9B%E7%A7%8D%E8%A7%A3%E5%AF%86&%E8%A7%A3%E5%8C%85%EF%BC%8C%E9%99%84%E5%B7%A5%E5%85%B7/"/>
<url>/2019/03/01/2019-03-01-%E7%BD%91%E6%98%93%E6%B8%B8%E6%88%8F%E7%9A%84%E5%9B%9B%E7%A7%8D%E8%A7%A3%E5%AF%86&%E8%A7%A3%E5%8C%85%EF%BC%8C%E9%99%84%E5%B7%A5%E5%85%B7/</url>
<content type="html"><![CDATA[<p>游戏中大概有3种文件,分别是pyc,nxs,npk,还有一些配置文件就不说了</p><p>这里的pyc是修改过opcode的,pyc文件加密后就是nxs文件,nxs文件会被打包到npk文件中</p><p>我们从里向外说吧,先放出GitHub链接</p><p><a href="https://github.com/yuanbi/NeteaseUnpackTools">https://github.com/yuanbi/NeteaseUnpackTools</a></p><h2 id="Pyc文件"><a href="#Pyc文件" class="headerlink" title="Pyc文件"></a>Pyc文件</h2><p>NeteasePycObject,这个文件夹中的文件是用来还原pyc opcode的,使用方式如下:</p><p>netpyc 输入文件 输出文件 [是否加密]</p><p>第三个参数是可选的,默认为0,代表解密,如果为1代表加密,就是将正常的opcode修改为游戏中python的opcode</p><p>这个工具的部分pyobject的偏移有问题,大部分文件不会出现问题,懂这方面的大佬可以自行修改下,后面修改过后也会在GitHub更新的</p><p>用netpyc修改opcode过后,可以使用uncomply6,进行反编译</p><p>还有一个问题就是部分pyc文件会有一个或两个字节码不是python中的字节码,极少数的文件会出现这种问题,这个时候在反编译的时候就会报error,如果报了error可以观察解析失败的opcode附近的代码,可以自行修改、</p><p>如果被修改过的字节码不在opcode中,就要手动分析了,当然这种情况出现的很少</p><h2 id="Nxs文件"><a href="#Nxs文件" class="headerlink" title="Nxs文件"></a>Nxs文件</h2><p>接下来就是nxs文件了,nxs文件直接使用NeteaseNxsUnpack中的NeteaseNxsUnpack.py进行解密,使用方法如下</p><p>python2 NeteaseNxsUnpack.py 输入文件 输出文件 </p><p>之后使用netpyc解密opcode,然后反编译就行了</p><h2 id="Npk文件"><a href="#Npk文件" class="headerlink" title="Npk文件"></a>Npk文件</h2><p>直接使用NeteaseNpkUnpack中的NeteaseNpkUnpack.py,使用方法如下</p><p>python2 NeteaseNpkUnpack.py 输入文件 输出文件夹,解密后的文件无后缀,文件类型需要自己分析</p><h2 id="Script-Npk解包"><a href="#Script-Npk解包" class="headerlink" title="Script.Npk解包"></a>Script.Npk解包</h2><p>网易游戏基本上会有script.npk,解包方法如下</p><p>先解包npk,解包后文件夹中的所有文件都是nxs文件,在进行nxs文件解密的步骤就行了</p><p><img src="http://imgset.gitee.io/img/1573207384695.png" alt="1573207384695"></p><h2 id="DecodeBuffer"><a href="#DecodeBuffer" class="headerlink" title="DecodeBuffer"></a>DecodeBuffer</h2><p>这些是三种文件,还有一种是这样的,这里说的是Windows的版本,在nxfilesystem.dll中会有一个decodebuffer函数,这个就是第四个,这个函数的上层是openwithloader</p><p>openwithloader作用是打开文件,文件校对,和部分初始化,在openwithloader中并不是所有的数据都要经过decodebuffer函数,部分数据需要解密,多数会是一些配置文件</p><p>在openwithloader中,捕获数据如下,在openwithloader的ret字节下断点,文件名,文件大小,会存储在[eax+4]的位置,如果我没记错的话</p><p>还有就是在Windows中并不是所有文件都存储在npk文件里,还有一部分存储在 C:\Users\用户名\AppData\Local\游戏名缩写 中,如果你在这里中找到了script文件夹,这个文件夹中会包含很多文件夹和一个文件</p><p>这些文件夹和文件的名字都是被加密过的,可以在nxfilesystem.dll中搜索导出的包含path的函数,具体名字忘记了,函数功能是这样的,传入正常路径,传出加密路径</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>vmp3.3.1 虚拟机分析</title>
<link href="/2019/01/01/2018-11-03-vmp3.3.1%20%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90/"/>
<url>/2019/01/01/2018-11-03-vmp3.3.1%20%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="虚拟机概述"><a href="#虚拟机概述" class="headerlink" title="虚拟机概述"></a>虚拟机概述</h1><p><img src="http://imgset.gitee.io/img/1564389415873.png" alt="1564389415873"></p><p>一个正常的二进制文件,假设为x,那么执行他的是Windows操作系统,也就是解释器,假设为A,</p><p>那么vmp加壳的时候,会根据加壳文件的代码,转化为自己的代码,但行为不变,假设push eax,的字节码为0x03,那么vmp就会修改这个值,假设为0x04,但实际执行的时候还是回执行push eax,现在出现了一个新的问题,push eax的字节码被修改了,解释器A,无法将0x04解释为push eax。在vmp将原字节码转换为vmp的字节码之后,vmp还会生成一个解释器,假设为B,解释器B就是来解释,经过vmp修改过后的代码,也就是上文中0x04这个字节码,实际的执行顺序如下</p><p><strong>解释器A – 执行 – 解释器B – 执行 – vmp修改过后的代码。</strong></p><p>当我们分析vmp代码的时候,实际上分析的是解释器B。</p><p>既然解释器B是在执行虚拟机的代码,而虚拟机代码是模拟的vmp加壳之前的代码,加壳之前的代码是需要寄存器来执行的,而我们在调试中看到的寄存器已经被解释器B使用了。</p><p>那么在堆栈中会出现一个结构体,我们称之为VMContext,下文会详细介绍。</p><p>还有就是vmcode有自己的堆栈地址,并不是保存在esp中。</p><h1 id="加密源码"><a href="#加密源码" class="headerlink" title="加密源码"></a>加密源码</h1><p><img src="http://imgset.gitee.io/img/1564401836257.png" alt="1564401836257"></p><p>开启变态加密</p><p>未开启反调试</p><p>生成后文件</p><p><img src="http://imgset.gitee.io/img/1564401897119.png" alt="1564401897119"></p><p>源文件3KB,加密后551KB,一个惊人的增长</p><h1 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h1><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p><img src="http://imgset.gitee.io/img/1564398341996.png" alt="1564398341996"><img src="http://imgset.gitee.io/img/1564398356653.png" alt="1564398356653"><img src="http://imgset.gitee.io/img/1564398371347.png" alt="1564398371347"><img src="http://imgset.gitee.io/img/1564398418945.png" alt="1564398418945"></p><p>上图为vmp开始的代码,我们只关心push指令,它将所有的寄存器压入栈,最后将0压入堆栈,</p><p>此时堆栈保存的数据顺序如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">0</span><br><span class="line">edx</span><br><span class="line">flags</span><br><span class="line">esi</span><br><span class="line">ecx</span><br><span class="line">ebp </span><br><span class="line">edi</span><br><span class="line">ebx</span><br><span class="line">eax</span><br><span class="line">key</span><br></pre></td></tr></table></figure><p>继续F7单步</p><p><img src="http://imgset.gitee.io/img/1564398935939.png" alt="1564398935939"></p><p>上面这段代码,主要的作用,取出vmcontext中的key,进行计算,而这个key计算结束的值就是vmcode的代码位置。</p><p>还有两行代码我们需要注意</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">00429382 8BFC mov edi,esp </span><br><span class="line">;将当前esp的值赋值给edi,这个edi就是vmcode的堆栈的栈顶</span><br><span class="line">;通过上文我们可以看出了栈顶的顺序就是上述堆栈的顺序</span><br><span class="line">;那么取出的值则为0</span><br><span class="line">00429384 8DA424 40FFFFFF lea esp,dword ptr ss:[esp-0xC0]</span><br><span class="line">;在堆栈开辟0xC0个字节的空间,这个空间就是vmcontext,它的首地址保存在esp中</span><br></pre></td></tr></table></figure><p>F7单步继续</p><p><img src="http://imgset.gitee.io/img/1564399065949.png" alt="1564399065949"></p><p>上面这段代码中最重要的就是 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lea esi, dword ptr ds :[0x462ABD]</span><br></pre></td></tr></table></figure><p>这个地址是第一个handler的地址,这个handler指的是执行vmcode的代码,也就是在vmcode中取出代码,在handler中执行,在上图中jmp之前的代码就是在vmcode的地址中取出下一行要执行的代码。</p><p>继续单步</p><p><img src="http://imgset.gitee.io/img/1564399507137.png" alt="1564399507137"></p><p>我们看到ebp进行了加4,可以看出虚拟机的代码是倒着走的。</p><p>单步一直走,走到带有ret的语句。</p><p><img src="http://imgset.gitee.io/img/1564399608000.png" alt="1564399608000"></p><p>上图中的ecx是经过计算后的偏移,和esi相加就是第一个handler的地址。</p><p>在以前的版本中会有一个VMDispatcher,来决定下一次handler跳转的位置,而在新版本中,去掉了这个功能,很多脱壳神器也就都失效了,换而取代的是 push .. ret或者jmp esi 等等。</p><p>跳到第一个handler地址,继续单步。</p><p><img src="http://imgset.gitee.io/img/1564400047871.png" alt="1564400047871"></p><p>上图为第一个handler的代码,执行到此,我们还需要说一下vmcode中的堆栈问题,vmcode的堆栈地址并不是固定保存在一个寄存器中的,vmcode的地址,和handler的地址也是一样,并不是固定在一个寄存器中的,它可能在某一个handler中就保存到其他的寄存器中了。</p><p>上图中的寄存器对应如下</p><ul><li><p>edi保存了vmcode的堆栈首地址</p></li><li><p>ebp保存了vmcode的首地址</p></li><li><p>handler的地址在esi中。</p></li><li><p>esp保存了vmcontext的首地址,vmcontext的地址将永远保存在esp中不会改变</p></li></ul><p>上图代码主要工作如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mov edx,dword ptr ds:[edi]</span><br><span class="line">lea edi,dword ptr ds:[edi+0x4]</span><br><span class="line">;上面两行代码是一个标准的pop指令,将栈顶的值临时保存在edx中</span><br><span class="line">movzx ecx,byte ptr ss:[ebp]</span><br><span class="line">lea ebp,dword ptr ss:[ebp+0x1]</span><br><span class="line">;将vmcode的下一个指令取出来</span><br></pre></td></tr></table></figure><p>F7单步经过一个jmp之后的代码如下</p><p><img src="http://imgset.gitee.io/img/1564401479169.png" alt="1564401479169"></p><p>上图中高亮代码,是将在栈顶取出来的值保存到vmcontext的偏移的位置中,我们刚有提过esp中保存的永远都是vmcontext的地址,上图中的ecx的值为0x10,edx为上一次在堆栈中取出的值为0。</p><p>而在这之后在ebp中取出4个字节的代码,并把ebp进行加4,然后edx解密得到偏移,加上esi就是下一个handler的地址。</p><p>在最后一行代码跳转过去的代码为jmp esi 跳转到下一个handler。</p><p><img src="http://imgset.gitee.io/img/1564402444688.png" alt="1564402444688"><img src="http://imgset.gitee.io/img/1564402703985.png" alt="1564402703985"></p><p>这个handler的代码作用与原理同上,在堆栈中取出内容保存到vmcontext,只是保存位置有变化,保存在0x4的偏移中,然后在ebp中取出代码经过计算通过jmp esi跳转到下一个handler。</p><p>后面的几个handler的都是在做这件事情(初始化vmcontext),避免篇幅过大,就不贴出代码了。</p><p>初始化结束后的vmcontext如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[vmcontext + <span class="number">0x10</span>] = <span class="number">0</span></span><br><span class="line">[vmcontext + <span class="number">0x04</span>] = edx = <span class="number">00401000</span></span><br><span class="line">[vmcontext + <span class="number">0x28</span>] = FLAGS = <span class="number">00000246</span></span><br><span class="line">[vmcontext + <span class="number">0x38</span>] = esi = <span class="number">00401000</span></span><br><span class="line">[vmcontext + <span class="number">0x24</span>] = ecx = <span class="number">00401000</span></span><br><span class="line">[vmcontext + <span class="number">0x0C</span>] = ebp = <span class="number">0019F</span>F7B</span><br><span class="line">[vmcontext + <span class="number">0x20</span>] = edi = <span class="number">00401000</span></span><br><span class="line">[vmcontext + <span class="number">0x1C</span>] = ebx = <span class="number">0027</span>D000</span><br><span class="line">[vmcontext + <span class="number">0x2C</span>] = eax = <span class="number">0019F</span>FCC</span><br><span class="line">[vmcontext + <span class="number">0x08</span>] = 返回地址 = <span class="number">004650</span>CB</span><br><span class="line">[vmcontext + <span class="number">0x14</span>] = key = A6EC6D77</span><br></pre></td></tr></table></figure><p>至此 vmcontext初始化完毕。</p><h2 id="正式执行代码"><a href="#正式执行代码" class="headerlink" title="正式执行代码"></a>正式执行代码</h2><p><img src="http://imgset.gitee.io/img/1564403874592.png" alt="1564403874592"></p><p>在上一行代码高亮的地方</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">0044CAC5 8DBF FCFFFFFF lea edi,dword ptr ds:[edi-0x4]</span><br><span class="line">;edi保存了vmcode堆栈的地址,进行减4,开辟4字节空间</span><br><span class="line">...</span><br><span class="line">0044CACF 8907 mov dword ptr ds:[edi],eax</span><br><span class="line">;eax的值为2019,将这个值放到edi中,也就是栈顶</span><br><span class="line">;上述代码可还原为 push 2019 也就是我们源代码的第一行代码</span><br></pre></td></tr></table></figure><p>继续F7单步走</p><p><img src="http://imgset.gitee.io/img/1564404138745.png" alt="1564404138745"><img src="http://imgset.gitee.io/img/1564404163486.png" alt="1564404163486"></p><p>每次压栈后,在跳转到下一个handler之前都会有一个当前vmcode栈空间的判断,如果当前栈顶超过了esp+60,就要进行栈空间分配。</p><p>分配的代码如下</p><p><img src="http://imgset.gitee.io/img/1564404303275.png" alt="1564404303275"><img src="http://imgset.gitee.io/img/1564404322967.png" alt="1564404322967"><img src="http://imgset.gitee.io/img/1564404338065.png" alt="1564404338065"><img src="http://imgset.gitee.io/img/1564404353276.png" alt="1564404353276"><img src="http://imgset.gitee.io/img/1564404365073.png" alt="1564404365073"></p><p>混肴代码太多</p><p>上图代码总结</p><ul><li>开辟空间</li><li>edi esi eflags 寄存器保存</li><li>利用 esi 和 edi 寄存器,保存开辟空间之前,和开辟空间之后的位置</li><li>通过ecx 和 rep mosb 指令将vmcontext进行恢复</li><li>恢复 edi esi eglags寄存器</li><li>跳转到下一个handler</li></ul><p>到此基本上了解了虚拟机的执行流程。</p><p>接下来的代码就没那么幸运了,因为在vmcode中,包含了很多混肴代码,做一些无用的工作,大大浪费了分析的空间,我通过在栈地址中使用硬件断点跳过这些代码,直接看vmp调用call的过程。</p><p><img src="http://imgset.gitee.io/img/1564407462138.png" alt="1564407462138"></p><p>将保存在vmcontext的值取出来赋值给原寄存器</p><p>此时的堆栈如下</p><p><img src="http://imgset.gitee.io/img/1564407515176.png" alt="1564407515176"></p><p>执行完成API返回到401026继续虚拟机指令。</p><p>Good Job.</p><h1 id="需要注意的地方"><a href="#需要注意的地方" class="headerlink" title="需要注意的地方"></a>需要注意的地方</h1><ul><li>同样的应用程序生成进行两次vmp加壳,所生成的两个EXE,那么这两个EXE的代码不论是在初始化还是在其他地方代码都会有很多不同,但是他们的目的是一致的,假设在初始化部分之前的寄存器压栈,两个不同的exe的jmp指令的多少或者位置都会不同,但它们都是在做同一件事情,就是将key、寄存器、0压入栈。</li><li>在不同的handler中,除了vmcontext的地址是固定保存在esp中之外,其他的均不会固定保存在一个寄存器中,而是在不同的寄存器中进行轮询。</li><li>vmcontext的偏移位置在每次编译的时候也不同</li></ul><p><img src="http://imgset.gitee.io/img/1571159879653.png" alt="1571159879653"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>vmp 过代码校验</title>
<link href="/2018/12/31/2018-12-30-vmp%20%E8%BF%87%E4%BB%A3%E7%A0%81%E6%A0%A1%E9%AA%8C/"/>
<url>/2018/12/31/2018-12-30-vmp%20%E8%BF%87%E4%BB%A3%E7%A0%81%E6%A0%A1%E9%AA%8C/</url>
<content type="html"><![CDATA[<h1 id="代码校验"><a href="#代码校验" class="headerlink" title="代码校验"></a>代码校验</h1><p>简化后的代码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">START:</span><br><span class="line">mov edx,dword ptr ss:[ebp]</span><br><span class="line">add ebp,0x4</span><br><span class="line">LOOP:</span><br><span class="line">xor eax,eax</span><br><span class="line">mov ecx,eax</span><br><span class="line">shl eax,0x7</span><br><span class="line">shr ecx,0x19</span><br><span class="line">or eax,ecx</span><br><span class="line">xor al,byte ptr ds:[edx]</span><br><span class="line">inc edx</span><br><span class="line">dec dword ptr ss:[ebp]</span><br><span class="line">jnz LOOP</span><br><span class="line">mov dword ptr ss:[ebp],eax</span><br><span class="line">END</span><br></pre></td></tr></table></figure><p>这个handler需要两个参数,分别是代码校验的地址和大小,通过xor 指令生成校验码,最后将堆栈的两个参数弹出,将校验码压栈,执行下一个handler</p><h1 id="调用分析"><a href="#调用分析" class="headerlink" title="调用分析"></a>调用分析</h1><p>可以通过OD的条件断点,在校验handler下断,分别打印出 vmcode(ESI) - 1 、传入的两个参数进行分析</p><p><img src="http://imgset.gitee.io/img/image-20191217205807576.png" alt="image-20191217205807576"></p><p>日志冗长,就不贴了,经分析后一共有4处调用了校验handler,校验的部分分别是文件校验,代码校验,内存校验,随机校验</p><p>这4处校验handler调用之后的校验码比对部分有一个共同点,就是它们都是对ZF标志位进行判断</p><p>还有一个点就是hash比较后的结果会存储在一个寄存器中的BL位,也就是低字节,由堆栈弹出1字节并赋值,这里说的寄存器指的是vm context中的数据,虽然没有在每个hash handler中验证,但我猜测是一致的</p><p>在前3次处理中,会在vm context中保存循环的次数:</p><p><img src="http://imgset.gitee.io/img/image-20191217211148244.png" alt="image-20191217211148244"></p><p>上图中的堆栈33F608的位置保存了循环次数为4次</p><p>这个值的位置是不国定的,需要自己手动分析</p><p>通过这个循环次数,和ZF的标志位判断就可以跳过校验部分了</p><h1 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h1><p>校验码计算是使用刚刚计算的校验码与原校验码相减,然后判断ZF标志位</p><p>运算过程如下:</p><p><img src="http://imgset.gitee.io/img/image-20191217212314663.png" alt="image-20191217212314663"></p><p>化简后是这样的:</p><p>a-b = <del>(</del>a + b)</p><p>f1 = eflags of (~a + b)</p><p>f2 = eflags of <del>(</del>a + b)</p><p>eflags = (f1 & 0x815) + (f2 & ~0x815)</p><p>M1 = eflags & 40</p><p>通过对M1进行右移6位取出ZF标志位并保存到vR13BL(vm context)中</p><p>上图为以前分析的一个vmp 的 hash过程的记录,从初始化到一次循环的记录,文末有下载链接,由于每一次jmp都会对vmcontext进行打乱,阅读会有些不连贯,只能作为参考</p><h1 id="跳过校验部分"><a href="#跳过校验部分" class="headerlink" title="跳过校验部分"></a>跳过校验部分</h1><p>前三次很好跳过,直接在代码校验的handler上下断,修改循环次数为1,不要修改为0,因为在handler中还要进行减一操作,如果你不知道循环次数在vmcontext中的保存的位置,可以用条件断点相对esi的值来判断</p><p>第四次并没有保存循环次数,但是它用到了右移指令,通常情况下,vmp会使用右移四位来判断zf位,如果zf位为1,那么右移4位后结果为4,否则为0</p><p>那么在第4次处理时首先在校验handler下断,断下后在右移handler下断,一般来说,第一次的右移是对hash校验码的判断,第二次右移是对循环的判断,所以当右移handler第二次断下,这个handler的结束会将生成的数值压栈,我们要做的就是把这个值修改为0,就可以跳过代码校验了</p><p>如果还是被检查出来了,问题就出在了每次处理的第一次检查的数据对比中,可以参考第四次的处理,在shr的handler中处理这个问题</p><p>vmp分析记录:</p><p><a href="https://basicbit.cn/doc/Vmp%20hash%20init%20analysis.docx">https://basicbit.cn/doc/Vmp%20hash%20init%20analysis.docx</a></p><p><a href="https://basicbit.cn/doc/Hash%20loop.docx">https://basicbit.cn/doc/Hash%20loop.docx</a></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> vmp </tag>
</tags>
</entry>
<entry>
<title>x64dbg python API</title>
<link href="/2018/12/20/2018-11-29-x64dbg%20python%20API/"/>
<url>/2018/12/20/2018-11-29-x64dbg%20python%20API/</url>
<content type="html"><![CDATA[<p>1</p><p><a href="https://github.com/x64dbg/x64dbgpy">https://github.com/x64dbg/x64dbgpy</a></p><p>asdfasfd</p><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi"><a href="#x64dbgpy-pluginsdk-scriptapi" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi"></a>x64dbgpy.pluginsdk._scriptapi</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi_<em>init</em>_.py</p><p>PACKAGE CONTENTS<br> argument<br> assembler<br> bookmark<br> comment<br> debug<br> flag<br> function<br> gui<br> label<br> memory<br> misc<br> module<br> pattern<br> register<br> stack<br> symbol</p><p>DATA<br> GUI_MAX_LINE_SIZE = 65536<br> MAX_COMMENT_SIZE = 512<br> MAX_ERROR_SIZE = 512<br> MAX_LABEL_SIZE = 256<br> MAX_MODULE_SIZE = 256<br> MAX_PATH = 260<br> MAX_STRING = 512</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-argument"><a href="#x64dbgpy-pluginsdk-scriptapi-argument" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.argument"></a>x64dbgpy.pluginsdk._scriptapi.argument</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\argument.py</p><p>FUNCTIONS<br> Add(start, end, manual, instructionCount=0)<br><br> AddInfo(info)<br><br> Clear()<br><br> Delete(addr)<br><br> DeleteRange(start, end, deleteManual=False)<br><br> Get(addr)<br><br> GetInfo(addr)<br><br> GetList()<br><br> Overlaps(start, end)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-assembler"><a href="#x64dbgpy-pluginsdk-scriptapi-assembler" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.assembler"></a>x64dbgpy.pluginsdk._scriptapi.assembler</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\assembler.py</p><p>FUNCTIONS<br> Assemble(addr, instruction)<br><br> AssembleEx(addr, instruction)<br><br> AssembleMem(addr, instruction)<br><br> AssembleMemEx(addr, instruction, fillnop)</p><p>DATA<br> MAX_ERROR_SIZE = 512<br> MAX_STRING = 512</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-bookmark"><a href="#x64dbgpy-pluginsdk-scriptapi-bookmark" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.bookmark"></a>x64dbgpy.pluginsdk._scriptapi.bookmark</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\bookmark.py</p><p>FUNCTIONS<br> Clear()<br><br> Delete(addr)<br><br> DeleteRange(start, end)<br><br> Get(addr)<br><br> GetInfo(addr)<br><br> GetList()<br><br> Set(addr, manual=False)<br><br> SetInfo(info)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-comment"><a href="#x64dbgpy-pluginsdk-scriptapi-comment" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.comment"></a>x64dbgpy.pluginsdk._scriptapi.comment</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\comment.py</p><p>FUNCTIONS<br> Clear()<br><br> Delete(addr)<br><br> DeleteRange(start, end)<br><br> Get(addr)<br><br> GetInfo(addr)<br><br> GetList()<br><br> Set(addr, text, manual=False)<br><br> SetInfo(info)</p><p>DATA<br> MAX_COMMENT_SIZE = 512</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-debug"><a href="#x64dbgpy-pluginsdk-scriptapi-debug" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.debug"></a>x64dbgpy.pluginsdk._scriptapi.debug</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\debug.py</p><p>CLASSES<br> HardwareType<br><br> class HardwareType<br> | Data and other attributes defined here:<br> |<br> | HardwareAccess = 0<br> |<br> | HardwareExecute = 2<br> |<br> | HardwareWrite = 1</p><p>FUNCTIONS<br> DeleteBreakpoint(address)<br><br> DeleteHardwareBreakpoint(address)<br><br> Run()<br><br> SetBreakpoint(address)<br><br> SetHardwareBreakpoint(address, type=2)<br><br> StepIn()<br><br> StepOut()<br><br> StepOver()<br><br> Stop()<br><br> Wait()</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-flag"><a href="#x64dbgpy-pluginsdk-scriptapi-flag" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.flag"></a>x64dbgpy.pluginsdk._scriptapi.flag</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\flag.py</p><p>CLASSES<br> FlagEnum<br><br> class FlagEnum<br> | Data and other attributes defined here:<br> |<br> | AF = 6<br> |<br> | CF = 2<br> |<br> | DF = 7<br> |<br> | IF = 8<br> |<br> | OF = 1<br> |<br> | PF = 3<br> |<br> | SF = 4<br> |<br> | TF = 5<br> |<br> | ZF = 0</p><p>FUNCTIONS<br> Flag_Get(flag)<br><br> Flag_Set(flag, value)<br><br> GetAF()<br><br> GetCF()<br><br> GetDF()<br><br> GetIF()<br><br> GetOF()<br><br> GetPF()<br><br> GetSF()<br><br> GetTF()<br><br> GetZF()<br><br> SetAF(value)<br><br> SetCF(value)<br><br> SetDF(value)<br><br> SetIF(value)<br><br> SetOF(value)<br><br> SetPF(value)<br><br> SetSF(value)<br><br> SetTF(value)<br><br> SetZF(value)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-function"><a href="#x64dbgpy-pluginsdk-scriptapi-function" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.function"></a>x64dbgpy.pluginsdk._scriptapi.function</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\function.py</p><p>FUNCTIONS<br> Add(start, end, manual, instructionCount=0)<br><br> AddInfo(info)<br><br> Clear()<br><br> Delete(addr)<br><br> DeleteRange(start, end, deleteManual=False)<br><br> Get(addr)<br><br> GetInfo(addr)<br><br> GetList()<br><br> Overlaps(start, end)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-gui"><a href="#x64dbgpy-pluginsdk-scriptapi-gui" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.gui"></a>x64dbgpy.pluginsdk._scriptapi.gui</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\gui.py</p><p>FUNCTIONS<br> Disassembly_SelectionGet()<br> Script::Gui::Disassembly</p><p> Disassembly_SelectionGetEnd()<br> <br> Disassembly_SelectionGetStart()<br> <br> Disassembly_SelectionSet(start, end)<br> <br> Dump_SelectionGet()<br> Script::Gui::Dump</p><p> Dump_SelectionGetEnd()<br> <br> Dump_SelectionGetStart()<br> <br> Dump_SelectionSet(start, end)<br> <br> Gui_SelectionGet(window)<br> Script::Gui</p><p> Gui_SelectionGetEnd(window)<br> <br> Gui_SelectionGetStart(window)<br> <br> Gui_SelectionSet(window, start, end)<br> <br> InputLine(title)<br> <br> InputValue(title)<br> <br> Message(message)<br> <br> MessageYesNo(message)<br> <br> Refresh()<br> <br> Stack_SelectionGet()<br> Script::Gui::Stack</p><p> Stack_SelectionGetEnd()<br> <br> Stack_SelectionGetStart()<br> <br> Stack_SelectionSet(start, end)</p><p>DATA<br> GUI_MAX_LINE_SIZE = 65536</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-label"><a href="#x64dbgpy-pluginsdk-scriptapi-label" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.label"></a>x64dbgpy.pluginsdk._scriptapi.label</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\label.py</p><p>FUNCTIONS<br> Clear()<br><br> Delete(addr)<br><br> DeleteRange(start, end)<br><br> FromString(label)<br><br> Get(addr)<br><br> GetInfo(addr)<br><br> GetList()<br><br> Set(addr, text, manual=False)<br><br> SetInfo(info)</p><p>DATA<br> MAX_LABEL_SIZE = 256</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-memory"><a href="#x64dbgpy-pluginsdk-scriptapi-memory" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.memory"></a>x64dbgpy.pluginsdk._scriptapi.memory</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\memory.py</p><p>FUNCTIONS<br> IsValidPtr(addr)<br><br> Read(addr, size)<br><br> ReadByte(addr)<br><br> ReadDword(addr)<br><br> ReadPtr(addr)<br><br> ReadWord(addr)<br><br> RemoteAlloc(size, addr=0)<br><br> RemoteFree(addr)<br><br> Write(addr, data)<br><br> WriteByte(addr, data)<br><br> WriteDword(addr, data)<br><br> WritePtr(addr, data)<br><br> WriteWord(addr, data)</p><hr><h1 id="x64dbgpy-pluginsdk-scriptapi-misc"><a href="#x64dbgpy-pluginsdk-scriptapi-misc" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.misc"></a>x64dbgpy.pluginsdk._scriptapi.misc</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\misc.py</p><p>FUNCTIONS<br> Alloc(size)<br><br> Free(ptr)<br><br> ParseExpression(expression)<br><br> RemoteGetProcAddress(module, api)<br><br> ResolveLabel(label)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-module"><a href="#x64dbgpy-pluginsdk-scriptapi-module" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.module"></a>x64dbgpy.pluginsdk._scriptapi.module</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\module.py</p><p>FUNCTIONS<br> BaseFromAddr(addr)<br><br> BaseFromName(name)<br><br> EntryFromAddr(addr)<br><br> EntryFromName(name)<br><br> GetList()<br><br> GetMainModuleBase()<br><br> GetMainModuleEntry()<br><br> GetMainModuleInfo()<br><br> GetMainModuleName()<br><br> GetMainModulePath()<br><br> GetMainModuleSectionCount()<br><br> GetMainModuleSectionList()<br><br> GetMainModuleSize()<br><br> InfoFromAddr(addr)<br><br> InfoFromName(name)<br><br> NameFromAddr(addr)<br><br> PathFromAddr(addr)<br><br> PathFromName(name)<br><br> SectionCountFromAddr(addr)<br><br> SectionCountFromName(name)<br><br> SectionFromAddr(addr, number)<br><br> SectionFromName(name, number)<br><br> SectionListFromAddr(addr)<br><br> SectionListFromName(name)<br><br> SizeFromAddr(addr)<br><br> SizeFromName(name)</p><p>DATA<br> MAX_MODULE_SIZE = 256<br> MAX_PATH = 260</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-pattern"><a href="#x64dbgpy-pluginsdk-scriptapi-pattern" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.pattern"></a>x64dbgpy.pluginsdk._scriptapi.pattern</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\pattern.py</p><p>FUNCTIONS<br> Find(data, pattern)<br><br> FindMem(start, size, pattern)<br><br> SearchAndReplace(data, searchpattern, replacepattern)<br><br> SearchAndReplaceMem(start, size, searchpattern, replacepattern)<br><br> Write(data, pattern)<br><br> WriteMem(start, size, pattern)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-register"><a href="#x64dbgpy-pluginsdk-scriptapi-register" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.register"></a>x64dbgpy.pluginsdk._scriptapi.register</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\register.py</p><p>FUNCTIONS GetAH()<br><br> GetAL()<br><br> GetAX()<br><br> GetBH()<br><br> GetBL()<br><br> GetBP()<br><br> GetBX()<br><br> GetCH()<br><br> GetCIP()<br> # Generic Registers<br><br> GetCL()<br><br> GetCSP()<br><br> GetCX()<br><br> GetDH()<br><br> GetDI()<br><br> GetDL()<br><br> GetDR0()<br> # x86 Debug Registers<br><br> GetDR1()<br><br> GetDR2()<br><br> GetDR3()<br><br> GetDR6()<br><br> GetDR7()<br><br> GetDX()<br><br> GetEAX()<br> # x86 Registers<br><br> GetEBP()<br><br> GetEBX()<br><br> GetECX()<br><br> GetEDI()<br><br> GetEDX()<br><br> GetEIP()<br><br> GetESI()<br><br> GetESP()<br><br> GetSI()<br><br> GetSP()<br><br> SetAH(value)<br><br> SetAL(value)<br><br> SetAX(value)<br><br> SetBH(value)<br><br> SetBL(value)<br><br> SetBP(value)<br><br> SetBX(value)<br><br> SetCH(value)<br><br> SetCIP(value)<br><br> SetCL(value)<br><br> SetCSP(value)<br><br> SetCX(value)<br><br> SetDH(value)<br><br> SetDI(value)<br><br> SetDL(value)<br><br> SetDR0(value)<br><br> SetDR1(value)<br><br> SetDR2(value)<br><br> SetDR3(value)<br><br> SetDR6(value)<br><br> SetDR7(value)<br><br> SetDX(value)<br><br> SetEAX(value)<br><br> SetEBP(value)<br><br> SetEBX(value)<br><br> SetECX(value)<br><br> SetEDI(value)<br><br> SetEDX(value)<br><br> SetEIP(value)<br><br> SetESI(value)<br><br> SetESP(value)<br><br> SetSI(value)<br><br> SetSP(value)<br><br> Size()</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-stack"><a href="#x64dbgpy-pluginsdk-scriptapi-stack" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.stack"></a>x64dbgpy.pluginsdk._scriptapi.stack</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\stack.py</p><p>FUNCTIONS<br> Peek(offset=0)<br><br> Pop()<br><br> Push(value)</p><hr><p>NAME</p><h1 id="x64dbgpy-pluginsdk-scriptapi-symbol"><a href="#x64dbgpy-pluginsdk-scriptapi-symbol" class="headerlink" title="x64dbgpy.pluginsdk._scriptapi.symbol"></a>x64dbgpy.pluginsdk._scriptapi.symbol</h1><p>FILE<br> c:\softfo~1\release\x32\plugins\x64dbgpy\x64dbgpy\pluginsdk_scriptapi\symbol.py</p><p>CLASSES<br> SymbolType<br><br> class SymbolType<br> | Data and other attributes defined here:<br> |<br> | Export = 2<br> |<br> | Function = 0<br> |<br> | Import = 1</p><p>FUNCTIONS<br> GetList()</p>]]></content>
<categories>
<category> Note </category>
</categories>
<tags>
<tag> x64dbg </tag>
</tags>
</entry>
<entry>
<title>python pyc文件结构</title>
<link href="/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/"/>
<url>/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/</url>
<content type="html"><![CDATA[<p>python反编译工具一抓一大把</p><p>为什么还要自己搞?</p><p>python混肴代码可以让部分工具反编译失败,这还不是最难受的,有的人直接修改了python字节码,自己编译了python,会有人这么无聊吗?没错我碰上了</p><p>碰上这种情况怎么办?搞一份python代码,在修改过的python里跑一遍,在原版的python里跑一遍,对比字节码在修改回来就可以反编译了</p><p>python编译后的字节码存储在pyc文件中,这个pyc文件实际上就是PyCodeObject对象的序列化文本,也就是说我们搞懂这个PyCodeObject结构就行了</p><p>这个结构体的定义如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Bytecode object */</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> PyObject_HEAD</span><br><span class="line"> <span class="keyword">int</span> co_argcount; <span class="comment">/* Code Block的位置参数个数,比如说一个函数的位置参数个数*/</span></span><br><span class="line"> <span class="keyword">int</span> co_nlocals; <span class="comment">/* Code Block中局部变量的个数,包括其中位置参数的个数 */</span></span><br><span class="line"> <span class="keyword">int</span> co_stacksize; <span class="comment">/* 执行该段Code Block需要的栈空间 */</span></span><br><span class="line"> <span class="keyword">int</span> co_flags; <span class="comment">/* CO_..., see below */</span></span><br><span class="line"> PyObject *co_code; <span class="comment">/* Code Block编译所得的字节码指令序列。以PyStingObjet的形式存在 */</span></span><br><span class="line"> PyObject *co_consts; <span class="comment">/* PyTupleObject对象,保存CodeBlock中的所常量 */</span></span><br><span class="line"> PyObject *co_names; <span class="comment">/* PyTupleObject对象,保存CodeBlock中的所有符号 */</span></span><br><span class="line"> PyObject *co_varnames; <span class="comment">/* Code Block中的局部变量名集合 */</span></span><br><span class="line"> PyObject *co_freevars; <span class="comment">/* Python实现闭包需要用的东西 */</span></span><br><span class="line"> PyObject *co_cellvars; <span class="comment">/* Code Block中内部嵌套函数所引用的局部变量名集合 */</span></span><br><span class="line"> <span class="comment">/* The rest doesn't count for hash/cmp */</span></span><br><span class="line"> PyObject *co_filename; <span class="comment">/* Code Block所对应的.py文件的完整路径 */</span></span><br><span class="line"> PyObject *co_name; <span class="comment">/* Code Block的名字,通常是函数名或类名 */</span></span><br><span class="line"> <span class="keyword">int</span> co_firstlineno; <span class="comment">/* Code Block在对应的.py文件中起始行 */</span></span><br><span class="line"> PyObject *co_lnotab; <span class="comment">/* 字节码指令与.py文件中source code行号的对应关系,以PyStringObject的形式存在 */</span></span><br><span class="line"> <span class="keyword">void</span> *co_zombieframe; <span class="comment">/* for optimization only (see frameobject.c) */</span></span><br><span class="line">} PyCodeObject;</span><br></pre></td></tr></table></figure><p>每个PyCodeObject代表一个Code Block,也可以称之为一个作用域</p><p>一个pyc文件中不止一个Code Block,一个文件,函数,类,都会对应一个Code Block</p><p>对应文件的PyCodeObject的子作用域存储在co_consts中 </p><p>口嗨多无聊,来份代码玩一玩吧</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="string">'string'</span></span><br><span class="line">i = <span class="number">10</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">func</span>():</span></span><br><span class="line"> <span class="built_in">print</span> <span class="string">'pyc file format'</span></span><br><span class="line"> ss = <span class="string">'new string'</span></span><br><span class="line"> <span class="keyword">return</span> ss</span><br><span class="line">s2 = func()</span><br><span class="line"><span class="built_in">print</span> s2</span><br></pre></td></tr></table></figure><p>编译成pyc文件:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python2 -m compileall main.py</span><br></pre></td></tr></table></figure><p>hexdump先来看一眼16进制</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">00000000 03 f3 0d 0a 6b af be 5d 63 00 00 00 00 00 00 00 |....k..]c.......|</span><br><span class="line">00000010 00 01 00 00 00 40 00 00 00 73 27 00 00 00 64 00 |[email protected]'...d.|</span><br><span class="line">00000020 00 5a 00 00 64 01 00 5a 01 00 64 02 00 84 00 00 |.Z..d..Z..d.....|</span><br><span class="line">00000030 5a 02 00 65 02 00 83 00 00 5a 03 00 65 03 00 47 |Z..e.....Z..e..G|</span><br><span class="line">00000040 48 64 03 00 53 28 04 00 00 00 74 06 00 00 00 73 |Hd..S(....t....s|</span><br><span class="line">00000050 74 72 69 6e 67 69 0a 00 00 00 63 00 00 00 00 01 |tringi....c.....|</span><br><span class="line">00000060 00 00 00 01 00 00 00 43 00 00 00 73 0f 00 00 00 |.......C...s....|</span><br><span class="line">00000070 64 01 00 47 48 64 02 00 7d 00 00 7c 00 00 53 28 |d..GHd..}..|..S(|</span><br><span class="line">00000080 03 00 00 00 4e 73 0f 00 00 00 70 79 63 20 66 69 |....Ns....pyc fi|</span><br><span class="line">00000090 6c 65 20 66 6f 72 6d 61 74 73 0a 00 00 00 6e 65 |le formats....ne|</span><br><span class="line">000000a0 77 20 73 74 72 69 6e 67 28 00 00 00 00 28 01 00 |w string(....(..|</span><br><span class="line">000000b0 00 00 74 02 00 00 00 73 73 28 00 00 00 00 28 00 |..t....ss(....(.|</span><br><span class="line">000000c0 00 00 00 73 07 00 00 00 6d 61 69 6e 2e 70 79 74 |...s....main.pyt|</span><br><span class="line">000000d0 04 00 00 00 66 75 6e 63 05 00 00 00 73 06 00 00 |....func....s...|</span><br><span class="line">000000e0 00 00 01 05 01 06 01 4e 28 04 00 00 00 74 01 00 |.......N(....t..|</span><br><span class="line">000000f0 00 00 73 74 01 00 00 00 69 52 02 00 00 00 74 02 |..st....iR....t.|</span><br><span class="line">00000100 00 00 00 73 32 28 00 00 00 00 28 00 00 00 00 28 |...s2(....(....(|</span><br><span class="line">00000110 00 00 00 00 73 07 00 00 00 6d 61 69 6e 2e 70 79 |....s....main.py|</span><br><span class="line">00000120 74 08 00 00 00 3c 6d 6f 64 75 6c 65 3e 02 00 00 |t....<module>...|</span><br><span class="line">00000130 00 73 08 00 00 00 06 01 06 02 09 04 09 01 |.s............|</span><br></pre></td></tr></table></figure><p>前4个字节magic number对应不同的python版本,低字节的0d0a就是\r\n</p><p>紧接着的4个字节 6b af be 5d 是时间戳,代表着修改的时间</p><p>一段一段来看吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">00000000 .. .. .. .. .. .. .. .. 63 00 00 00 00 00 00 00 |....k..]c.......|</span><br><span class="line">00000010 00 01 00 00 00 40 00 00 00 73 27 00 00 00 64 00 |[email protected]'...d.|</span><br><span class="line">00000020 00 5a 00 00 64 01 00 5a 01 00 64 02 00 84 00 00 |.Z..d..Z..d.....|</span><br><span class="line">00000030 5a 02 00 65 02 00 83 00 00 5a 03 00 65 03 00 47 |Z..e.....Z..e..G|</span><br><span class="line">00000040 48 64 03 00 53</span><br></pre></td></tr></table></figure><ul><li><p>紧跟着的是0x63,字符‘c’,这是一个标识(TYPE_CODE)</p></li><li><p>跟着这个标识的4个字节是全局 code block的位置的参数数量(co_argument),上述代码为0</p></li><li><p>在后面的4个字节是code block的局部变量参数个数(co_nlocals),上述代码同样为0</p></li><li><p>在后面的4个字节就是栈空间了,针对当前的code block,上述代码栈值为1</p></li><li><p>在后面的4个字节为co_flags,上述代码为0x40</p></li></ul><p>到了重要的环节了,看到紧跟着的0x73了吗,在这之后就是字节码了,0x73代表的是TYPE_STRING,也就是PyStringObject的标识,PyCodeObject的字节码序列是用PyStringObject对象来保存的</p><p>0x73后4个字节是字节码的大小 ,上述代码为0x27,也就是说在0x64(包括)后的0x27个字节都是python的字节码</p><p>用python的dis模块来验证下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>f = <span class="built_in">open</span>(<span class="string">'main.pyc'</span>) <span class="comment">#读取pyc文件</span></span><br><span class="line"><span class="meta">>>> </span>f.read(<span class="number">8</span>)</span><br><span class="line"><span class="string">'\x03\xf3\r\nk\xaf\xbe]'</span> <span class="comment">#跳过python版本标识和时间戳</span></span><br><span class="line"><span class="meta">>>> </span>c = marshal.load(f) <span class="comment">#反序列化</span></span><br><span class="line"><span class="meta">>>> </span>c.co_consts </span><br><span class="line">(<span class="string">'string'</span>, <span class="number">10</span>, <code <span class="built_in">object</span> func at <span class="number">0x7f392fbbbc30</span>, file <span class="string">"main.py"</span>, line <span class="number">5</span>>, <span class="literal">None</span>)</span><br><span class="line"><span class="meta">>>> </span>c.co_names</span><br><span class="line">(<span class="string">'s'</span>, <span class="string">'i'</span>, <span class="string">'func'</span>, <span class="string">'s2'</span>)</span><br><span class="line"><span class="meta">>>> </span>dis.dis(c) <span class="comment">#字节码</span></span><br><span class="line"> <span class="number">2</span> <span class="number">0</span> LOAD_CONST <span class="number">0</span> (<span class="string">'string'</span>)</span><br><span class="line"> <span class="number">3</span> STORE_NAME <span class="number">0</span> (s)</span><br><span class="line"></span><br><span class="line"> <span class="number">3</span> <span class="number">6</span> LOAD_CONST <span class="number">1</span> (<span class="number">10</span>)</span><br><span class="line"> <span class="number">9</span> STORE_NAME <span class="number">1</span> (i)</span><br><span class="line"></span><br><span class="line"> <span class="number">5</span> <span class="number">12</span> LOAD_CONST <span class="number">2</span> (<code <span class="built_in">object</span> func at <span class="number">0x7f392fbbbc30</span>, file <span class="string">"main.py"</span>, line <span class="number">5</span>>)</span><br><span class="line"> <span class="number">15</span> MAKE_FUNCTION <span class="number">0</span></span><br><span class="line"> <span class="number">18</span> STORE_NAME <span class="number">2</span> (func)</span><br><span class="line"></span><br><span class="line"> <span class="number">9</span> <span class="number">21</span> LOAD_NAME <span class="number">2</span> (func)</span><br><span class="line"> <span class="number">24</span> CALL_FUNCTION <span class="number">0</span></span><br><span class="line"> <span class="number">27</span> STORE_NAME <span class="number">3</span> (s2)</span><br><span class="line"></span><br><span class="line"> <span class="number">10</span> <span class="number">30</span> LOAD_NAME <span class="number">3</span> (s2)</span><br><span class="line"> <span class="number">33</span> PRINT_ITEM </span><br><span class="line"> <span class="number">34</span> PRINT_NEWLINE </span><br><span class="line"> <span class="number">35</span> LOAD_CONST <span class="number">3</span> (<span class="literal">None</span>)</span><br><span class="line"> <span class="number">38</span> RETURN_VALUE </span><br><span class="line"><span class="meta">>>> </span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>刚好39个字节(0x27),dis输出代表值:</p><table><thead><tr><th>所在列</th><th>说明</th></tr></thead><tbody><tr><td>第 1 列</td><td>在源代码中的行数</td></tr><tr><td>第 2 列</td><td>该指令在co_code中的偏移</td></tr><tr><td>第 3 列</td><td>opcode,分为有操作数和无操作数两种,是一个字节的整数</td></tr><tr><td>第 4 列</td><td>操作数,占两个字节</td></tr></tbody></table><p>python opcode对应字节码就不说了,自行查看吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">00000040 .. .. .. .. .. 28 04 00 00 00 74 06 00 00 00 73 |Hd..S(....t....s|</span><br><span class="line">00000050 74 72 69 6e 67 69 0a 00 00 00 63 00 00 00 00 01 |tringi....c.....|</span><br><span class="line">00000060 00 00 00 01 00 00 00 43 00 00 00 73 0f 00 00 00 |.......C...s....|</span><br><span class="line">00000070 64 01 00 47 48 64 02 00 7d 00 00 7c 00 00 53 00</span><br></pre></td></tr></table></figure><ul><li><p>opcode结束了,在0x28开始就是co_consts的内容了,这里保存了code block的常量</p></li><li><p>紧跟着的4个字节是元素数量,本例中为0x4,有4个元素</p></li><li><p>第一个数据类型是PyStringObject,TYPE_CODE为0x74,0x74后面的4个字节为字符串长度,后面为字符串内容</p></li><li><p>第二个数据类型为int,对应TYPE_CODE为0x69,后面的4个字节为内容,0xA</p></li><li><p>第三个数据类型为PyCodeObject,TYPE_CODE为0x63,和上面一样重新分析,在这不赘述了</p></li></ul><p>跳过上段的code block之后,就是文件信息了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">000000c0 .. .. .. 73 07 00 00 00 6d 61 69 6e 2e 70 79 74 |...s....main.pyt|</span><br><span class="line">000000d0 04 00 00 00 66 75 6e 63 05 00 00 00 73 06 00 00 |....func....s...|</span><br><span class="line">000000e0 00 00 01 05 01 06 01 4e 28 04 00 00 00 74 01 00 |.......N(....t..|</span><br></pre></td></tr></table></figure><p>0x73,字符类型,0x07,字符长度,后面是字符串</p><p>紧跟着的是co_name,标识为0x74,然后是长度0x4,跟着就是4个字节的函数名,func,后面还有4个字节,代表的是在文件中的行数,上例中为5</p><p>然后是字节码指令与源文件行号对应的co_lnotab,以PyStringObject对象存储,先是标识0x73(‘s’),然后是4字节的长度0x00000006,然后是内容0x010601050100</p><p>剩下的内容:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">000000f0 00 00 73 74 01 00 00 00 69 52 02 00 00 00 74 02 |..st....iR....t.|</span><br><span class="line">00000100 00 00 00 73 32 28 00 00 00 00 28 00 00 00 00 28 |...s2(....(....(|</span><br><span class="line">00000110 00 00 00 00 73 07 00 00 00 6d 61 69 6e 2e 70 79 |....s....main.py|</span><br><span class="line">00000120 74 08 00 00 00 3c 6d 6f 64 75 6c 65 3e 02 00 00 |t....<module>...|</span><br><span class="line">00000130 00 73 08 00 00 00 06 01 06 02 09 04 09 01 |.s............|</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> Python </tag>
</tags>
</entry>
<entry>
<title>反调试总结</title>
<link href="/2018/11/30/2018-11-30-%E5%8F%8D%E8%B0%83%E8%AF%95%E6%80%BB%E7%BB%93/"/>
<url>/2018/11/30/2018-11-30-%E5%8F%8D%E8%B0%83%E8%AF%95%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h1 id="API-检测"><a href="#API-检测" class="headerlink" title="API 检测"></a>API 检测</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PBYTE pCC = (PBYTE)MessageBoxW;</span><br><span class="line"><span class="keyword">if</span> (*pCC == <span class="number">0xCC</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line">MessageBoxW(<span class="number">0</span>, <span class="string">L"未发现调试器!\n"</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>在调试器中下断点时,会将目标地址的首字节替换为0xCC,上述代码以此进行判断会否处于调试状态</p><p>破解思路:避免在函数的首地址下断点,可以在函数的其他代码部分下断,通常在ret指令处下断</p><h1 id="PEB-BeingDebugged"><a href="#PEB-BeingDebugged" class="headerlink" title="PEB BeingDebugged"></a>PEB BeingDebugged</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">__asm {</span><br><span class="line">MOV EAX, DWORD PTR FS : [0x30] ;获取PEB</span><br><span class="line">MOV AL, BYTE PTR DS : [EAX + 2]</span><br><span class="line">MOV bDebugged, AL</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当进程被调试时,AL的值为1,这个值取自PEB的BeingDebugged成员.</p><h1 id="代码校验"><a href="#代码校验" class="headerlink" title="代码校验"></a>代码校验</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_Checksum</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">BOOL bDebugging = FALSE;</span><br><span class="line">__asm {</span><br><span class="line">call CHECKBEGIN</span><br><span class="line">CHECKBEGIN:</span><br><span class="line">pop esi</span><br><span class="line">mov ecx, <span class="number">0x15</span> <span class="comment">// ecx : loop count</span></span><br><span class="line"><span class="keyword">xor</span> eax, eax <span class="comment">// eax : checksum</span></span><br><span class="line"><span class="keyword">xor</span> ebx, ebx</span><br><span class="line"></span><br><span class="line">_CALC_CHECKSUM :</span><br><span class="line">movzx ebx, byte ptr ds : [esi]</span><br><span class="line">add eax, ebx</span><br><span class="line">rol eax, <span class="number">1</span></span><br><span class="line">inc esi</span><br><span class="line">loop _CALC_CHECKSUM</span><br><span class="line">cmp eax, <span class="number">0x1859a602</span></span><br><span class="line">je _NOT_DEBUGGING</span><br><span class="line">mov bDebugging, <span class="number">1</span></span><br><span class="line">_NOT_DEBUGGING:</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugging;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="调试信号判断"><a href="#调试信号判断" class="headerlink" title="调试信号判断"></a>调试信号判断</h1><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="comment">// 尝试打开互斥体,确定是否首次运行程序</span></span><br><span class="line">HANDLE hMutex = <span class="built_in">OpenMutex</span>(MUTEX_MODIFY_STATE, FALSE, <span class="string">L"Global\\MyMutex"</span>);</span><br><span class="line"><span class="keyword">if</span> (hMutex)</span><br><span class="line">{</span><br><span class="line"><span class="comment">// 打开成功说明第2次运行,执行正常代码</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"正被调试运行!\n"</span>);</span><br><span class="line"><span class="built_in">getchar</span>();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="comment">// 打开失败说明第1次运行,创建互斥体,并调试创建自身进程</span></span><br><span class="line"><span class="built_in">CreateMutex</span>(<span class="literal">NULL</span>, FALSE, <span class="string">L"Global\\MyMutex"</span>);</span><br><span class="line">TCHAR szPath[MAX_PATH] = {};</span><br><span class="line"><span class="built_in">GetModuleFileName</span>(<span class="literal">NULL</span>, szPath, MAX_PATH);</span><br><span class="line"><span class="comment">// 调试方式打开程序</span></span><br><span class="line">STARTUPINFO si = { <span class="built_in"><span class="keyword">sizeof</span></span>(STARTUPINFO) };</span><br><span class="line">PROCESS_INFORMATION pi = {};</span><br><span class="line"><span class="comment">// 正常创建,后面附加调试</span></span><br><span class="line">BOOL bStatus = <span class="built_in">CreateProcess</span>(szPath, <span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>, FALSE,</span><br><span class="line">CREATE_NEW_CONSOLE,</span><br><span class="line"><span class="literal">NULL</span>, <span class="literal">NULL</span>, &si, &pi);</span><br><span class="line"><span class="keyword">if</span> (!bStatus) {</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"创建进程失败!\n"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">DebugActiveProcess</span>(pi.dwProcessId)) {</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"附加进程失败!\n"</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 初始化调试事件结构体</span></span><br><span class="line">DEBUG_EVENT DbgEvent = { <span class="number">0</span> };</span><br><span class="line">DWORD dwState = DBG_EXCEPTION_NOT_HANDLED;</span><br><span class="line"><span class="comment">// 等待目标Exe产生调试事件</span></span><br><span class="line">BOOL bExit = FALSE;</span><br><span class="line"><span class="keyword">while</span> (!bExit) {</span><br><span class="line"><span class="built_in">WaitForDebugEvent</span>(&DbgEvent, INFINITE);</span><br><span class="line"><span class="keyword">if</span> (DbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)</span><br><span class="line">{</span><br><span class="line"><span class="comment">// 被调试进程退出</span></span><br><span class="line">bExit = TRUE;</span><br><span class="line">}</span><br><span class="line"><span class="built_in">ContinueDebugEvent</span>(DbgEvent.dwProcessId, DbgEvent.dwThreadId, dwState);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以调试状态启动自身,等待调试事件触发,通过调试事件Code判断当前是否是调试状态,由于创建了互斥体,所以自身创建的调试进程启动后就退出了,不会出现两个软件同时运行的情况</p><p>破解思路: 跳转patch</p><h1 id="窗口遍历"><a href="#窗口遍历" class="headerlink" title="窗口遍历"></a>窗口遍历</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">PROCESSENTRY32 pe32 = { <span class="keyword">sizeof</span>(pe32) };</span><br><span class="line">HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (hProcessSnap == INVALID_HANDLE_VALUE)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line">Process32First(hProcessSnap, &pe32);</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">{</span><br><span class="line"><span class="comment">// 这里只比较了OllyDbg,也可以添加其他的调试分析工具名</span></span><br><span class="line"><span class="keyword">if</span> (_tcsicmp(pe32.szExeFile, TEXT(<span class="string">"OllyDbg.exe"</span>)) == <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line">CloseHandle(hProcessSnap);</span><br><span class="line"><span class="keyword">return</span> TRUE;</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">while</span> (Process32Next(hProcessSnap, &pe32));</span><br><span class="line">CloseHandle(hProcessSnap);</span><br></pre></td></tr></table></figure><p>通过遍历当前所有的窗口列表,以此判断是否有进程名为调试器名字的软件</p><h1 id="父进程判断"><a href="#父进程判断" class="headerlink" title="父进程判断"></a>父进程判断</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">PROCESS_BASIC_INFORMATION</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line">DWORD ExitStatus;</span><br><span class="line">DWORD PebBaseAddress;</span><br><span class="line">DWORD AffinityMask;</span><br><span class="line">DWORD BasePriority;</span><br><span class="line">ULONG UniqueProcessId;</span><br><span class="line">ULONG InheritedFromUniqueProcessId;</span><br><span class="line">}pbi = {};</span><br><span class="line">NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, (PVOID)&pbi, <span class="keyword">sizeof</span>(pbi), <span class="literal">NULL</span>);</span><br><span class="line">PROCESSENTRY32 pe32 = { <span class="keyword">sizeof</span>(pe32) };</span><br><span class="line">HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (hProcessSnap == INVALID_HANDLE_VALUE)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line">Process32First(hProcessSnap, &pe32);</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span> (pbi.InheritedFromUniqueProcessId == pe32.th32ProcessID)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span> (_tcsicmp(pe32.szExeFile, TEXT(<span class="string">"explorer.exe"</span>)) == <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line">CloseHandle(hProcessSnap);</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line">CloseHandle(hProcessSnap);</span><br><span class="line"><span class="keyword">return</span> TRUE;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">while</span> (Process32Next(hProcessSnap, &pe32));</span><br><span class="line">CloseHandle(hProcessSnap);</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br></pre></td></tr></table></figure><p>NtQueryInformationProcess这个函数是未被公开的一个函数,在ntdll中,可以通过getprocaddress来调用,函数原型:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">NTSTATUS WINAPI <span class="title">NtQueryInformationProcess</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> _In_ HANDLE ProcessHandle,</span></span></span><br><span class="line"><span class="params"><span class="function"> _In_ PROCESSINFOCLASS ProcessInformationClass,</span></span></span><br><span class="line"><span class="params"><span class="function"> _Out_ PVOID ProcessInformation,</span></span></span><br><span class="line"><span class="params"><span class="function"> _In_ ULONG ProcessInformationLength,</span></span></span><br><span class="line"><span class="params"><span class="function"> _Out_opt_ PULONG ReturnLength</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>;</span><br></pre></td></tr></table></figure><p>ProcessHandle:查询进程的句柄</p><p>ProcessInformationClass: 查找信息的标识符,可以取以下值:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ProcessBasicInformation 0</span><br><span class="line">ProcessDebugPort 7 </span><br><span class="line">ProcessWow64Information 26</span><br><span class="line">ProcessImageFileName 27</span><br><span class="line">ProcessBreakOnTermination 29</span><br></pre></td></tr></table></figure><p>ProcessInformation:要存放查询结果的缓冲区,这个结构要根据第二个参数来决定,<br>ProcessInformationLength:缓冲区大小</p><p>ReturnLength:实际返回的写入缓冲区的字节数</p><p>当调用此函数,传入当前进程的句柄,参数2传入ProcessBasicInfomation,在参数3 就会返回PROCESS_BASIC_INFORMATION这个结构体,文中有定义,这个结构体中就包含了父进程的句柄,在通过遍历的方式判断父进程的名字是不是explorer即可</p><h1 id="FindWindow"><a href="#FindWindow" class="headerlink" title="FindWindow"></a>FindWindow</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_FindWindow</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">// OD的主窗口类名为 OLLYDBG,也可以查询其他调试器的类名</span></span><br><span class="line"><span class="comment">// 其他常用调试器的类名可以使用Spy++查看</span></span><br><span class="line"><span class="keyword">if</span> (FindWindow(TEXT(<span class="string">"OLLYDBG"</span>), <span class="literal">NULL</span>))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function">BOOL CALLBACK <span class="title">EnumWindowProc</span><span class="params">(HWND hWnd, LPARAM lParam)</span> </span>{</span><br><span class="line">TCHAR winTitle[<span class="number">0x100</span>] = {};</span><br><span class="line">GetWindowText(hWnd, winTitle, <span class="number">0x100</span>);</span><br><span class="line"><span class="keyword">if</span> (_tcsstr(winTitle, TEXT(<span class="string">"OllyDbg"</span>)))</span><br><span class="line">{</span><br><span class="line"><span class="comment">// nFind=true</span></span><br><span class="line">*((<span class="keyword">int</span>*)lParam) = <span class="literal">true</span>;</span><br><span class="line"><span class="comment">// 找到目标窗口停止遍历</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 继续遍历下一个窗口</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_EnumWindow</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">int</span> nFind = <span class="literal">false</span>;</span><br><span class="line">EnumWindows(EnumWindowProc, (LPARAM)&nFind);</span><br><span class="line"><span class="keyword">return</span> nFind != <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个和之前的窗口遍历差不多,不过实现方式不同</p><h1 id="硬件断点-amp-异常"><a href="#硬件断点-amp-异常" class="headerlink" title="硬件断点 & 异常"></a>硬件断点 & 异常</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// API查询</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_HB</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">CONTEXT context;</span><br><span class="line">HANDLE hThread = GetCurrentThread();</span><br><span class="line">context.ContextFlags = CONTEXT_DEBUG_REGISTERS;</span><br><span class="line">GetThreadContext(hThread, &context);</span><br><span class="line"><span class="keyword">if</span> (context.Dr0 != <span class="number">0</span> || context.Dr1 != <span class="number">0</span> || context.Dr2 != <span class="number">0</span> || context.Dr3 != <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> TRUE;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 触发异常查询</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_HB_EXCEPTION</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">BOOL bDebugging = FALSE;</span><br><span class="line">__asm {</span><br><span class="line"><span class="comment">// install SEH</span></span><br><span class="line">push handler</span><br><span class="line">push DWORD ptr fs : [<span class="number">0</span>]</span><br><span class="line">mov DWORD ptr fs : [<span class="number">0</span>], esp</span><br><span class="line">__emit(<span class="number">0xcc</span>)</span><br><span class="line">mov bDebugging, <span class="number">1</span></span><br><span class="line">jmp normal_code</span><br><span class="line">handler :</span><br><span class="line">mov eax, dword ptr ss : [esp + <span class="number">0xc</span>];<span class="comment">// ContextRecord</span></span><br><span class="line">mov dword ptr ds : [eax + <span class="number">0xb8</span>], offset normal_code;</span><br><span class="line">mov ecx, [eax + <span class="number">4</span>];<span class="comment">// Dr0</span></span><br><span class="line"><span class="keyword">or</span> ecx, [eax + <span class="number">8</span>];<span class="comment">// Dr1</span></span><br><span class="line"><span class="keyword">or</span> ecx, [eax + <span class="number">0x0C</span>];<span class="comment">// Dr2</span></span><br><span class="line"><span class="keyword">or</span> ecx, [eax + <span class="number">0x10</span>];<span class="comment">// Dr3</span></span><br><span class="line">je NoDebugger;</span><br><span class="line">mov ecx, [eax + <span class="number">0xb4</span>];<span class="comment">// ebp</span></span><br><span class="line"><span class="comment">// vs2015 debug下bDebugging的地址为ebp-c</span></span><br><span class="line">mov [ecx<span class="number">-0x0c</span>],<span class="number">1</span> <span class="comment">// bDebugging</span></span><br><span class="line">NoDebugger:</span><br><span class="line"><span class="keyword">xor</span> eax, eax</span><br><span class="line">retn</span><br><span class="line">normal_code :</span><br><span class="line"><span class="comment">// remove SEH</span></span><br><span class="line">pop dword ptr fs : [<span class="number">0</span>]</span><br><span class="line">add esp, <span class="number">4</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugging;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>CheckDebug_HB函数,通过判断是否有硬件断点,如果有则为调试状态</p><p>CheckDebug_HB_EXCEPTION函数,设置SEH异常处理函数后触发异常,如果未在调试状态,则会走到自己的函数中,否则在调试状态下</p><h1 id="INT-2D"><a href="#INT-2D" class="headerlink" title="INT 2D"></a>INT 2D</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_INT_2D</span><span class="params">()</span> </span>{</span><br><span class="line">BOOL bDebugging = FALSE;</span><br><span class="line">__asm {</span><br><span class="line"><span class="comment">// install SEH</span></span><br><span class="line">push handler</span><br><span class="line">push DWORD ptr fs : [<span class="number">0</span>]</span><br><span class="line">mov DWORD ptr fs : [<span class="number">0</span>], esp</span><br><span class="line"><span class="comment">// OD会忽略0x2d和nop,继续向后执行</span></span><br><span class="line"><span class="comment">// 这时候可以选择只是检测调试器还是跑飞</span></span><br><span class="line"><span class="keyword">int</span> <span class="number">0x2d</span></span><br><span class="line">nop</span><br><span class="line">mov bDebugging, <span class="number">1</span></span><br><span class="line">jmp normal_code</span><br><span class="line">handler :</span><br><span class="line">mov eax, dword ptr ss : [esp + <span class="number">0xc</span>]</span><br><span class="line">mov dword ptr ds : [eax + <span class="number">0xb8</span>], offset normal_code</span><br><span class="line">mov bDebugging, <span class="number">0</span></span><br><span class="line"><span class="keyword">xor</span> eax, eax</span><br><span class="line">retn</span><br><span class="line">normal_code :</span><br><span class="line"><span class="comment">// remove SEH</span></span><br><span class="line">pop dword ptr fs : [<span class="number">0</span>]</span><br><span class="line">add esp, <span class="number">4</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugging;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此方法和CheckDebug_HB_EXCEPTION差不多,只不过触发异常的方式不同</p><h1 id="NtGlobalFlag"><a href="#NtGlobalFlag" class="headerlink" title="NtGlobalFlag"></a>NtGlobalFlag</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> nNtFlag = <span class="number">0</span>;</span><br><span class="line">__asm {</span><br><span class="line">MOV EAX, DWORD PTR FS : [<span class="number">0x30</span>]</span><br><span class="line">MOV EAX, DWORD PTR DS : [EAX + <span class="number">0x68</span>]</span><br><span class="line">MOV nNtFlag, EAX</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> nNtFlag==<span class="number">0x70</span>;</span><br></pre></td></tr></table></figure><p>在32位机器上, <code>NtGlobalFlag</code>字段位于<code>PEB</code>(进程环境块)<code>0x68</code>的偏移处, 64位机器则是在偏移<code>0xBC</code>位置. 该字段的默认值为0. 当调试器正在运行时, 该字段会被设置为一个特定的值:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">FLG_HEAP_ENABLE_TAIL_CHECK (0x10)</span><br><span class="line">FLG_HEAP_ENABLE_FREE_CHECK (0x20)</span><br><span class="line">FLG_HEAP_VALIDATE_PARAMETERS (0x40)</span><br></pre></td></tr></table></figure><p>可以通过内存访问断点来定位反调试代码</p><p><strong>或者修改注册表,来替换GlobalFlag的值:</strong></p><p>注册表<code>HKLM\System\CurrentControlSet\Control\SessionManager</code>的<code>GlobalFlag</code>的值会替换进行<code>NtGlobalFlag</code>字段. 尽管它随后还可能由Windows改变(以下会介绍), 注册表键值会对系统中所有进程产生影响并在重启后生效.</p><h1 id="NtQueryInformationProcess的另外三种调试判断"><a href="#NtQueryInformationProcess的另外三种调试判断" class="headerlink" title="NtQueryInformationProcess的另外三种调试判断"></a>NtQueryInformationProcess的另外三种调试判断</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_DebugPort</span><span class="params">()</span> </span>{</span><br><span class="line">DWORD dwDebugPort = <span class="number">0</span>;</span><br><span class="line">NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &dwDebugPort, <span class="number">4</span>, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">return</span> dwDebugPort == <span class="number">-1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_DebugHandle</span><span class="params">()</span> </span>{</span><br><span class="line">DWORD dwDebugHandle = <span class="number">0</span>;</span><br><span class="line">NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)<span class="number">0x1E</span>, &dwDebugHandle, <span class="number">4</span>, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">return</span> dwDebugHandle != <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法win7下已失效</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_DebugFlags</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">bool</span> bDebugFlags = <span class="number">0</span>;</span><br><span class="line">NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)<span class="number">0x1F</span>, &bDebugFlags, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, bDebugFlags);</span><br><span class="line"><span class="keyword">return</span> bDebugFlags == <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>参数二 = ProcessDebugPort </p><p>非调试状态下debugport == 0</p><p>参数二 = ProcessDebugObjectHandle</p><p>第三个参数返回为被调试对象句柄,当返回NULL时说明进程处于非调试状态</p><p>参数二 = ProcessDebugFlags</p><p>rocessDebugFlags参数用于获取调试标识,DebugFlag的值若为0则处于调试状态,若为1则处于非调试状态</p><h1 id="NtQueryObject"><a href="#NtQueryObject" class="headerlink" title="NtQueryObject"></a>NtQueryObject</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_QueryObject</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">OBJECT_TYPE_INFORMATION</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line">UNICODE_STRING TypeNames;</span><br><span class="line">ULONG TotalNumberOfHandles;</span><br><span class="line">ULONG TotalNumberOfObjects;</span><br><span class="line">}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">OBJECT_ALL_INFORMATION</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line">ULONG NumberOfObjectsTypes;</span><br><span class="line">OBJECT_TYPE_INFORMATION ObjectTypeInfo[<span class="number">1</span>];</span><br><span class="line">}OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;</span><br><span class="line"><span class="comment">//1.获取欲查询信息大小</span></span><br><span class="line">ULONG uSize = <span class="number">0</span>;</span><br><span class="line">NtQueryObject(<span class="literal">NULL</span>, (OBJECT_INFORMATION_CLASS)<span class="number">0x03</span>, &uSize, <span class="keyword">sizeof</span>(uSize), &uSize);</span><br><span class="line"><span class="comment">//2.获取对象大信息</span></span><br><span class="line">POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION) <span class="keyword">new</span> BYTE[uSize+<span class="number">4</span>];</span><br><span class="line">NtQueryObject(<span class="literal">NULL</span>, (OBJECT_INFORMATION_CLASS)<span class="number">0x03</span>, pObjectAllInfo, uSize, &uSize);</span><br><span class="line"><span class="comment">//3.循环遍历并处理对象信息</span></span><br><span class="line">POBJECT_TYPE_INFORMATION pObjectTypeInfo = pObjectAllInfo->ObjectTypeInfo;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < pObjectAllInfo->NumberOfObjectsTypes; i++)</span><br><span class="line">{</span><br><span class="line"><span class="comment">//3.1查看此对象的类型是否为DebugObject</span></span><br><span class="line"><span class="keyword">if</span> (!wcscmp(<span class="string">L"DebugObject"</span>, pObjectTypeInfo->TypeNames.Buffer))</span><br><span class="line">{</span><br><span class="line"><span class="keyword">delete</span>[] pObjectAllInfo;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//3.2获取对象名占用空间大小(考虑到了结构体对齐问题)</span></span><br><span class="line">ULONG uNameLength = pObjectTypeInfo->TypeNames.Length;</span><br><span class="line">ULONG uDataLength = uNameLength - uNameLength % <span class="keyword">sizeof</span>(ULONG) + <span class="keyword">sizeof</span>(ULONG);</span><br><span class="line"><span class="comment">//3.3指向下一个对象信息</span></span><br><span class="line">pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjectTypeInfo->TypeNames.Buffer;</span><br><span class="line">pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)((PBYTE)pObjectTypeInfo + uDataLength);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">delete</span>[] pObjectAllInfo;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>系统中某个调试器调试进程时,会创建一个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试</p><p>ntdll!NtQueryObject可获得系统各种内核对象信息</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">NTSTATUS NtQueryObject(</span><br><span class="line"> _In_opt_ HANDLE Handle,</span><br><span class="line"> _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,</span><br><span class="line"> _Out_opt_ PVOID ObjectInformation,</span><br><span class="line"> _In_ ULONG ObjectInformationLength,</span><br><span class="line"> _Out_opt_ PULONG ReturnLength</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>该API与上面讲过的API使用方法类似</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">typedef enum _OBJECT_INFORMATION_CLASS { </span><br><span class="line"> ObjectBasicInformation,</span><br><span class="line"> ObjectTypeInformation,</span><br><span class="line"> ObjectNameInformation,</span><br><span class="line"> ObjectAllInformation, //3</span><br><span class="line"> ObjectHandleInformation</span><br><span class="line">} OBJECT_INFORMATION_CLASS;</span><br></pre></td></tr></table></figure><p>ObjectAllInformation(0x3)</p><p>使用ObjectAllInformation可获得系统所有对象信息,然后从中检测是否存在调试对象</p><h1 id="NtQuerySystemInformation"><a href="#NtQuerySystemInformation" class="headerlink" title="NtQuerySystemInformation"></a>NtQuerySystemInformation</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">bool CheckDebug_KernelDebug() {</span><br><span class="line">struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION</span><br><span class="line">{</span><br><span class="line">BOOLEAN DebuggerEnabled;</span><br><span class="line">BOOLEAN DebuggerNotPresent;</span><br><span class="line">}DebuggerInfo = { 0 };</span><br><span class="line">NtQuerySystemInformation(</span><br><span class="line">(SYSTEM_INFORMATION_CLASS)0x23,//查询信息类型</span><br><span class="line">&DebuggerInfo,//输出查询信息</span><br><span class="line">sizeof(DebuggerInfo),//查询类型大小</span><br><span class="line">NULL);//实际返回数据大小</span><br><span class="line">return DebuggerInfo.DebuggerEnabled;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>NtQuerySystemInformation这个函数用来获取当前的运行信息</p><p>当调用NtQuerySystemInfromation()API的时候,第一个参数SystemInformationClass的值被设置为systemKernelDebuggerInfromation(0x32的时候,函数调用返回的时候,若系统处于调试状态下,DebuggerEnable的值设置为1</p><h1 id="ss寄存器"><a href="#ss寄存器" class="headerlink" title="ss寄存器"></a>ss寄存器</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">__asm</span><br><span class="line">{</span><br><span class="line"> push ss</span><br><span class="line"> pop ss</span><br><span class="line"> mov eax, 0xC000C1EE // This line will be traced over by debugger</span><br><span class="line"> xor edx, edx // Debugger will step to this line</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当使用ss堆栈段寄存器进行操作时,调试器将跳过指令跟踪。如下图所示,调试器将立即移至xor edx,edx指令,同时执行上一条指令</p><h1 id="代码运行时长"><a href="#代码运行时长" class="headerlink" title="代码运行时长"></a>代码运行时长</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_QueryPerformanceCounter</span><span class="params">()</span> </span>{</span><br><span class="line">LARGE_INTEGER startTime , endTime ;</span><br><span class="line">QueryPerformanceCounter(&startTime);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"我是核心代码\n也可以是核心代码前的反调试时间检测代码\n"</span>);</span><br><span class="line">QueryPerformanceCounter(&endTime);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%llx\n"</span>, endTime.QuadPart - startTime.QuadPart);</span><br><span class="line"><span class="keyword">return</span> endTime.QuadPart - startTime.QuadPart > <span class="number">0x500</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>代码运行时长检测不止这一种方法,像GetTickCount等函数都可以实现</p><h1 id="rdtsc指令"><a href="#rdtsc指令" class="headerlink" title="rdtsc指令"></a>rdtsc指令</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_RDTSC</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">int64_t</span> t1=<span class="number">0</span>, t2=<span class="number">0</span>;</span><br><span class="line"><span class="keyword">int</span> lo=<span class="number">0</span>, hi=<span class="number">0</span>;</span><br><span class="line">__asm {</span><br><span class="line">rdtsc</span><br><span class="line">mov [lo], eax</span><br><span class="line">mov [hi], edx</span><br><span class="line">}</span><br><span class="line">t1 = ((<span class="keyword">int64_t</span>)lo) | ((<span class="keyword">int64_t</span>)hi << <span class="number">32</span>);</span><br><span class="line">__asm{</span><br><span class="line">rdtsc</span><br><span class="line">mov[lo], eax</span><br><span class="line">mov[hi], edx</span><br><span class="line">}</span><br><span class="line">t2 = ((<span class="keyword">int64_t</span>)lo) | ((<span class="keyword">int64_t</span>)hi << <span class="number">32</span>);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"t2-t1=%llx\n"</span>, t2 - t1);</span><br><span class="line"><span class="comment">// 不同的CPU该差值不同,所以谨慎使用这种反调试方法</span></span><br><span class="line"><span class="keyword">return</span> t2 - t1 > <span class="number">0x100</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此方法与代码运行时长类似</p><h1 id="注册表查找调试器"><a href="#注册表查找调试器" class="headerlink" title="注册表查找调试器"></a>注册表查找调试器</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_Registry</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">// 判断当前系统是32还是64位</span></span><br><span class="line">BOOL b64 = FALSE;</span><br><span class="line">IsWow64Process(GetCurrentProcess(), &b64);</span><br><span class="line">HKEY hkey = <span class="literal">NULL</span>;</span><br><span class="line">TCHAR *reg = b64 ?</span><br><span class="line">TEXT(<span class="string">"SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\AeDebug"</span>)</span><br><span class="line">: TEXT(<span class="string">"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"</span>);</span><br><span class="line"><span class="comment">// 打开注册表键</span></span><br><span class="line">DWORD ret = RegCreateKey(HKEY_LOCAL_MACHINE, reg, &hkey);</span><br><span class="line"><span class="keyword">if</span> (ret != ERROR_SUCCESS) <span class="keyword">return</span> FALSE;</span><br><span class="line">TCHAR *subkey = TEXT(<span class="string">"Debugger"</span>);</span><br><span class="line">TCHAR value[<span class="number">256</span>] = {};</span><br><span class="line">DWORD len = <span class="number">256</span>;</span><br><span class="line"><span class="comment">// 查询对应项的值</span></span><br><span class="line">ret = RegQueryValueEx(hkey, subkey, <span class="literal">NULL</span>,<span class="literal">NULL</span>,(LPBYTE)value, &len);</span><br><span class="line">RegCloseKey(hkey);</span><br><span class="line"><span class="comment">// 这里只查找了OD,也可以同时查找WinDbg,x64Dbg等常用调试器</span></span><br><span class="line"><span class="keyword">if</span> (_tcsstr(value, TEXT(<span class="string">"OLLYDBG"</span>)) != <span class="literal">NULL</span>)</span><br><span class="line"><span class="keyword">return</span> TRUE;</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过注册表查询是否包含调试器</p><h1 id="rep指令"><a href="#rep指令" class="headerlink" title="rep指令"></a>rep指令</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_CheckRepCC</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">BOOL bDebugging = FALSE;</span><br><span class="line">__asm {</span><br><span class="line"><span class="keyword">xor</span> eax,eax</span><br><span class="line"><span class="keyword">xor</span> ecx,ecx</span><br><span class="line">inc ecx</span><br><span class="line">lea esi,key</span><br><span class="line"><span class="comment">// 此处步过时key处会被下0xCC断点</span></span><br><span class="line"><span class="comment">// 将key处的首字节给AL</span></span><br><span class="line">rep lodsb</span><br><span class="line">key:</span><br><span class="line">cmp al,<span class="number">0xcc</span></span><br><span class="line">je debuging</span><br><span class="line">jmp over</span><br><span class="line">debuging:</span><br><span class="line">mov bDebugging,<span class="number">1</span></span><br><span class="line">over :</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugging;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行到rep指令时,key处地址会被下断,从而与0xCC判断,是否处于调试状态</p><h1 id="权限判断"><a href="#权限判断" class="headerlink" title="权限判断"></a>权限判断</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_SeDebugPrivilege</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">// 获取CsrGetProcessId函数地址</span></span><br><span class="line">HMODULE hMod = GetModuleHandle(<span class="string">L"ntdll.dll"</span>);</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">int</span><span class="params">(*CSRGETPROCESSID)</span><span class="params">()</span></span>;</span><br><span class="line">CSRGETPROCESSID CsrGetProcessId = (CSRGETPROCESSID)GetProcAddress(hMod, <span class="string">"CsrGetProcessId"</span>);</span><br><span class="line"><span class="comment">// 获取csrss.exe的PID</span></span><br><span class="line">DWORD pid = CsrGetProcessId();</span><br><span class="line"><span class="comment">// 打开成功说明管理员+调试权限</span></span><br><span class="line">HANDLE hCsr = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);</span><br><span class="line"><span class="keyword">if</span> (!hCsr)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line">CloseHandle(hCsr);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function">BOOL <span class="title">CheckDebug_EnumProcess_Csrss</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">DWORD pid=<span class="number">0</span>;</span><br><span class="line">DWORD ret = <span class="number">0</span>;</span><br><span class="line">PROCESSENTRY32 pe32;</span><br><span class="line">pe32.dwSize = <span class="keyword">sizeof</span>(pe32);</span><br><span class="line">HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (hProcessSnap == INVALID_HANDLE_VALUE)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line">Process32First(hProcessSnap, &pe32);</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span> (wcscmp(pe32.szExeFile, <span class="string">L"csrss.exe"</span>) == <span class="number">0</span>)</span><br><span class="line">{</span><br><span class="line">pid = pe32.th32ProcessID;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">while</span> (Process32Next(hProcessSnap, &pe32));</span><br><span class="line">CloseHandle(hProcessSnap);</span><br><span class="line">HANDLE hCss = OpenProcess(PROCESS_QUERY_INFORMATION, <span class="literal">NULL</span>, pid);</span><br><span class="line"><span class="keyword">if</span> (!hCss)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br><span class="line">CloseHandle(hCss);</span><br><span class="line"><span class="keyword">return</span> FALSE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述两个函数的实现不同,但作用是相同的,通过当前权限来判断是否处于调试状态</p><h1 id="SEH异常1"><a href="#SEH异常1" class="headerlink" title="SEH异常1"></a>SEH异常1</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_SEH</span><span class="params">()</span> </span>{</span><br><span class="line">BOOL bDebugging = FALSE;</span><br><span class="line">__asm {</span><br><span class="line"><span class="comment">// install SEH</span></span><br><span class="line">push handler</span><br><span class="line">push DWORD ptr fs : [<span class="number">0</span>]</span><br><span class="line">mov DWORD ptr fs : [<span class="number">0</span>], esp</span><br><span class="line">__emit(<span class="number">0xcc</span>)</span><br><span class="line"><span class="comment">// 只检测有无调试器</span></span><br><span class="line"><span class="comment">// 若把mov bDebugging, 1改成__emit(0xE9)</span></span><br><span class="line"><span class="comment">// 在调试器中就会跑飞</span></span><br><span class="line">mov bDebugging, <span class="number">1</span></span><br><span class="line">jmp normal_code</span><br><span class="line">handler :</span><br><span class="line">mov eax, dword ptr ss : [esp + <span class="number">0xc</span>];<span class="comment">// ContextRecord</span></span><br><span class="line">mov dword ptr ds : [eax + <span class="number">0xb8</span>], offset normal_code</span><br><span class="line"><span class="keyword">xor</span> eax, eax</span><br><span class="line">retn</span><br><span class="line">normal_code :</span><br><span class="line"><span class="comment">// remove SEH</span></span><br><span class="line">pop dword ptr fs : [<span class="number">0</span>]</span><br><span class="line">add esp, <span class="number">4</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugging;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果不在调试器中,则会走到代码中设置的的seh handler,否则将会继续执行</p><h1 id="SEH异常2"><a href="#SEH异常2" class="headerlink" title="SEH异常2"></a>SEH异常2</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">LONG WINAPI <span class="title">Fun</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">_In_ struct _EXCEPTION_POINTERS *ExceptionInfo</span></span></span><br><span class="line"><span class="params"><span class="function">)</span> </span>{</span><br><span class="line"><span class="comment">// 跳过mov bDebug, 1这条指令</span></span><br><span class="line"><span class="comment">// int 3异常时,eip会被回拨到cc处,所以要+5</span></span><br><span class="line">ExceptionInfo->ContextRecord->Eip += <span class="number">5</span>;</span><br><span class="line"><span class="keyword">return</span> EXCEPTION_CONTINUE_EXECUTION;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_SetUnhandledExceptionFilter</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">bool</span> bDebug = <span class="literal">false</span>;</span><br><span class="line">__asm {</span><br><span class="line">__emit(<span class="number">0xCC</span>);</span><br><span class="line"><span class="comment">// 正常运行时,Fun函数会跳过这条指令</span></span><br><span class="line"><span class="comment">// 调试时,调试器会不停收到int 3异常,程序崩溃</span></span><br><span class="line">mov bDebug, <span class="number">1</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebug;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在调用CheckDebug_SetUnhandledExceptionFilter之前要先调用SetUnhandledExceptionFilter将Fun设置为SEH的handler,此方法与SEH异常处理1相似</p><h1 id="单步检测"><a href="#单步检测" class="headerlink" title="单步检测"></a>单步检测</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_SingleStep</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">bool</span> bDebugged = <span class="literal">false</span>;</span><br><span class="line">__asm {</span><br><span class="line"><span class="comment">// install SEH</span></span><br><span class="line">push handler</span><br><span class="line">push DWORD ptr fs : [<span class="number">0</span>]</span><br><span class="line">mov DWORD ptr fs : [<span class="number">0</span>], esp</span><br><span class="line">pushfd</span><br><span class="line"><span class="keyword">or</span> dword ptr ss : [esp], <span class="number">0x100</span></span><br><span class="line">popfd</span><br><span class="line"><span class="comment">// 被调试就继续执行</span></span><br><span class="line">nop</span><br><span class="line">mov bDebugged,<span class="number">1</span></span><br><span class="line">jmp normal_code</span><br><span class="line">handler :</span><br><span class="line">mov bDebugged, <span class="number">1</span></span><br><span class="line">mov eax, dword ptr ss : [esp + <span class="number">0xc</span>]</span><br><span class="line">mov ebx, normal_code</span><br><span class="line">mov dword ptr ds : [eax + <span class="number">0xb8</span>], ebx</span><br><span class="line"><span class="keyword">xor</span> eax, eax</span><br><span class="line">retn</span><br><span class="line">normal_code :</span><br><span class="line"><span class="comment">// remove SEH</span></span><br><span class="line">pop dword ptr fs : [<span class="number">0</span>]</span><br><span class="line">add esp, <span class="number">4</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> bDebugged;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>TF=1 的时候,会触发单步异常。该方法属于异常处理,不过比较特殊:未修改的 OD 无论<br>是 F9 还是 F8 都不能处理异常,有插件的 OD 在 F9 时能正确处理,F8 时不能正确处理。</p><h1 id="StartInfo检测"><a href="#StartInfo检测" class="headerlink" title="StartInfo检测"></a>StartInfo检测</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_STARTUPINFO</span><span class="params">()</span> </span>{</span><br><span class="line">STARTUPINFO si = {};</span><br><span class="line">GetStartupInfo(&si);</span><br><span class="line"><span class="keyword">if</span> (si.dwX || si.dwY || si.dwXSize || si.dwYSize)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%x %x %x %x\n"</span>, si.dwX, si.dwY, si.dwXSize, si.dwYSize);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="ZwSetInformationThread"><a href="#ZwSetInformationThread" class="headerlink" title="ZwSetInformationThread"></a>ZwSetInformationThread</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_SetInformationThread</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">THREAD_INFO_CLASS</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line">ThreadHideFromDebugger = <span class="number">17</span></span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">NTSTATUS</span><span class="params">(NTAPI *ZW_SET_INFORMATION_THREAD)</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">IN HANDLE ThreadHandle,</span></span></span><br><span class="line"><span class="params"><span class="function">IN THREAD_INFO_CLASS ThreadInformationClass,</span></span></span><br><span class="line"><span class="params"><span class="function">IN PVOID ThreadInformation,</span></span></span><br><span class="line"><span class="params"><span class="function">IN ULONG ThreadInformationLength)</span></span>;</span><br><span class="line">ZW_SET_INFORMATION_THREAD ZwSetInformationThread;</span><br><span class="line">ZwSetInformationThread = (ZW_SET_INFORMATION_THREAD)GetProcAddress(LoadLibrary(<span class="string">L"ntdll.dll"</span>), <span class="string">"ZwSetInformationThread"</span>);</span><br><span class="line">ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}<span class="function"><span class="keyword">bool</span> <span class="title">CheckDebug_STARTUPINFO</span><span class="params">()</span> </span>{</span><br><span class="line">STARTUPINFO si = {};</span><br><span class="line">GetStartupInfo(&si);</span><br><span class="line"><span class="keyword">if</span> (si.dwX || si.dwY || si.dwXSize || si.dwYSize)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%x %x %x %x\n"</span>, si.dwX, si.dwY, si.dwXSize, si.dwYSize);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>ZwSetInformationThread是一个未公开的函数,该API被调试者可将自身从调试器中分离出来(使调试进程终止运行,同时终止自身进程),该API不会对正常运行的程序(非调试运行)产生任何影响</p><h1 id="TLS反调试"><a href="#TLS反调试" class="headerlink" title="TLS反调试"></a>TLS反调试</h1><p>TLS相关知识:<a href="https://bbs.pediy.com/thread-249572.htm">https://bbs.pediy.com/thread-249572.htm</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdafx.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><Windows.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iomanip></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> comment(linker, <span class="meta-string">"/INCLUDE:__tls_used"</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">lookupprocess</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Debugger</span><span class="params">(<span class="keyword">void</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> NTAPI <span class="title">tls_callback</span><span class="params">(PVOID h, DWORD reason, PVOID pv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> lookupprocess();</span><br><span class="line"> Debugger();</span><br><span class="line"> MessageBox(<span class="literal">NULL</span>,<span class="string">"Not Main!"</span>,<span class="string">"Test1"</span>,MB_OK);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//创建TLS段</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> data_seg(<span class="meta-string">".CRT$XLT"</span>)</span></span><br><span class="line"><span class="comment">//定义回调函数</span></span><br><span class="line">PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> data_seg()</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义静态TLS全局变量</span></span><br><span class="line">__declspec(thread) <span class="keyword">int</span> value =<span class="number">0xcccccccc</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">DWORD WINAPI <span class="title">NewThread</span> <span class="params">( LPVOID lParam )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// 设置子线程value,并不影响其他线程</span></span><br><span class="line"> value = *((<span class="keyword">int</span>*)lParam);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(<span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>,value);</span><br><span class="line"> Sleep(<span class="number">1000</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span> ;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> THREAD_NUM 3</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> arry[<span class="number">3</span>]={<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>};</span><br><span class="line"> <span class="comment">// 设置主线程静态TLS的value为5</span></span><br><span class="line"> value = <span class="number">5</span> ;</span><br><span class="line"> <span class="comment">// 创建子线程</span></span><br><span class="line"> HANDLE hThread[THREAD_NUM];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> loop = <span class="number">0</span>; loop < THREAD_NUM; loop++)</span><br><span class="line"> {</span><br><span class="line"> hThread[loop] = CreateThread ( <span class="literal">NULL</span>, <span class="number">0</span>, NewThread, &arry[loop], <span class="number">0</span>, <span class="literal">NULL</span> );</span><br><span class="line"> Sleep(<span class="number">20000</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待直到子线程结束</span></span><br><span class="line"> WaitForMultipleObjects(THREAD_NUM, hThread, TRUE, INFINITE);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">lookupprocess</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> PROCESSENTRY32 pe32;</span><br><span class="line"> pe32.dwSize = <span class="keyword">sizeof</span>(pe32);</span><br><span class="line"> HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,<span class="number">0</span>);</span><br><span class="line"> BOOL bMore = ::Process32First(hProcessSnap,&pe32);</span><br><span class="line"> <span class="keyword">while</span>(bMore)</span><br><span class="line"> {</span><br><span class="line"> strlwr(pe32.szExeFile);</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(pe32.szExeFile,<span class="string">"ollyice.exe"</span>))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(pe32.szExeFile,<span class="string">"ollydbg.exe"</span>))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(pe32.szExeFile,<span class="string">"peid.exe"</span>))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(pe32.szExeFile,<span class="string">"idaq.exe"</span>))</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> bMore = ::Process32Next(hProcessSnap,&pe32);</span><br><span class="line"> }</span><br><span class="line"> ::CloseHandle(hProcessSnap);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//anti-debug2</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Debugger</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> result=<span class="number">0</span>;</span><br><span class="line"> __asm</span><br><span class="line"> {</span><br><span class="line"> mov eax, dword ptr fs:[<span class="number">30</span>h]<span class="comment">//TEB偏移30H处</span></span><br><span class="line"> movzx eax, byte ptr ds:[eax+<span class="number">2</span>h]<span class="comment">//取PEB中BeingDebug,若为1则被调试</span></span><br><span class="line"> mov result,eax</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (result)</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>网易xx游戏辅助的心路历程</title>
<link href="/2018/11/20/2018-11-19-%E7%BD%91%E6%98%93%E6%B8%B8%E6%88%8F%E8%BE%85%E5%8A%A9%E7%9A%84%E5%BF%83%E8%B7%AF%E5%8E%86%E7%A8%8B/"/>
<url>/2018/11/20/2018-11-19-%E7%BD%91%E6%98%93%E6%B8%B8%E6%88%8F%E8%BE%85%E5%8A%A9%E7%9A%84%E5%BF%83%E8%B7%AF%E5%8E%86%E7%A8%8B/</url>
<content type="html"><![CDATA[<p>喜欢上了网易的一款游戏,但是太肝了,开新区要连续肝6、7个小时,这太难受了,萌生了写辅助的想法,开始分析找到了几个Hook点,辅助这个东西难度不大,用dll注入的方式很快就写完了,随便贴个图</p><p><img src="http://imgset.gitee.io/img/1572183105475.png" alt="1572183105475"></p><p>刚刚写完,到了晚上游戏更新,dll注入后游戏就退出,网易的效率还真是高[微笑]</p><p>换成代码注入吧,换汤不换药,改完了之后,跑起来没问题了</p><p>emmm 一段时间过去了,,,,,,</p><p>猜的没错,游戏更新后有几个Hook点失效了,重新分析了下,就是偏移变了,可是后面在更新我还要改,顺道把收包和发包解决了下</p><p>有些发包数据没分析,因为太多了,Hook点也能用,发包分析就搁置了</p><p><img src="http://imgset.gitee.io/img/1572184653951.png" alt="1572184653951"></p><p>几天过后,来了一波大更新,发包分析和一些其他的Hook点失效了,收包还能用,这就够了?调试器不能下断点,猜测是某个线程进行代码校验,可我只是想写一款辅助啊</p><p>唉,python图片转换文字,模拟点击,是很low,可是长久啊</p><p><img src="http://imgset.gitee.io/img/1572184852450.png" alt="1572184852450"></p><p>用了几天的时间,搞定了之后,也没去玩,辅助给朋友用了,过了一阵子,朋友说辅助打开后游戏就关闭</p><p>我只是想写一款辅助啊,我影响你们盈利了?我因此受益了?</p><p>记得刚开始分析的时候,log信息会记录鼠标点击的位置和时间,想都不要想问题一定在Log信息这</p><p>些(这)许(是)生(仇)气(恨)</p><p>脱壳,干就完事了</p><p>脱壳后,断点虽然能下了,保险起见,把Log、SEH异常、dump模块统统干掉</p><p><img src="http://imgset.gitee.io/img/1572185119275.png" alt="1572185119275"></p><p>网易用的自己的游戏引擎,查不到过多的资料,分析起来或许不是那么顺畅</p><p>不过还有两个比较好的点,就是二进制文件中的调试信息没有删除,核心调度用python写的,pyc或者pyo是可以像java一样反编译的哦,更令人开心的是,部分python代码还添加了中文注释,爱了爱了</p><p>根据调试信息和x64dbg进行分析,很快就找到了部分资源加密的部分,来两张图吧,剩下的就是时间的问题了</p><p><img src="http://imgset.gitee.io/img/1572185458932.png" alt="1572185458932"></p><p><img src="http://imgset.gitee.io/img/1572185470971.png" alt="1572185470971"></p><p>这些是文件路径的解密</p><p>emmm,准备把它搞个底掉</p><p>就说到这吧,其实也没什么就是有点些(这)许(是)生(仇)气(恨)</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>Themida & WinLicense 2.0 - 2.4.6 脱壳</title>
<link href="/2018/11/16/2018-11-16-Themida%20&%20WinLicense%202.0%20-%202.4.6%20%E8%84%B1%E5%A3%B3/"/>
<url>/2018/11/16/2018-11-16-Themida%20&%20WinLicense%202.0%20-%202.4.6%20%E8%84%B1%E5%A3%B3/</url>
<content type="html"><![CDATA[<p>碰上了这个壳,具体文件就不说了</p><p>百度查了一圈找不到相关文章?难道要手脱?</p><p>濒临绝望之前,看到了国外某大佬的文章,链接:<a href="https://zenhax.com/viewtopic.php?f=4&t=1051Hello">https://zenhax.com/viewtopic.php?f=4&t=1051Hello</a></p><p>来看过程吧</p><p>PEID查壳</p><p><img src="http://imgset.gitee.io/img/1571830112209.png" alt="1571830112209"></p><p>脱壳需要的文件如下:</p><p>OD</p><p>插件:</p><p>ODBGScript v1.82.6<br>StrongOD 0.4.8.892<br>PhantOm 1.79<br>ARImpRec.dll</p><p>脱壳脚本(Themida - Winlicense Ultra Unpacker 1.4)</p><p>文件会在文末留下链接</p><p>在使用脱壳脚本之前,需要修改一下ARImpRec.dll的路径。</p><p>打开脚本文件搜索HERE_ENTER_YOUR_DLL_PATH_TO_ARIMPREC_DLL:</p><p><img src="http://imgset.gitee.io/img/1571830437147.png" alt="1571830437147"></p><p>在324行中修改为你存储此dll的绝对路径。</p><p>还有一点需要注意的就是,要使用英文版本的OD,下图中的OllyDBG.exe,然后备份OllyDBG.ini文件。</p><p>并创建一个新的OllyDBG.ini,使其文件内容为空</p><p><img src="http://imgset.gitee.io/img/1571830775825.png" alt="1571830775825"></p><p>打开OllDBG.exe,加载待脱壳文件,然后加载脚本</p><p><img src="http://imgset.gitee.io/img/1571831000405.png" alt="1571831000405"></p><p>运行脚本</p><p><img src="http://imgset.gitee.io/img/1571831031397.png" alt="1571831031397"></p><p>点击是</p><p><img src="http://imgset.gitee.io/img/1571831063517.png" alt="1571831063517"></p><p>点击否</p><p><img src="http://imgset.gitee.io/img/1571831111781.png" alt="1571831111781"></p><p>脚本已经开始工作了</p><p><img src="http://imgset.gitee.io/img/1571831144245.png" alt="1571831144245"></p><p>在运行几秒后,脚本暂停在了上图的位置,继续运行脚本即可</p><p><img src="http://imgset.gitee.io/img/1571831251717.png" alt="1571831251717"></p><p>随后我们获得了如下图的弹窗,需要在OllyDBG.ini文件中,根据提示修改文件,并重新运行脚本,我在文末的打包文件中已经修改好了</p><p><img src="http://imgset.gitee.io/img/1571831293135.png" alt="1571831293135"></p><p>关掉弹窗,继续运行</p><p><img src="http://imgset.gitee.io/img/1571831409264.png" alt="1571831409264"></p><p>点击是,进行更多的检查</p><p><img src="http://imgset.gitee.io/img/1571831485640.png" alt="1571831485640"></p><p>在这一步,点击否</p><p><img src="http://imgset.gitee.io/img/1571832082751.png" alt="1571832082751"></p><p>如果你在虚拟机中运行点击时,真机的话点击否就好了</p><p><img src="http://imgset.gitee.io/img/1571832196873.png" alt="1571832196873"></p><p>搞定了,现在可以进行dump了,点击是</p><p><img src="http://imgset.gitee.io/img/1571832266301.png" alt="1571832266301"></p><p>按照上图说的去做,第一次看到这个弹窗,点击否,不是第一次点击是</p><p><img src="http://imgset.gitee.io/img/1571832353478.png" alt="1571832353478"></p><p>文件大小可以接受,不需要压缩,点击是</p><p><img src="http://imgset.gitee.io/img/1571832437892.png" alt="1571832437892">、</p><p>一些文件信息,点击是</p><p>OK,脱壳结束,脱壳的文件被命名为<原文件名>_DP。</p><p>再来查一下壳</p><p><img src="http://imgset.gitee.io/img/1571832578604.png" alt="1571832578604"></p><p>搞定。</p><p>Themida & WinLicense 2.0 - 2.4.6 </p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>微信 消息结构分析</title>
<link href="/2018/11/16/2018-11-16-%E5%BE%AE%E4%BF%A1%E6%8E%A5%E5%8F%A3%E5%88%86%E6%9E%90/"/>
<url>/2018/11/16/2018-11-16-%E5%BE%AE%E4%BF%A1%E6%8E%A5%E5%8F%A3%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<p>微信接口分析</p><p>之前搞过一次微信结构分析,想做一次记录,微信最近更新了,那就重新分析下吧。</p><p>微信版本号:</p><p><img src="http://imgset.gitee.io/img/1571745519002.png" alt="1571745519002"></p><p>主要分析发送和接收消息。</p><p>用到的工具CE、x64dbg。</p><p>文中主要解决的点:</p><p>找到接收消息的关键点,实现可拦截,修改。</p><p>找到发送消息的关键点,实现可拦截,可外部调用。</p><p>先来分析接收消息吧,分析前先猜测下微信的消息处理流程。</p><p>我的过程查找如下:</p><ul><li>启动微信,并登录。</li><li>x64dbg附加进来(Alt + a)。</li><li>启动CE,并选择微信应用。</li></ul><p>做完上述内容后,用其他账号给当前账号发送消息,用CE搜索发送的消息内容(123)。</p><p>搜索结果如下:</p><p><img src="http://imgset.gitee.io/img/1571745340960.png" alt="1571745340960"></p><p>336条太多了。</p><p>再次发送消息(2563),进行过滤:</p><p>最终出现两条结果:</p><p><img src="http://imgset.gitee.io/img/1571745476665.png" alt="1571745476665"></p><p>经筛选最终选用05CA9185。</p><p>在x64dbg中下硬件写入断点。</p><p><img src="http://imgset.gitee.io/img/1571745953091.png" alt="1571745953091"></p><p>为什么下写入而不是读取?因为出现新消息会写入这个地址呀。</p><p>在向电脑登陆的微信发送一条消息,由于x64dbg对中文支持不是很友好,建议发送字母或数字(567891234)。</p><p>发送消息后,程序断下,此时我们来看看调用堆栈。</p><p><img src="http://imgset.gitee.io/img/1571746238446.png" alt="1571746238446"></p><p>函数调用还挺多的,当然这里的不一定全。</p><p>再来看看堆栈。</p><p><img src="http://imgset.gitee.io/img/1571746278324.png" alt="1571746278324"></p><p>一直向下拉,看看有没有什么可用的信息。</p><p><img src="http://imgset.gitee.io/img/1571746335556.png" alt="1571746335556"></p><p>看到上图中的我们发送的消息内容了吗?那么这附近的函数调用一定要着重观察。</p><p>各个call的分析我们就不说的,主要看call传递的参数信息是否有我们所需要的,最终找到的的call如下,地址:wechatwin:base + 0x2650F4,可能除了这个call还有其他call可以用,文中就用这个吧:</p><p><img src="http://imgset.gitee.io/img/1571746692912.png" alt="1571746692912"></p><p>这个函数只压入了一个参数,消息内容就在 [[esp]] 的位置:</p><p><img src="http://imgset.gitee.io/img/1571746763957.png" alt="1571746763957"></p><p>这里分别列出了微信ID和消息内容,这里的微信ID和我们在微信里看到的微信ID并不一样,具体我也不知道为什么。</p><p>这两个字符串后面跟随的4个字节就是字符串的长度,因此如修改内容需要将字符串长度一并修改。</p><p>这里实际上是一个结构体,包含的信息不止这两个内容,具体分析看文末。</p><p>接收消息搞定了,来看看发送消息吧,并且要进行。</p><p>在电脑端的微信中向文件传输助手中发送消息,发送结束后,在CE中搜索filehelper,这个就是文件传输助手的微信ID。</p><p><img src="http://imgset.gitee.io/img/1571747328061.png" alt="1571747328061"></p><p>搜索出23条结果。</p><p>在向其他账号发送消息,观察CE变动的的数据。</p><p><img src="http://imgset.gitee.io/img/1571747393910.png" alt="1571747393910"></p><p>变动的不多,经分析可以看出05CA814D保存的就是刚刚发送的消息的接收者的ID。</p><p>老规矩,对这个地址进行硬件写入断点:</p><p><img src="http://imgset.gitee.io/img/1571747709507.png" alt="1571747709507"></p><p>再次向文件传输助手发送一条消息,断点断下。</p><p>向上文一样,找call,主要看传递的参数,再次不在赘述了。</p><p>最终找到了两个call,两个call描述如下:</p><ul><li>第一个call,传递了this指针,和其他参数,数量未知,看到this指针就把它pass了</li><li>第二个call,只压入了一个参数,并且未用到寄存器传参数。</li></ul><p>果断用第二个call,为什么?,难道会有人喜欢分析 类的结构吗?</p><p>call的地址:wechatwin:base + 0x4805FC</p><p>在此call的地址下断</p><p>再次发送消息,程序在这个call中断下。</p><p>来看看我们选中的call吧:</p><p><img src="http://imgset.gitee.io/img/1571748204692.png" alt="1571748204692"></p><p>这个call传递的重要参数在[[esp] + c]中:</p><p><img src="http://imgset.gitee.io/img/1571748871478.png" alt="1571748871478"></p><p>这里的参数并不是所有的都有用,有些是上层函数需要用到的。</p><p>我们自己调用这个call的话,就需要对内容进行分析,可以先将一些参数置0,然后运行,把无用的参数过滤掉,再来分析有用的参数分别代表了什么。</p><p>这个call和我上次分析的低版本用到的非常相似,我上次针对这两个接口写了个辅助工具,上次分析忘记参数位置了,好像和这次的差不多,改改还能用。</p><p>链接:<a href="https://pan.baidu.com/s/1NKz_IZqqQLy5OkUzWlEG7A">https://pan.baidu.com/s/1NKz_IZqqQLy5OkUzWlEG7A</a><br>提取码:j0t4 </p><p>具体功能如下:</p><ul><li>接收消息拦截,消息类型分析,发送和接收分离。</li><li>发送消息</li></ul><p><strong>辅助编写步骤如下,未接触过win32编程的可以看下:</strong></p><p>可以使用dll注入技术,我没有使用dll注入。</p><p>技术栈:远程代码写入(WriteProcessMemory),进程间共享内存,远程函数调用(CreateRemoteProcess)。</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>微信 - 撤回分析 & patch</title>
<link href="/2018/11/16/2018-11-16-%E5%BE%AE%E4%BF%A1%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E%E5%88%86%E6%9E%90/"/>
<url>/2018/11/16/2018-11-16-%E5%BE%AE%E4%BF%A1%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<p>1</p><p>emmm 进入正题吧</p><p>在微信撤回的时候,会提示xxxx撤回了一条消息,类似下图这样,就以它为入手点吧</p><p><img src="http://imgset.gitee.io/img/1571916194546.png" alt="1571916194546"></p><p>打开CE搜索这个字符串,</p><p><img src="http://imgset.gitee.io/img/1571916287471.png" alt="1571916287471"></p><p>9个结果,筛选后找到了一个可用的</p><p>然后栈回溯分析了几个可疑断点</p><p>后来,,,,,,无果</p><p>但是在分析的过程中,发现了一个字符串revoke,在wechatwin.dll中</p><p>对这个模块进行字符串扫描吧:</p><p><img src="http://imgset.gitee.io/img/1571916502370.png" alt="1571916502370"></p><p><img src="http://imgset.gitee.io/img/1571916764067.png" alt="1571916764067"></p><p>和我们猜测的一样,只在撤回的时候断下</p><p>此地址附近的代码:</p><p><img src="http://imgset.gitee.io/img/1571917012715.png" alt="1571917012715"></p><p>猜测:撤回消息和接收消息应该在前几个函数调用是相同的,会在其中一个函数中堆栈开始变化,因为执行的函数不同,这样撤回断下的堆栈和消息接受的堆栈一对比,就可以找到关键点了(不知怎么解决接收消息请看《微信 - 消息结构分析与调用》)</p><p>实践?不浪费时间了,失败了</p><p>怎么办?去刚刚找到的字符串使用的位置下断点,进行栈回溯,老老实实的一步一步分析。</p><p>经分析,上图中标注为撤回点所在的函数,在撤回消息和接收消息中都调用了,这就很明朗了,问题就在这,简单的浏览一下代码,发现在字符串使用的位置上方有一个跳转</p><p><img src="http://imgset.gitee.io/img/1571917246718.png" alt="1571917246718"></p><p>跳转的位置刚刚好跳出了撤回消息调用的函数,这个跳转的判断源于一个函数的返回值,先不去看,直接patch掉,把jz修改为jmp</p><p>打开微信测试下,已经没什么问题了,撤回消息的微信正常显示,而电脑端并未显示撤回消息也没有消失</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>打补丁?怎么少得了dll注入</title>
<link href="/2018/11/16/2018-11-17-DLL%E6%B3%A8%E5%85%A5/"/>
<url>/2018/11/16/2018-11-17-DLL%E6%B3%A8%E5%85%A5/</url>
<content type="html"><![CDATA[<p>dll注入的方法还是挺多的,dll替换,注册表等等,来说说比较常用的一种吧</p><p>这个方法不仅适用dll注入,inline Hook同样适用</p><p>既然是DLL注入,就需要写一个DLL,这个DLL的代码会在被注入的进程中运行,我的代码如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">BOOL APIENTRY <span class="title">DllMain</span><span class="params">( HMODULE hModule,</span></span></span><br><span class="line"><span class="params"><span class="function"> DWORD ul_reason_for_call,</span></span></span><br><span class="line"><span class="params"><span class="function"> LPVOID lpReserved</span></span></span><br><span class="line"><span class="params"><span class="function"> )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in"><span class="keyword">switch</span></span> (ul_reason_for_call)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> DLL_PROCESS_ATTACH:</span><br><span class="line">{</span><br><span class="line"><span class="built_in">MessageBoxA</span>(<span class="literal">NULL</span>, <span class="string">"I am dll."</span>, <span class="string">"I am dll"</span>, <span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"> <span class="keyword">case</span> DLL_THREAD_ATTACH:</span><br><span class="line"> <span class="keyword">case</span> DLL_THREAD_DETACH:</span><br><span class="line"> <span class="keyword">case</span> DLL_PROCESS_DETACH:</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> TRUE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当DLL被加载时,会进行弹窗,仅此而已</p><p>在开始之前需要先了解一下,关于窗口和进程的知识</p><ul><li><p>每个进程都有一个标识,这个我们都知道,特点是每次启动的值都不一样</p></li><li><p>每个窗口也会有一个标识,和进程一样每次启动的值都不一样</p></li></ul><p>那么所谓的DLL注入,就是在目标进程上开辟一个线程,并调用LoadLibrary</p><p>想在别的进程上创建一个线程,就需要这个进程的句柄</p><p>获取这个句柄就需要它的PID</p><p>而PID每次启动的值都不一样,那么每次注入都要看一眼PID?有些麻烦</p><p>这个时候窗口标识就需要用到了,可以通过窗口句柄获取到进程的PID</p><p>而获取窗口句柄需要窗口的标题和类名,这两个内容,可以通过spy++来找到</p><p><img src="http://imgset.gitee.io/img/1572008641838.png" alt="1572008641838"></p><p>如上图,只需要拖动<img src="http://imgset.gitee.io/img/1572008662314.png" alt="1572008662314">到目标窗口上去就好了</p><p>来看代码把,把窗口句柄转换为进程ID:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>当然你也可以使用遍历所有窗口的方式进行筛选,实现的方式可不止这一个</p><p>获取到窗口句柄,就可以创建远程线程了</p><p>在创建线程的时候,需要一个函数地址,和一个函数参数</p><p>只需要一个参数?有没有想到LoadLibrary函数,这个函数只需要一个参数</p><p>也就是说我们完全可以使用LoadLibrary的地址来作为线程地址</p><p>那么LoadLibrary函数还需要一个参数,既然是在别的进程上创建线程,那么参数的内容也要在目标进程中,我们要在目标进程上开辟内存空间并写入参数值:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在目标进程分配内存,内存大小为参数的大小</span></span><br><span class="line">VOID *pRemoteBuf = <span class="built_in">VirtualAllocEx</span>(hProcess, <span class="literal">NULL</span>, dwBufSize, MEM_COMMIT, PAGE_READWRITE);</span><br><span class="line"><span class="comment">//将参数值写入到目标进程中</span></span><br><span class="line"><span class="built_in">WriteProcessMemory</span>(hProcess, pRemoteBuf, (LPVOID)pFilePath, dwBufSize, <span class="literal">NULL</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>参数写进去了,还需要LoadLibrary的地址,就可以创建远程线程了:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>函数的地址有了,参数的地址有了,可以创建远程线程了:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>来看看效果:</p><p><img src="http://imgset.gitee.io/img/1572009493193.png" alt="1572009493193"></p><p><img src="http://imgset.gitee.io/img/1572009373345.png" alt="1572009373345"></p><p>注入成功</p>]]></content>
<categories>
<category> Develop </category>
</categories>
</entry>
<entry>
<title>多重继承?抽象类?C++的内存布局并不复杂</title>
<link href="/2018/11/12/2018-11-13-%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%EF%BC%8C%E6%8A%BD%E8%B1%A1%EF%BC%8C%E7%BB%88%E7%BB%93%E7%AF%87/"/>
<url>/2018/11/12/2018-11-13-%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%EF%BC%8C%E6%8A%BD%E8%B1%A1%EF%BC%8C%E7%BB%88%E7%BB%93%E7%AF%87/</url>
<content type="html"><![CDATA[<h1 id="多重继承"><a href="#多重继承" class="headerlink" title="多重继承"></a>多重继承</h1><p>先来看看多重继承吧</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cFa</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cFa</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cFa</span>() { };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cMo</span>{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cMo</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cMo</span>() { };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cChild</span> :</span> <span class="keyword">public</span> cFa, <span class="keyword">public</span> cMo {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cChild</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cChild</span>() { };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">cChild cCh;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>先来看构造函数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">001917DD pop ecx </span><br><span class="line">001917FC mov ecx,dword ptr [this] ;取出this指针</span><br><span class="line">001917FF call cFa::cFa (01913B1h) ;调用cFa构造函数</span><br><span class="line">00191804 mov dword ptr [ebp-4],0 ;异常计数 可以忽略</span><br><span class="line">0019180B mov ecx,dword ptr [this] ;获取this指针</span><br><span class="line">0019180E add ecx,4 ;this指针偏移</span><br><span class="line">00191811 call cMo::cMo (0191302h) ;调用cMo的构造函数 </span><br><span class="line">00191816 mov eax,dword ptr [this] ;获取this指针</span><br><span class="line">00191819 mov dword ptr [eax],offset cChild::`vftable' (0198B4Ch) ;设置虚函数表</span><br><span class="line">0019181F mov eax,dword ptr [this] ;获取this指针</span><br><span class="line">00191822 mov dword ptr [eax+4],offset cChild::`vftable' (0198B58h) ;设置第二个虚函数表</span><br><span class="line">00191829 mov dword ptr [ebp-4],0FFFFFFFFh </span><br><span class="line">00191830 mov eax,dword ptr [this] ;返回this指针</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>在上面的代码,我们看出构造函数的调用顺序是根据,从左到右的继承顺序来依次调用父类函数的</p><p>在调用cFa的构造函数时,直接传递了this指针,也就是cCh的地址,而在调用cMo的时候,传递的是this指针加4个字节的地址,也就是跳过了cFa所占的空间。</p><p>那么也就是说,父类对象在子类的内存布局的顺序和构造函数的调用顺序是一样的,那么现在cCh对象的内存结构如下图:</p><p><img src="http://imgset.gitee.io/img/1571226517696.png" alt="1571226517696"></p><p>来看看这两个父类的构造函数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line">CFa:</span><br><span class="line">......</span><br><span class="line">001918AD mov eax,dword ptr [this] </span><br><span class="line">001918B0 mov dword ptr [eax],offset cFa::`vftable' (0198B34h) </span><br><span class="line">001918B6 mov eax,dword ptr [this] </span><br><span class="line">......</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">cMo:</span><br><span class="line">......</span><br><span class="line">0019190D mov eax,dword ptr [this] </span><br><span class="line">00191910 mov dword ptr [eax],offset cMo::`vftable' (0198B40h) </span><br><span class="line">00191916 mov eax,dword ptr [this] </span><br><span class="line">......</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>过滤掉了一些无用的代码,构造函数和非多重继承无任何区别,只是将传进来的对象的虚表换成自己的虚表。</p><p> <strong>有营养的部分来了:</strong></p><p>两个构造函数结束后,出现了两个虚表赋值,为什么是两个虚表?</p><p>因为有两个父类,当调用父类函数的时候,需要通过偏移取得父类对象,传递指针,并调用函数。</p><p>这两个虚表在本例中意义不大,因为子类没有覆盖父类的虚函数,如果有覆盖的情况出现,这两个虚表中会保存子类覆盖的虚函数,和父类未覆盖的虚函数。那么调用就很简单了,虚表偏移。</p><p>析构函数就不上代码了,和构造函数的顺序恰巧相反。</p><p>如果在main函数中添加一行如下代码会发生什么?</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">cMo *pMo = (cMo*)&cCh; </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>编译器会找到cCh的地址,根据cCh父类中的cMo对象的偏移位置,获取到cCh父类中的cMo的地址,并返回赋值给pMo。</p><h1 id="抽象类"><a href="#抽象类" class="headerlink" title="抽象类"></a>抽象类</h1><p>上代码</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CAbstractBase</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CAbstractChild</span> :</span> <span class="keyword">public</span> CAbstractBase{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Show</span><span class="params">()</span> </span>{ <span class="built_in">printf</span>(<span class="string">"show"</span>); };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">CAbstractChild cAb;</span><br><span class="line">cAb.<span class="built_in">Show</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不说废话,直接进入子类的构造函数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">;CAbstractChild 构造函数</span><br><span class="line">......</span><br><span class="line">00A1198F pop ecx </span><br><span class="line">00A11990 mov dword ptr [this],ecx </span><br><span class="line">00A11993 mov ecx,dword ptr [this] </span><br><span class="line">00A11996 call CAbstractBase::CAbstractBase (0A11474h) </span><br><span class="line">00A1199B mov eax,dword ptr [this] ;获取this指针</span><br><span class="line">00A1199E mov dword ptr [eax],offset CAbstractChild::`vftable' (0A18B40h) ;虚函数表初始化</span><br><span class="line">00A119A4 mov eax,dword ptr [this] ;返回this指针</span><br><span class="line">......</span><br><span class="line"></span><br><span class="line">;CAbstractBase 构造函数</span><br><span class="line">......</span><br><span class="line">00A118BF pop ecx </span><br><span class="line">00A118C0 mov dword ptr [this],ecx </span><br><span class="line">00A118C3 mov eax,dword ptr [this] ;获取this指针</span><br><span class="line">00A118C6 mov dword ptr [eax],offset CAbstractBase::`vftable' (0A18B34h) ;虚函数表初始化</span><br><span class="line">00A118CC mov eax,dword ptr [this] ;返回this指针</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>看起来似乎和普通的继承没什么不同?</p><p>我们来看看CAbstractBase中的虚函数表(0A18B34h)指向的Show函数吧。</p><p><img src="http://imgset.gitee.io/img/1571230318091.png" alt="1571230318091"></p><p>它的调用约定是__purecall (IDA会识别出来) ,这个函数实际上并不是CAbstractBase的Show函数,而是编译器生成一个函数,这个函数的主要作用就是调用 _amsg_exit函数来结束程序,并返回错误码 0x19。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这里包含上篇文章的总结《我们聊聊继承吧,从继承的角度出发再来聊聊多态》</p><p><strong>单类继承:</strong></p><ul><li>在类对象占用的内存空间,只保留一份虚表指针,也就只有一个虚表</li><li>虚表中各项保存了类中各虚函数的首地址</li><li>构造函数先构造父类,在构造自身</li><li>析构函数先析构自身,在析构父类</li></ul><p><strong>多重继承:</strong></p><ul><li> 在类对象所占用的内存空间中,根据继承父类的个数保存对应的虚表指针</li><li>根据所保存的虚表指针的个数,对应产生相应个数的虚表</li><li>转换父类指针时,需要调整到对象的首地址</li><li>构造时需要调用多个父类构造函数</li><li>构造时先构造继承列表中第一个父类,然后依次调用到最后一个继承的父类构造函数。</li><li>析构与构造顺序相反</li><li>当对象作为成员时,整个类对象的内存结构和多重继承很相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可按实际情况进行还原,当父类或成员对象存在虚函数,通过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数量及目标地址,来还原继承或成员关系。</li></ul><p><img src="http://imgset.gitee.io/img/1571159873270.png" alt="1571159873270"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>从继承的角度出发再探多态</title>
<link href="/2018/11/12/2018-11-12-%E4%BB%8E%E7%BB%A7%E6%89%BF%E7%9A%84%E8%A7%92%E5%BA%A6%E5%86%8D%E6%9D%A5%E8%81%8A%E8%81%8A%E5%A4%9A%E6%80%81%E5%90%A7/"/>
<url>/2018/11/12/2018-11-12-%E4%BB%8E%E7%BB%A7%E6%89%BF%E7%9A%84%E8%A7%92%E5%BA%A6%E5%86%8D%E6%9D%A5%E8%81%8A%E8%81%8A%E5%A4%9A%E6%80%81%E5%90%A7/</url>
<content type="html"><![CDATA[<p>我们先通过一段代码来理解继承的底层实现。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CBase</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">CBase</span>() {};</span><br><span class="line">~<span class="built_in">CBase</span>() {};</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">SetNumber</span><span class="params">(<span class="keyword">int</span> nNum)</span> </span>{ nNumber = nNum; };</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">int</span> nNumber;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CChild</span> :</span> CBase {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ShowNumber</span><span class="params">(<span class="keyword">int</span> nNum)</span> </span>{</span><br><span class="line"><span class="built_in">SetNumber</span>(nNum);</span><br><span class="line">nNumberChild = nNum + <span class="number">1</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, nNumber);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="keyword">int</span> nNumberChild;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">CChild cChild;</span><br><span class="line">cChild.<span class="built_in">ShowNumber</span>(<span class="number">23</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的代码中子类虽然没有写构造函数和析构函数,但是编译器还是自动生成了它们,子类构造函数、析构函数和父类的构造函数、析构函数调用顺序如下:</p><p>父类构造函数 -> 子类构造函数 -> 子类析构函数 -> 父类析构函数</p><p>我们关注的重点并不在这里,而是子类对象和父类对象的关系。</p><p>走进ShowNumber函数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">0099198D mov eax,dword ptr [ebp+8] ;参数nNum</span><br><span class="line">00991990 push eax ;参数压栈</span><br><span class="line">00991991 mov ecx,dword ptr [ebp-8] ;获取this指针</span><br><span class="line">00991994 call 0099102D ;调用父类的SetNumber</span><br><span class="line">}</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>在子类调用父类函数时,直接传递了子类的this指针,我们走进这个SetNumber :</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">0099192D mov eax,dword ptr [ebp-8] ;获取this指针</span><br><span class="line">00991930 mov ecx,dword ptr [ebp+8] ;取出nNum的值</span><br><span class="line">00991933 mov dword ptr [eax],ecx ;将nNum赋值到this指针的前4个字节也就是代码中的nNumber变量</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>执行结束回到ShowNumber继续执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">00991994 call 0099102D ;调用父类的SetNumber</span><br><span class="line"></span><br><span class="line">00991999 mov eax,dword ptr [ebp+8] ;取出参数nNum</span><br><span class="line">0099199C add eax,1 ;临时nNum + 1</span><br><span class="line">0099199F mov ecx,dword ptr [ebp-8] ;获取this指针</span><br><span class="line">009919A2 mov dword ptr [ecx+4],eax ;赋值到nNumberChild</span><br><span class="line"></span><br><span class="line">009919A5 mov eax,dword ptr [ebp-8] ;取出this指针</span><br><span class="line">009919A8 mov ecx,dword ptr [eax] ;取出nNumber</span><br><span class="line">009919AA push ecx ;压栈nNumber</span><br><span class="line">009919AB push 998B30h ;压栈字符串</span><br><span class="line">009919B0 call 00991050 ;调用printf</span><br><span class="line">009919B5 add esp,8 </span><br></pre></td></tr></table></figure><p>由此我们看出父类的nNumber赋值到this的前4个字节,而子类的nNumberChild赋值到this的第四个字节开始的后面四个字节。</p><p>那么此时this的内存结构如下图:</p><p><img src="http://imgset.gitee.io/img/1571140201271.png" alt="1571140201271"></p><p>由此,我们可以总结出,父类对象在子类对象开始处,那么将上例中的CChild的类修改为下面的样子,则他们的内存结构时完全一样的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CChild</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ShowNumber</span><span class="params">(<span class="keyword">int</span> nNum)</span> </span>{</span><br><span class="line"><span class="built_in">SetNumber</span>(nNum);</span><br><span class="line">nNumberChild = nNum + <span class="number">1</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, nNumber);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> CBase cBase;</span><br><span class="line"><span class="keyword">int</span> nNumberChild;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这种内存结构的优势是什么?</p><p>很明显,子类对象调用父类的函数,直接传递子类的对象地址就可以了,那么子类对象指针可以强制转换为父类对象指针来使用,反之则不行。</p><p><strong>——————->分割线</strong></p><p>再来聊聊多态,上代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cBase</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cBase</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cBase</span>() {};</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Print</span><span class="params">()</span> </span>{ <span class="built_in">printf</span>(<span class="string">"I am cBase\n"</span>); };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cChild0</span> :</span> cBase{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cChild0</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cChild0</span>() {};</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Print</span><span class="params">()</span> </span>{ <span class="built_in">printf</span>(<span class="string">"I am cChild0\n"</span>); };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cChild1</span> :</span> cBase{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">cChild1</span>() {};</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">cChild1</span>() {};</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">Print</span><span class="params">()</span> </span>{ <span class="built_in">printf</span>(<span class="string">"I am cChild1\n"</span>); };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">GoPrint</span><span class="params">(cBase* pBase)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">pBase-><span class="built_in">Print</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">cChild0 cCCHild0;</span><br><span class="line">cChild1 cCCHild1;</span><br><span class="line"></span><br><span class="line"><span class="built_in">GoPrint</span>((cBase*)&cCCHild0);</span><br><span class="line"><span class="built_in">GoPrint</span>((cBase*)&cCCHild1);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>先来看看输出:</p><p><img src="http://imgset.gitee.io/img/1571141767552.png" alt="1571141767552"></p><p>是不是意料之中的结果?</p><p>来看看内部实现吧,先从cChild0的构造函数开始吧:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">00D7183F pop ecx </span><br><span class="line">00D71840 mov dword ptr [this],ecx </span><br><span class="line">00D7184D mov ecx,dword ptr [this] ;以上为this指针操作</span><br><span class="line">00D71850 call cBase::cBase (0D713EDh) ;调用父类构造函数</span><br><span class="line">00D71855 mov eax,dword ptr [this] ;取出this指针</span><br><span class="line">00D71858 mov dword ptr [eax],offset cChild0::`vftable' (0D78B54h) ;虚表赋值 </span><br><span class="line">00D7185E mov eax,dword ptr [this] ;返回this指针</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>首先调用了父类的构造函数,然后赋值虚表为本类(cChild0)的虚表。</p><p>走进cBase的构造函数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">00D717DF pop ecx </span><br><span class="line">00D717E0 mov dword ptr [this],ecx </span><br><span class="line">00D717ED mov eax,dword ptr [this] ;以上为this指针操作</span><br><span class="line">00D717F0 mov dword ptr [eax],offset cBase::`vftable' (0D78B34h) ;初始化虚表</span><br><span class="line">00D717F6 mov eax,dword ptr [this] ;返回this指针</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>在构造函数中只做一件事,就是赋值虚表为本类(cBase)的虚表。</p><p>总结下,在cChild0的构造函数中做了以下的事情:</p><p>调用父类构造函数 -> 在父类的构造函数中设置虚表为本类(cBase)的虚表 -> 设置虚表为本类的(cChild0)虚表 </p><p><strong>需要注意的是,在上文中设置两次虚表都是cChild0 this指针的前四个字节。</strong></p><p>在cChild1中做了同样的事情,就不再次赘述了。</p><p>那么现在已经很清晰了,这两个子类对象在构造函数调用之后会将虚表都设成自己的虚表。</p><p>现在我们来看看GoPrint函数吧:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">00D725E8 mov eax,dword ptr [pBase] ;取出参数,传递进来的对象</span><br><span class="line">00D725EB mov edx,dword ptr [eax] ;取出虚表</span><br><span class="line">00D725ED mov esi,esp </span><br><span class="line">00D725EF mov ecx,dword ptr [pBase] ;设置this指针</span><br><span class="line">00D725F2 mov eax,dword ptr [edx+4] ;根据虚表偏移取出虚函数</span><br><span class="line">00D725F5 call eax ;调用虚函数</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>GoPrint函数就很清晰了,直接取出虚表根据偏移调用虚函数,也就理解了程序上面的输出。</p><p>现在我们在说说在《我们来聊聊C++多态吧,理解它,并找到它》中我们没有说到的内容,为什么在虚构函数中,要对多态表重新赋值。</p><p>在上例中,析构函数的执行顺序如下:</p><p>子类析构函数 -> 父类析构函数 </p><p>那么问题出现了,假设在这两个析构函数中同时调用虚函数,如果在析构函数中没有对虚函数表重新赋值,那么在父类的析构函数中就会调用子类的析构函数,而这个时候子类也许有一些资源已经释放了,那么问题就已经很清晰了,内存泄漏!</p><p><img src="http://imgset.gitee.io/img/1571159873270.png" alt="1571159873270"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>10分钟破解一款收银软件</title>
<link href="/2018/11/12/2018-11-14-10%E5%88%86%E9%92%9F%E7%A0%B4%E8%A7%A3%E6%94%B6%E9%93%B6%E8%BD%AF%E4%BB%B6/"/>
<url>/2018/11/12/2018-11-14-10%E5%88%86%E9%92%9F%E7%A0%B4%E8%A7%A3%E6%94%B6%E9%93%B6%E8%BD%AF%E4%BB%B6/</url>
<content type="html"><![CDATA[<p>一个朋友在理发店工作,需要一款收银软件,这种东西还用想?百度一大堆,我去百度Down了两个,在云沙箱上跑,都有恶意代码。</p><p>后来就有了这篇文章。</p><p>我在xxxx官网下载了一款试用版的,有限制,进入正文。</p><p>软件是这个样子的</p><p><img src="http://imgset.gitee.io/img/1571567963572.png" alt="1571567963572"></p><p>我简单运行了一下,添加会员会有人数限制,一会再来说这些吧。</p><p>PEID查壳:</p><p><img src="http://imgset.gitee.io/img/1571568122093.png" alt="1571568122093"></p><p>.Net ?? 记得dnspy这款神器吗? 没了解过没关系,2分钟后你就会见识到它的强大。</p><p> IntelliLock v.1.5.x.0 直接用d4dot来搞定,你可能不认识这款工具百度down下来就好了。</p><p>下载好之后,打开cmd,并进入这款软件的目录,d4dot的使用非常简单:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>我们采用第二种方法,d4dot -r “目录” 输出如下:</p><p><img src="http://imgset.gitee.io/img/1571568768381.png" alt="1571568768381"></p><p>这款软件会将解密的文件,做一个备份,名字就是源文件名后面加一个clean,类似这样:</p><p><img src="http://imgset.gitee.io/img/1571568831859.png" alt="1571568831859"></p><p>如果文件不需要解密,将不会产生这个文件。</p><p>现在我们把解密后的文件,和无需解密的文件复制出来,放在一个新的文件夹中,并将解密的文件名中多余的-cleaned删除掉,就把这个文件夹命名为Unpacked01吧。</p><p>现在我们针对Unpacked01文件夹,再次执行一遍上面的步骤,生成Unpacked02文件夹。</p><p>脱壳结束了?对就是这么easy。</p><p>轮到神器dnspy登场了,使用dnspy软件打开Unpacked02文件夹中的主程序文件,也就是INTMS.exe,如下图。</p><p><img src="http://imgset.gitee.io/img/1571569395715.png" alt="1571569395715"></p><p>C#大佬可以关闭这个页面了。</p><p>这是什么?源码,没错,还支持源码调试呢。</p><p>还记得文首说的软件限制吗,先来看看限制的提示吧。</p><p><img src="http://imgset.gitee.io/img/1571569764482.png" alt="1571569764482"></p><p>关键点怎么找?直接搜索添加会员Button的名字就行了,源码摆在那,随你怎么玩。</p><p><img src="http://imgset.gitee.io/img/1571569880657.png" alt="1571569880657"></p><p>上图就是关键点,代码我已经修改过了,SoftType支持两个参数,一个是加密狗模式,一个是测试模式,运行的时候根据这个参数来进行不同的限制,如果不在二者其一,就会跳过限制代码。</p><p>只要将SoftType改为不在这两个模式中就可以了,我就随便改了个数字。</p><p>现在再去添加会员就看不到温馨提示了。</p><p>搞定。</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>C++基于SEH二次封装的异常处理 - 之数据结构篇</title>
<link href="/2018/11/12/2018-11-14-C++%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%9A%84%E4%BA%8C%E6%AC%A1%E5%B0%81%E8%A3%85/"/>
<url>/2018/11/12/2018-11-14-C++%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%9A%84%E4%BA%8C%E6%AC%A1%E5%B0%81%E8%A3%85/</url>
<content type="html"><![CDATA[<p><img src="http://imgset.gitee.io/img/1571397583862.png" alt="1571397583862"></p><p>本文将围绕上图来介绍C++异常的数据结构。</p><p>在C++中如果函数中包含异常处理,将会在此函数中的开始部分注册一个异常回调函数,当函数中有异常抛出的时候,便会调用这个回调函数,也就是在SEH中注册一个函数(异常回调函数)。</p><p>这个异常回调函数指向的地址的汇编码通常是这样的:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">00D963A4 mov eax,0D9A064h </span><br><span class="line">00D963A9 jmp ___CxxFrameHandler3 (0D910FFh) </span><br></pre></td></tr></table></figure><p>很明显代码中的eax的值保存了关键信息。</p><p>这个0D9A064h所指向的地址就是上图中的FuncInfo结构体:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">FuncInfo</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> DWORD magicNumber; <span class="comment">//编译器生成的固定数字</span></span><br><span class="line"> DWORD maxState; <span class="comment">//最大栈展开数的下标值</span></span><br><span class="line"> DWORD pUnwindMap; <span class="comment">//指向栈展开函数表的指针,指向UnwindMapEntry表结构</span></span><br><span class="line"> DWORD dwTryCount; <span class="comment">//try块的数量</span></span><br><span class="line"> DWORD pTryBlockMap; <span class="comment">//try块列表,指向TryBlockMapEntry结构体</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这个struct包含了两个struct,分别为UnwindMapEntry和TryBlockMapEntry。</p><p>先来看看UnwindMapEntry,UnwindMapEntry表配合maxState项来使用。</p><p>maxState记录了异常发生时try块展开的次数,展开时执行的函数由UnwindMapEntry表结构记录,结构体信息如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>在try块展开的过程中,可能存在多个对象,每个对象的析构信息会以数组的形式记录。</p><p>toState用来判断结构是否位于数组中,lpFuncAction保存析构函数所在的地址。</p><p>TryBlockMapEntry结构如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">TryBlockMapEntry</span>{</span></span><br><span class="line"> DWORD tryLow ;<span class="comment">//try块的最小状态索引,用于范围检查</span></span><br><span class="line"> DWORD tryHigh ;<span class="comment">//try块的最大状态索引,用于范围检查</span></span><br><span class="line"> DWORD catchHigh ;<span class="comment">//catch块的最高状态索引,用域范围检查</span></span><br><span class="line"> DWORD dwCatchCount; <span class="comment">//catch块个数</span></span><br><span class="line"> DWORD pCatchHandlerArray ; <span class="comment">//catch块的描述,指向_msRttiDscr表结构</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个struct用于判断异常产生在哪个try块中。t</p><p>ryLow和tryHigh用于检查长生的异常是否来源于try块中。</p><p>catchHigh用于匹配catch块时的检查项。</p><p>每个catch块都会对应一个_msRttiDscr表结构,保存在pCatchHandlerArray指向的地址中(数组方式存放)。</p><p>再来看看_msRttiDscr吧</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> _<span class="title">msRttiDscr</span> </span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> DWORD nFlag ;<span class="comment">//用域catch块的匹配检查</span></span><br><span class="line"> DWORD pType ;<span class="comment">//catch块要捕捉的类型,指向TypeDescriptor表结构</span></span><br><span class="line"> DWORD dispCatchObjOffset; <span class="comment">//同于定位异常对象在当前EBP中的偏移位置</span></span><br><span class="line"> DWORD CatchProc; <span class="comment">//catch块的首地址</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>nFlag同来检查catch块匹配的类型,含义值如下:</p><ul><li>1:常量</li><li>2:变量</li><li>4:未知</li><li>8:引用</li></ul><p>此结构中的pType和CatchProc为关键数据,当抛出异常对象时,需要赋值抛出的异常对象信息,dispCatchObjOffset用于定位异常对象在当前EBP中的偏移位置。</p><p>CatchProc项中保存了异常处理catch块的首地址,这样在匹配异常后,就可以正确的执行catch语句块,异常的匹配信息记录在pType所指向的结构中,结构信息如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>当异常发生时,就可以通过以上信息于抛出异常时的信息进行对比,得到对应表的结构,最后通过_msRttiDscr表中的CatchProc项得到catch块的首地址。从而走到正确的catch块中。</p><p>现在我们再来说说throw吧,抛出异常时的代码通常如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">00D918BC push offset __TI1H (0D9A094h) </span><br><span class="line">00D918C1 lea eax,[ebp-0F0h] </span><br><span class="line">00D918C7 push eax </span><br><span class="line">00D918C8 call __CxxThrowException@8 (0D913A7h) </span><br></pre></td></tr></table></figure><p>在抛出一场函数时传递了一个全局参数__TI1H。</p><p>这个地址中指向就是抛出异常时需要的结构信息ThrowInfo:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>上述结构体包含了类型信息,用于匹配抛出的异常类型。</p><p>nFlag = 1= 常量类型异常</p><p>nFlag = 2 = 变量类型异常</p><p>由于在try块中发生异常后不会再反汇try块中,pDestructor的作用就是记录try块中的异常对象的析构函数地址,在异常处理完成后调用。</p><p>抛出异常所对应的catch块的类型信息就被记录在pCatchTableTypeArray所指向的结构中。</p><p>结构体CatchTableTypeArray如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>ppCatchTableType指向指针数组,里面保存了CatchTableType的地址列表。</p><p>dwCount来描述数组中有多少个元素。</p><p>来看看CatchTableType里面有什么:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>还记得上文中的TypeDescriptor结构吗,在异常处理的时候,可以与上述结构中的pTypeInfo进行对比,并找到正确的catch块。</p><p>flag标记用于判断异常对象属于那种类型,类如,指针、引用等。</p><p>标记值含义如下:</p><ul><li>0x1:简单类型复制</li><li>0x2:已被捕获</li><li>0x4:有虚表基类复制</li><li>0x8:指针和引用类型复制</li></ul><p>如果异常类型是对象,那么就会把他们的结构信息存储下来,存储在thisDisPlacement中:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>以上就是C++异常处理所用到的所有的数据结构,建议读者阅读结束后,再看下文首中的图片加深记忆。</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>C++基于SEH二次封装的异常流程与识别</title>
<link href="/2018/11/12/2018-11-14-C++%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%9A%84%E6%B5%81%E7%A8%8B%E4%B8%8E%E8%AF%86%E5%88%AB/"/>
<url>/2018/11/12/2018-11-14-C++%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%9A%84%E6%B5%81%E7%A8%8B%E4%B8%8E%E8%AF%86%E5%88%AB/</url>
<content type="html"><![CDATA[<p>在茫茫的汇编中,怎么来识别try结构呢?</p><p>在看代码之前我们先连简单的看下try的处理流程吧</p><ul><li>函数入口设置回调函数</li><li>函数的异常抛出使用了__CxxThrowException函数,此函数包含了两个参数,分别是抛出一场关键字的throw的参数的指针,另一个抛出信息类型的指针(ThrowInfo *)。</li><li>在异常回调函数中,可以得到异常对象的地址和对应ThrowInfo数据的地址以及FunInfo表结构的地址。根据记录的异常类型,进行try块的匹配工作</li><li>没找到try块怎么办?先调用异常对象的析构函数,然后反汇ExcetionContinueSearch,继续反回到SEH继续执行。</li><li>找到了try块?通过TryBlockMapEntry结构中的pCatch指向catch信息,用ThrowInfo结构中的异常类型遍历查找相匹配的catch块,比较关键字名称,找到有效的catch块。</li><li>然后进行栈展开。</li><li>析构try块中的对象</li><li>跳转到catch块中执行</li><li>调用_JumpToContinuation函数,返回catch语句块的结束地址。</li></ul><p>上面的步骤,就是典型的异常处理的顺序。</p><p>光看文字多无趣,上代码 - 实例分析,我们来跑一遍:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CExcepctionBase</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">CExcepctionBase</span>()</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"CExcepctionBase() \r\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">CExcepctionBase</span>()</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"~CExcepctionBase()\r\n"</span>);</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CExcepctionDiv0</span> :</span> <span class="keyword">public</span> CExcepctionBase</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">CExcepctionDiv0</span>()</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"CExcepctionDiv0()\r\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">CExcepctionDiv0</span>(){</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"~CExcepctionDiv0()\r\n"</span>);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取错误码</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">char</span> * <span class="title">GetErrorInfo</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">return</span> <span class="string">"CExcepctionDiv0"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">int</span> m_nErrorId ;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CExcepctionAccess</span> :</span> <span class="keyword">public</span> CExcepctionBase</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="built_in">CExcepctionAccess</span>()</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"CExcepctionAccess()\r\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">CExcepctionAccess</span>(){</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"~CExcepctionAccess()\r\n"</span>);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取错误码</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">char</span> * <span class="title">GetErrorInfo</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">return</span> <span class="string">"CExcepctionAccess"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">TestException</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">1</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> n;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">2</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> <span class="number">3.0f</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">3</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> <span class="string">'3'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">4</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> <span class="number">3.0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">5</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> <span class="built_in">CExcepctionDiv0</span>();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">6</span>)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">throw</span> <span class="built_in">CExcepctionAccess</span>();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(n == <span class="number">7</span>)</span><br><span class="line">{</span><br><span class="line">CExcepctionBase cExceptBase;</span><br><span class="line"><span class="keyword">throw</span> cExceptBase;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(<span class="keyword">int</span> n)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch int \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(<span class="keyword">float</span> f)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch float \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(<span class="keyword">char</span> c)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch char \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(<span class="keyword">double</span> d)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch double \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(CExcepctionBase cBase)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch CExcepctionBase \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(CExcepctionAccess cAccess)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch int \n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in"><span class="keyword">catch</span></span>(...)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"catch ... \n"</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">8</span>; i++)</span><br><span class="line">{</span><br><span class="line"><span class="built_in">TestException</span>(i);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>先来看看函数开始的代码吧:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">.text:004011A5 push offset SEH_4011A0</span><br><span class="line">.text:004011AA mov eax, large fs:0</span><br><span class="line">.text:004011B0 push eax</span><br><span class="line">.text:004011B1 sub esp, 40h</span><br><span class="line">.text:004011B4 push ebx</span><br><span class="line">.text:004011B5 push esi</span><br><span class="line">.text:004011B6 push edi</span><br><span class="line">.text:004011B7 mov eax, ___security_cookie</span><br><span class="line">.text:004011BC xor eax, ebp</span><br><span class="line">.text:004011BE push eax</span><br><span class="line">.text:004011BF lea eax, [ebp+var_C]</span><br><span class="line">.text:004011C2 mov large fs:0, eax</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>函数开始将异常回调函数压栈,在上文结尾的部分将此函数加入SEH中,这里并不讲解SEH相关信息,除了设置异常回调函数,和参数压栈还设置了security_cookie,防止栈溢出的检查数据,在此同样不予讲述。</p><p>我们走进SEH_4011A0看下实现:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">.text:0040CAB1 mov eax, offset stru_40F53C</span><br><span class="line">.text:0040CAB6 jmp ___CxxFrameHandler3</span><br></pre></td></tr></table></figure><p>无疑此项就是编译器产生的异常回调函数。</p><p>继续看异常抛出的部分:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">.text:004011CE mov [ebp+var_4], 0</span><br><span class="line">.text:004011D5 cmp eax, 1</span><br><span class="line">.text:004011D8 jnz short loc_4011EB</span><br><span class="line">.text:004011DA mov [ebp+var_18], eax</span><br><span class="line">.text:004011DD push offset __TI1H ;ThrowInfo</span><br><span class="line">.text:004011E2 lea eax, [ebp+var_18];获取参数</span><br><span class="line">.text:004011E5 push eax;压栈参数</span><br><span class="line">.text:004011E6 call __CxxThrowException@8 ; _CxxThrowException(x,x)</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>熟悉的__CxxThrowException?没错他就是用来抛出异常的函数。</p><p>这里的__TI1H就是ThrowInfo结构,那么var_18也就是throw关键字后面跟随的数据。</p><p>后面连续的几个throw语句也差不多。</p><p>直到抛出对象的时候,代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">......</span><br><span class="line">.text:0040123A loc_40123A: ; CODE XREF: sub_4011A0+81↑j</span><br><span class="line">.text:0040123A cmp eax, 5</span><br><span class="line">.text:0040123D jnz short loc_401255</span><br><span class="line">.text:0040123F lea ecx, [ebp+var_34]</span><br><span class="line">.text:00401242 call sub_401030</span><br><span class="line">.text:00401247 push offset __TI2?AVCExcepctionDiv0@@ ;</span><br><span class="line">.text:0040124C lea ecx, [ebp+var_34]</span><br><span class="line">.text:0040124F push ecx</span><br><span class="line">.text:00401250 call __CxxThrowException@8 ; _CxxThrowException(x,x)</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>这里很在抛出异常之前调用了一个函数sub_401030,这个函数的作用就是设置var_34的值,后面与前面的基本相同。</p><p>代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">.text:00401048 mov dword ptr [esi], offset ??_7CExcepctionDiv0@@6B@ ; const CExcepctionDiv0::`vftable'</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>IDA友情提示,这是一个虚表。</p><p>这两个函数代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">.text:004010A0 ; int __thiscall sub_4010A0(void *, char)</span><br><span class="line">.text:004010A0 sub_4010A0 proc near ; DATA XREF: .rdata:const CExcepctionDiv0::`vftable'↓o</span><br><span class="line">.text:004010A0</span><br><span class="line">.text:004010A0 arg_0 = byte ptr 8</span><br><span class="line">.text:004010A0</span><br><span class="line">.text:004010A0 push ebp</span><br><span class="line">.text:004010A1 mov ebp, esp</span><br><span class="line">.text:004010A3 push esi</span><br><span class="line">.text:004010A4 mov esi, ecx</span><br><span class="line">.text:004010A6 push offset aCexcepctiondiv ; "~CExcepctionDiv0()\r\n"</span><br><span class="line">.text:004010AB mov dword ptr [esi], offset ??_7CExcepctionDiv0@@6B@ ; const CExcepctionDiv0::`vftable'</span><br><span class="line">.text:004010B1 call _printf</span><br><span class="line">.text:004010B6 push offset aCexcepctionbas ; "~CExcepctionBase()\r\n"</span><br><span class="line">.text:004010BB mov dword ptr [esi], offset ??_7CExcepctionBase@@6B@ ; const CExcepctionBase::`vftable'</span><br><span class="line">.text:004010C1 call _printf</span><br><span class="line">.text:004010C6 add esp, 8</span><br><span class="line">.text:004010C9 test [ebp+arg_0], 1</span><br><span class="line">.text:004010CD jz short loc_4010D8</span><br><span class="line">.text:004010CF push esi ; void *</span><br><span class="line">.text:004010D0 call ??3@YAXPAX@Z ; operator delete(void *)</span><br><span class="line">.text:004010D5 add esp, 4</span><br><span class="line">.text:004010D8</span><br><span class="line">.text:004010D8 loc_4010D8: ; CODE XREF: sub_4010A0+2D↑j</span><br><span class="line">.text:004010D8 mov eax, esi</span><br><span class="line">.text:004010DA pop esi</span><br><span class="line">.text:004010DB pop ebp</span><br><span class="line">.text:004010DC retn 4</span><br><span class="line">.text:004010DC sub_4010A0 endp</span><br></pre></td></tr></table></figure><p>在004010C9地址处做了一个判断,根据传入参数来决定是否释放空间(标准的虚析构函数),因为IDA载入了pdb文件,所以通过IDA的注释可以很清晰的理解这个函数是CExcepctionDiv0的析构函数。</p><p>另一个函数代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">.text:00401090</span><br><span class="line">.text:00401090 sub_401090 proc near ; DATA XREF: .rdata:0040D180↓o</span><br><span class="line">.text:00401090 mov eax, offset aCexcepctiondiv_1 ; "CExcepctionDiv0"</span><br><span class="line">.text:00401095 retn</span><br><span class="line">.text:00401095 sub_401090 endp</span><br></pre></td></tr></table></figure><p>这个函数就很简单了直接返回字符串“CExcepctionDiv0”。</p><p>在以上的代码来看识别throw语句并不困难,只要找到__CxxThrowException函数就可以找到throw语句了,并根据throw传递的参数,可以断定抛出的数据类型。</p><p>来看看catch吧:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">.text:00401295 loc_401295: ; DATA XREF: .rdata:0040F570↓o</span><br><span class="line">.text:00401295 ; catch(float) // owned by 4011CE</span><br><span class="line">.text:00401295 push offset aCatchFloat ; "catch float \n"</span><br><span class="line">.text:0040129A call _printf</span><br><span class="line">.text:0040129F add esp, 4</span><br><span class="line">.text:004012A2 mov eax, offset loc_4012A8</span><br><span class="line">.text:004012A7 retn</span><br><span class="line">.text:004012A7 ; } // starts at 4011CE</span><br><span class="line">.text:004012A7 ; } // starts at 4011A0</span><br></pre></td></tr></table></figure><p>同样IDA通过pdb文件为我们做出了友好的注释,但是所有的catch语句都会具有以下特点:</p><ul><li>没有平衡函数开始的堆栈</li><li>返回时将eax赋值为一个地址</li></ul><p>通过这两个特点来找到catch语句块是不是很轻松呢,毕竟不平衡堆栈就返回的情况可以说是极少数了吧。</p><p>其他的catch我们就不看了,代码都是类似的,那么赋值给eax的地址里面保存了何方神圣?</p><p>来看一看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">.text:004012A8 loc_4012A8: ; CODE XREF: sub_4011A0+107↑j</span><br><span class="line">.text:004012A8 ; DATA XREF: sub_4011A0+102↑o</span><br><span class="line">.text:004012A8 mov ecx, [ebp+var_C]</span><br><span class="line">.text:004012AB mov large fs:0, ecx</span><br><span class="line">.text:004012B2 pop ecx</span><br><span class="line">.text:004012B3 pop edi</span><br><span class="line">.text:004012B4 pop esi</span><br><span class="line">.text:004012B5 pop ebx</span><br><span class="line">.text:004012B6 mov esp, ebp</span><br><span class="line">.text:004012B8 pop ebp</span><br><span class="line">.text:004012B9 retn</span><br></pre></td></tr></table></figure><p>这样看起来是不是合理多了,没错这个地址的代码就是用来恢复函数开始压入到堆栈的数据(平衡堆栈)。</p><p>我们也可以通过以下的规则来找出catch语句块:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CATCH0_BEGIN: //IDA中的地址标号</span><br><span class="line">.... //CATCH实现代码</span><br><span class="line">mov eax, CATCH_END ; 函数平衡堆栈的代码</span><br><span class="line">retn</span><br><span class="line"></span><br><span class="line">PS:如果同一个函数包含多个catch语句块,那么后面他们一定时挨着的。</span><br></pre></td></tr></table></figure><p>避免篇幅庞大,将不在列出后续catch代码。</p><p><strong>结构体一揽?从ThrowInfo开始看起吧:</strong></p><p>还记得上文中提过的__TI1H吗,这是IDA为我们生成的名字,他就是我们要找的ThrowInfo,双击进去看看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__TI1H ThrowInfo <0, 0, 0, 40F5D0h></span><br></pre></td></tr></table></figure><p>这个结构体是我自己创建的,为了方便观察。</p><p>根据ThrowInfo的定义(具体请看我的上一篇文章),第四个参数也就是40F5D0h便是CatchTableTypeArray。</p><p>代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">.rdata:0040F5D0 __CTA1H dd 1 ; count of catchable type addresses following</span><br><span class="line">.rdata:0040F5D4 dd offset __CT??_R0H@8 ; catchable type 'int'</span><br></pre></td></tr></table></figure><p>这个结构体的第二项是pTypeInfo,指向异常类型结构TypeDescriptor,双击进去看看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.rdata:0040F5D8 __CT??_R0H@8 dd CT_IsSimpleType ; DATA XREF: .rdata:0040F5D4↑o</span><br><span class="line">.rdata:0040F5D8 ; attributes</span><br><span class="line">.rdata:0040F5DC dd offset ??_R0H@8 ; int `RTTI Type Descriptor'</span><br></pre></td></tr></table></figure><p>上面代码的第二个dd是识别错误,它实际上是.H代表的是int类型,IDA为ThrowInfo命名的最后一个字母对应的就是这个类型,当然除了.H还有其他字母例如:</p><ul><li>.M = float</li><li>.D = char</li><li>.N = double</li><li>……</li></ul><p><strong>从catch块入手,得到catch语句的信息</strong>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">.text:00401295 loc_401295: ; DATA XREF: .rdata:0040F570↓o</span><br><span class="line">.text:00401295 push offset aCatchFloat ; "catch float \n"</span><br><span class="line">.text:0040129A call _printf</span><br><span class="line">.text:0040129F add esp, 4</span><br><span class="line">.text:004012A2 mov eax, offset loc_4012A8</span><br><span class="line">.text:004012A7 retn</span><br></pre></td></tr></table></figure><p>在loc_401295的右侧我们看到IDA给我们标出来的注释,这个注释代表此地址的引用位置,双击进去看看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.rdata:0040F570 HandlerType <0, offset ??_R0M@8, -60, offset loc_401295> ; float `RTTI Type Descriptor'</span><br></pre></td></tr></table></figure><p>这个HandlerType实际就是_msRttiDscr,根据结构定义,最后一项就是CatchProc,也就是catch语句块起始处的地址。</p><p>实际上在0040F570附近定义了此函数中所有的catch块,可以通过这一个_msRttiDscr找到此函数中所有_msRttiDscr的信息,也就可以找到所有的catch语句块了。</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>局部静态变量只能初始化一次实现原理</title>
<link href="/2018/11/12/2018-11-14-%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F/"/>
<url>/2018/11/12/2018-11-14-%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F/</url>
<content type="html"><![CDATA[<p>静态变量可以分为全局静态变量,和局部静态变量,先来说说全局的吧</p><p>全局静态变量和全局变量的区别并不大,只是全局静态变量只能在当前文件中使用,而在反汇编中二者并无区别,只可以在当前文件中使用,不过是编译器做出的限制。</p><p>局部静态变量,会有些特殊,它不会随着作用域结束而消失,在未进入作用于之前就已经存在。</p><p>局部静态变量和全局变量都保存在二进制文件的数据区,而在代码中的限制,不过是编译器限制而已。</p><p>那么当某个函数频繁调用局部静态变量时,C++的语法规定局部静态变量只能初始化一次,那么编译器是怎么做到的呢。</p><p>来看代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ShowStatic</span><span class="params">(<span class="keyword">int</span> nNum)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span> gnNumber = nNum;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, gnNumber);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>汇编代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">00E51738 mov eax,dword ptr ds:[00E5A148h] </span><br><span class="line">00E5173D and eax,1 </span><br><span class="line">00E51740 jne ShowStatic+47h (0E51757h) </span><br><span class="line">00E51742 mov eax,dword ptr ds:[00E5A148h] </span><br><span class="line">00E51747 or eax,1 </span><br><span class="line">00E5174A mov dword ptr ds:[00E5A148h],eax </span><br><span class="line">00E5174F mov eax,dword ptr [nNum] </span><br><span class="line">00E51752 mov dword ptr [gnNumber (0E5A144h)],eax </span><br></pre></td></tr></table></figure><p>可以看出,静态变量的赋值比普通变量赋值多了很多步骤,我们来分析下。</p><p>首先在地址00E5A148h中保存了局部静态变量的标志,这个标志占1个字节。通过位运算,将标志中的一位数据置1,来判断局部静态变量是否初始化过。而这个标志可以同时保存8个局部静态变量的初始状态。</p><p>通常这个标志出现在最先定义的局部静态变量的附近,例如此例局部变量应出现在 00E5A144h 或 00E5A14Ch中。当同一个作用域内超过了8个静态局部变量,下一个标记将会除了现在第9个定义的局部静态变量地址的附近。</p><p>现在再来看上面的汇编代码就很清晰了:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">00E51738 mov eax,dword ptr ds:[00E5A148h] </span><br><span class="line">00E5173D and eax,1 </span><br><span class="line">00E51740 jne ShowStatic+47h (0E51757h) </span><br></pre></td></tr></table></figure><p>判断是否已经初始化,如果已经初始化就跳转到printf输出内容,否则不跳转继续执行。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">00E51742 mov eax,dword ptr ds:[00E5A148h] </span><br><span class="line">00E51747 or eax,1 </span><br><span class="line">00E5174A mov dword ptr ds:[00E5A148h],eax </span><br><span class="line">00E5174F mov eax,dword ptr [nNum] </span><br><span class="line">00E51752 mov dword ptr [gnNumber (0E5A144h)],eax </span><br></pre></td></tr></table></figure><p>未初始化的情况,将标志位置位为1,并初始化gnNumber。</p><p>结束了?并没有</p><p>还有这样一个问题,编译器让其他作用域对局部静态变量不可见,这是怎么做到的?</p><p>在编译的过程中,编译器会对变量,函数等进行名称粉碎,也就是静态变量被重新命名了。</p><p>读者可将上面的代码编译链接,然后找到编译期结束后生成的obj文件,在这个文件中搜索静态变量的名字(本文用HxD软件打开obj文件),搜索结果如下图:</p><p><img src="http://imgset.gitee.io/img/1571316371117.png" alt="1571316371117"></p><p>名称粉碎后,在原有名称中加加入了一些额外信息,入作用域,类型等。</p><p>像C++重载也是名称粉碎的原理。</p><p>下面的汇编是在C++11中编译的结果,显然和上文的有些差距:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">static int gnNumber = nNum;</span><br><span class="line">00C11818 mov eax,dword ptr [_tls_index (0C1B190h)] </span><br><span class="line">00C1181D mov ecx,dword ptr fs:[2Ch] </span><br><span class="line">00C11824 mov edx,dword ptr [ecx+eax*4] </span><br><span class="line">00C11827 mov eax,dword ptr ds:[00C1B150h] </span><br><span class="line">00C1182C cmp eax,dword ptr [edx+104h] </span><br><span class="line">00C11832 jle ShowStatic+6Fh (0C1185Fh) </span><br><span class="line">00C11834 push 0C1B150h </span><br><span class="line">00C11839 call __Init_thread_header (0C110DCh) </span><br><span class="line">00C1183E add esp,4 </span><br><span class="line">00C11841 cmp dword ptr ds:[0C1B150h],0FFFFFFFFh </span><br><span class="line">00C11848 jne ShowStatic+6Fh (0C1185Fh) </span><br><span class="line">00C1184A mov eax,dword ptr [nNum] </span><br><span class="line">00C1184D mov dword ptr [gnNumber (0C1B14Ch)],eax </span><br><span class="line">00C11852 push 0C1B150h </span><br><span class="line">00C11857 call __Init_thread_footer (0C11177h) </span><br><span class="line">00C1185C add esp,4 </span><br></pre></td></tr></table></figure><p>前三行代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">00C11818 mov eax,dword ptr [_tls_index (0C1B190h)] </span><br><span class="line">00C1181D mov ecx,dword ptr fs:[2Ch] </span><br><span class="line">00C11824 mov edx,dword ptr [ecx+eax*4] </span><br></pre></td></tr></table></figure><p>TLS?怎么还多了两个函数?<code>__Init_thread_header</code>和<code>_Init_thread_footer</code> </p><p>这两个函数是用来保证局部的静态对象的初始化线程安全。</p><p>但局部变量的互斥还是老样子,只不过被封装进上述的两个函数之中了。</p><p>有兴趣的读者可以自己上机调试一番。</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>C++ 多态的实现</title>
<link href="/2018/11/11/2018-11-11-%E6%88%91%E4%BB%AC%E6%9D%A5%E8%81%8A%E8%81%8AC++%E5%A4%9A%E6%80%81%E5%90%A7%EF%BC%8C%E7%90%86%E8%A7%A3%E5%AE%83%EF%BC%8C%E5%B9%B6%E6%89%BE%E5%88%B0%E5%AE%83/"/>
<url>/2018/11/11/2018-11-11-%E6%88%91%E4%BB%AC%E6%9D%A5%E8%81%8A%E8%81%8AC++%E5%A4%9A%E6%80%81%E5%90%A7%EF%BC%8C%E7%90%86%E8%A7%A3%E5%AE%83%EF%BC%8C%E5%B9%B6%E6%89%BE%E5%88%B0%E5%AE%83/</url>
<content type="html"><![CDATA[<p>我们通过一段代码,先来了解多态的底层实现 </p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">cVirtual</span> </span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">func0</span><span class="params">()</span> </span>{}; </span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">func1</span><span class="params">()</span> </span>{};</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{ </span><br><span class="line"> cVirtual cv; </span><br><span class="line"> cv.<span class="built_in">func0</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>非常简单的类,如果这里没有多态函数的话这个类对象实际只占1个字节(占位字节),有了多态函数后类对象里会保存一张多态函数的地址表,那么这个对象就会占4个字节。</p><p>来看反汇编。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cVirtual cv;</span><br><span class="line">005719B3 lea ecx,[ebp-0Ch];分配对象占用内存</span><br><span class="line">005719B6 call 005713C5;调用构造函数</span><br></pre></td></tr></table></figure><p> </p><p>CALL指令调用的就是编译器为这个类生成的构造函数,这么简单的类也要有构造函数??,猜的没错,就是用来初始化多态表的,走进去看。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">...... </span><br><span class="line">0057179F pop ecx ;恢复this指针</span><br><span class="line">005717A0 mov dword ptr [ebp-8],ecx ;保存this指针</span><br><span class="line">005717A3 mov eax,dword ptr [ebp-8] ;取出this指针</span><br><span class="line">005717A6 mov dword ptr [eax],577BF8h ;多态表初始化</span><br><span class="line">005717AC mov eax,dword ptr [ebp-8] ;返回this指针</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>我把多余的代码删掉了,构造函数只做了这一件事情,就是把多态表的指针赋值到对象地址中,也就是文中的this指针。</p><p>由上面的代码可以看出,此类对象的内存应该如下图:</p><p><img src="http://imgset.gitee.io/img/1571234058141.png" alt="1571234058141"></p><p>我们看下 0x577BF8 中保存的内容:</p><p><img src="http://imgset.gitee.io/img/1571234067497.png" alt="1571234067497"></p><p>整理一下是这样的,0x005713C0,0x005713BB,0x0000000。</p><p>那么这两个有效地址就是我们代码中的两个多态函数,我们来验证下。</p><p><img src="http://imgset.gitee.io/img/640.png" alt="img"></p><p>so,我们可以总结出,<strong>对象的虚表指针在以对象为基址的前4个字节中,虚表指针指向的是一个地址表,地址表中的每一个地址对应这个类中的每一个虚函数。</strong></p><p>在上例中,析构函数中做了和构造函数一模一样的事情,因为在构造函数中已经对虚表赋值了,在析构函数中是不是有点多此一举?并不是,析构函数中重新赋值是防止读取的虚表不是自己的虚表,读者可以从继承的角度出发,来思考这个问题。</p><p>搞不懂去看我写的继承的文章吧,可能还没更。</p><p><strong>重点来了:</strong></p><ul><li><strong>虚表信息是在编译后会被链接到二进制文件中,so 虚表是一个固定地址。</strong></li><li><strong>虚表中的虚函数地址排序顺序依据虚函数在类中的声明顺序而定。</strong></li><li><strong>当虚函数被访问时,会根据对象的首地址,取出虚表地址,在取出虚表元素,需要多次寻址才能完成。</strong></li><li><strong>通过间接寻址访问虚表,只发生在使用对象的指针或者引用调用虚函数的时候才会发生,当使用对象调用虚函数,不需要查表访问。(调用自身的函数,未构成多态,查虚表只会降低效率)</strong></li></ul><p><strong>——————————->找到它</strong></p><p>在茫茫汇编代码中找到多态需要关注以下几点:</p><ul><li>类中隐式定义了一个数据成员</li><li>该数据成员在首地址处,并占4个字节</li><li>构造函数会将此数据成员初始化为某个数组的首地址</li><li>这个地址属于数据区,是固定地址</li><li>在这个数组内,每个元素都是函数指针</li><li>这些函数它们被调用时,第一个参数一定是this指针,注意调用约定</li><li>在这些函数内部,很有可能会对this指针使用相对间接的访问方式</li></ul><p>虚表初始化特征码(在构造或析构函数中出现):</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">;下面这段代码出现的函数,需要具有成员函数特征,传递对象首地址作为this指针</span><br><span class="line">lea ecx,[ebp - 8];获取对象首地址</span><br><span class="line">call xxxxxxxxh ;函数调用</span><br><span class="line">mov reg,this ;某寄存器得到对象首地址</span><br><span class="line">mov dword ptr[eax], xxxxxxxxh</span><br><span class="line">; 向对象首地址写入4字节数据,查看并确认这4字节是否为函数地址表的首地址</span><br></pre></td></tr></table></figure><p>如果上述代码出现,应该高度怀疑此函数是一个构造或析构函数。</p><p>Good job.</p><p><img src="http://imgset.gitee.io/img/1571160018433.png" alt="1571160018433"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>Window PE -- 区块</title>
<link href="/2018/11/01/2018-11-01-Windows%20PE%20%E5%8C%BA%E5%9D%97/"/>
<url>/2018/11/01/2018-11-01-Windows%20PE%20%E5%8C%BA%E5%9D%97/</url>
<content type="html"><![CDATA[<h1 id="Window-PE-–-区块"><a href="#Window-PE-–-区块" class="headerlink" title="Window PE – 区块"></a>Window PE – 区块</h1><h2 id="区块"><a href="#区块" class="headerlink" title="区块"></a>区块</h2><p>在PE件头与原始数据之间存在一个区块表(SectionTable)。区块表中包含每个块在映像中的信息,分别指向不同的区块实体。</p><h3 id="区块表"><a href="#区块表" class="headerlink" title="区块表"></a>区块表</h3><p>跟IMAGE_NT_HEADERS的是区块表,它是一个IMAGE_SECTION_HEADER结构数组。每个IMAGE_SECTION_HEADER结构包含了它所关联的区块的信息,例如位置、长度、属性,该数组的数目由MAGE_NT_HEADERS.FileHeader.NumberOfSections指出。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">IMAGE_SECTION_HEADER</span> {</span></span><br><span class="line"> BYTE Name[IMAGE_SIZEOF_SHORT_NAME];</span><br><span class="line"> <span class="class"><span class="keyword">union</span> {</span></span><br><span class="line"> DWORD PhysicalAddress;</span><br><span class="line"> DWORD VirtualSize;</span><br><span class="line"> } Misc;</span><br><span class="line"> DWORD VirtualAddress;</span><br><span class="line"> DWORD SizeOfRawData;</span><br><span class="line"> DWORD PointerToRawData;</span><br><span class="line"> DWORD PointerToRelocations;</span><br><span class="line"> DWORD PointerToLinenumbers;</span><br><span class="line"> WORD NumberOfRelocations;</span><br><span class="line"> WORD NumberOfLinenumbers;</span><br><span class="line"> DWORD Characteristics;</span><br><span class="line">} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;</span><br></pre></td></tr></table></figure><p>为了便于理解,我们假设PE.exe的区块表含有3个块的描述,分别是.text、.rdata和.data。每个块对应于一个MAGE_SECTION_HEADER结构,用十六进制工具查看块表,如下表。表中的标号对应于第1个IMAGE_SECTION_HEADER结构的以下字段。</p><p><img src="http://imgset.gitee.io/img/1554724992303.png" alt="1554724992303"></p><ol><li><p>Name:块名。这是一个8位ASCII码名(不是Unicode内码),用来定义块名。多数块名以-个“.”开始(例如.text),这个“.”实际上不是必需的。值得注意的是,如果块名超过8字节,则没有最后的终止标志“NULL”字节。带有一个“$”的区块名会被链接器特殊对待,前面有“$”的同名区块会被合并。这些区块是按“$”后面的字符的字母顺序合并的。</p></li><li><p>VirtualSize:指出实际被使用的区块的大小,是在进行对齐处理前区块的实际大小。如果VirtualSize的值大于SizeOfRawData值,那么SizeOfRawData表示来自可执行文件初始化数据的大小,与VirtualSize相差的字节用填充。这个字段在OBJ文件中是被设置为0的。</p></li><li><p>VirtualAddress:该块装载到内存中的RVA。这个地址是按照内存页对齐的,它的数值总是SectionAlignr阳It的整数倍。在Microsoft工具中,第1个块的默认RVA值为lOOOh。在OBJ中,该字段没有意义,并被设置为0</p></li><li><p>SizeOfRawData:该块在磁盘中所占的空间。在可执行文件中,该字段包含经FileAlignment调整的块的长度。例如,指定FileAlignment的值为200h,如果VirtualSize中的块长度为19Ah字节,该块应保存的长度为200h字节。</p></li><li><p>PointerToRawData:该块在磁盘文件中的偏移。程序经编译或汇编后生成原始数据,这个字段用于给出原始数据在文件中的偏移。如果程序自装载PE或COFF文件(而不是由操作系统载入的),这一字段将比Vi山alAddress还重要在这种状态下,必须完全使用线性映像的方法载入文件,所以需要在该偏移处找到块的数据,而不是VirtualAddress段中的RVA地址。</p></li><li><p>PointerToRelocations:在EXE件中无意义。在OBJ文件中,表示本块重定位信息的偏移量在OBJ文件中,如果该值不是0,会指向一个IMAGE_RELOCATION结构数组。</p></li><li><p>PointerToLineNumbers:行号表在文件中的偏移量,这是文件调试信息。</p></li><li><p>NumberOfRelocations:在EXE文件中元意义。在OBJ文件中,表示本块在重定位表中的重定位数目。</p></li><li><p>NumberOfLinenur由巳rs:该块在行号表中的行号数目。</p></li><li><p>Chacteristics:块属性。该字段是一指出块属性(例如代码/数据、可读/可写等)的标志比较重要的标志如表11.5所示,多个标志值求或即为Characteristics的值。这些标志中的很多都可以通过链接器的/SECTION开关设置。例如,“E0000020h=20000000hI40000000hI80000000hI00000020h”表示该块包含执行代码,可读、可写、可执行,“C00000040h=40000000hI80000000hI00000040h”表示该块可读、可写,包含已初始化的数据,“60000020h=20000000hI40000000hI00000020h”表示该块包含执行代码,可读、可执行。</p><p><img src="http://imgset.gitee.io/img/1554725224302.png" alt="1554725224302"><img src="http://imgset.gitee.io/img/1554725236875.png" alt="1554725236875"></p></li></ol><h3 id="常见区块与区块合并"><a href="#常见区块与区块合并" class="headerlink" title="常见区块与区块合并"></a>常见区块与区块合并</h3><p>区块中的数据逻辑通常是关联的。PE文件一般至少有两个区块,一个是代码块,另一个是数据块。每个区块都有特定的名字,这个名字用于表示区块的用途。例如,一个块叫作.rdata,表明它是一个只读区块。区块在映像中是按起始地址(RVA)排列的,而不是按字母表顺序排列的。使用区块名只是为了方便,它对操作系统来说是无关紧要的。微软给这些区块分别取了有特色的名字,但这不是必需的。Borland链接器使用的是像“CODE”和“ATA”这样的名字。</p><p>EXE和OBJ文件的一些常见区块如表11.6所示。非另外声明,表中的区块名称来自微软的定义。</p><p><img src="http://imgset.gitee.io/img/1554725317252.png" alt="1554725317252"><img src="http://imgset.gitee.io/img/1554725336273.png" alt="1554725336273"></p><p>虽然编译器自动产生一系列标准的区块,但这没有什么不可思议的读者可以创建和命名自己的区块。在VisualC++中用#pragma来声明,告诉编译器将数据插入一个区块,代码如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> data_seg ( <span class="meta-string">"MY_DATA"</span> )</span></span><br></pre></td></tr></table></figure><p>这样,所有被VisualC++处理的数据都将放到一个叫作MY_DATA的区块内,而不是默认.data区块内。大部分程序只使用编译器产生的默认区块,但偶尔可能有-些特殊的需求,需要将代码或数据放到一个单独的区块里,例如建立一个全局共享块。</p><p>区块并非全部在链接时形成,更准确地说,它们一般是从OBJ文件开始被编译器放置的。链接器的工作就是合并所有OBJ和库中需要的块,使其最终成为一个合适的区块。例如,工程中的每一个OBJ至少有一个包含代码的.text区块,链接器把这些区块合并成一个.text区块。链接器遵循一套完整的规则,以判断哪些块需要合并及如何合并。OBJ件中的一个区块可能是为链接器准备,不会放入最后的可执行文件中(这样的区块主要用于编译器向链接器传递信息)。</p><p>接器的一个有趣的特征就是能够合并区块。如果两个区块有相似或一致的属性,那么它们在链接时能合并成一个区块,这取决于是否使用/MERGE开关。如下链接器选项将.rdata与.text区块合并为一个.text区块。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/MERG E: .rdata =.text</span><br></pre></td></tr></table></figure><p>合并区块优点是节省磁盘和内存空间。个区块至少占用1个内存,如果能将可执行文件内的区块数从4个减少到3个,就可能少用1个内存页。当然,这依颇于两个合并区块结尾的未用空间加起来是否能达到l个内存页。</p><p>当合并区块时,事情将变得有趣,因为这没有什么硬性规定。例如,把.rdata合并到.text里不会有问题,但不应该将.rsre、.reloc或.rdata合并到其他区块里。在VisualStudio.NET出现之前,可以将.idata合并到其他区块里,但在VisualStudio.NET中就不允许进行这样的操作了。不过,在制作发行版本时,链接器经常将.idata的一部分合并到其他区块里,例如.rdata。</p><p>为部分输入数据是在载入内存时由Windows加载器写人的,所以读者可能会对它们如何被放入只读区块感到疑惑。这是由于在加载时,系统会临时修改那些包含输入数据的页属性为可读、可写,初始化完成后恢复为原来的属性。</p><h3 id="区块的对齐值"><a href="#区块的对齐值" class="headerlink" title="区块的对齐值"></a>区块的对齐值</h3><p>区块的大小是要对齐的。有两种对齐值,一种用于盘文件内,另一种用于内存中。PE文件头指出了这两个值,它们可以不同。</p><p>PE文件头里,FileAlignment定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始,而区块的实际代码或数据的大小不一定刚好是这么多,所以在不足的地方一般以OOh来填充,这就是区块的间隙。例如,在PE文件中,一个典型的对齐值是200h,这样每个区块从200h的倍数的文件偏移位置开始。假设区块的第1个节在400h处,长度为90h,那么00h~490h为这一区块的内容,而文件对齐值是200h,为了使这一节的长度为FileAlignment的整数倍,490h~600h会被0填充,这段空间称为区块间隙,下一个区块的开始地址为600h。</p><p>PE文件头里,SectionAlignment义了内存中区块的对齐值。当PE文件被映射到内存中时,区块总是至少从一个页边界处开始。也就是说,当一个PE文件被映射到内存中时,每个区块的第l个字节对应于某个内存页。在x86系列CPU中,内存页是按4KB(1000h)排列的;在x64中,内存页是按8KB(2000h排列的。所以,在x86系统中,PE文件区块的内存对齐值一般为1000h,每个区块从1000h的倍数的内存偏移位置开始。</p><p>非使用/OPT:OWIN98或/ALIGN开关,否则VisualStudio6.0中的默认值都是4KB,VisualStudio.NET链器依然使用默认的10町:WIN98开关,但如果文件大小小于特定值,就会以200h为对齐值。另一种对齐方式来自.NET文件的规定。.NET文件的内存对齐值为8KB,而不是普通x86平台上的4KB,这样就保证了在x86平台上编译的程序可以在x64平台上运行。如果内存对齐值为4KB,那么x64加载器就不能载入这个程序了,因为64位Windows中的内存页大小是8KB。</p><p>可以建立一个区块在文件中的偏移与在内存中的偏移相同的PE文件。虽然这样做会使可执行文件变大,但是可以提高载入速度VisualStudio6.0的默认选项/OPT:WIN98将使PE文件按照这种方式来创建。在VisualStudio.NET中,链接器可以不使用/OPT:NOWIN98开关,这取决于文件是否足够小。</p><h3 id="文件偏移与虚拟地址的转换"><a href="#文件偏移与虚拟地址的转换" class="headerlink" title="文件偏移与虚拟地址的转换"></a>文件偏移与虚拟地址的转换</h3><p>一些PE文件为减小体积磁盘对齐值不是一个内存页1000h而是200h。当这类文件被映射到内存中后,同一数据相对于文件头的偏移量在内存中和磁盘文件中是不同的,这偏移地址与虚拟地址的转换问题。而那些磁盘对齐值(1000h)与内存页相同的区块,同一数据在磁盘文件中的偏移与在内存中的偏移相同,因此不需要转换。</p><p>区块显示了实例文件在磁盘与内存中各区块的地址、大小等信息。虚拟地址和虚拟大小是指该区块在内存中的地址和大小。物理地址和物理大小是指该区块在磁盘文件中的地址和大小。由于其磁盘对齐值为200h,与内存对齐值不同,故其磁盘|泱像和内存映像不同。</p><p><img src="http://imgset.gitee.io/img/1554725645315.png" alt="1554725645315"></p><p>可以看出,文件被映射到内存中时,MS-DOS头部、PE文件头和块表的偏移位置与大小均没有变化,而当各区块被映射到内存中后,其偏移位置就发生了变化。例如,磁盘文件中.text块起始端与文件头的偏移量为add1,映射到内存后,.text块起始端与文件头(基地址)的偏移量为add2。同时,.text块与块表之间形成了一大段空隙,这部分数据全是以0填充的。在这里,addl的值就是文件偏移地址(FileOffset),add2的值就是相对虚拟地址(RVA)。假设它们的差值为此,则文件偏移地址与虚拟地址的关系如下。</p><p><img src="http://imgset.gitee.io/img/1554725718474.png" alt="1554725718474"></p><p>在同一区块中,各地址的偏移量是相等的,可用上面的公式对此区块中的任意FileOffset与VA进行转换。但请不要错误地认为在整个文件里FileOffset与VA的差值是Δk</p><p>,因为各区块在内存中是以一个页边界开始的,从第l个区块的结束到第2个区块的开始(1000h对齐处)全以数据0填充,所以不同区块在磁盘与内存中的差值不同。如下表所示是该实例文件各区块在磁盘与内存中的起始地址差值。</p><p><img src="http://imgset.gitee.io/img/1554725752993.png" alt="1554725752993"></p><p>例如,假设某一虚拟地址(VA)为401112h,要计算它的文件偏移地址。401112h在.text块中,此时D,.k=0C00h,故</p><p>Fi le Offset= VA - ImageBase - Δk = 401l12h - 400000h - 0C00h = 512h</p><p>再来看一看虚拟地址4020D2h的转换。4020D站在.rdata块中,此时Δk=IA00h,故</p><p>File Offset = VA - ImageBase - Δk = 4020D2h - 400000h - IA00h = 6D2h</p><p>实际操作中,建议使用RVA-Offset类的转换工具。</p><p><img src="http://imgset.gitee.io/img/1571159911481.png" alt="1571159911481"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>Window PE -- 文件头</title>
<link href="/2018/11/01/2018-11-01-Windows%20PE%20%E6%96%87%E4%BB%B6%E5%A4%B4/"/>
<url>/2018/11/01/2018-11-01-Windows%20PE%20%E6%96%87%E4%BB%B6%E5%A4%B4/</url>
<content type="html"><![CDATA[<h1 id="Window-PE-–-文件头"><a href="#Window-PE-–-文件头" class="headerlink" title="Window PE – 文件头"></a>Window PE – 文件头</h1><h2 id="PE文件头"><a href="#PE文件头" class="headerlink" title="PE文件头"></a>PE文件头</h2><p>紧跟着DOSstub的是PE文件头(PEHeader)。“PEHeader,,是PE关结构NT映像头(IMAGE_NT_HEADERS)的简称,其中包含许多PE装载器能用到的重要字段。当执行体在支持PE文件结构的操作系统中执行时,PE装载器将从IMAGE_DOS_HEADER结构的e_lfanew字段里找到PEHeader的起始偏移量,用其加上基址,得到PE文件头的指针。</p><p>PNTHeader = lmageBase + dosHeader->e_Ifanew</p><p>实际上有两个版本的IMAGENT_HEADER结构,一个是为PE32(32位版本)可执行文件准备的,另一个是PE32+(64位版本)。因为它们几乎没有区别,所以在以后的讨论中将不作区分。</p><p>IMAGE_NT_HEADER是由3个字段(左边的数字是到PE文件头的偏移量)组成的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>PE32+的IMAGE_NT_HEADER64结构如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Signature字段"><a href="#Signature字段" class="headerlink" title="Signature字段"></a>Signature字段</h2><p>在一个有效的PE文件里,Signature字段被设置为Ox00004550,ASCII码字符是“PE\0\0。”,“#define IMAGE_NT_SIGNATURE”定义了这个值,示例如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> IMAGE_NT_SIGNATURE 0x00004550</span></span><br></pre></td></tr></table></figure><p>PE\0\0”是PE文件头的开始,MS一DOS头部的ιlianew字段正是指向“PE\0\0”的。</p><h2 id="IMAGE-FILE-HEADER结构"><a href="#IMAGE-FILE-HEADER结构" class="headerlink" title="IMAGE_FILE_HEADER结构"></a>IMAGE_FILE_HEADER结构</h2><p>lMAGE_FILE_HEADER(映像文件头)结构包含PE文件的一些基本信息,最重要的是,其中的一个域指出了IMAGE_OPTIONAL_HEADER的大小。下面介绍IMAGE_FILE_HEADER结构的各个字段,并对这些字段进行说明。这个结构也能在COFF格式的OBJ文件的开始处找到,因此也称其为“COFF File Header”注释中的偏移量是基于PE文件头(IMAGE_NT_HEADERS)的。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">IMAGE_FILE_HEADER</span> {</span></span><br><span class="line"> WORD Machine;</span><br><span class="line"> WORD NumberOfSections;</span><br><span class="line"> DWORD TimeDateStamp;</span><br><span class="line"> DWORD PointerToSymbolTable;</span><br><span class="line"> DWORD NumberOfSymbols;</span><br><span class="line"> WORD SizeOfOptionalHeader;</span><br><span class="line"> WORD Characteristics;</span><br><span class="line">} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;</span><br></pre></td></tr></table></figure><p>用十六进制工具查看IMAGE_FILE_HEADER结构的情况,如图所示,图中的标号对应于以下字段。</p><p><img src="http://imgset.gitee.io/img/1554716150866.png" alt="1554716150866"></p><ol><li>Machine:可执行文件的目标CPU类型。PE文件可以在多种机器上使用,不同平台上指令的机器码不同。如表是几种典型的机器类型标志。<img src="http://imgset.gitee.io/img/1554716220204.png" alt="1554716220204"></li><li>NumberOfSections:区块(Section)的数目,块表紧跟在IMAGE_NT_HEADERS后面。</li><li>TimeDateStamp:表示文件的创建时间。这个值是自1970年l月1日以来用格林威治时间(GMT)计算的秒数,是一个比文件系统的日期/时间更精确的文件创建时间指示器。将这个值翻译为易读的字符串需要使用_clime函数(它是时区敏感型的)。另一个对此字段计算有用的函数是gmtime。</li><li>PointerToSymbolTable:COFF符号表的文件偏移位置(参见Microsoft规范的5.4节)。因为采用了较新的debug格式,所以COFF符号表在PE文件中较为少见。在VisualStudio.NET出现之前,COFF符号表可以通过设置链接器开关(/DEBUGTYPE:COFF)来创建。COFF符号表几乎总能在目标文件中找到,若没有符号表存在,将此值设置为0。</li><li>NumberOISymbols:如果有COFF符号表,它代表其中的符号数目。COFF符号是一个大小固定的结构,如果想找到COFF符号表的结束处,需要使用这个域。</li><li>SizeOfOptionalHeader:紧跟IMAGEFILE_HEADER,表示数据的大小。在PE文件中,这个数据结构叫作IMAGE_O阿IONAL_HEADER,其大小依赖于当前文件是32位还是64位文件。对32位PE文件,这个域通常是OOEOh;对64位PE32+文件,这个域是OOFOh。不管怎样,这些是要求的最小值,较大的值也可能会出现。</li><li>Characteristics:文件属性,有选择地通过几个值的运算得到。这些标志的有效值是定义于winnt.h内的IMAGE_FILE_xxx值,具体如表l1.2所示。普通EXE文件的这个字段的值一般是010仙,DLL文件的这个字段的值一般是2102h。</li></ol><p><img src="http://imgset.gitee.io/img/1554716339696.png" alt="1554716339696"></p><h2 id="IMAGE-OPTIONAL-HEADER结构"><a href="#IMAGE-OPTIONAL-HEADER结构" class="headerlink" title="IMAGE_OPTIONAL_HEADER结构"></a>IMAGE_OPTIONAL_HEADER结构</h2><p>尽管可选映像头(IMAGE_OPTIONAL_HEADER)是一个可选的结构,但IMAGE_FILE_HEADER结构不足以定义PE文件属性,因此可选映像头中定义了更多的数据,完全不必考虑两个结构的区别在哪里,将两者连起来就是一个完整的“PE文件头结构飞IMAGE_OPTIONAL_HEADER32结构如下,字段前的数字标出了字段相对于PE文件头的偏移量。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">IMAGE_OPTIONAL_HEADER</span> {</span></span><br><span class="line"> WORD Magic;</span><br><span class="line"> BYTE MajorLinkerVersion;</span><br><span class="line"> BYTE MinorLinkerVersion;</span><br><span class="line"> DWORD SizeOfCode;</span><br><span class="line"> DWORD SizeOfInitializedData;</span><br><span class="line"> DWORD SizeOfUninitializedData;</span><br><span class="line"> DWORD AddressOfEntryPoint;</span><br><span class="line"> DWORD BaseOfCode;</span><br><span class="line"> DWORD BaseOfData;</span><br><span class="line"> DWORD ImageBase;</span><br><span class="line"> DWORD SectionAlignment;</span><br><span class="line"> DWORD FileAlignment;</span><br><span class="line"> WORD MajorOperatingSystemVersion;</span><br><span class="line"> WORD MinorOperatingSystemVersion;</span><br><span class="line"> WORD MajorImageVersion;</span><br><span class="line"> WORD MinorImageVersion;</span><br><span class="line"> WORD MajorSubsystemVersion;</span><br><span class="line"> WORD MinorSubsystemVersion;</span><br><span class="line"> DWORD Win32VersionValue;</span><br><span class="line"> DWORD SizeOfImage;</span><br><span class="line"> DWORD SizeOfHeaders;</span><br><span class="line"> DWORD CheckSum;</span><br><span class="line"> WORD Subsystem;</span><br><span class="line"> WORD DllCharacteristics;</span><br><span class="line"> DWORD SizeOfStackReserve;</span><br><span class="line"> DWORD SizeOfStackCommit;</span><br><span class="line"> DWORD SizeOfHeapReserve;</span><br><span class="line"> DWORD SizeOfHeapCommit;</span><br><span class="line"> DWORD LoaderFlags;</span><br><span class="line"> DWORD NumberOfRvaAndSizes;</span><br><span class="line"> IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];</span><br><span class="line">} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;</span><br></pre></td></tr></table></figure><p>IMAGE_OPTIONAL_HEADER64结构有少许变化,PE32中的BaseOfData不存在于PE32+中,在PE32+中Magic的值是020Bh。IMAGE_OPTIONAL_HEADER64结构如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">IMAGE_OPTIONAL_HEADER64</span> {</span></span><br><span class="line"> WORD Magic;</span><br><span class="line"> BYTE MajorLinkerVersion;</span><br><span class="line"> BYTE MinorLinkerVersion;</span><br><span class="line"> DWORD SizeOfCode;</span><br><span class="line"> DWORD SizeOfInitializedData;</span><br><span class="line"> DWORD SizeOfUninitializedData;</span><br><span class="line"> DWORD AddressOfEntryPoint;</span><br><span class="line"> DWORD BaseOfCode;</span><br><span class="line"> ULONGLONG ImageBase;</span><br><span class="line"> DWORD SectionAlignment;</span><br><span class="line"> DWORD FileAlignment;</span><br><span class="line"> WORD MajorOperatingSystemVersion;</span><br><span class="line"> WORD MinorOperatingSystemVersion;</span><br><span class="line"> WORD MajorImageVersion;</span><br><span class="line"> WORD MinorImageVersion;</span><br><span class="line"> WORD MajorSubsystemVersion;</span><br><span class="line"> WORD MinorSubsystemVersion;</span><br><span class="line"> DWORD Win32VersionValue;</span><br><span class="line"> DWORD SizeOfImage;</span><br><span class="line"> DWORD SizeOfHeaders;</span><br><span class="line"> DWORD CheckSum;</span><br><span class="line"> WORD Subsystem;</span><br><span class="line"> WORD DllCharacteristics;</span><br><span class="line"> ULONGLONG SizeOfStackReserve;</span><br><span class="line"> ULONGLONG SizeOfStackCommit;</span><br><span class="line"> ULONGLONG SizeOfHeapReserve;</span><br><span class="line"> ULONGLONG SizeOfHeapCommit;</span><br><span class="line"> DWORD LoaderFlags;</span><br><span class="line"> DWORD NumberOfRvaAndSizes;</span><br><span class="line"> IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];</span><br><span class="line">} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;</span><br></pre></td></tr></table></figure><p>用十六进制工具查看IMAGE_OPTIONAL_HEADER32结构,如下图,图中的标号对应于以下字段。</p><p><img src="http://imgset.gitee.io/img/1554723046973.png" alt="1554723046973"></p><ol><li><p>Magic:这是一个标记字,说明文件是ROM映像(0107h)还是普通可执行的映像(010Bh),一般是010Bh,如果是PE32+,则是020Bh。</p></li><li><p>MajorLinkerVersion:链接程序的主版本号。</p></li><li><p>MinorLinkerVersion:链接程序的次版本号。</p></li><li><p>SizeOfCode:有IMAGE_SCN_CNT_CODE属性的区块的总大小(只入不舍),这个值是向上对齐某一个值的整数倍。例如,本例是200h,即对齐的是一个磁盘扇区字节数(200h)的整数倍。在通常情况下,多数文件只有1个Code块,所以这个字段和.text块的大小匹配。</p></li><li><p>SizeOflnitializedData:已初始化数据块的大小,即在编译时所构成的块的大小(不包括代码段)。但这个数据不太准确。</p></li><li><p>SizeOfUninitializedData:未初始化数据块的大小,装载程序要在虚拟地址空间中为这些数据约定空间。这些块在磁盘文件中不占空间,就像“UninitializedData”这一术语所暗示的一样,这些块在程序开始运行时没有指定值。未初始化数据通常在.bss块中。</p></li><li><p>AddressOfE川ryPoint:程序执行人口RVA。对于DLL,这个人口点在进程初始化和关闭时及线程创建和毁灭时被调用。在大多数可执行文件中,这个地址不直接指向Main、WinMain或DllMain函数,而指向运行时的库代码井由它来调用上述函数。在DLL中,这个域能被设置为0,此时前面提到的通知消息都无法收到。链接器的/NOENTRY开关可以设置这个域为0。</p></li><li><p>BaseOfCode:代码段的起始RVA。在内存中,代码段通常在PE文件头之后,数据块之前。在Microsoft链接器生成的可执行文件中,RVA的值通常是lOOOh。Borland的白ink32用lrnageBase加第l个CodeSection的RVA,并将结果存入该字段。</p></li><li><p>BaseOfData:数据段的起始RVA。数据段通常在内存的末尾,即PE文件头和CodeSection之后。可是,这个域的值对于不同版本的Microsoft链接器是不一致的,在64位可执行文件中是不会出现的。</p></li><li><p>lmageBase:文件在内存中的首选载入地址。如果有可能(也就是说,如果目前没有其他文件占据这块地址,它就是正确对齐的并且是一个合法的地址),加载器会试图在这个地址载入PE文件。如果PE文件是在这个地址载人的,那么加载器将跳过应用基址重定位的步骤。</p></li><li><p>SectionAlignment:载入内存时的区块对齐大小。每个区块被载入的地址必定是本字段指定数值的整数倍。默认的对齐尺寸是目标CPU的页尺寸。对运行在Windows9x/Me下的用模式可执行文件,最小的对齐尺寸是每页1000h(4KB)。这个字段可以通过链接器的/ALIGN开关来设置。在IA-64上,这个字段是按8KB排列的。</p></li><li><p>FileAlignment:磁盘上PE文件内的区块对齐大小,组成块的原始数据必须保证从本字段的倍数地址开始。对于x86可执行文件,这个值通常是200h或1000h,这是为了保证块总是从磁盘的扇区开始,这个字段的功能等价于NE格式文件中的段/资源、对齐因子使用不同版本的Microsoft链接器,默认值会改变。这个值必须是2的幕,其最小值为200h而且,如果SectionAlignment小于CPU的页尺寸,这个域就必须与SectionA!ignment匹配。链器开关/OPT:WIN98设置x86可执行文件的对齐值为1000h,/OPT:NOWIN98设置对齐值为200h。</p></li><li><p>MajorOperatingSystemVersion:要求操作系统的最低版本号的主版本号。随着这么多版本的Windows的出现,这个字段显然变得不切题了</p></li><li><p>MinorOperatingSystemVersion:要求操作系统的最低版本号的次版本号。</p></li><li><p>MajorOperatingSystemVersion:该可执行文件的主版本号,由程序员定义。它不被系统使用,并可以设置为0,可以通过链接器的NERSION开关来设置。</p></li><li><p>MinorlmageVersion:该可执行文件的次版本号,由程序员定义。</p></li><li><p>MajorSubsystemVersion:要求最低子系统版本的主版本号。这个值与下一个字段一起,通常被设置为4,可以通过链接器开关/SUBSYSTEM来设置。</p></li><li><p>MinorSubsystemVersion:要求最低子系统版本的次版本号。</p></li><li><p>Win32VersionValue:另一个从来不用的字段,通常被设置为0。</p></li><li><p>SizeOflmage:映像载入内存后的总尺寸,是指载入文件从Irr吨eBase到最后一个块的大小。最后一个块根据其大小向上取整。</p></li><li><p>SizeOfHeaders:MS-DOS头部、PE文件头、区块表的总尺寸。这些项目出现在PE文件中的所有代码或数据区块之前,域值四舍五入至文件对齐值的倍数。</p></li><li><p>CheckSum:映像的校验和。IMAGEHLP.DLL中的CheckSumMappedFile函数可以计算该值。一般的EXE文件该值可以是0,但一些内核模式的驱动程序和系统DLL必须有一个校验和。当链接器的/RELEASE开关被使用时,校验和被置于文件中。</p></li><li><p>Subsystem:一个标明可执行文件所期望的子系统(用户界面类型)的枚举值。这个值只对EXE重要,如下表</p><p><img src="http://imgset.gitee.io/img/1554723738509.png" alt="1554723738509"></p></li><li><p>DllCharacteristics:DllMain()函数何时被调用。默认值为0。</p></li><li><p>SizeOfStackReserve:在EXE文件里为线程保留的拢的大小。它在一开始只提交其中一部,只有在必要时才提交剩下的部分。</p></li><li><p>SizeOfStackCommit:在EXE文件里,一开始即被委派给拢的内存,默认值是4KB。</p></li><li><p>SizeOfHeapReserve:在EXE文件里,为进程的默认堆保留的内存,默认值是1MB。</p></li><li><p>SizeOfHeapCommit:在EXE文件里,委派给堆的内存,默认值是4KB 。</p></li><li><p>LoaderFlags:与调试有关,默认值为0。</p></li><li><p>NumberOfRvaAndSizes:数据目录的项数。这个字段的值从WindowsNT发布以来一直是16。</p></li><li><p>DataDirect01y[l6]:数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块等数据。IMAGE_DATADIRECTORY的结构定义如下。</p></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>根据目录表成员的结构如下表所示,各项成员含义后面会详细介绍。</p><p><img src="http://imgset.gitee.io/img/1554723877710.png" alt="1554723877710"></p><p>PE文件定位输出表、输入表和资源等重要数据时,就是从IMAGE_DATA_DIRECTORY结构开始的。</p><p>例PE.exe的数据目录表位于128h~1A7h,每个成员占8字节,分别指向相关的结构,如图11.6所示。128h数据目录表的第1项,其值为0,即这个实例的输出表地址与大小皆为0,表示无输出表。130h是第2项,表示输入表地址为2040h(RVA),大小为3Ch。</p><p><img src="http://imgset.gitee.io/img/1554723908440.png" alt="1554723908440"></p><p>PE编辑工具(例如LordPE)查看实例PE.exe文件的E结构。单LordPE的“PEEditor”按钮,打开E_Offset文件,在面板上会直接显示PE结构中的主要字段,如图11.7所示。单击“Directories”按钮,可以打开数据目录表查看面板,如图</p><p><img src="http://imgset.gitee.io/img/1554723928180.png" alt="1554723928180"></p><p><img src="http://imgset.gitee.io/img/1571159906781.png" alt="1571159906781"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>Window PE -- 输入表</title>
<link href="/2018/11/01/2018-11-01-Windows%20PE%20%E8%BE%93%E5%85%A5%E8%A1%A8/"/>
<url>/2018/11/01/2018-11-01-Windows%20PE%20%E8%BE%93%E5%85%A5%E8%A1%A8/</url>
<content type="html"><![CDATA[<h1 id="Window-PE-–-输入表"><a href="#Window-PE-–-输入表" class="headerlink" title="Window PE – 输入表"></a>Window PE – 输入表</h1><h2 id="输入表"><a href="#输入表" class="headerlink" title="输入表"></a>输入表</h2><p>可执行文件使用来自其他DLL的代码或数据的动作称为输入。当PE文件被载入时,Windows加载器的工作之一就是定位所有被输入的函数和数据,并让正在入的文件可以使用那些地址。这个过程是通过PE文件的输入表(Impo1tTable,简称“IT”,也称导人表)完成的。输入表中保存的是函数名和其驻留的DLL名等动态链接所需的信息。输入表在软件外亮技术中的地位非常重要,读者在研究与外壳相关的技术时一定要彻底掌握这部分知识。</p><h2 id="输入表的调用"><a href="#输入表的调用" class="headerlink" title="输入表的调用"></a>输入表的调用</h2><p>在代码分析或编程中经常会遇到输入函数(ImportFunctions,或称导人函数)。输入函数就是被程序调用但其执行代码不在程序中的函数,这些函数的代码位于相关的DLL文件中,在调用者程序中只保留相关的函数信息,例如函数名、DLL文件名等。对磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址。只有当PE文件载入内存后,Windows加载器才将相关DLL载人,并将调用输入函数的指令和函数实际所处的地址联系起来。</p><p>当应用程序调用一个DLL的代码和数据时,它正在被隐式地链接到DLL,这个过程完全由Windows加载器完成。另一种链接是运行期的显式链接,这意味着必须确定目标DLL已经被加载,然后寻找API的地址,这几乎总是通过调用LoadLibra巧和GetProcAddress完成的。</p><p>当隐含地链接一个API时,类似LoadLibrary和GetProcAddress的代码始终在执行,只不过这是由Windows加载器自动完成的。Windows加载器还保证了PE文件所需的任何附加的DLL都巳载入例如,Windows2000/XP上每个由VisualC++创建的正常程序都要链接KERNEL32.DLL,而它又从NTDLL.DLL中输入函数。同样,如果链接了GDI32.DLL,它又依赖USER32、ADVAPI32、NTDLL和KERNEL32等DLL的函数,那么都要由Windows加载器来保证载入并解决输入问题。</p><p>在PE文件内有一组数据结构,它们分别对应于被输入的DLL。每一个这样的结构都给出了被输入的DLL的名井指向一组函数指针。这组函数指针称为输入地址表(ImportAddressTable,IAT)。每一个被引人的API在IAT里都有保留的位置,在那里它将被Windows加载器写人输入函数的地址。最后一点特别重要:一旦模块被载入,IAT中将包含所要调用输入函数的地址。</p><p>把所有输入函数放在IAT中的同一个地方是很有意义的。这样,无论在代码中调用一个输入函数多少次,都会通过IAT中的同一个函数指针来完成。</p><p>现在看看怎样调用一个输入函数。需要考虑两种情况,即高效和低效。最好的情况是像下面这样,直接调用00402010h处的函数,00402010h位于IAT中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CALL DWORD PTR [00402010]</span><br></pre></td></tr></table></figure><p>而实际上,对一个被输入的API的低效调用像下面这样(实例PE.exe中调用LoadlconA函数的代码)。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Call 00401164</span><br><span class="line">...</span><br><span class="line">:00401164</span><br><span class="line">Jmp dword ptr [00402010]</span><br></pre></td></tr></table></figure><p>这种情况下,CALL令把控制权转交给一个子程序,子程序中的JMP指令跳转到IAT中的00402010h。简单地说就是:使用5字节的额外代码;由于使用了额外的JMP指令,将花费更多的执行时间。</p><p>有人可能会问:为什么要采用此种低效的方法?对这个问题有一很好的解释:编译器元法区分输入函数调用和普通函数调用。对每个函数调用,编译器使用同样形式的CALL指令,示例如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CALL XXXXXXXX</span><br></pre></td></tr></table></figure><p>“xxxxxxxx”是一个由链接器填充的实际地址。注意,这条指令不是从函数指针来的,而是从代码中的实际地址来的。为了实现因果平衡,链接器必须产生一块代码来取代“xxxxxxxx”,简单的方法就是像上面一样调用一个JMPstub。</p><p>JMP指令来自为输入函数准备的输入库。如果读者检查过输入库,在输入函数名字的关联处就会发现与上面的JMPstub相似的指令,即在默认情况下,对被输入API的调用将使用低效的形式。</p><p>如何得到优化的形式?答案来自一个给编译器的提示形式。可以使用修饰函数的_declspec(dllimport)来告诉编译器,这个函数来自另一个DLL,这样编译器就会产生指令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CALL DWORD PTR [XXXXXXXX]</span><br></pre></td></tr></table></figure><p>而不是指令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CALL XXXXXXXX</span><br></pre></td></tr></table></figure><p>此外,编译器将给函数加上“_imp_"前缀,然后将函数送给链接器,这样就可以直接把一imp_xxx送到IAT中,而不需要调用JMPstub了。</p><p>如果要编写一个输出函数,井为它们提供一个头文件,不要忘了在函数的前面加上修饰符“_declspec(dllimport)”,在winnt.h等系统头文件中就是这样做的,示例如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">_declspec(dllimport) void Foo(void)</span><br></pre></td></tr></table></figure><h3 id="输入表结构"><a href="#输入表结构" class="headerlink" title="输入表结构"></a>输入表结构</h3><p>PE文件头的可选映像头中,数据目录表的第2个成员指向输入表。入表以一个IMAGE_IMPORT_DESCRI凹、OR(IID)数组开始。每个被PE文件隐式链接的DLL都有一个IID。在这个数组中,没有字段指出该结构数组的项数,但它的最后一个单元是“NULL”,由此可以计算出该数组的项数。如,某个PE文件从两个DLL文件中引人函数,因此存在两个IID结构来描述这些DLL文件,并在两个IID结构的最后由一个内容全为0的IID结构作为结束。IID的结构如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">typedef struct _IMAGE_IMPORT_DESCRIPTOR {</span><br><span class="line">union {</span><br><span class="line">DWORD Characteristics;</span><br><span class="line">DWORD OriginalFirstThunk;//INT(Import Name Table) address (RVA)</span><br><span class="line">};</span><br><span class="line">DWORD TimeDateStamp;</span><br><span class="line">DWORD ForwarderChain;</span><br><span class="line">DWORD Name;//library name string address (RVA)</span><br><span class="line">DWORD FirstThunk;//IAT(Import Address Table) address (RVA)</span><br><span class="line">} IMAGE_IMPORT_DESCRIPTOR;</span><br></pre></td></tr></table></figure><ul><li>OriginalFirstThunk(Characte1istics):包含指向输入名称表(INT)的RVA。INT是一个IMAGETHUNK_DATA结构的数组,数组中的每个MAGE_THUNK_DATA结构都指向IMAGEIMPORT_BY_NAME构,数组以一个内容为0的IMAGE_THUNK_DATA结构结束。</li><li>TimeDateStamp:一个32位的时间标志,可以忽略。</li><li>ForwarderChain:这是第l个被转向的A凹的索引,一般为0,在程序引用一个DLL中的API,而这个API又在引用其他DLL的API时使用(但这样的情况很少出现)。</li><li>Name:DLL名字的指针。它是一个以“00”结尾的ASCII字符的RVA地址该字符串包含输入的DLL名,例如“KERNEL32.DLL”USER32.DLL”。</li><li>FirstThunk:包含指向输入地址表(IAT)的RVA。IAT是一个IMAGE_THUNK_DATA结构的数、</li></ul><p>OriginalFirst 和 FirstThunk结构类似。他们分别指向两个本质上相同的数组IMAGE_THUNK_DATA,这些数组有好几种叫法,最常见的是输入名称表(ImportNameTable,INT)和输入地址表(lmpot Address Table,IAT)。如下图所示为一个可执行文件正在从USER32DLL里输入一些API。</p><p><img src="http://imgset.gitee.io/img/1554866976205.png" alt="1554866976205"></p><p>两个数组中都有IMAGE_THUNK_DATA结构类型的元素,它是一个指针大小的联合(union)。每个IMAGE_THUNK_DATA元素对应于一个从可执行文件输入的函数。两个数组的结束都是由一个值为0的IMAGE_THUNK_DATA元素表示的。IMAGE_THUNK_DATA结构实际上是一个双字,该结构在不同时刻有不同的含义,具体如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">typedef struct _IMAGE_THUNK_DATA32 {</span><br><span class="line"> union {</span><br><span class="line"> DWORD ForwarderString; // PBYTE 指向一个转向者字符串的RVA</span><br><span class="line"> DWORD Function; // PDWORD 被输入的函数的内存地址</span><br><span class="line"> DWORD Ordinal; // 被输入的 API 的序数值</span><br><span class="line"> DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME 指向 IMAGE_IMPORT_BY_NAME</span><br><span class="line"> } u1;</span><br><span class="line">} IMAGE_THUNK_DATA32;</span><br><span class="line">typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;</span><br></pre></td></tr></table></figure><p>IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式输入,这时低31位(或者一个64位可执行文件的低63位)被看成一个函数序号。当双字的最高位为0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个RVA,指向一个IMAGE_lMPORT_BY_NAME结构。</p><p>IMAGE_IMPORT_BY_NAME结构仅有1个字大小,存储了一个输入函数的相关信息,结构如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> IMAGE_IMPORT_BY_NAME STRUCT</span><br><span class="line"> Hint WORD ?</span><br><span class="line"> Name BYTE ?</span><br><span class="line"> IMAGE_IMPORT_BY_NAME ENDS</span><br></pre></td></tr></table></figure><ul><li>Hint:本函数在其所驻留DLL的输出表中的序号。该域被PE装载器用来在DLL的输出表里快速查询函数。该值不是必需的,一些链接器将它设为0。</li><li>Name:含有输入函数的函数名。函数名是一个ASCII字符串,以“NULL”结尾。注意,这里虽然将Name的大小以字节为单位进行定义,但其实它是一个可变尺寸域,由于没有更好的表示方法,只好在上面的定义中写成“BYTE”。</li></ul><h2 id="输入地址表"><a href="#输入地址表" class="headerlink" title="输入地址表"></a>输入地址表</h2><p>为什么会有两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?第1个数组(由OriginalFirstThunk所指向)是单独的一项,不可改写,称为INT,有时也称为提示名表(Hint-nameTable)。第2个数组(由FirstThunk所指向)是由PE装载器重写的。PE装载器先搜索OriginalFirstThunk,如果找到,加载程序就迭代搜索数组中的每个指针,找出每个IMAGE_IMPORT_BY_NAME结构所指向的输入函数的地址。然后,加载器用函数真正的人口地址来替代由FirstThunk指向的IMAGE_THUNK_DATA数组里元素的值。“Jmpdwordptr[xxxxxxxx]”语句中的“[口xxxxxx]”是指FirstThunk数组中的一个人口,因此称为输入地址表(Import Address Table,IAT)。所以,当PE文件装载内存后准备执行时,图11.13己转换成如图11.14所示的状态,所有函数人口地址排列在一起。此时,输入表中的其他部分就不重要了,程序依靠IAT提供的函数地址就可以正常运行。</p><p><img src="http://imgset.gitee.io/img/1554867171146.png" alt="1554867171146"></p><p>在某些情况下,一些函数仅由序号引出。也就是说,不能用函数名来调用它们,只能用它们的位置来调用它们。此时,IMAGE_THUNK_DATA值的低位字指示函数序数,最高有效位(MSB)设为1。微软提供了一个方便的常量IMAGE_ORDINAL_FLAG32来测试DWORD值的M钮,其值为80000000h(在PE32+中是IMAGE_ORDINAL_FLAG64,其值为8000000000000000h)。</p><p>另一种情况是程序OrignalFirstThunk的值为0。在初始化时,系统根据FirstThunk的值找到指向函数名的地址串,根据地址串找到函数名,再根据函数名得到人口地址,然后用入口地址取代FirstThunk指向的地址串中的原值。</p><p><img src="http://imgset.gitee.io/img/1571159894317.png" alt="1571159894317"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>Window PE -- 资源</title>
<link href="/2018/11/01/2018-11-01-Windows%20PE%20%E8%B5%84%E6%BA%90/"/>
<url>/2018/11/01/2018-11-01-Windows%20PE%20%E8%B5%84%E6%BA%90/</url>
<content type="html"><![CDATA[<p>Window PE – 资源</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>Windows程序的各种界面称为资源,包括加速键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(DialogBox)、图标(Icon)、菜单(Menu)、串表(StringTable)、工具栏(Toolbar)和版本信息(VersionInformation)等。在PE文件的所有结构中,资源部分是最复杂的。</p><h2 id="资源结构"><a href="#资源结构" class="headerlink" title="资源结构"></a>资源结构</h2><p>资源用类似于磁盘目录结构的方式保存,目录通常包含3层。第l层目录类似于一个文件系统的根目录,每个根目录下的条目总是在它自己权限下的一个目录。第2层目录中的每一个都对应于一个资源类型(字符串表、菜单、对话框、菜单等)。每个第2层资源类型目录下是第3层目录。</p><p><img src="http://imgset.gitee.io/img/1554868326942.png" alt="1554868326942"></p><h2 id="资源目录结构"><a href="#资源目录结构" class="headerlink" title="资源目录结构"></a>资源目录结构</h2><p>数据目录表中的IMAGEDIRECTORY_ENTRY_RESOURCE条目包含资源的RVA和大小。资源目录结构中的每一个节点都是由IMAGE_RESOURCE_DIRECTORY结构和紧随其后的数个IMAGE_RESOURCE_DIRECTORY_ENTRY结构组成的,这两种结构组成了一个目录块。</p><p>IMAGE_RESOURCE_DIRECTORY结构长度为16字节,共有6个字段,其定义如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">typedef struct _IMAGE_RESOURCE_DIRECTORY {</span><br><span class="line"> ULONG Characteristics;</span><br><span class="line"> ULONG TimeDateStamp;</span><br><span class="line"> USHORT MajorVersion;</span><br><span class="line"> USHORT MinorVersion;</span><br><span class="line"> USHORT NumberOfNamedEntries;</span><br><span class="line"> USHORT NumberOfIdEntries;</span><br><span class="line">} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;</span><br></pre></td></tr></table></figure><p>这个结构中让人感兴趣的字段是NumberOfNamedEntries和NumberOfldEntries,它们说明了本目录中目录项的数量。umberOfNamedEntries字段是以字符串命名的资源数量,NumberOfldEntries字段是以整型数字命名的资源数量,两者加起来是本目录中的目录项总和,即紧随其后的IMAGE_RESOURCE_DIRECTORY_ENTRY结构的数量。</p><h2 id="资源目录入口结构"><a href="#资源目录入口结构" class="headerlink" title="资源目录入口结构"></a>资源目录入口结构</h2><p>紧跟资源目录结构的就是资源目录入口(ResourceDirEntries)结构,此结构长度为8字节,包含2个字段。IMAGE_RESOURCE_DIRECTORY_ENTRY结构定义如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">typedef struct IMAGE_RESOURCE_DIRECTORY_ENTRY</span><br><span class="line">{</span><br><span class="line">Name DWORD ?</span><br><span class="line">OffsetToData DWORD ?</span><br><span class="line">}IMAGE_RESOURCE_DIRECTORY_ENTRY,*PIMAGE_RESOURCE_DIRECTORY_ENTRY;</span><br></pre></td></tr></table></figure><p>根据不同的情况,这2个字段的含义有所不同。</p><ul><li><p>Name字段:定义目录项的名称或ID。当结构用于第1层目录时,定义的是资源类型;当结构用于第2层目录时,定义的是资源的名称;当结用于第3层目录时,定义的是代码页编号。当最高位为0时,表示字段的值作为ID使用;当最高位为1时,表示字段的低位作为指针使用,资源名称字符串使用Unicode编码,这个指针不直接指向字符串,而指向一个IMAGE_RESOURCE_DIR_STRING_U结构。Name字段定义如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">IMAGE RESOURCE DIR STRING U STRUCT</span><br><span class="line">Length</span><br><span class="line">NameStr in g</span><br><span class="line">WORD ?</span><br><span class="line">WCHAR ?</span><br><span class="line">IMAGE RESOURCE DIR STRING U ENDS</span><br></pre></td></tr></table></figure></li></ul><ul><li>OffsetToData字段:一个指针当最高位(位31)为1时,低位数据指向下一层目录块的起始地址;当最高位为0时,指针指向IMAGE_RESOURCE_DATA_ENTRY结构。在将Name和OffsetToData作为指针时需要注意,该指针从资源区块开始处计算偏移量,并非从RVA(根目录的起始位置)开始处计算偏移量。</li></ul><p>有一点要说明的是,当IMAGE_RESOURCE_DIRECTORY_ENTRY在第1层目录中,它的Name字段作为资源类型使用。当资源类型以ID定义且数值在1到16之间时,表示是系统预定义的类型,具体如下表所示。</p><p><img src="http://imgset.gitee.io/img/1554868630997.png" alt="1554868630997"></p><h2 id="资源数据入口"><a href="#资源数据入口" class="headerlink" title="资源数据入口"></a>资源数据入口</h2><p>经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少,第1层是资源类型,第2层是资源名,第3层是资源的Language),第3层录结构中的OffsetToData将指向IMAGE_RESOURCE_DATA_ENTRY结构。该结构描述了资、源数据的位置和大小,其定义如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">IMAGE RESOURCE DATA ENTRY STRUCT</span><br><span class="line">OffsetToData</span><br><span class="line">Size</span><br><span class="line">Code Page</span><br><span class="line">DWORD ?</span><br><span class="line">DWORD ?</span><br><span class="line">DWORD ?</span><br><span class="line">Reserved DWORD ?</span><br><span class="line">} IMA GE RESOURCE DATA ENTRY ENDS</span><br></pre></td></tr></table></figure><p>经过多层结构,此处的IMAGE_RESOURCE_DATA_ENTRY结构就是真正的资源数据了。结构中的OffsetToData指向资源数据的指针(其为RVA值)。</p><p><img src="http://imgset.gitee.io/img/1571159898220.png" alt="1571159898220"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>Window PE -- 输出表 基址重定位</title>
<link href="/2018/11/01/2018-11-01-Windows%20PE%20%E8%BE%93%E5%87%BA%E8%A1%A8/"/>
<url>/2018/11/01/2018-11-01-Windows%20PE%20%E8%BE%93%E5%87%BA%E8%A1%A8/</url>
<content type="html"><![CDATA[<p>Window PE – 输出表 基址重定位</p><h1 id="输出表"><a href="#输出表" class="headerlink" title="输出表"></a>输出表</h1><p>创建一个DLL时,实际上创建了一组能让XE或其他DLL调用的函数,此时PE装载器根据DLL文件中输出的信息修正被执行文件的IAT。当一个LL函数能被EXE或另一个DLL文件使用时,它就被“输出了”(Exported)。其中,输出信息被保存在输出表中,DLL文件通过输出表向系统提供输出函数名、序号和人口地址等信息。</p><p>EXE文件中一般不存在输出表,而大部分DLL文件中存在输出表。当然,这也不是绝对的,有些EE文件中也存在输出函数。</p><h2 id="输出表结构"><a href="#输出表结构" class="headerlink" title="输出表结构"></a>输出表结构</h2><p>输出表(ExportTable)主要内容是一个表格,其中包括函数名称、输出序数等。序数是指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。在此不提倡仅通过序数引出函数,这会带来DLL维护上的问题。一旦DLL升级或被修改,调用该DLL的程序将无法工作。</p><p>输出表是数据目录表的第I个成员,指向IMAGE_EXPORT_DIRECTORY(简称“IED”)结构。IED结构定义如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">IMAGE_EXPORT_DIRECTORY STRUCT【导出表,共40字节】</span><br><span class="line">{</span><br><span class="line">+00 h DWORD Characteristics ; 未使用,总是定义为0</span><br><span class="line">+04 h DWORD TimeDateStamp ; 文件生成时间</span><br><span class="line">+08 h WORD MajorVersion ; 未使用,总是定义为0</span><br><span class="line">+0A h WORD MinorVersion ; 未使用,总是定义为0</span><br><span class="line">+0C h DWORD Name ; 模块的真实名称</span><br><span class="line">+10 h DWORD Base ; 基数,加上序数就是函数地址数组的索引值</span><br><span class="line">+14 h DWORD NumberOfFunctions ; 导出函数的总数</span><br><span class="line">+18 h DWORD NumberOfNames ; 以名称方式导出的函数的总数</span><br><span class="line">+1C h DWORD AddressOfFunctions ; 指向输出函数地址的RVA</span><br><span class="line">+20 h DWORD AddressOfNames ; 指向输出函数名字的RVA</span><br><span class="line">+24 h DWORD AddressOfNameOrdinals ; 指向输出函数序号的RVA</span><br><span class="line">};IMAGE_EXPORT_DIRECTORY ENDS</span><br></pre></td></tr></table></figure><ul><li>Characteristics:输出属性的旗标。前还没有定义,总是为0。</li><li>TimeDateStamp:输出表创建的时间(GMT时间)。</li><li>MajorVersion:输出表的主版本号未使用,设置为0。</li><li>MinorVersion:输出表的次版本号。未使用,设置为。</li><li>Name:指向一个ASCII字符串的RVA。这个字符串是与这些输出函数相关联的DLL的名字(例如KERNEL32.DLL)。</li><li>Base:这个字段包含用于这个PE文件输出表起始序数值(基数)。在正常情况下这个值是1,但并非必须如此。当通过序数来查询一个输出函数时,这个值从序数里被减去,其结果将为进入输出地址表(EAT)的索引。</li><li>NumberOfFunctions:EAT中的条目数量。注意,一些条目可能是0,这个序数值表明没有代码或数据被输出。</li><li>NumberOfNames:输出函数名称表(ENT)里的条目数量。NumberOlNames的值总是小于或等于NumberOfFunctions的值,小于的情况发生在符号只通过序数输出的时候。另外,当被赋值的序数里有数字间距时也会是小于的情况,这个值也是输出序数表的长度。</li><li>AddressOfFunctions:EAT的RVA。EAT是一个RVA数组,数组中的每一个非零的RVA都对应于一个被输出的符号。</li><li>AddressOlNames:ENTRVA。ENT一个指向ASCII字符串的RVA组。每一个ASCII字符串对应于一个通过名字输出的符号。因为这个表是要排序的,所以ASCII字符串也是按顺序排列的。这允许加载器在查询一个被输出的符号时使用进制查找方式,名称的排序是二进制的(就像C++RTL中strcmp函数提供的一样),而不是一个环境特定的字母顺序。</li><li>AddressOfNameOrdinals:输出序数表的RVA。这个表是字的数组。个表将ENT中的数组索引映射到相应的输出地址表条目。</li></ul><p>设计输出表是为了方便PE装载器工作。首先,模块必须保存所有输出函数的地址,供PE装载器查询。块将这些息保存在AddressOfFunctions域所指向的数组中,而数组元素数目存放在NumberOfFunctions域中。如果模块引出了40个函数,那么在AddressOfFunctions指向的数组中必定有40个元素,NumberOfFunctions的值为40。如果有些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些名字的RVA值存放在一个数组中,供PE装载器查询。该数组由AddressOfNames指向,NumberOfNames中包含名字数目。PE装载知道函数名,并想、以此获取这些函数的地址。目前已有两个模块,分别是名字数组和地址数组,但两者之还没有联系的纽带,需要一些联系函数名及其地址为它们建立联系。PE文档指出,可以使用指向地址数组的索引作为连接,因此PE装载器在名字数组中找到匹配名字的同时,也获了指向地址表中对应元素的索引。这些索引保存在由AddressOfNameOrdinals域所指向的另一个数组(最后一个)中。由于该数组起联系名字和地址的作用,其元素数目一定与名字数组相同。例如,每个名字有且仅有1个相关地址,反过来则不一定(一个地址可有好几个名字来对应)因此,需要给同一个地址取“别名”。为了发挥连接作用,名字数组和索引数组必须并行成对使用,例如索引数组的第1个元素必定含有第1个名字的索引,依此推。</p><p><img src="http://imgset.gitee.io/img/1554867753098.png" alt="1554867753098"></p><h1 id="基址重定位"><a href="#基址重定位" class="headerlink" title="基址重定位"></a>基址重定位</h1><p>当链接器生成一个PE文件时,会假设这个文件在执行时被装载到默认的基地址处,并把code和data的相关地址都入PE文件。如果载入时将默认的值作为基地址载入,则不需要重定位。但是,如果PE文件被装载到虚拟内存的另一个地址中,链接器登记的那个地址就是错误的,这时就需要用重定位表来调整。在PE文件中,重定位表往往单独作为一块,用“.reloc”表示。</p><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>和NE格式的重定位方式不同,PE格式的做法十分简单。PE格式不参考外部DLL或模块中的其区块,而是把文件中所有可能需要修改的地址放在一个数组里。如果PE文件不在首选的地址载入,那么文件中的每一个定位都需要被修正。对加载器来说,它不需要知道关于地址使用的任何细节,只要知道有一系列的数据需要以某种一致的方式来修正就可以了。下面以实例DllDemo.DLL为例讲述其重定位过程。如下代码中两个加粗的地址指针就是需要重定位的数据。</p><p><img src="http://imgset.gitee.io/img/1554867886934.png" alt="1554867886934"></p><p>分析一下0040100Eh处,其作用是将一个指针压人枝,00402000h是某一字符串的指针。这句指令有5字节长,第l个字节(68h)是指令的操作码,后4个字节用来保存一个DWORD大小的地址(00402000h)。在这个例子中,指令来自-个基址为00400000h的DLL文件,因此这个字符串的RVA值是2000h。如果PE文件确实在00400000h处载人,指令就能够按照现在的样子正确执行。但是,当DLL执行时,Windows加载器决定将其映射到00870000h处(映射基址由系统决定),加载器就会比较基址和实际的载入地址,计算出一个差值。在这个例子中,差值是470000h,这个差值能被加载到DWORD大小的地址里以形成新地址。在前面的例子中,地址0040100Fh是指令中双字的定位,对它将有一个基址重定位,实际上字符串的新地址就是00872000h。为了让Windows有能力进行这样的调整,可执行文件中有多个“基址重定位数据”。本例中的Windows加载器应把470000h加给00402000h,并将结果00872000h写回原处。这个过程如下图所示。</p><p><img src="http://imgset.gitee.io/img/1554867929435.png" alt="1554867929435"></p><p>DllDemo.DLL在内存中进行重定位处理后的代码如下。</p><p><img src="http://imgset.gitee.io/img/1554867947723.png" alt="1554867947723"></p><p>对EXE文件来说,每个文件总是使用独立的虚拟地址空间,所以EXE总是能够按照这个地址载人,这意味着EXE文件不再需要重定位信息。对DLL来说,因为多个DLL文件使用宿主EXE文件的地址空间,不能保证载入地址没有被其他DLL使用,所以DLL文件中必须包含重定位信息,除非用一个/FIXED开关来忽略它们。在VisualStudio.NET中,链接器会为Debug和Release模式的EXE文件省略基址重定位,因此,在不同系统中跟踪同一个DLL文件时,其虚拟地址是不同的,也就是说,在读者的机器里运行DllDemo.DLL,Windows加载器映射的基址可能不是00870000h,而是其他地址。</p><h2 id="重定位表的结构"><a href="#重定位表的结构" class="headerlink" title="重定位表的结构"></a>重定位表的结构</h2><p>基址重定位表(BaseRelocationTable)位于一个.reloc区块内,找到它们的正确方式是通过数据目录表的IMAGE_DIRECTORY_ENTRY_BASERELOC条目查找。基址重定位数据采用类似按页分割的方法组织,是由许多重定位块串接成的,每个块中存放4KB(lOOOh)的重定位信息,每个重定位数据块的大小必须以DWORD(4字节)对齐。它们以一个IMAGE_BASE_RELOCATION结构开始,格式如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">IMAGE_BASE_RELOCATION STRUC 【基址重定位位于数据目录表的第六项,共8+N字节】</span><br><span class="line">{</span><br><span class="line">+00 h DWORD VirtualAddress ;重定位数据开始的RVA 地址</span><br><span class="line">+04 h DWORD SizeOfBlock ;重定位块得长度,标识重定向字段个数</span><br><span class="line">+08 h WORD TypeOffset ;重定项位数组相对虚拟RVA,个数动态分配</span><br><span class="line">};</span><br><span class="line">IMAGE_BASE_RELOCATION ENDS</span><br></pre></td></tr></table></figure><ul><li>Virtua!Address:这组重定位数据的开始RVA地址。各重定位项的地址加这个值才是该重定位项的完整RVA地址。</li><li>SizeOfBlock:当前重定位结构的大小。因为Virtua!Address和SizeOfBlock的大小都是固定的4字节,所以这个值减8就是TypeOffset数组的大小。</li><li>TypeOffset:一个数组。数组每项大小为2字节,共16位。这16位分为高4位和低12位。高4位代表重定位类型;低12位是重定位地址,它与VirtualAddress相加就是指向PE映像中需要修改的地址数据的指针。</li></ul><p>常见的重定位类型如下表所示。虽然有多种重定位类型,但对x86可执行文件来说,所有的基址重定位类型都是IMAGE_REL_BASED_HIGHLOW。在一组重定位结束的地方会出现一个类型是IMAGE_REL_BASED_ABSOLUTE的重定位,这些重定位什么都不做,只用于填充,以便下一个IMAGE_BASE_RELOCATION按4字节分界线对齐。所有重定位块以一个V川ua!Address段为0的IMAGE_BASE_RELOCATION结构结束。</p><p><img src="http://imgset.gitee.io/img/1554868151561.png" alt="1554868151561"></p><p>重定位表的结构如下图所示,它由数个IMAGE_BASE_RELOCATION结构组成,每个结构由VirtualAddress、SizeOfBlock和TypeOffset3部分组成。</p><p><img src="http://imgset.gitee.io/img/1554868174075.png" alt="1554868174075"></p><p>对于IA-64可执行文件,重定位类型似乎总是IMAGE_REL_BASED_DIR64。就像x86重定位,也用IMAGE_REL_BASED_ABSOLUTE重定位类型进行填充有趣的是,尽管IA-64的EE页大小是8KB,但基址重定位仍是4KB的块。</p><p><img src="http://imgset.gitee.io/img/1571159890689.png" alt="1571159890689"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>一个简单的CTF小DEMO</title>
<link href="/2018/11/01/2018-11-02%20%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84CTF%E5%B0%8Fdemo/"/>
<url>/2018/11/01/2018-11-02%20%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84CTF%E5%B0%8Fdemo/</url>
<content type="html"><![CDATA[<h1 id="一个简单的CTF小DEMO"><a href="#一个简单的CTF小DEMO" class="headerlink" title="一个简单的CTF小DEMO"></a>一个简单的CTF小DEMO</h1><p>不说废话 ,开始看代码</p><p><img src="http://imgset.gitee.io/img/1563290467065.png" alt="1563290467065"></p><p>main函数的开始提示输入FLAG</p><p><img src="http://imgset.gitee.io/img/1563290577373.png" alt="1563290577373"></p><p>输入完FLAG之后进行长度判断 上图中的jnb跳转到跳转到失败输出代码,也就是说FLAG的长度小于0x1E,下面ja跳转到FLAG判断代码。</p><p>这两个cmp转换为c代码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(<span class="number">4</span> < flag < <span class="number">0x1E</span>)</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1563290874171.png" alt="1563290874171"></p><p>上图为jnb跳转的位置,输出Sorry,keep trying!,跳转到return</p><p><img src="http://imgset.gitee.io/img/1563291020325.png" alt="1563291020325"></p><p>上图为ja跳转的位置loc_401093。</p><p>for循环判断输入的 FLAG的前四个字节是否为EIS{,上图中的变量register_header保存的ASSCII码“EIS{”,循环跳出条件为register_header的长度。</p><p><img src="http://imgset.gitee.io/img/1563291253721.png" alt="1563291253721"></p><p> for循环结束又是一个判断,判断FLAG的第0x1C字节处的ASCII码是否为‘}’,如果不是输出Sorry ,keep trying !并跳转到return,那么0x1C + 1就是FLAG的长度,去掉EIS{},真正参与计算的代码的长度为24。</p><p><img src="http://imgset.gitee.io/img/1563291427190.png" alt="1563291427190"></p><p>上图为ja跳转的位置,传入FLAG,调用calc_register_code,判断其返回值,calc_register_code也就是真正的计算函数。</p><p><img src="http://imgset.gitee.io/img/1563291512011.png" alt="1563291512011"></p><p>进入calc_register_code</p><p>首先判断FLAG是否大于4,如果小于等于4跳转到return。</p><p><img src="http://imgset.gitee.io/img/1563291610231.png" alt="1563291610231"></p><p>紧接着又是一个for循环,把FLAG+4之后的数据拷贝一份拷贝到back_str</p><p><img src="http://imgset.gitee.io/img/1563291796614.png" alt="1563291796614"></p><p>上图中黄色部分把拷贝出来的back_str最后一个字节赋值为0,也就是将 ‘}’ 修改为0,此时的back_str保存了EIS{….},括号里的内容长度为24字节。</p><h2 id="计算部分"><a href="#计算部分" class="headerlink" title="计算部分"></a>计算部分</h2><p><img src="http://imgset.gitee.io/img/1563292006037.png" alt="1563292006037"></p><p>for循环部分,如果循环次数大于back_str的长度跳出循环</p><p><img src="http://imgset.gitee.io/img/1563292026464.png" alt="1563292026464"></p><p>if判断,在两个cmp中看到如果back_str[i]不是小写字母,则跳到loc_4012FF处,如果是小写字母,将这个字节进行减掉0x20转换为大写字母。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//C伪代码</span></span><br><span class="line"><span class="keyword">if</span>(<span class="string">'a'</span> <= back_str[i] <= <span class="string">'z'</span>)</span><br><span class="line">back_str[i] -= <span class="number">0x20</span></span><br></pre></td></tr></table></figure><p>字母转换结束后,在上图的最后一行代码,将var_B0赋值为(var_B0为DWORD类型)1。</p><p><img src="http://imgset.gitee.io/img/1563292351121.png" alt="1563292351121"></p><p>紧接着又是一个if判断,这个if判断,首先判断var_B0是否为0,不为0跳转到loc_401340处,然后判断back_str[i]是否为大写字母,如果是大写字母进行加0x20,转换为小写字母。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//C伪代码</span></span><br><span class="line"><span class="keyword">if</span>(var_B0 != <span class="number">0</span> && <span class="string">'A'</span> <= back_str[i] <= <span class="string">'Z'</span>)</span><br><span class="line">back_str[i] += <span class="number">0x20</span></span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1563292555155.png" alt="1563292555155"></p><p>字母转换之后将转换之后的字节传入到char_calc进行计算。</p><p><img src="http://imgset.gitee.io/img/1563292601289.png" alt="1563292601289"></p><p>char_calc将传入的字节进行异或0x55,后在加上0x48返回。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//c伪代码</span></span><br><span class="line"><span class="function"><span class="keyword">char</span> <span class="title">char_calc</span><span class="params">(<span class="keyword">char</span> ch)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">ch |= <span class="number">0x55</span>;</span><br><span class="line">ch += <span class="number">0x48</span></span><br><span class="line"><span class="keyword">return</span> ch;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1563292724261.png" alt="1563292724261"></p><p>char_calc计算之后,将计算的结果和calc_code[i]进行异或计算,最后结果保存到var_AC[i]中。</p><p>calc_code 的内容如下</p><p><img src="http://imgset.gitee.io/img/1563292853691.png" alt="1563292853691"></p><p>保存完成之后进行第二次for循环。</p><p><img src="http://imgset.gitee.io/img/1563292932569.png" alt="1563292932569"></p><p>for循环结束后,将计算出来的结果和<img src="http://imgset.gitee.io/img/1563295477991.png" alt="1563295477991">进行比较,如果一样返回1,否则返回0。</p><p>Good Job.</p><h3 id="A"><a href="#A" class="headerlink" title="A"></a>A</h3><p><img src="http://imgset.gitee.io/img/1563293861722.png" alt="1563293861722"></p><p><img src="http://imgset.gitee.io/img/1563293876622.png" alt="1563293876622"></p><h3 id="最终的注册码"><a href="#最终的注册码" class="headerlink" title="最终的注册码"></a>最终的注册码</h3><p><img src="http://imgset.gitee.io/img/1563293808998.png" alt="1563293808998"></p><p>上图中的done_code为注册码。</p><p><a href="http://imgset.gitee.io/img/1571159884917.png">1571159884917</a></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>Window PE -- 初识</title>
<link href="/2018/10/31/2018-10-31-Windows%20PE%20%E5%88%9D%E8%AF%86/"/>
<url>/2018/10/31/2018-10-31-Windows%20PE%20%E5%88%9D%E8%AF%86/</url>
<content type="html"><![CDATA[<h1 id="Window-PE-–-初识"><a href="#Window-PE-–-初识" class="headerlink" title="Window PE – 初识"></a>Window PE – 初识</h1><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>PE文件使用的是一个平面地址空间,所有代码和数据都合并在一起,组成了一个很大的结构。文件的内容被分割为不同的区块(Section,又称区段、节等,在本章中不区分“区块”与“块”),区块中包含代码或数据,各个区块按页边界对齐。区块没有大小限制,是一个连续结构。每个块都有它自己在内存中的一套属性,例如这个块是否包含代码、是否只读或可读/写等。</p><p>认识到PE文件不是作为单一内存映射文件被载入内存是很重要的。Windows加载器(又称PE装载器)遍历PE文件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。盘文件一旦被载入内存,磁盘上的数据结构布局和内存中的数据结构布局就是一致的。这样,如果在磁盘的数据结构中寻找一些内容,那么几乎都能在被载人的内存映射文件中找到相同的信息,但数据之间的相对位置可能会改变,某项的偏移地址可能区别于原始的偏移位置。</p><p>不管怎样,对所有表现出来的信息,都允许进行从磁盘文件偏移到内存偏移的转换。</p><h3 id="基地址"><a href="#基地址" class="headerlink" title="基地址"></a>基地址</h3><p>当PE文件通过Windows加载器载入内存后,内存中的版本称为模块(Module)。映射文件的起始地址称为模块句柄(hModule),可以通过模块句柄访问内存中的其他数据结构。这个初始内存地址也称为基地址(ImageBase)。准确地说,对于WindowsCE,这是不成立的,一个模块句辆在WindowsCE下井不等同于安装的起始地址。</p><p><img src="http://imgset.gitee.io/img/1554714173177.png" alt="1554714173177"></p><p>内存中的模块代表进程将这个可执行文件所需要的代码、数据、资源、输入表、输出表及其他有用的数据结构所使用的内存都放在一个连续的内存块中,程序员只要知道装载程序文件映像到内存后的基地址即可。PE文件的剩余部分可以被读人,但可能无法被映射。例如,在将调试信息放到文件尾部时,PE的一个字段会告诉系统把文件映射到内存时需要使用多少内存,不能被映射的数据将被放置在文件的尾部。方便起见,WindowNT或Windows95将Modul的基地址作为Module的实例句柄(InstanceHandle,即Hinstance)。</p><p>在32位Windows系统中,因为InstanceHandle来源于16位的Windows3.1,其中每执行实例都有自己的数据段并以此来互相区分(这就是InstanceHandle的来历)。在32位Windows系统中,因为不存在共享地址空间,所以应用程序无须加以区别。当然,16位Windows系统和32位Windows系统中的Hinstance还有些联系:在32位Windows系统中可以直接调用GetModuleHandle以取得指向DLL的指针,通过指针访问该DLLModule的内容,示例如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>调用该函数时会传递一个可执行文件或DLL文件名字符串,如果系统找到文件,则返回该可执行文件或DLL文件映像所加载的基地址。也可以调用GetModuleHandle来传递NULL参数,此时将返回调用的可执行文件的基地址。</p><p>基地址的值是由PE文件本身设定的。按照默认设置,用VisualC++建立的EXE文件的基地址是400000h、DLL文件的基地址是10000000h。可以在创建应用程序的EXE文件时改变这个地址,方法是在链接应用时使用链接程序的/BASE选项,或者在链接后通过REBASE应用程序进行设置。</p><h3 id="虚拟地址"><a href="#虚拟地址" class="headerlink" title="虚拟地址"></a>虚拟地址</h3><p>在Windows系统中,PE文件被系统加载器映射到内存中。每个程序都有自己的虚拟空间,这个虚拟空间的内存地址称为虚拟地址(VirtualAddress,VA)</p><h3 id="相对虚拟地址"><a href="#相对虚拟地址" class="headerlink" title="相对虚拟地址"></a>相对虚拟地址</h3><p>在可执行文件中,有许多地方需要指定内存中的地址。例如,引用全局变量时需要指定它的地址。PE文件尽管有一个首选的载入地址(基地址),但是它们可以载入进程空间的任何地方,所以不能依赖PE的载入点。因此必须有一个方法来指定地址(不依赖PE载入点的地址)。</p><p>为了避免在E文件中出现绝对内存地址引人了相对虚拟地址(RelativeVirtualAddress,RVA)的概念。RVA只是内存中的一个简单的、相对于PE件载入地址的偏移位置,它是一个“相对”地址(或称偏移量)。例如,假设一个EXE文件从400000h处载人,而且它的代码区块开始于401000h处,代码区块的RVA计算方法如下:</p><p>目标地址401000h - 载入地址400000h=RVA 1000h</p><p>将一个VA转换成真实的地址只是简单地翻转这个过程,即用实际的载入地址加RVA得到实际的内存地址。它们之间的关系如下:</p><p>虚拟地址(VA)=基地址(ImageBase)+相对虚拟地址(RVA)</p><h2 id="文件虚拟地址"><a href="#文件虚拟地址" class="headerlink" title="文件虚拟地址"></a>文件虚拟地址</h2><p>当PE文件储存在磁盘中时,某个数据的位置相对于文件头的偏移量称为文件偏移地址(FileOffset)或物理地址(RAWOffset)。文件偏移地址从PE文件的第1个字节开始计数,起始值为0。用十六进制工具(例如HexWorkshopWinHex等)打开文件时所显示的地址就是文件偏移地址。</p><h2 id="MS-DOS头部"><a href="#MS-DOS头部" class="headerlink" title="MS-DOS头部"></a>MS-DOS头部</h2><p>每个PE文件都是以一个DOS程序开始的,有了它,一旦程序在DOS下执行,DOS就能识别出这是一个有效的执行体,然后运行紧随MZ header的DOS stub(DOS块)。DOSstub实际上是一个有效的EXE,在不支持PE文件格式的操作系统中它将单地显示一个错误提示,类似于字符串“This program cannot be rnn in MS-DOS mode”。程序员也可以根据自己的意图实现完整的DOS代码。用户通常对DOSstub不太感兴趣,因为在大多数情况下它是由汇编器/编译器自动生成的。我们通常把DOS MZ头与DOS stub合称为DOS文件头。</p><p>PE件的第个字节位于一个传统的MS-DOS头部,称作IMAGE_DOS_HEADER,其结构如下(左边的数字是到文件头的偏移量)。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">IMAGE_DOS_HEADER</span> {</span> <span class="comment">// DOS .EXE header</span></span><br><span class="line"> USHORT e_magic; <span class="comment">// Magic number</span></span><br><span class="line"> USHORT e_cblp; <span class="comment">// Bytes on last page of file</span></span><br><span class="line"> USHORT e_cp; <span class="comment">// Pages in file</span></span><br><span class="line"> USHORT e_crlc; <span class="comment">// Relocations</span></span><br><span class="line"> USHORT e_cparhdr; <span class="comment">// Size of header in paragraphs</span></span><br><span class="line"> USHORT e_minalloc; <span class="comment">// Minimum extra paragraphs needed</span></span><br><span class="line"> USHORT e_maxalloc; <span class="comment">// Maximum extra paragraphs needed</span></span><br><span class="line"> USHORT e_ss; <span class="comment">// Initial (relative) SS value</span></span><br><span class="line"> USHORT e_sp; <span class="comment">// Initial SP value</span></span><br><span class="line"> USHORT e_csum; <span class="comment">// Checksum</span></span><br><span class="line"> USHORT e_ip; <span class="comment">// Initial IP value</span></span><br><span class="line"> USHORT e_cs; <span class="comment">// Initial (relative) CS value</span></span><br><span class="line"> USHORT e_lfarlc; <span class="comment">// File address of relocation table</span></span><br><span class="line"> USHORT e_ovno; <span class="comment">// Overlay number</span></span><br><span class="line"> USHORT e_res[<span class="number">4</span>]; <span class="comment">// Reserved words</span></span><br><span class="line"> USHORT e_oemid; <span class="comment">// OEM identifier (for e_oeminfo)</span></span><br><span class="line"> USHORT e_oeminfo; <span class="comment">// OEM information; e_oemid specific</span></span><br><span class="line"> USHORT e_res2[<span class="number">10</span>]; <span class="comment">// Reserved words</span></span><br><span class="line"> LONG e_lfanew; <span class="comment">// File address of new exe header</span></span><br><span class="line"> } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</span><br></pre></td></tr></table></figure><p>其中有两个字段比较重要,分别是e_magic和e_lfanew 。e_magic字段(1个字大小)的值需要被设置为5A4Dh。这个值有一个#define,名为IMAGE_DOS_SIGNATURE,在ASCII表示法里它的ASCII值为“MZ”,是MS-DOS的创建者之一MarkZbikowski名字的缩写。e_lfanew字段是真正的PE文件头的相对偏移(RVA),指出真正的PE头的文件偏移位置,占用4字节,位于从文件开始偏移3Ch字节处。</p><p>用十六进制编辑器(WinHex,HexWorkshop等带偏移量显示功能的尤佳)打开随书文件中的示例程序PE.exe定位在文件起始位置,此处就是MS-DOS头部,如图11.3所示。文件的第l个字柯:“MZ”就是e_magic字段;偏移量3Ch就是e_lfanew的值,在这里显示为“B000000。”。为IntelCPU属于Little-Endian类,字符储存时低位在前,高位在后,所以,将次序恢复后,e_lfanew的值为000000b0h,这个值就是真正的PE文件头偏移量。</p><p><img src="http://imgset.gitee.io/img/1554715138941.png" alt="1554715138941"></p><h1 id=""><a href="#" class="headerlink" title=""></a><img src="http://imgset.gitee.io/img/1571159929511.png" alt="1571159929511"></h1>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> PE </tag>
</tags>
</entry>
<entry>
<title>Window SEH异常 -- 异常基础</title>
<link href="/2018/10/31/2018-10-31-Window%20SEH%20%E5%8E%9F%E7%90%86/"/>
<url>/2018/10/31/2018-10-31-Window%20SEH%20%E5%8E%9F%E7%90%86/</url>
<content type="html"><![CDATA[<h1 id="Window-SEH异常-–-异常基础"><a href="#Window-SEH异常-–-异常基础" class="headerlink" title="Window SEH异常 – 异常基础"></a>Window SEH异常 – 异常基础</h1><h2 id="SEH数据结构"><a href="#SEH数据结构" class="headerlink" title="SEH数据结构"></a>SEH数据结构</h2><h3 id="TIB-结构"><a href="#TIB-结构" class="headerlink" title="TIB 结构"></a>TIB 结构</h3><p>TIB(ThreadInformatio它位于TEB(ThreadEnvironmentBlock,线程环境块)的头部,而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">NT_TIB</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> PEXCEPTION_REGISTRATION_RECORD ExceptionList;</span><br><span class="line"> PVOID StackBase;</span><br><span class="line"> PVOID StackLimit;</span><br><span class="line"> PVOID SubSystemTib;</span><br><span class="line"> <span class="class"><span class="keyword">union</span></span></span><br><span class="line"><span class="class"> {</span></span><br><span class="line"> PVOID FiberData;</span><br><span class="line"> ULONG Version;</span><br><span class="line"> };</span><br><span class="line"> PVOID ArbitraryUserPointer;</span><br><span class="line"> PNT_TIB Self;</span><br><span class="line">} NT_TIB, *PNT_TIB;</span><br></pre></td></tr></table></figure><p>然Windows系统经历了多次更新换代,但是从Windows 2000到Windows10,TIB结构都没有变化。其中,与异常处理相关的项是指向EXCEPTION_REGISTRATION_RECORD结构的指针ExceptionList,它位于TIB的偏移0处,同时在TEB的偏移0处。在x86平台的用户模式下,Windows将FS段选择器指向当前线程的TEB据,即TEB总是由fs:[O]指向的(在x64平台上,这个指向关系变成了gs:[O]。关于x64平台上的异常处理,会在8.5节详细讲述)。而当线程运行在内核模式下时,Windows将FS段选择器指向内核中的KPCRB结构(Processor Control Region Block ,处理器控制块),该结构的头部同样是上述的NT_TIB构。</p><h3 id="EXCEPTION-REGISTRATION-RECORD结构"><a href="#EXCEPTION-REGISTRATION-RECORD结构" class="headerlink" title="EXCEPTION_REGISTRATION_RECORD结构"></a>EXCEPTION_REGISTRATION_RECORD结构</h3><p>TEB移量为0的_EXCEPTION_REGISTRATION_RECORD主要用T描述线程异常处理程的地址,多个该结构的链表描述了多个线程异常处理过程的嵌套层次关系,其定义如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">EXCEPTION_REGISTRATION_RECORD</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> PEXCEPTION_REGISTRATION_RECORD Next;</span><br><span class="line"> PEXCEPTION_DISPOSITION Handler;</span><br><span class="line">} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;</span><br></pre></td></tr></table></figure><p>其中,“Next”是指向下一个_EXCEPTIONREGISTRATION_RECORD(简称“ERR”)的指针,形成一链状结构,而链表头就存放在也:[O]指向的TEE中;"Handler”指向异常处理回调函数,如下图。</p><p><img src="http://imgset.gitee.io/img/1554693090658.png" alt="1554693090658"></p><p>当程序运行过程中产生异常时,系统的异常分发器就会从fs:[0]处取得异常处理的链表头,然后查找异常处理链表并依次调用各个链表节点中的异常处理回调函数。由于TEE是线程的私有数据结构,相应地,钱程也都有自己的异常处理链表,即SEH机制的作用范围仅限于当前线程。从数据结构的角度来讲,SEH链就是一个只允许在链表头部进行增加和删除节点操作的单向链表,且链表头部永远保存在fs:[0]处的TEB结构中。</p><h3 id="EXCEPTION-POINTERS结构"><a href="#EXCEPTION-POINTERS结构" class="headerlink" title="EXCEPTION_POINTERS结构"></a>EXCEPTION_POINTERS结构</h3><p>一个异常发生时,在没有调试器干预的情况下,操作系统会将异常信息转交给用户态的异常处理过程。实际上,由于同一个线程在用户态和内核态使用的是两个不同的拢,为了让用户态的常处理程序能够访问与异常相关的数据,操作系统必须把与本次异常相关联的EXCEPTION_RECORD结构和CONTEXT结构放到用户态战中,同时在党中放置一个_EXCEPTION_POINTERS结构,它包含两个指针,一指向EXCEPTION_RECORD锚构,另一个指向CONTEXT结构,示例如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"></span><br></pre></td></tr></table></figure><p>这样,用户态的异常处理程序就能够取得异常的具体信息和发生异常时线程的状态信息,并根据具体情况进行处理了。</p><h2 id="SEH处理程序的安装与卸载"><a href="#SEH处理程序的安装与卸载" class="headerlink" title="SEH处理程序的安装与卸载"></a>SEH处理程序的安装与卸载</h2><p>由于fs:[0]总是指向当前异常处理程序的链表头,当程序中需要安装一个新的SEH异常处理程序时,只要填写一个新的EXCEPTION_REGISTRATION_RECORD结构,并将其插入该链表的头部即可。根据SEH的设计要求,它的作用范围与安装它的函数相同,所以通常在函数头部安装SEH异常处理程序,在函数返回前卸载可以说,SEH是基于战帧的异常处理机制。</p><p>在安装SEH理程序之前,需要准备一个符合SEH标准的回调函数,然后使用如下代码进行SEH异常处理程序的安装。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">assume fs:nothing</span><br><span class="line">push offset SEHandler</span><br><span class="line">push fs:[<span class="number">0</span>]</span><br><span class="line">mov fs:[<span class="number">0</span>],esp</span><br></pre></td></tr></table></figure><p>“assume fs:nothing”是MASM编译器的特殊要求,若不满足该要求将出现编译错误,后面3行则是注册回调函数。“push offset SEHandler”和“push fs:[0]”相继向棋中压入了Handler和当前的SEH链表头,这两个元素构成了一个新的_EXCEPTION_REGISTRATION_RECORD结构,此时它的位置就在桔顶,即esp指向的位置。然后,esp(也就是最新的链表头)保存到fs:[0]中也就是修改TIB结中的ExceptionList,相当于向链表中插入了一个新节点。该操作前后SEH链表的变化如下图。</p><p><img src="http://imgset.gitee.io/img/1554693437750.png" alt="1554693437750"></p><p>理解了SEH的安装过,再看SEH的卸载就比较简单了,只要把刚才保存的fs:[O]的原始值填回并恢复栓的平衡即可,相当于从链表头部删掉了一个节点,示例如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mov esp,dword ptr fs:[<span class="number">0</span>]</span><br><span class="line">pop dword ptr fs:[<span class="number">0</span>]</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1571159936722.png" alt="1571159936722"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> SEH </tag>
</tags>
</entry>
<entry>
<title>Window SEH异常 -- 异常初识</title>
<link href="/2018/10/31/2018-10-31-Windows%20Seh%20%E5%88%9D%E8%AF%86/"/>
<url>/2018/10/31/2018-10-31-Windows%20Seh%20%E5%88%9D%E8%AF%86/</url>
<content type="html"><![CDATA[<p> Window SEH异常 – 异常初识</p><h2 id="异常处理的基本概念"><a href="#异常处理的基本概念" class="headerlink" title="异常处理的基本概念"></a>异常处理的基本概念</h2><p>所谓异常就是在应用程序正常执行过程中发生的不正常事件。由CPU引发的异常称为硬件异常,例如访问一个无效的内存地址由操作系统或应用程序引发的异常称为软件异常。</p><p>常见的异常见下表</p><p><img src="http://imgset.gitee.io/img/1554688283899.png" alt="1554688283899"></p><h2 id="异常处理的基本过程"><a href="#异常处理的基本过程" class="headerlink" title="异常处理的基本过程"></a>异常处理的基本过程</h2><p>Windows常启动后,将运行在保护模式下,当有中断或异常发生时,CPU会通过中断描述柯:表(IDT)来寻找处理函数。因此,IDT表是CPU(硬件)和操作系统(软件)交接中和异常的关口。</p><h3 id="IDT"><a href="#IDT" class="headerlink" title="IDT"></a>IDT</h3><p>IDT是一张位于物内存中的线性表,共有256项。在32位模式下每个IDT项的长度是8字节,在64位模式下则为64字节。</p><p>操作系统在启动阶段会初始化这个表,系统中的每个CPU都有一份IDT的拷贝。下面主要讨论32位模式下的IDT。IDT的位置和长度是由CPU的IDTR寄存器描述的。IDTR寄存器共有48位,其中高32位是表的基址,低16位是表的长度。尽管可以使用SIDT和LIDT令来读写该寄存器,但LIDT是特权指令,只能在Ring 0特权级下运行。</p><p>IDT的每一项是一个门结构,它是发生中断或异常时CPU转移控制权的必经之路,包括如下3种门描述符。</p><ul><li>任务门(Task-gate)描述符,主要用于CPU的任务切换(TSS功能)。</li><li>中断门(Interrupt-gate)描述符,主要用于描述中断处理程序的人口。</li><li>陷阱门(Trap-gate)描述符,主要用于描述异常处理程序的人口。</li></ul><p>使用WinDbg的本地内核调试模式可以比较方便地观察IDT。</p><p><img src="http://imgset.gitee.io/img/1554688741507.png" alt="1554688741507"></p><p><img src="http://imgset.gitee.io/img/1554688751713.png" alt="1554688751713"></p><p>可以看到,02、08和12项就是任务门的处理过程,其他项是陷阱门的处理过程,在一些没有显示的内容中包含了中断门的处理过程。</p><h3 id="异常处理的准备工作"><a href="#异常处理的准备工作" class="headerlink" title="异常处理的准备工作"></a>异常处理的准备工作</h3><p>当有中断或异常发生时,CPU会根据中断类型号(这里其实把异常也视为一种中断)转而执行对应的中断处理程序,对异常来说就是上面看到的KiTrapXX函数。例如,中断号03对应于一个断点异常,当该异常发生时,CPU就会执行nt!KiTrap03函数来处理该异常。各个异常处理函数除了针对本异常的特定处理之外,通常会将异常信息进行封装,以便进行后续处理。</p><p>封装的内容主要有两部分。一部分是异常记录,包含本次异常的信息,该结构定义如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">EXCEPTION_RECORD</span> {</span></span><br><span class="line"> DWORD ExceptionCode;</span><br><span class="line"> DWORD ExceptionFlags;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">EXCEPTION_RECORD</span> *<span class="title">ExceptionRecord</span>;</span></span><br><span class="line"> PVOID ExceptionAddress;</span><br><span class="line"> DWORD NumberParameters;</span><br><span class="line"> ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];</span><br><span class="line">} EXCEPTION_RECORD;</span><br></pre></td></tr></table></figure><p>其中,ExceptionCode字段定义了异常的产生原因,下表列出了一些常见的异常产生原因。当然,也可以定义自己的Excer廿ExceptionCode,自定义代码可在API函数RaiseException中使用。</p><p><img src="http://imgset.gitee.io/img/1554688974585.png" alt="1554688974585"></p><p>一部分被封装的内容称为陷阱帧,它精确描述了发生异常时线程的状态(Windows的任务调度是基于线程的)。该结构与处理器高度相关,因此在不同的平台上(Intel x86/x64、MIPS、Alpha和PowerPC处理器等)有不同的定义。在常见的x86平台上,该结构定义如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">KTRAP_FRAME</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> ULONG DbgEbp;</span><br><span class="line"> ULONG DbgEip;</span><br><span class="line"> ULONG DbgArgMark;</span><br><span class="line"> ULONG DbgArgPointer;</span><br><span class="line"> WORD TempSegCs;</span><br><span class="line"> UCHAR Logging;</span><br><span class="line"> UCHAR Reserved;</span><br><span class="line"> ULONG TempEsp;</span><br><span class="line"> ULONG Dr0;</span><br><span class="line"> ULONG Dr1;</span><br><span class="line"> ULONG Dr2;</span><br><span class="line"> ULONG Dr3;</span><br><span class="line"> ULONG Dr6;</span><br><span class="line"> ULONG Dr7;</span><br><span class="line"> ULONG SegGs;</span><br><span class="line"> ULONG SegEs;</span><br><span class="line"> ULONG SegDs;</span><br><span class="line"> ULONG Edx;</span><br><span class="line"> ULONG Ecx;</span><br><span class="line"> ULONG Eax;</span><br><span class="line"> ULONG PreviousPreviousMode;</span><br><span class="line"> PEXCEPTION_REGISTRATION_RECORD ExceptionList;</span><br><span class="line"> ULONG SegFs;</span><br><span class="line"> ULONG Edi;</span><br><span class="line"> ULONG Esi;</span><br><span class="line"> ULONG Ebx;</span><br><span class="line"> ULONG Ebp;</span><br><span class="line"> ULONG ErrCode;</span><br><span class="line"> ULONG Eip;</span><br><span class="line"> ULONG SegCs;</span><br><span class="line"> ULONG EFlags;</span><br><span class="line"> ULONG HardwareEsp;</span><br><span class="line"> ULONG HardwareSegSs;</span><br><span class="line"> ULONG V86Es;</span><br><span class="line"> ULONG V86Ds;</span><br><span class="line"> ULONG V86Fs;</span><br><span class="line"> ULONG V86Gs;</span><br><span class="line">} KTRAP_FRAME, *PKTRAP_FRAME;</span><br></pre></td></tr></table></figure><p>可以看到,上述结构中包含每个寄存器的状态,但该结构一般仅供系统内核自身或者调试系统使用。当需要把控制权交给用户注册的异常处理程序时,会将上述结构情换成一个名为CONTEXT的结构,它包含线程运行时处理器各主要寄存器的完整镜像,用于保存全程运行环境。</p><p>x86平台上的CONTEXT结构如下。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">CONTEXT</span> {</span></span><br><span class="line"> DWORD64 P1Home;</span><br><span class="line"> DWORD64 P2Home;</span><br><span class="line"> DWORD64 P3Home;</span><br><span class="line"> DWORD64 P4Home;</span><br><span class="line"> DWORD64 P5Home;</span><br><span class="line"> DWORD64 P6Home;</span><br><span class="line"> DWORD ContextFlags;</span><br><span class="line"> DWORD MxCsr;</span><br><span class="line"> WORD SegCs;</span><br><span class="line"> WORD SegDs;</span><br><span class="line"> WORD SegEs;</span><br><span class="line"> WORD SegFs;</span><br><span class="line"> WORD SegGs;</span><br><span class="line"> WORD SegSs;</span><br><span class="line"> DWORD EFlags;</span><br><span class="line"> DWORD64 Dr0;</span><br><span class="line"> DWORD64 Dr1;</span><br><span class="line"> DWORD64 Dr2;</span><br><span class="line"> DWORD64 Dr3;</span><br><span class="line"> DWORD64 Dr6;</span><br><span class="line"> DWORD64 Dr7;</span><br><span class="line"> DWORD64 Rax;</span><br><span class="line"> DWORD64 Rcx;</span><br><span class="line"> DWORD64 Rdx;</span><br><span class="line"> DWORD64 Rbx;</span><br><span class="line"> DWORD64 Rsp;</span><br><span class="line"> DWORD64 Rbp;</span><br><span class="line"> DWORD64 Rsi;</span><br><span class="line"> DWORD64 Rdi;</span><br><span class="line"> DWORD64 R8;</span><br><span class="line"> DWORD64 R9;</span><br><span class="line"> DWORD64 R10;</span><br><span class="line"> DWORD64 R11;</span><br><span class="line"> DWORD64 R12;</span><br><span class="line"> DWORD64 R13;</span><br><span class="line"> DWORD64 R14;</span><br><span class="line"> DWORD64 R15;</span><br><span class="line"> DWORD64 Rip;</span><br><span class="line"> <span class="class"><span class="keyword">union</span> {</span></span><br><span class="line"> XMM_SAVE_AREA32 FltSave;</span><br><span class="line"> NEON128 Q[<span class="number">16</span>];</span><br><span class="line"> ULONGLONG D[<span class="number">32</span>];</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> M128A Header[<span class="number">2</span>];</span><br><span class="line"> M128A Legacy[<span class="number">8</span>];</span><br><span class="line"> M128A Xmm0;</span><br><span class="line"> M128A Xmm1;</span><br><span class="line"> M128A Xmm2;</span><br><span class="line"> M128A Xmm3;</span><br><span class="line"> M128A Xmm4;</span><br><span class="line"> M128A Xmm5;</span><br><span class="line"> M128A Xmm6;</span><br><span class="line"> M128A Xmm7;</span><br><span class="line"> M128A Xmm8;</span><br><span class="line"> M128A Xmm9;</span><br><span class="line"> M128A Xmm10;</span><br><span class="line"> M128A Xmm11;</span><br><span class="line"> M128A Xmm12;</span><br><span class="line"> M128A Xmm13;</span><br><span class="line"> M128A Xmm14;</span><br><span class="line"> M128A Xmm15;</span><br><span class="line"> } DUMMYSTRUCTNAME;</span><br><span class="line"> DWORD S[<span class="number">32</span>];</span><br><span class="line"> } DUMMYUNIONNAME;</span><br><span class="line"> M128A VectorRegister[<span class="number">26</span>];</span><br><span class="line"> DWORD64 VectorControl;</span><br><span class="line"> DWORD64 DebugControl;</span><br><span class="line"> DWORD64 LastBranchToRip;</span><br><span class="line"> DWORD64 LastBranchFromRip;</span><br><span class="line"> DWORD64 LastExceptionToRip;</span><br><span class="line"> DWORD64 LastExceptionFromRip;</span><br><span class="line">} CONTEXT, *PCONTEXT;</span><br></pre></td></tr></table></figure><p>结构的大部分域是不言自明的。需要解释的是,其第1个域ContextFlags表示该结构中的哪些域有效,当需要用CONTEXT结构保存的信息恢复执行时可对应更新,这为有选择地更新部分域而非全部域提供了有效的手段。</p><p>包装完毕,异常处理函数会进一步调用系统内核的nt!KiDispatchException函数来处理异常。因此,只有深入分析KiDispatchException函数的执行过程,才能理解异常是如何被处理的。该函数原型及各参数的含义如下,其第i个和第3个参数正是上面封装的两个结构。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">VOID </span></span><br><span class="line"><span class="function"><span class="title">KiDispatchException</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">IN PEXCEPTION RECORD ExceptionRecord,<span class="comment">//异常结构信息</span></span></span></span><br><span class="line"><span class="params"><span class="function">IN PKEXCEPTION_FRAME ExceprionFrame,<span class="comment">//对NT386系统总是为NULL,未使用INPKTRAP_FRAMETrapFrame,发生异常时的陷阱帧</span></span></span></span><br><span class="line"><span class="params"><span class="function">IN KPROCESSOR_MODE PreviousMode,<span class="comment">//发生异常时的CPU模式是内核模式还是用户模式</span></span></span></span><br><span class="line"><span class="params"><span class="function">IN BOOLEAN FirstChance//是否第<span class="number">1</span>次处理该异常</span></span></span><br></pre></td></tr></table></figure><p>在该函数中,系统会根据是否存在内核调试器、用户态调试器及调试器对异常的干预结果完成不同的处理过程。</p><h3 id="内核态的异常处理过程"><a href="#内核态的异常处理过程" class="headerlink" title="内核态的异常处理过程"></a>内核态的异常处理过程</h3><p>当PreviousMode为KernelMocle时,表示是内核模式下产生的异常,此时KiDispatchException会按以下步骤分发异常。</p><ol><li>检测当前系统是否正在被内核调试器调试。如果内接调试器不存在,就跳过本步骤。如果内核i式器存在,系统就会把异常处理的控制权转交给内核调试器,并注明是第l次处理机会(FirstChance)内核调试器取得控制权之,会根据用户对异常处理的设置来确定是否要处理该异常。如果无法确定该异常是否需要处理,就会发生中断,把控制权交给用户,由用户决定是否处理。</li><li>如果不存在内核调试器,或者在第l次处理机会出现时调试器选悔不处理该异常,系统就会调用nt!RtlDispatchException函数,根据统程注册的结构化异常处理过程来处理该异常。</li><li>如果nt!RtlDispatchException函数没有处理该异常,系统会给调试器第2次处理机会(SecondChance),此时调试器可以再次取得对异常的处理权。</li><li>如果不存在内核调试器,或者在第2次机会调试器仍不处理,系统就认为在这种情况下不能继续运行了。为了避免引起更加严重的、不可预知的错误,系统会直接调用KeBugCheckEx产生一个错误码为“KERNEL_MODE_EXCEPTION_NOT_HANDLED”(其值为Ox0000008E)的BSOD(俗称蓝屏错误)。</li></ol><p>可以看到,在上述异常处理过程中,只有在某一步骤中异常未得到处理,才会进行下一处理过程。在任何时候,只要异常被处理了,就会终止整个异常处理过程。</p><h3 id="用户态的异常处理过程"><a href="#用户态的异常处理过程" class="headerlink" title="用户态的异常处理过程"></a>用户态的异常处理过程</h3><p>当PreviousMode为UserMocle时,表示是用户模式下产生的异常。此时KiDispatchException函数仍然会检测内核调试器是否存在。如果内核调试器存在,会优先把控制权交给内核调试器进行处理。所以,使用内核调试器调试用户态程序是完全可行的,并且不依赖进程的调试端口。在大多数情况下,内核调试器对用户态的异常不感兴趣,也就不会去处理它,比时nt!KiDispatchException函数仍然像处理内核态异常一样按两次处理机会进行分发,主要过程如下。</p><ol><li><p>如果发生异常的程序正在被调试,那么将异常信息发送给正在调试它的用户态调试器,给调试器第l次处理机会;如果没有被调试,跳过本步。</p></li><li><p>如果不存在用户态调试器或调试器未处理该异常,那么在枝上放置EXCEPTION_RECORD和CONTEXT两个结构,并将控制权返回用户态ntdll.dll中的KiUserExceptidnDspatcher函数,由它调用ntdll!RtlDispatchException函数进行用户态的异常处理。这一部分涉及SEH和VEH两种异常处理机制。中,SEH部分包括应用程序调用API函数SetUnhandleExceptionFilter但如果有调试器存在,顶级异常处理会被跳过,进入下一阶段的处理,居则将由顶级异常处理程序进行终结处理(通常是显示一个应用程序错误对话框并根据用户的选择快定是终止程序还是附加到调试器)。如果没有调试器能附加于其上或调试器还是处理不了异常,系统就调用ExitProcess函数来终结程序。</p></li><li><p>如果ntdll!RtlDispatchException函数在调用用户态的异常处理过程中未能处理该异常,那么异常处理过程会再次返回nt!KiDispatchException,它将再次把异常信息库送给用户态的调试器,给调试器第2次处理机会。如果没有调试器存在,则不会进行第次分发,而是直接结束进程。</p></li><li><p>如果第2次机会调试器仍不处理,nt!KiDispatchException会再次尝试把异常分发给进程的异常端口进行处理。该端口通常由子系统进程csrss.exe进行监听。子系统监听到该错误后,通常会显示一个“应用程序错误”对话框,用户可以单击“确定”按钮或者最后将其附加到调试器上的“取消”按钮。如果没有调试器能附加于其上,或者调试器还是处理不了异常,系统就调用ExitProcess函数来终结程序。</p><p><img src="http://imgset.gitee.io/img/1554690873141.png" alt="1554690873141"></p></li><li><p>在终结程序之前,系统会再次调用发生异常的线程中的所有异常处理过程,这是线程异常’处理过程所获得的清理未释放资源的最后机会,此后程序就终结了。</p></li></ol><h3 id="END"><a href="#END" class="headerlink" title="END"></a>END</h3><p><img src="http://imgset.gitee.io/img/1571159924755.png" alt="1571159924755"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> SEH </tag>
</tags>
</entry>
<entry>
<title>静态变量底层实现</title>
<link href="/2018/10/20/2018-10-20-%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0/"/>
<url>/2018/10/20/2018-10-20-%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h1 id="静态变量底层实现"><a href="#静态变量底层实现" class="headerlink" title="静态变量底层实现"></a>静态变量底层实现</h1><p>静态变量有局部静态变量(作用域内的静态变量),全局静态变量,然而他们的实现和全局变量是一样的,而局部静态变量只能在作用域访问是C语言的优化。</p><p>局部静态变量不会随作用域结束而销毁,并且在未进入作用域之前就已经存在,其生命周期也与全局变量相同。局部静态变量和全局变量都保存在执行文件中的数据区中。</p><p>局部静态变量会预先被作为全局变量处理,而它的初始化部分,只是在做赋值操作而已。</p><p>在C++语法中局部静态变量只能被初始化一次,编译器的内部实现如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">void Static(int nNum)</span><br><span class="line">{</span><br><span class="line"> static int g_nNum = nNum;</span><br><span class="line"> xor eax,eax</span><br><span class="line"> mov al,byte ptr['Static'::'2'::$$1 (004257cc)]</span><br><span class="line"> and eax,1</span><br><span class="line"> test eax,eax</span><br><span class="line"> jne pp</span><br><span class="line"> mov cl,byte ptr['Static'::'2'::$$1 (004257cc)]</span><br><span class="line"> or cl,1</span><br><span class="line"> mov byte ptr['Static'::'2'::$$1 (004257cc)], cl</span><br><span class="line"> mov edx,[ebp + 8]</span><br><span class="line"> mov [_sbh_sizeHeaderList+4 (004257c8)],edx</span><br><span class="line">pp:</span><br><span class="line"> printf("%d", g_nNum);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述代码首先从004257cc处取出1字节内容进行判断,这个byte的内容实际上就是局部静态变量初始状态的一个标志,这个标志占1字节,通过位运算,将标志的一位置1,而1个字节占8位,so这个标志可以同时表示8个局部静态变量的初始状态。</p><p>标志坐在的内存地址在最先定义的局部静态变量地址的附近,如最先定义的整型局部静态变量在地址0x004257C0处,那么标记位地址通常在0x004257C4或0x004257BC处。当同一作用域内超过8个局部静态变量时,下一个标记位将会在第9个定义的局部静态变量地址附近。</p><p>当局部静态变量为数组时,那么只检查数组的第1个字节是否进行过初始化,其他位将不进行检查。</p><p><img src="http://imgset.gitee.io/img/1571159941863.png" alt="1571159941863"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>Linux kernel4.0 内核环境搭建,签名问题解决</title>
<link href="/2018/10/16/2018-10-16-%E7%BC%96%E8%AF%91Linux%E5%86%85%E6%A0%B84.17/"/>
<url>/2018/10/16/2018-10-16-%E7%BC%96%E8%AF%91Linux%E5%86%85%E6%A0%B84.17/</url>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>在Linux3.7之后对驱动添加了签名的限制,更多细节在此不做过多叙述。</p><p>当我们自己编译的ko文件,进行签名时,是不会得到Ubuntu编译时的私钥的。</p><p>那我们就要使用自己编译的内核,在编译的过程中将驱动签名的验证关闭。</p><h2 id="首先下载内核源码"><a href="#首先下载内核源码" class="headerlink" title="首先下载内核源码"></a>首先下载内核源码</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://git.kernel.org/torvalds/t/linux-4.17-rc2.tar.gz</span><br></pre></td></tr></table></figure><p>我在这下载的时linux 4.17-rc2的版本。</p><h2 id="环境安装"><a href="#环境安装" class="headerlink" title="环境安装"></a>环境安装</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison</span><br></pre></td></tr></table></figure><h2 id="解压源码"><a href="#解压源码" class="headerlink" title="解压源码"></a>解压源码</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar xvf linux-4.17-rc2.tar.gz</span><br></pre></td></tr></table></figure><h2 id="内核配置"><a href="#内核配置" class="headerlink" title="内核配置"></a>内核配置</h2><p>在正式编译内核之前,我们首先必须配置需要包含哪些模块。实际上,有一些非常简单的方式来配置。使用一个命令,你能拷贝当前内核的配置文件,然后使用可靠的 <code>menuconfig</code> 命令来做任何必要的更改。使用如下命令来完成:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /boot/config-$(uname -r) .config</span><br></pre></td></tr></table></figure><p>得到这个.config文件之后,我们需要修改一些配置,你也可所以输入make menuconfig进行图形化配置。</p><p><img src="http://imgset.gitee.io/img/1555305009746.png" alt="1555305009746"></p><p>我们需要修改 or 添加如下属性</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">CONFIG_MODULE_SIG=n</span><br><span class="line">CONFIG_MODULE_SIG_FORCE=n</span><br><span class="line">CONFIG_MODULE_SIG_SHA512=n</span><br></pre></td></tr></table></figure><p>将这3个属性关闭,就可以加载未签名的驱动了。</p><p>接下来就可以编译内核了。</p><h2 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make modules_install</span><br></pre></td></tr></table></figure><p>过程耗时较长。</p><h2 id="安装内核"><a href="#安装内核" class="headerlink" title="安装内核"></a>安装内核</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo make install</span><br></pre></td></tr></table></figure><p><code>make install</code> 命令将比 <code>make modules_install</code> 命令花费更多的时间。</p><h2 id="启用内核作为引导"><a href="#启用内核作为引导" class="headerlink" title="启用内核作为引导"></a>启用内核作为引导</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo update-initramfs -c -k 4.17.0-rc2</span><br></pre></td></tr></table></figure><h2 id="更新grub"><a href="#更新grub" class="headerlink" title="更新grub"></a>更新grub</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo update-grub</span><br></pre></td></tr></table></figure><p>到此我们内核编译安装的过程已经结束了。</p><h2 id="内核切换"><a href="#内核切换" class="headerlink" title="内核切换"></a>内核切换</h2><p>下面我们要做的就是,切换当前使用的内核为我们刚刚编译的内核。</p><p>你可以将默认内核切换为刚刚编译的内核。</p><p>我在这选择的是开机时切换。</p><p>在这还需要配置/etc/default/grub文件中的 GRUB_TIMEOUT=10。</p><p>!!!!不要在=号两边加空格</p><p><img src="http://imgset.gitee.io/img/1555337063789.png" alt="1555337063789"></p><p>到此就可以在开机时切换内核为刚刚编译的内核了。</p><h2 id="驱动代码"><a href="#驱动代码" class="headerlink" title="驱动代码"></a>驱动代码</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">hi.c</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string"><linux/init.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string"><linux/module.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">hello_init</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"> printk(KERN_INFO <span class="string">"Hello World enter\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">module_init(hello_init);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> __exit <span class="title">hello_exit</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"> printk(KERN_INFO <span class="string">"Hello World exit\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">module_exit(hello_exit);</span><br><span class="line"></span><br><span class="line">MODULE_AUTHOR(<span class="string">"Barry Song <[email protected]>"</span>);</span><br><span class="line">MODULE_LICENSE(<span class="string">"GPL v2"</span>);</span><br><span class="line">MODULE_DESCRIPTION(<span class="string">"A simple Hello World Module"</span>);</span><br><span class="line">MODULE_ALIAS(<span class="string">"a simplest module"</span>);</span><br><span class="line">MODULE_INFO(intree, <span class="string">"Y"</span>);</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Makefile</span><br><span class="line"></span><br><span class="line">ifeq ($(KERNELRELEASE),)</span><br><span class="line"></span><br><span class="line">KERNELDIR ?= /lib/modules/$(shell uname -r)/build</span><br><span class="line">PWD := $(shell pwd)</span><br><span class="line"></span><br><span class="line">.PHONY: build clean</span><br><span class="line"></span><br><span class="line">build:</span><br><span class="line"> $(MAKE) -C $(KERNELDIR) M=$(PWD) modules</span><br><span class="line"></span><br><span class="line">clean:</span><br><span class="line"> rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c</span><br><span class="line"> else</span><br><span class="line"></span><br><span class="line">$(info Building with KERNELRELEASE = ${KERNELRELEASE})</span><br><span class="line">obj-m := hi.o</span><br><span class="line"></span><br><span class="line">endif</span><br></pre></td></tr></table></figure><p>将两个文件放在同一个文件夹。</p><p>输入make即可编译。</p><h2 id="加载驱动"><a href="#加载驱动" class="headerlink" title="加载驱动"></a>加载驱动</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo insmod hi.ko</span><br></pre></td></tr></table></figure><h2 id="卸载驱动"><a href="#卸载驱动" class="headerlink" title="卸载驱动"></a>卸载驱动</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo rmmode hi</span><br></pre></td></tr></table></figure><h2 id="查看驱动信息"><a href="#查看驱动信息" class="headerlink" title="查看驱动信息"></a>查看驱动信息</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">modinfo hi.ko</span><br></pre></td></tr></table></figure><h2 id="查看驱动输出"><a href="#查看驱动输出" class="headerlink" title="查看驱动输出"></a>查看驱动输出</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dmesg | grep hi</span><br></pre></td></tr></table></figure><h2 id="另一种解决方案"><a href="#另一种解决方案" class="headerlink" title="另一种解决方案"></a>另一种解决方案</h2><p>创建一个签名加载内核中,需要用efi的方式安装Ubuntu。</p><p>安装之后在BIOS中关闭<strong>Secure Boot</strong> 。</p><h2 id="创建签名文件"><a href="#创建签名文件" class="headerlink" title="创建签名文件"></a>创建签名文件</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=Descriptive name/"</span><br></pre></td></tr></table></figure><h2 id="将签名文件加载进驱动"><a href="#将签名文件加载进驱动" class="headerlink" title="将签名文件加载进驱动"></a>将签名文件加载进驱动</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 ./MOK.priv ./MOK.der /path/to/module</span><br></pre></td></tr></table></figure><h2 id="将key加载进Securt-Boot"><a href="#将key加载进Securt-Boot" class="headerlink" title="将key加载进Securt Boot"></a>将key加载进Securt Boot</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mokutil --import MOK.der</span><br></pre></td></tr></table></figure><h2 id="重启"><a href="#重启" class="headerlink" title="重启"></a>重启</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reboot</span><br></pre></td></tr></table></figure><p>重启之后就可以用上面的命令加载ko模块了。</p><h2 id="Ubuntu-论坛相关"><a href="#Ubuntu-论坛相关" class="headerlink" title="Ubuntu 论坛相关"></a>Ubuntu 论坛相关</h2><p><a href="https://askubuntu.com/questions/762254/why-do-i-get-required-key-not-available-when-install-3rd-party-kernel-modules">https://askubuntu.com/questions/762254/why-do-i-get-required-key-not-available-when-install-3rd-party-kernel-modules</a></p><p><img src="http://imgset.gitee.io/img/1571159948743.png" alt="1571159948743"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>VS2017静态编译QT with openssl</title>
<link href="/2018/10/08/2018-10-08-VS2017%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91QT%20with%20openssl/"/>
<url>/2018/10/08/2018-10-08-VS2017%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91QT%20with%20openssl/</url>
<content type="html"><![CDATA[<h1 id="VS2017-静态编译QT-with-openssl"><a href="#VS2017-静态编译QT-with-openssl" class="headerlink" title="VS2017 静态编译QT with openssl"></a>VS2017 静态编译QT with openssl</h1><p>在GitHub已经有人写了一个很好用的脚本。</p><p><a href="https://github.com/fpoussin/Qt5-MSVC-Static">Click me</a></p><p><img src="http://imgset.gitee.io/img/1538994302212.png" alt="1538994302212"></p><p>在根目录会看到两个lnk 对应不同版本的vs console (x86 or x64)</p><p>在这我编译的32位的版本</p><p>建议把脚本放在盘符根目录 QT会因为路经过长而出现BUG</p><p>下载脚本 双击VS2017_Win32</p><p><img src="http://imgset.gitee.io/img/1538994774433.png" alt="1538994774433"></p><p>在个console中调用qt.bat 来下载编译</p><p>在console中输入 qt download 脚本会下载并解压qt源码 和 openssl 源码 </p><p>QT的源码在最新版本的VS2017 中编译是有问题的 在这QT官方给了一个新的源码下载链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull https://codereview.qt-project.org/qt/qtbase refs/changes/05/235205/7</span><br></pre></td></tr></table></figure><p>替换掉脚本下载的QT源码在sources文件夹中</p><p><img src="http://imgset.gitee.io/img/1538995628677.png" alt="1538995628677"></p><p>文件夹的名字不要换</p><p>回到脚本中 执行 qt openssl 编译openssl</p><p><img src="http://imgset.gitee.io/img/1538995705184.png" alt="1538995705184"></p><p>在编译完之后 我们就可以编译qt了 在编译qt之前 还需要修改下编译的脚本</p><p>位于 tools\setup_qt.bat </p><p><img src="http://imgset.gitee.io/img/1538995856677.png" alt="1538995856677"></p><p>找到编译代码</p><p>做一个整体替换</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> Configuring Qt...</span><br><span class="line">start %QTDIR%\configure.bat -prefix %QTINSTALLDIR% -platform %PLATFORM% ^</span><br><span class="line">-opensource -confirm-license -debug-and-release -confirm-license -opengl dynamic -static -static-runtime -no-shared ^</span><br><span class="line">-qt-libpng -qt-libjpeg -qt-zlib -qt-pcre -no-compile-examples -nomake examples ^</span><br><span class="line">-no-icu -optimize-size %EXTRABUILDOPTIONS% ^</span><br><span class="line">-openssl-linked -I %SSLINSTALLDIR%\include -L %SSLINSTALLDIR%\lib OPENSSL_LIBS=<span class="string">"-llibeay32 -lssleay32"</span> ^&^& pause <span class="built_in">exit</span></span><br><span class="line">IF %errorlevel% NEQ 0 <span class="built_in">exit</span> /b %errorlevel%</span><br></pre></td></tr></table></figure><p>回到console中 输入qt setup </p><p>紧接着 输入 qt build</p><p>就开始静态编译安装QT了</p><p>编译完成后默认存放目录在 c:\Qt\5.10.1 这个文件夹中</p><p>因为我们之前替换过代码 实际我们编译的版本是5.11.1 的版本</p><p>Good Job.</p><p><img src="http://imgset.gitee.io/img/1571159962140.png" alt="1571159962140"></p>]]></content>
<categories>
<category> Develop </category>
</categories>
</entry>
<entry>
<title>VS2017编译 Bitcoin master</title>
<link href="/2018/10/08/2018-10-09-VS2017%E7%BC%96%E8%AF%91bitcoin%20master/"/>
<url>/2018/10/08/2018-10-09-VS2017%E7%BC%96%E8%AF%91bitcoin%20master/</url>
<content type="html"><![CDATA[<h1 id="VS2017编译-Bitcoin-master-with-Qt"><a href="#VS2017编译-Bitcoin-master-with-Qt" class="headerlink" title="VS2017编译 Bitcoin master with Qt"></a>VS2017编译 Bitcoin master with Qt</h1><h2 id="下载源码"><a href="#下载源码" class="headerlink" title="下载源码"></a>下载源码</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/bitcoin/bitcoin</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1539065582759.png" alt="1539065582759"></p><p>下载Vcpkg 用来配置bitcoin需要用到的第三方库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/Microsoft/vcpkg</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1539065625326.png" alt="1539065625326"></p><h2 id="初始化Vcpkg"><a href="#初始化Vcpkg" class="headerlink" title="初始化Vcpkg"></a>初始化Vcpkg</h2><p>打开cmd </p><p> cd vcpkg-master </p><p>执行 bootstrap-vcpkg.bat 进行编译 vcpkg.exe</p><p>编译结束就可以在vcpkg根目录看到vcpkg.exe了</p><p><img src="http://imgset.gitee.io/img/1539065902565.png" alt="1539065902565"></p><p>然后,运行如下命令使计算机的所有用户都可以使用vcpkg</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vcpkg integrate install</span><br></pre></td></tr></table></figure><p>初始化搞定</p><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><p>在命令行中执行</p><p>vcpkg install boost:x86-windows-static</p><p>install 安装命令</p><p>boost 库的名字</p><p>‘ : ’后面是安装库的版本 在我我们安装库的版本全部为32位静态库 如果你想安装动态库,直接干掉-static 就行了,如果编译64位,把x86换成x64。</p><p>接下来执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">vcpkg install libevent:x86windows-static</span><br><span class="line">vcpkg install openssl:x86-windows-static</span><br><span class="line">vcpkg install zeromq:x86-windows-static</span><br><span class="line">vcpkg install berkeleydb:x86-windows-static</span><br><span class="line">vcpkg install secp256k1:x86-windows-static</span><br><span class="line">vcpkg install leveldb:x86-windows-static</span><br></pre></td></tr></table></figure><p>这个过程会很耗费时间 建议挂一个vpn </p><p>安装完成之后 </p><p>我们打开到Bitcoin源码目录下面的build_msvc文件夹 看到msvc-autogen.py文件</p><p>执行这个文件 python msvc-autogen.py</p><p><img src="http://imgset.gitee.io/img/1539069538346.png" alt="1539069538346"></p><p>现在我们就可以打开 build_msvc目录下的bitcoin.sln了 </p><p>现在就可以编译比特币了 </p><p>编译完之后并没有bitcoin-qt </p><h2 id="编译bitcoin-qt"><a href="#编译bitcoin-qt" class="headerlink" title="编译bitcoin-qt"></a>编译bitcoin-qt</h2><h3 id="首先我们要先编译Protobuf-库"><a href="#首先我们要先编译Protobuf-库" class="headerlink" title="首先我们要先编译Protobuf 库"></a>首先我们要先编译Protobuf 库</h3><p><a href="https://github.com/yuanbi/VS2017-Bitcoin">下载链接</a></p><p>我下载的是2.6.1的版本</p><p><img src="http://imgset.gitee.io/img/1539072815818.png" alt="1539072815818"></p><p>打开C:\deps\protobuf-2.6.1\vsprojects下的protobuf.sln,生成解决方案</p><p>Protobuf库是用来把paymentrequest.proto文件生成对应的.h、.cc文件的</p><p>打开命令行 进入C:\bitcoin-master\src\qt 目录</p><p>在这里执行protoc.exe </p><p>命令格式</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">protoc --proto_path=e:/bitcoin/qt --cpp_out=e:/bitcoin/qt e:/bitcoin/qt/paymentrequest.proto</span><br></pre></td></tr></table></figure><h3 id="接下来我们要自己编译static-qt"><a href="#接下来我们要自己编译static-qt" class="headerlink" title="接下来我们要自己编译static qt"></a>接下来我们要自己编译static qt</h3><p>在我之前的博客有教程,在这就不说了。<a href="https://www.hackyuan.top/2018/10/08/VS2017%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91QT-with-openssl/">电梯</a></p><p>编译完static qt之后 需要在VS2017中安装一个Qt插件</p><p>打开VS2017 选中 工具—->拓展和更新</p><p><img src="http://imgset.gitee.io/img/1539070073798.png" alt="1539070073798"></p><p>点击 联机 – 搜索Qt – 下载</p><p><img src="http://imgset.gitee.io/img/1539070118357.png" alt="1539070118357"></p><p>下载完成后 会出现“扩展和更新窗口”的底部会出现“更新已列入计划…”的提示信息</p><p>关闭VS2017 </p><p>会弹出安装界面 点击修改即可</p><p><img src="http://imgset.gitee.io/img/1539070250808.png" alt="1539070250808"></p><p>安装结束之后打开VS2017</p><p>在菜单栏就可以看见 Qt VS Tools 了</p><p><img src="http://imgset.gitee.io/img/1539070318094.png" alt="1539070318094"></p><p>接下来添加我们之前编译的Static Qt 到 Qt VS Tools</p><p><img src="http://imgset.gitee.io/img/1539070415782.png" alt="1539070415782"></p><p>点击Qt Options</p><p><img src="http://imgset.gitee.io/img/1539070436942.png" alt="1539070436942"></p><p>点击Add</p><p><img src="http://imgset.gitee.io/img/1539070489872.png" alt="1539070489872"></p><p>选中我们编译的Static Qt的目录</p><p>点击确定 </p><p>搞定</p><p>接下来我们需要创建一个Qt项目</p><p><img src="http://imgset.gitee.io/img/1539070779780.png" alt="1539070779780"></p><p>选中Qt Gui Application</p><p><img src="http://imgset.gitee.io/img/1539070799504.png" alt="1539070799504"></p><p>添加Bitcoin-qt 源码进来</p><p><img src="http://imgset.gitee.io/img/1539070877737.png" alt="1539070877737"></p><p>我们需要添加</p><p>bitcoin-master\src\qt下面所有文件 </p><p>bitcoin-master\src\interfaces下面所有文件 </p><p>bitcoin-master\src\bitcoin-config.h 这个文件包含了一些预定义的宏 可以在我的GitHub上面下载 <a href="https://github.com/yuanbi/VS2017-Bitcoin">直达电梯</a></p><p>添加结束之后 打开项目属性 – C/C/++ – 常规 – 附加包含目录</p><p><img src="http://imgset.gitee.io/img/1539071829785.png" alt="1539071829785"></p><p>在这里需要添加 我们之前 在vcpkg 编译库的include目录</p><p>添加之前编译bitcoin-master中的编译生成目录 </p><p>我这里是 C:\bitcoin-master\build_msvc\Release</p><p>添加Bitcoin-master\src 比特币源码目录</p><p>添加Qt plugins 目录</p><p>添加protobuf-2.6.1\src protocbuf源码目录</p><p><img src="http://imgset.gitee.io/img/1539073087038.png" alt="1539073087038"></p><p>接下来选中代码生成 – 运行库 修改为 多线程-MT</p><p><img src="http://imgset.gitee.io/img/1539073463472.png" alt="1539073463472"></p><p>选中 预处理器 – 预处理器定义</p><p>添加下面的宏进去 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">WIN32</span><br><span class="line">_WIN32_WINDOWS</span><br><span class="line">HAVE_WORKING_BOOST_SLEEP</span><br><span class="line">LEVELDB_PLATFORM_WINDOWS</span><br><span class="line">OS_WIN</span><br><span class="line">ENABLE_WALLET</span><br><span class="line">_CRT_SECURE_NO_WARNINGS</span><br><span class="line">_SCL_SECURE_NO_WARNINGS</span><br><span class="line">HAVE_CONFIG_H</span><br><span class="line">_X86_</span><br></pre></td></tr></table></figure><p><img src="http://imgset.gitee.io/img/1539073560643.png" alt="1539073560643"></p><p>接着 选中 配置属性 – 链器 – 常规 –附加库目录</p><p>添加Qt plugins 目录</p><p>添加之前编译bitcoin-master中编译的lib目录</p><p>我这里是 C:\bitcoin-master\build_msvc\Release</p><p>添加qtserialport lib目录 这个如果没有 自行下载编译 </p><p>添加Protocbuf下lib 目录</p><p>我这里的目录是</p><p>C:\qtserialport\build-qtserialport-Qt_5_11_1_msvc2017_x86_static_temporary-Release\lib</p><p>在这里需要添加 我们之前 在vcpkg 编译库的lib目录</p><p><img src="http://imgset.gitee.io/img/1539073929179.png" alt="1539073929179"></p><p>接着选中连接器 – 输入 – 附加依赖项</p><p>添加如下lib</p><p>Qt5ThemeSupport.lib<br>qflatpak.lib<br>Qt5FbSupport.lib<br>Qt5EventDispatcherSupport.lib<br>shell32.lib<br>dwmapi.lib<br>oleaut32.lib<br>Qt5Widgets.lib<br>Qt5Gui.lib<br>Qt5Core.lib<br>Mincore.lib<br>crypt32.lib<br>Qt5OpenGLExtensions.lib<br>Qt5FontDatabaseSupport.lib<br>Qt5WindowsUIAutomationSupport.lib<br>Qt5OpenGL.lib<br>qtlibpng.lib<br>qtuiotouchplugin.lib<br>windowsprintersupport.lib<br>Qt5Xml.lib<br>qgenericbearer.lib<br>qgif.lib<br>qico.lib<br>qjpeg.lib<br>qsqlodbc.lib<br>qsqlite.lib<br>qwindowsvistastyle.lib<br>qdirect2d.lib<br>libEGL.lib<br>libGLESv2.lib<br>qtfreetype.lib<br>qtmain.lib<br>qtpcre2.lib<br>qtharfbuzz.lib<br>qminimal.lib<br>qoffscreen.lib<br>qwindows.lib<br>imm32.lib<br>winmm.lib<br>opengl32.lib<br>ws2_32.lib<br>wsock32.lib<br>Iphlpapi.lib<br>libbitcoin_crypto.lib<br>libbitcoin_cli.lib<br>libbitcoin_server.lib<br>libbitcoin_wallet.lib<br>libbitcoin_zmq.lib<br>libbitcoin_common.lib<br>libbitcoin_util.lib<br>libunivalue.lib<br>libbitcoin_consensus.lib<br>boost_filesystem-vc140-mt.lib<br>libprotoc.lib<br>libprotobuf.lib<br>boost_atomic-vc140-mt.lib<br>boost_chrono-vc140-mt.lib<br>boost_container-vc140-mt.lib<br>boost_context-vc140-mt.lib<br>boost_contract-vc140-mt.lib<br>boost_coroutine-vc140-mt.lib<br>boost_date_time-vc140-mt.lib<br>boost_exception-vc140-mt.lib<br>boost_fiber-vc140-mt.lib<br>boost_graph-vc140-mt.lib<br>boost_iostreams-vc140-mt.lib<br>boost_locale-vc140-mt.lib<br>boost_log-vc140-mt.lib<br>boost_log_setup-vc140-mt.lib<br>boost_math_c99-vc140-mt.lib<br>boost_math_c99f-vc140-mt.lib<br>boost_math_c99l-vc140-mt.lib<br>boost_math_tr1-vc140-mt.lib<br>boost_math_tr1f-vc140-mt.lib<br>boost_math_tr1l-vc140-mt.lib<br>boost_program_options-vc140-mt.lib<br>boost_python36-vc140-mt.lib<br>boost_random-vc140-mt.lib<br>boost_regex-vc140-mt.lib<br>boost_serialization-vc140-mt.lib<br>boost_signals-vc140-mt.lib<br>boost_stacktrace_noop-vc140-mt.lib<br>boost_stacktrace_windbg-vc140-mt.lib<br>boost_stacktrace_windbg_cached-vc140-mt.lib<br>boost_system-vc140-mt.lib<br>boost_thread-vc140-mt.lib<br>boost_timer-vc140-mt.lib<br>boost_type_erasure-vc140-mt.lib<br>boost_unit_test_framework-vc140-mt.lib<br>boost_wave-vc140-mt.lib<br>boost_wserialization-vc140-mt.lib<br>zlib.lib<br>ssleay32.lib<br>secp256k1.lib<br>python36.lib<br>lzma.lib<br>libzmq-mt-s-4_3_1.lib<br>libeay32.lib<br>libdb48.lib<br>leveldb.lib<br>event_extra.lib<br>event_core.lib<br>event.lib<br>bz2.lib<br>Shlwapi.lib<br>Qt5Network.lib<br>glu32.lib<br>Qt5SerialPort.lib<br>Qt5Sql.lib<br>Qt5Concurrent.lib<br>Qt5DBus.lib<br>Qt5PrintSupport.lib</p><p><img src="http://imgset.gitee.io/img/1539073995808.png" alt="1539073995808"></p><p>点击确定 </p><p>接下来就可以编译了</p><p><img src="http://imgset.gitee.io/img/1539074425293.png" alt="1539074425293"></p><p><img src="http://imgset.gitee.io/img/1539074472422.png" alt="1539074472422"></p><p>Good Job.</p><p><img src="http://imgset.gitee.io/img/1571159956351.png" alt="1571159956351"></p>]]></content>
<categories>
<category> Develop </category>
</categories>
</entry>
<entry>
<title>Inline Hook</title>
<link href="/2018/01/16/2018-11-18-Inline%20Hook/"/>
<url>/2018/01/16/2018-11-18-Inline%20Hook/</url>
<content type="html"><![CDATA[<h3 id="Inline-原理与流程"><a href="#Inline-原理与流程" class="headerlink" title="Inline 原理与流程"></a>Inline 原理与流程</h3><p>Inline hook 就是在运行的流程中插入跳转指令(call/jmp)来抢夺程序运行流程的一个方法。</p><p>在编程中我们常用if else或其他语句来控制程序的流程,而在汇编中使用的是cmp和jmp等指令,</p><p>cmp是比较,jmp是无条件跳转。</p><p>jmp指令后面跟随一个地址,像这样:jmp 0x1234567</p><p>inline hook中用到的就是jmp指令,在c中的函数编译之后函数名实际上就是地址,也就是函数所在的内存位置,假设我们想hook一个函数,就跳转到这个函数的首地址,将他的第一条指令修改为jmp指令,这将会覆盖函数原本存在的指令,jmp的地址就是我们自己写的函数。</p><p>在执行我们自己写的函数之后,先执行函数中被我们(jmp)覆盖的指令之后,再次调用jmp指令,跳转回去hook之前的代码中执行,不要忘记备份寄存器。</p><h3 id="需要注意的地方"><a href="#需要注意的地方" class="headerlink" title="需要注意的地方"></a>需要注意的地方</h3><ol><li>inline hook 的一般流程:<br> 源程序流 -> jmpIn -> 保存寄存器 -> 具体处理 -> 恢复寄存器 -> jmpOut -> 源程序流<br> 被跳转指令覆盖的代码可以在 jmpIn之后 或者 jmpOut之前</li><li>被覆盖的指令大小和大于等于5,因为我们要塞进去5个字节(0xE9 + Address)。只要你处理跳转指令覆盖的指令,理论上可以 inline hook 函数中的任意位置。</li><li>x86函数调用的开头一般都是下面这个样子的,能很方便的 inline hook </li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov edi, edi</span><br><span class="line">push ebp</span><br><span class="line">mov ebp, esp</span><br></pre></td></tr></table></figure><h4 id="jmp-公式:"><a href="#jmp-公式:" class="headerlink" title="jmp 公式:"></a>jmp 公式:</h4><h5 id="高地址向低地址跳-–-向前跳:"><a href="#高地址向低地址跳-–-向前跳:" class="headerlink" title="高地址向低地址跳 – 向前跳:"></a>高地址向低地址跳 – 向前跳:</h5><p><strong>E9指令地址(4011d2)减去跳转指令地址(401000)</strong></p><p><strong>等于偏移大小,偏移大小取反,加一,减五,就是</strong></p><p><strong>E9后面的偏移地址</strong></p><h5 id="低地址向高地址跳-–-向后跳:"><a href="#低地址向高地址跳-–-向后跳:" class="headerlink" title="低地址向高地址跳 – 向后跳:"></a>低地址向高地址跳 – 向后跳:</h5><p><strong>跳转指令地址(4011C1)减去E9偏移地址(401040)</strong> </p><p><strong>等于偏移大小,偏移大小减五,就是E9后面的偏移地址</strong></p><h3 id="Inline-Hook实例分析:"><a href="#Inline-Hook实例分析:" class="headerlink" title="Inline Hook实例分析:"></a>Inline Hook实例分析:</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdafx.h"</span></span></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">__declspec(naked) <span class="function"><span class="keyword">int</span> <span class="title">add</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> __asm </span><br><span class="line"> {</span><br><span class="line"> mov edi, edi</span><br><span class="line"> push ebp</span><br><span class="line"> mov ebp, esp</span><br><span class="line"> </span><br><span class="line"> sub esp, <span class="number">8</span></span><br><span class="line"> mov eax, [ebp + <span class="number">0x08</span>]</span><br><span class="line"> mov [ebp - <span class="number">4</span>], eax</span><br><span class="line"> mov eax, [ebp + <span class="number">0x0C</span>]</span><br><span class="line"> mov [ebp - <span class="number">8</span>], eax</span><br><span class="line"> </span><br><span class="line"> mov eax, [ebp - <span class="number">4</span>]</span><br><span class="line"> add eax, [ebp - <span class="number">8</span>]</span><br><span class="line"> </span><br><span class="line"> mov esp, ebp</span><br><span class="line"> pop ebp</span><br><span class="line"> ret</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">__declspec(naked) <span class="function"><span class="keyword">void</span> <span class="title">hookProxy</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// moved bytes</span></span><br><span class="line"> __asm </span><br><span class="line"> {</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// save regs</span></span><br><span class="line"> <span class="comment">// __asm</span></span><br><span class="line"> <span class="comment">// {</span></span><br><span class="line"> <span class="comment">// pushad</span></span><br><span class="line"> <span class="comment">// pushfd</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// do something (or call specific function.)</span></span><br><span class="line"> __asm</span><br><span class="line"> {</span><br><span class="line"> add [esp + <span class="number">0x08</span>], <span class="number">1</span></span><br><span class="line"> add [esp + <span class="number">0x0C</span>], <span class="number">1</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// store old regs</span></span><br><span class="line"> <span class="comment">// __asm</span></span><br><span class="line"> <span class="comment">// {</span></span><br><span class="line"> <span class="comment">// popfd</span></span><br><span class="line"> <span class="comment">// popad</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// jump back code</span></span><br><span class="line"> __asm </span><br><span class="line"> {</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> nop</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">inlineHook</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">char</span>* pMovedBytes = &((<span class="keyword">char</span>*)hookProxy)[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">char</span>* pJmpBackCode = &((<span class="keyword">char</span>*)hookProxy)[<span class="number">0xF</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// move old bytes</span></span><br><span class="line"> <span class="built_in">memcpy</span>((<span class="keyword">char</span>*)hookProxy, (<span class="keyword">char</span>*)add, <span class="number">5</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// fill jump in code</span></span><br><span class="line"> <span class="keyword">char</span> jmpInBuffer[<span class="number">5</span>];</span><br><span class="line"> jmpInBuffer[<span class="number">0</span>] = <span class="number">0xE9</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span>* pJmpInOffset = (<span class="keyword">int</span>*)&jmpInBuffer[<span class="number">1</span>];</span><br><span class="line"> *pJmpInOffset = (<span class="keyword">char</span>*)hookProxy - ((<span class="keyword">char</span>*)add + <span class="number">5</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">memcpy</span>((<span class="keyword">char</span>*)add, jmpInBuffer, <span class="number">5</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// fill jump out code</span></span><br><span class="line"> <span class="keyword">char</span> jmpOutBuffer[<span class="number">5</span>];</span><br><span class="line"> jmpOutBuffer[<span class="number">0</span>] = <span class="number">0xE9</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span>* pJmpOutOffset = (<span class="keyword">int</span>*)&jmpOutBuffer[<span class="number">1</span>];</span><br><span class="line"> *pJmpOutOffset = ((<span class="keyword">char</span>*)add + <span class="number">5</span>) - ((<span class="keyword">char</span>*)pJmpBackCode + <span class="number">5</span>);</span><br><span class="line"> <span class="built_in">memcpy</span>(pJmpBackCode, jmpOutBuffer, <span class="number">5</span>);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"><span class="keyword">int</span> _tmain(<span class="keyword">int</span> argc, _TCHAR* argv[])</span><br><span class="line">{</span><br><span class="line"> _asm <span class="keyword">int</span> <span class="number">3</span>;</span><br><span class="line"> </span><br><span class="line"> DWORD oldProtect;</span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">VirtualProtect</span>((LPVOID)add, <span class="number">4096</span>, PAGE_EXECUTE_READWRITE, &oldProtect) )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">inlineHook</span>();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"result = %d \n"</span>, <span class="built_in">add</span>(<span class="number">1</span>, <span class="number">2</span>));</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">system</span>(<span class="string">"pause"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="解析过程"><a href="#解析过程" class="headerlink" title="解析过程"></a>解析过程</h4><ol><li><p>把Add函数的前5个字节搬到了 hookProxy的前5个字节。</p></li><li><p>然后再 Add函数的前5个字节填充跳转到 hookProxy 的跳转指令</p></li><li><p>在hookProxy的末尾5个字节填充跳转回 Add函数 + 5 的跳转指令</p></li><li><p>原本 Add 函数的结构是3,被 hook 之后会变成 5</p></li></ol><p>有兴趣的读者可以上机调试一番</p>]]></content>
<categories>
<category> Reverse </category>
</categories>
<tags>
<tag> C++ </tag>
</tags>
</entry>
<entry>
<title>Window PE感染型木马分析</title>
<link href="/2017/07/14/2017-07-14-PE%E6%84%9F%E6%9F%93%E5%9E%8B%E6%9C%A8%E9%A9%AC%E6%B1%87%E7%BC%96%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2017/07/14/2017-07-14-PE%E6%84%9F%E6%9F%93%E5%9E%8B%E6%9C%A8%E9%A9%AC%E6%B1%87%E7%BC%96%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="PE感染型木马分析"><a href="#PE感染型木马分析" class="headerlink" title="PE感染型木马分析"></a>PE感染型木马分析</h1><p>在正式看代码之前先对exe结构(PE)做一个简单了解</p><h2 id="PE中的Section"><a href="#PE中的Section" class="headerlink" title="PE中的Section"></a>PE中的Section</h2><p>当我们写完代码,编译为二进制文件之后。在代码中初始化变量的值会被全部提取出来,并整合在一起,在二进制文件中,找到一块连续的空间放进去并称之为节(section)。在生成之前的图片,文件等其他资源,同样被整合在一起,放在一个节中。代码里面的const不可修改的变量的初始值,不会和可修改的放在一起,会被提取出来单独放在一个节中。代码也是一样单独放在一个节中。除了这些还有其他的节,在这就不全部讲解了。</p><p><strong>我们还需要了解一个机制</strong></p><h2 id="随机基址"><a href="#随机基址" class="headerlink" title="随机基址"></a>随机基址</h2><p>什么是随机基址,当我们双击运行一个exe文件,它会被PE装载器,放到内存里面。那么在内存中每一bit都是有数字来告诉你它在哪,称之为地址,那么加载的这个exe是从哪开始加载的,那么这个位置就被称之为基址,随机地址顾名思义,当你多次执行exe的时候,每一次加载的位置都不一样。</p><p>那 么 就会引发一个问题</p><h5 id="全局变量的问题"><a href="#全局变量的问题" class="headerlink" title="全局变量的问题"></a>全局变量的问题</h5><p>全局变量在内存中也是通过编号进行访问,也就是我们说的地址,但是它的寻址方式不是相对的,也就是说你在编译二进制文件的时候,全局变量的地址就已经写进文件中了,当我们编译的时候选用了随机基址,假设我们默认的加载位置是0x401000,有一个全局变量的地址为0x402004的位置,那么因为我们开启了随机基址,我们加载exe的位置很有可能不是0x401000,这个时候0x402004的位置也就不是我们的全局变量了,那找不到全局变量的位置。</p><h5 id="解决这个问题"><a href="#解决这个问题" class="headerlink" title="解决这个问题"></a>解决这个问题</h5><p>当你写好代码,点击编译为二进制文件的时候(开启随机基址),会添加一个新的节,<strong>重定位</strong>节,这里面包含了所有需要绝对寻址的方式的地址,那么当你双击运行的时候,PE装载器就会找到重定位节在里面读取出所有的这个节里面保存的地址,然后找到这个exe默认加载的基址A(写在二进制文件中),在拿到本次加载的位置B,计算这两个地址的差值C,最后把重定位节里面找到的所有地址和差值C进行相加或者相减,在放回去。<strong>搞定!</strong></p><h1 id="PE中的Header"><a href="#PE中的Header" class="headerlink" title="PE中的Header"></a>PE中的Header</h1><p>我们之前说到了节,PE装载器会把这些节加载到内存,还有你的第一行代码从哪个地址开始执行,PE装在器通过读取PE 中的Header 获取节位置、属性、大小、权限等信息,并且限制每一个节的行为,比如const不可修改的原因就是因为所在节的属性选中了不可修改、数据节里不能执行代码也是这个原因。</p><p>那pe感染型病毒是怎么执行的呢,它修改了这个二进制文件,首先添加了一个节在PE header,然后拓展文件大小,把代码写进去,把节的属性写进去,在修改执行第一行的代码的位置,修改为自己拓展位置的代码地址,然后搞定。</p><p>这里面涉及文件对齐、内存对齐、等其他PE基础知识请看我的其他文章。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><p>首先代码中必须解决重定位的问题 so easy</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">call @F</span><br><span class="line">@@: </span><br><span class="line"> pop ebx</span><br><span class="line"> sub ebx,offset @B</span><br></pre></td></tr></table></figure><p>这里涉及到的栈知识,请看我的其他文章。</p><p>上面的几行代码执行过后就会获得,我们之前所说的PE装载器获取的默认加载基址 与本次加载基址的差值,我们获取到这个差值,代码中的重定位问题就解决了。</p><p>接下来获取kernel32.dll的地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">GetKernelBase proc </span><br><span class="line"> local @dwRet:dword</span><br><span class="line"> pushad</span><br><span class="line"> assume fs:nothing</span><br><span class="line"> mov eax,fs:[30h]</span><br><span class="line"> mov eax,[eax+0ch]</span><br><span class="line"> mov eax,[eax+1ch]</span><br><span class="line"> mov eax,[eax]</span><br><span class="line"> mov eax,[eax]</span><br><span class="line"> mov eax,[eax + 8]</span><br><span class="line"> mov @dwRet,eax</span><br><span class="line"> popad</span><br><span class="line"> mov eax,@dwRet</span><br><span class="line"> </span><br><span class="line"> ret</span><br><span class="line">GetKernelBase endp</span><br></pre></td></tr></table></figure><p>这里涉及到的TEB、PEB的知识请看我的其他文章。</p><p>通过进程环境(TEB),获取kernel32.dll的地址 相当于调用了LoadLibrary(“kernel32.dll”);</p><p>这个地址实际上就是kernel32.dll加载的基址。</p><p>那么这里面就包含了很多系统函数,通过遍历导出表,或者字节查找的方式找到GetProcAddress()的函数地址。</p><p>贴代码</p><p>这段代码是通过名字进行遍历</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">GetApi proc _lpKernel,_lpNamex</span><br><span class="line">local @dwApiSize</span><br><span class="line"> xor al,al</span><br><span class="line"> mov ecx,-1</span><br><span class="line"> mov edi,lpName</span><br><span class="line"> cld</span><br><span class="line"> repne scasb</span><br><span class="line"> mov ecx,edi</span><br><span class="line"> sub ecx,lpName</span><br><span class="line"> mov @dwApiSize,ecx</span><br><span class="line"> mov esi,_lpKernel</span><br><span class="line"> add esi,[esi + 3ch]</span><br><span class="line">assume esi:ptr IMAGE_NT_HEADERS</span><br><span class="line">mov esi,[esi].OptionalHeader.DataDirectory[0].VirtualAddress</span><br><span class="line">add esi,_lpKernel</span><br><span class="line">assume esi:ptr IMAGE_EXPORT_DIRECTORY</span><br><span class="line">mov ecx,[esi].NumberOfNames</span><br><span class="line">mov ebx,[esi].AddressOfNames</span><br><span class="line">add ebx,_lpKernel</span><br><span class="line">.while ecx</span><br><span class="line"> push ecx</span><br><span class="line"> push esi</span><br><span class="line"> mov edi,[ebx]</span><br><span class="line"> add edi,_lpKernel</span><br><span class="line"> mov esi,_lpName</span><br><span class="line"> mov ecx,@dwApiSize</span><br><span class="line"> repz cmpsb</span><br><span class="line"> .if !ecx</span><br><span class="line"> pop esi</span><br><span class="line"> pop ecx</span><br><span class="line"> jmp @F</span><br><span class="line"> .endif</span><br><span class="line"> add ebx,4</span><br><span class="line"> pop esi</span><br><span class="line"> pop ecx</span><br><span class="line"> dec ecx</span><br><span class="line">.endw</span><br><span class="line">@@:</span><br><span class="line"> mov ebx,[esi].NumberOfNames</span><br><span class="line"> sub ebx,ecx </span><br><span class="line"> shl ebx,1</span><br><span class="line"> mov edi,[esi].AddressOfNameOrdinals</span><br><span class="line"> add edi,ebx</span><br><span class="line"> add edi,lpKernel</span><br><span class="line"> mov ax,word ptr[edi]</span><br><span class="line"> movzx eax,ax</span><br><span class="line"> shl eax,2</span><br><span class="line"> mov edi,[esi].AddressOfFunctions</span><br><span class="line"> add edi,eax</span><br><span class="line"> add edi,lpKernel</span><br><span class="line"> mov edi,[edi]</span><br><span class="line"> add edi,_lpKernel</span><br><span class="line"> mov eax,edi</span><br><span class="line"> ret</span><br><span class="line">GetApi end</span><br></pre></td></tr></table></figure><p>当我们得到GetProcAddress地址之后,就可以通过这个地址之后,就可以使用这个函数来获取LoadLibrary函数的地址,我们需要两个参数,第一个参数是你要获取的这个函数所在dll的基址,也就是kernel32.dll的基址,我们之前已经获取过了,第二个参数就是要获取函数的名字。</p><p>上代码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mov ecx,offset szLoadLibrary;"LoadLibraryA/W"字符串地址</span><br><span class="line">add ecx,ebx;重定位问题</span><br><span class="line"></span><br><span class="line">push ecx</span><br><span class="line">push edi</span><br><span class="line">call ebp;getprocaddress(handle,LoadLibrary)</span><br><span class="line"></span><br><span class="line">mov ecx,offset ddLoadLibrary;存储LoadLiabraryA/W地址位置</span><br><span class="line">add ecx,ebx;重定位问题</span><br><span class="line"></span><br><span class="line">mov [ecx],eax;保存地址</span><br></pre></td></tr></table></figure><p>现在我们获取到GetProcAddress 和 LoadLibraryA/W的地址,现在就可以为所欲为了。</p><p>当你写完代码之后编译为二进制文件。</p><p>把代码的字节提取出来,在PE 代码节中。</p><p>提取出来之后写进你要感染的exe中。</p><p>修改目标exe的第一行代码执行的位置。</p><p>修改你添加代码的最后5个字节。</p><p>修改为跳转到目标exe原第一行代码执行的位置。</p><p><strong>搞定!</strong></p><p><img src="http://imgset.gitee.io/img/1571159978825.png" alt="1571159978825"></p>]]></content>
<categories>
<category> Reverse </category>
</categories>
</entry>
<entry>
<title>Windows内核对象</title>
<link href="/2017/06/13/2017-06-13-Windows%E5%86%85%E6%A0%B8%E5%AF%B9%E8%B1%A1/"/>
<url>/2017/06/13/2017-06-13-Windows%E5%86%85%E6%A0%B8%E5%AF%B9%E8%B1%A1/</url>
<content type="html"><![CDATA[<h2 id="内核对象概述"><a href="#内核对象概述" class="headerlink" title="内核对象概述"></a>内核对象概述</h2><p><img src="http://imgset.gitee.io/img/1550655557295.png" alt="1550655557295"></p><p>在Windows内核中有一种很重要的数据结构管理机制,就是内核对象。引用层的进程、线程、文件、驱动模块、时间、信号量等对象或者打开的句柄在内核中都有之对应的内核结构对象。</p><p>如上图,一个Windows内核对象可以分为对象头和对象体两个部分。在对象头中至少有1个OBJECT_HEADER和对象的额外信息。对象体紧接着对象头中的OBJECT_HEADER。一个对象指针总是 指向对象体而不是对象头。如果要访问对象头,需要将对象体指针减去一个特定的偏移值,以获取OBJECT_HEADER结构,通过OBJECT_HEADER结构定位从而访问其他对象结构辅助信息。对象体内部一般会有一个type和一个size成员,用来表示对象的类型和大小。</p><p>Windows内核对象可以分为如下3中类型。</p><h2 id="1-Dispatcher对象"><a href="#1-Dispatcher对象" class="headerlink" title="1. Dispatcher对象"></a>1. Dispatcher对象</h2><p>这种对象在对象体开始位置放置了一个共享的公共数据结构DISPATCHER_HEADER,其结构代码如下。包含DISPATCHER_HEADER结构的内核对象的名字都以字母 K 开头,表明这是一个内核对象,例如KPROCESS、KTHREAD、KEVENT、KSEMAPHORE、KTIMER、KQUEUE、KMUTANT、KMUTEX,但以字母 K 开头的内核对象不一定是Dispatcher对象。包含DISPATCHER_HEADER结构的内核对象都是可以等待的,也就是说。这些恶和对象可以作为参数传给内核的KeWaitForSingleObject()和KeWaitForMultipleObjects()函数,以及应用层的WaitForSingleObject()和WaitForMultipleObjects()函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">DISPATCHER_HEADER</span>{</span></span><br><span class="line"> UCHAR Type ; <span class="comment">//DISP_TYPE_*</span></span><br><span class="line"> UCHAR Abso lu te ;</span><br><span class="line"> UCHAR Size ; <span class="comment">//number of DWORDs</span></span><br><span class="line"> UCHAR Inserted ;</span><br><span class="line"> LONG</span><br><span class="line"> LIST_ENTRY WaitListHead ;</span><br><span class="line">}</span><br><span class="line">DISPATCHER_HEADER,</span><br><span class="line">*PDISPATCHER_HEADER,</span><br><span class="line">**PDISPATCHER_HEADER;</span><br></pre></td></tr></table></figure><h3 id="2-I-O对象"><a href="#2-I-O对象" class="headerlink" title="2. I/O对象"></a>2. I/O对象</h3><p>I/O对象在对象体开始位置并未放置DISPATCHER_HEADER结构,但通常会放置一个与type和size有关的整型成员,以表示该内核对象的类型(例如文件内核对象的类型为26)和大小。常见的IO对象包括DEVICE_OBJECT、DRIVER_OBJECT、FILE_OBJECT、IRP、VPB、KPROFILE等。</p><h3 id="3-其他对象"><a href="#3-其他对象" class="headerlink" title="3. 其他对象"></a>3. 其他对象</h3><p>除了Dispatcher对象和IO对象,剩下的都属于其他内核对象。其中有两个最常用的内核对象,分别是进程对象(EPROCESS)与线程对象(ETHREAD)。</p><h4 id="EPROCESS"><a href="#EPROCESS" class="headerlink" title="EPROCESS"></a>EPROCESS</h4><p>EPROCESS用于在内核中管理进程的各种信息,每个进程都对应于一个EPROCESS结构,用于记录进程执行期间的各种数据。尽管EPROCESS结构非常大,但他是一个不透明的结构(Opaque Structure),具体成员并未导出,随操作系统的变化而变化。因此,想查看EPROCESS结构中的成员,只能查阅网上资料或使用Windbg调试器加载内核符号后进行。</p><p>所有进程的EPROCESS内核结构都被放入一个双向链表,R3在枚举系统进程的时候,通过遍历这个链表获得了进程的列表。因此,有的Rootkit会试图将自己进程的EPROCESS结构从这个链表中摘掉,从而达到隐藏自己的目的。</p><p>EPROCESS结构中的一些关键数据:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">KPROCESS pcb;//进程的内核对象</span><br><span class="line">PVOID UniqueProcessId;//进程的PID</span><br><span class="line">PVOID DebugPort;//调试端口,设置为0,禁止进程被调试</span><br><span class="line">EX_FAST_REF Token;//进程的权限token</span><br><span class="line">UCHAR ImageFileName[16];//进程的名字,最长16字节</span><br><span class="line">PPEB Peb;//进程的环境快</span><br><span class="line">PEJOB Job;//进程的job</span><br><span class="line">PVOID Win32Process;</span><br><span class="line">LIST_ENTRY ActiveProccessLinks;//指向正在运行的系统进程列表</span><br><span class="line">PHANDLE_TABLE ObjectTable;//进程的handle表</span><br></pre></td></tr></table></figure><p>调用线面的内核函数可以获得进程的EPROCESS结构。PsLookupProcessByProcessId函数的结构如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">NTSTATUS <span class="title">PsLookupProcessByProcessId</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="comment">//根据进程PID拿到进程的EPROCESS结构</span></span></span></span><br><span class="line"><span class="params"><span class="function">IN HANDLE ProcessId,</span></span></span><br><span class="line"><span class="params"><span class="function">OUT PEPROCESS *Process</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">PEPROCESS <span class="title">PsGetCurrentProcess</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="comment">//直接获取当前进程的EPROCESS结构</span></span></span></span><br><span class="line"><span class="params"><span class="function">VOID</span></span></span><br><span class="line"><span class="params"><span class="function">)</span>;</span></span><br></pre></td></tr></table></figure><h4 id="ETHREAD"><a href="#ETHREAD" class="headerlink" title="ETHREAD"></a>ETHREAD</h4><p>ETHREAD结构式线程的内核管理对象,每个线程都有一个对应的ETHREAD结构。ETHREAD结构也是一个不透明的结构,具体成员你并未导出,而且会随着操作系统的版本的变化而变化。在ETHREAD结构中,第一个成员就是线程对象KTHREAD成员,所有的ETHREAD结构也被放在一个双向链表里进行管理。</p><p>ETHREAD结构中的一些成员信息:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">KTHREAD Tcb;//线程内核对象</span><br><span class="line">CLIENT_ID Cid;//进程PID</span><br></pre></td></tr></table></figure><p>EPROCESS、KPROCESS、ETHREAD、KTHREAD结构之间的关系图如下。可以看出,EPROCESS和ETHREAD结构都是通过双向链表组织管理的。一个EPROCESS结构中包含了一个KPROCESS结构,而在一个KPROCESS结构又有一个指向ETHREAD结构的指针。在ETHREAD结构中,又包含了KTHREAD结构成员。</p><p><img src="http://imgset.gitee.io/img/1550658031014.png" alt="1550658031014"></p><p> EPROCESS、KPROCESS、ETHREAD、KTHREAD结构关系图</p><p><img src="http://imgset.gitee.io/img/1571159984250.png" alt="1571159984250"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>SSDT、PEB、TEB & Hook</title>
<link href="/2017/06/13/2017-07-15-SSDT,PEB,TEB/"/>
<url>/2017/06/13/2017-07-15-SSDT,PEB,TEB/</url>
<content type="html"><![CDATA[<h2 id="SSDT"><a href="#SSDT" class="headerlink" title="SSDT"></a>SSDT</h2><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>SSDT全称是”System Services Descriptor Table”(系统服务描述符表),在内核中的实际名称是”KeSeriveDescriptorTable“。这个表已通过内核ntoskrnl.exe导出(在x64里不导出)。</p><p>SSDT用于处理应用层通过kernel32.dll下发的各个API操作请求。ntdll.dll中的API是一个简单的包装函数,当kernel32.dll中的API通过ntdll.dll时,会先完成对参数的检查,在调用一个中断(int 2Eh 或者 Sys Enter指令),从而实现从R3层进入R0层,并将要调用的服务号(也就是SSDT数组中的索引号index值)存放到寄存器EAX中,最后根据存放在EAX中的索引值在SSDT数组中调用指定的服务(Nt*系列函数)如下图所示。</p><p><img src="http://imgset.gitee.io/img/1550663088705.png" alt="1550663088705"></p><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>SSDT表的结构定义如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> pack(1)</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">ServiceDescriptorEntry</span>{</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> *ServiceTableBase;<span class="comment">//Table Base Address</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> *ServiceCounterTableBase;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> NumberOfServices;<span class="comment">// Count of Service function in table</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">char</span> *ParamTableBas;</span><br><span class="line">}</span><br><span class="line">ServiceDescriptorTableEntry_t,</span><br><span class="line">*PServiceDescriptorTableEntry_t;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> pack()</span></span><br></pre></td></tr></table></figure><p>其中最重要的2个成员为ServiceTableBase(SSDT表的基地址)和 NumberOfServices(表示系统中SSDT服务函数的个数)。SSDT表其实就是一个连续存放这个函数指针的数组。</p><h4 id="原理与Hook"><a href="#原理与Hook" class="headerlink" title="原理与Hook"></a>原理与Hook</h4><p>SSDT表的导入方法如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;</span><br></pre></td></tr></table></figure><p>由此可以知道SSDT表的基地址(数组的首地址)和SSDT函数的索引号(index),从而求出对应的服务函数的地址。在x86平台上,它们之间满足如下规则。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FuncAddr = KeServiceDescriptortable + 4 * index</span><br></pre></td></tr></table></figure><p>与x86平台上直接在SSDT中存放SSDT函数地址不同,在x64平台上,SSDT中存放的时索引号所对应SSDT函数地址和SSDT表基地址的偏移 * 16 的值,计算公式如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FuncAddr = ([KeServiceDescriptortable + index * 4] >> 4 + KeServiceDescriptorTable)</span><br></pre></td></tr></table></figure><p>通过这个公式,只要知道SSDT表的首地址和对应函数的索引号,就可以将对应位置的服务函数替换成自己的函数,从而完成SSDT Hook过程了。</p><p>Shadow SSDT的原理比SSDT类似,它对应的表名为KeServiceDescriptorTableShadow,是内核未导出的另一张表,包含Ntoskrnel.exe和win32k.sys服务函数,主要处理来自User32.dll和GDI32.dll的系统调用。与SSDT不同,Shadow SSDT是未导出的,因此不能在自己的模块中导入和直接引用。</p><p>挂钩该表中的NtGdiBitBlt、NtGdiStretchBlt 可以实现截屏保护。挂钩NtUserSetWindowsHookEx函数可以防止或保护键盘钩子,挂钩与按键相关的函数NtUserSendInput可以防止模拟按键,挂钩NtUserFindWindowEx函数可以防止或保护键盘钩子,挂钩与按键相关的函数NtUserSendInput可以防止模拟按键,挂钩NtUserFindWindowEx函数可以防止搜索窗口,挂钩与窗口相关的函数NtUserPostMessage、NtUserQueryWindow可以防止窗口被关闭。</p><p>Shadow SSDT的管沟原理和SSDT的挂钩原理一样,只不过由于未导出,需要使用不同的方法来获取该表的地址及服务函数的索引号。例如,硬编码与KeServiceDescriptorTable在不同系统中的位置偏移,搜索KeAddSystemServiceTable、KTHREAD.ServiceTable,以及有效内存搜索等。</p><p>KeServiceDescriptorTableShadow实际上也是一个SSDT结构数组,也就是说,KeServiceDescriptorTableShadow是一组系统描述表。在Windows XP中,KeServiceDescriptorTableShadow表位于KeServiceDescriptorTable表上方便宜0x40处。</p><p>KeServiceDescriptorTableShadow包含四个子结构,示例如下。第一个子结构是”ntoskrnl.exe(native api)”,与KeServiceDescriptorTable的指向相同。真正需要获得的是第二个子结构,即”win32k.sys(gdi/user support)”。第三个和第四个子结构一般不使用。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">typedef struct _SERVICE_DESCRIPTOR_TABLE</span><br><span class="line">{</span><br><span class="line"> SYSTEM_SERVICE_TABLE ntoskrnl;//ntoskrnl.exe(native api)</span><br><span class="line"> SYSTEM_SERVICE_TABLE win32k;//win32k.sys(gdi/user support)</span><br><span class="line"> SYSTEM_SERVICE_TABLE Table3;//Not used</span><br><span class="line"> SYSTEM_SERVICE_TABLE Table4;//Not used</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="TEB"><a href="#TEB" class="headerlink" title="TEB"></a>TEB</h2><p>TEB和PEB一样,不在系统内核空间中,而是之外应用层中的结构。TEB结构比较重要。</p><h4 id="概述-1"><a href="#概述-1" class="headerlink" title="概述"></a>概述</h4><p>TEB 全称 Thread environment block,线程环境块,结构中包含了系统频繁使用的一些与线程相关的数据。进程中的每个线程(系统线程除外)都有一个自己的TEB。一个进程的所有TEB都存放在0x7FFDE00开始的线性内存中,,每4KB未一个完整的TEB。</p><h4 id="1-TEB结构体"><a href="#1-TEB结构体" class="headerlink" title="1. TEB结构体"></a>1. TEB结构体</h4><p>与EPROCESS类似(其他博文已有描述),在不同的Windows中,TEB结构略有差异。例如,在R3级的应用程序中,fs:[0]的地址指向TEB结构,这个结构的开头是一个NT_TIB结构,具体如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">0:000> dt _nt_tib</span><br><span class="line">ntdll!_NT_TIB</span><br><span class="line"> +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD</span><br><span class="line"> +0x004 StackBase : Ptr32 Void</span><br><span class="line"> +0x008 StackLimit : Ptr32 Void</span><br><span class="line"> +0x00c SubSystemTib : Ptr32 Void</span><br><span class="line"> +0x010 FiberData : Ptr32 Void</span><br><span class="line"> +0x010 Version : Uint4B</span><br><span class="line"> +0x014 ArbitraryUserPointer : Ptr32 Void</span><br><span class="line"> +0x018 Self : Ptr32 _NT_TIB</span><br></pre></td></tr></table></figure><p>(摘自 Windbg)</p><p>NT_TIB结构的0x18偏移处是一个Self指针,指向这个结构自身,也就是TEB结构的开头。TEB结构的0x30偏移处是指向PEB的指针。</p><p>利用Windbg的本地调试可以查看系统中的TEB结构。启动WinDbg,选择 File –> Kernel Debug –> Local 选项,然后在弹出的对话框中单击 Local 标签,就可以打开WinDbg的本机调试功能。在 Windows Vista 及以后的版本中会弹出信息,提示系统不支持本地内核调试,这时可以使用管理员模式打开cmd ,输入命令 bcdedit -debug on,重新启动计算机,在以管理员身份打开WinDbg。</p><p>输入 !teb 即可查看TEB结构数据</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">0:000> !teb</span><br><span class="line">TEB at 00620000</span><br><span class="line"> ExceptionList: 0093f6f4</span><br><span class="line"> StackBase: 00940000</span><br><span class="line"> StackLimit: 0093c000</span><br><span class="line"> SubSystemTib: 00000000</span><br><span class="line"> FiberData: 00001e00</span><br><span class="line"> ArbitraryUserPointer: 00000000</span><br><span class="line"> Self: 00620000</span><br><span class="line"> EnvironmentPointer: 00000000</span><br><span class="line"> ClientId: 00003220 . 00002294</span><br><span class="line"> RpcHandle: 00000000</span><br><span class="line"> Tls Storage: 0062002c</span><br><span class="line"> PEB Address: 0061d000</span><br><span class="line"> LastErrorValue: 0</span><br><span class="line"> LastStatusValue: 0</span><br><span class="line"> Count Owned Locks: 0</span><br><span class="line"> HardErrorMode: 0</span><br></pre></td></tr></table></figure><p>继续查看,代码如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">0:000> dt _teb 00620000</span><br><span class="line">ntdll!_TEB</span><br><span class="line"> +0x000 NtTib : _NT_TIB</span><br><span class="line"> +0x01c EnvironmentPointer : (null) </span><br><span class="line"> +0x020 ClientId : _CLIENT_ID</span><br><span class="line"> +0x028 ActiveRpcHandle : (null) </span><br><span class="line"> +0x02c ThreadLocalStoragePointer : 0x0062002c Void</span><br><span class="line"> +0x030 ProcessEnvironmentBlock : 0x0061d000 _PEB</span><br><span class="line"> +0x034 LastErrorValue : 0</span><br><span class="line"> +0x038 CountOfOwnedCriticalSections : 0</span><br><span class="line"> +0x03c CsrClientThread : (null) </span><br><span class="line"> +0x040 Win32ThreadInfo : (null) </span><br><span class="line"> +0x044 User32Reserved : [26] 0</span><br><span class="line"> +0x0ac UserReserved : [5] 0</span><br><span class="line"> +0x0c0 WOW32Reserved : 0x77196000 Void</span><br><span class="line"> +0x0c4 CurrentLocale : 0x804</span><br><span class="line"> +0x0c8 FpSoftwareStatusRegister : 0</span><br><span class="line"> +0x0cc ReservedForDebuggerInstrumentation : [16] (null) </span><br><span class="line"> +0x10c SystemReserved1 : [26] (null) </span><br><span class="line"> +0x174 PlaceholderCompatibilityMode : 0 ''</span><br><span class="line"> +0x175 PlaceholderHydrationAlwaysExplicit : 0 ''</span><br><span class="line"> +0x176 PlaceholderReserved : [10] ""</span><br><span class="line"> +0x180 ProxiedProcessId : 0</span><br><span class="line"> +0x184 _ActivationStack : _ACTIVATION_CONTEXT_STACK</span><br><span class="line"> +0x19c WorkingOnBehalfTicket : [8] ""</span><br><span class="line"> +0x1a4 ExceptionCode : 0n0</span><br><span class="line"> +0x1a8 ActivationContextStackPointer : 0x00620184 _ACTIVATION_CONTEXT_STACK</span><br><span class="line"> +0x1ac InstrumentationCallbackSp : 0</span><br><span class="line"> //更多代码略</span><br></pre></td></tr></table></figure><p>在TEB中,0x30偏移处时PEB结构,地址为0x00620000。可以使用dt命令进一步查看PEB结构成员中的值。</p><h4 id="2-访问TEB"><a href="#2-访问TEB" class="headerlink" title="2. 访问TEB"></a>2. 访问TEB</h4><p>可以通过NtCurrentTeb函数调用和FS段寄存器访问这两种方法访问TEB结构。</p><h5 id="(1)NtCurrentTeb函数调用"><a href="#(1)NtCurrentTeb函数调用" class="headerlink" title="(1)NtCurrentTeb函数调用"></a>(1)NtCurrentTeb函数调用</h5><p>从ntdll.dll中道出了一个函数NtCurrentTeb函数,该函数可以返回当前线程的TEB结构体的地址。通过喜爱按的代码,就i可以从ntdll.dll中找到对应的NtCurrentTeb函数地址并调用它,返回TEB结构的地址。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">TEB</span>{</span></span><br><span class="line"> NT_TIB Tib;</span><br><span class="line"> PVOID EnvironmentPointer;</span><br><span class="line"> CLIENT_ID Cid;</span><br><span class="line"> PVOID ActiveRpcInfo;</span><br><span class="line"> PPEB Peb;</span><br><span class="line">}TEB , *PTEB;</span><br><span class="line"><span class="keyword">typedef</span> PTEB (NTAPI *NtCurrentTeb)();</span><br><span class="line">NtCurrentTeb fnNtCurrentTeb = (NtCurrentTeb)GetProocAddress(GetModuleHandle(<span class="string">L"ntdll.dll"</span>)), <span class="string">"NtCurrentTeb"</span>);</span><br><span class="line">PTEB pTeb = fnNtCurrentTeb();</span><br></pre></td></tr></table></figure><h5 id="(2)FS段寄存器访问"><a href="#(2)FS段寄存器访问" class="headerlink" title="(2)FS段寄存器访问"></a>(2)FS段寄存器访问</h5><p>FS是段寄存器,当代码运行在R3时,基地址即为当前线程的线程环境块(TEB),所以该段也称为 TEB段 。运行如下代码可获得TEB的指针。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mov eax, dword ptr fs:[18h]</span><br></pre></td></tr></table></figure><h2 id="PEB"><a href="#PEB" class="headerlink" title="PEB"></a>PEB</h2><h3 id="概述-2"><a href="#概述-2" class="headerlink" title="概述"></a>概述</h3><p>PEB 全称 ProcessEnvironment Block,进程环境块,存在于用户地址空间中,记录了进程的相关信息。每个进程有自己的PEB信息。</p><h3 id="PEB访问"><a href="#PEB访问" class="headerlink" title="PEB访问"></a>PEB访问</h3><p>TEB中的ProcessEnvironmentBlock 就是PEB结构的地址,其结构的0x30偏移处是一个指向PEB的指针。PEB的0x2偏移处是一个UChar成员,名叫“BeginDebugged”,进程被调试时值为1,否则为0.因此访问PEB有两种方法。</p><ul><li><p>直接获取</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mov eax , dword ptr fs:[30] ;fs[30]里面存放的即为PEB地址</span><br></pre></td></tr></table></figure></li><li><p>通过TEB获取</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mov eax,dword ptr fs:[18h] ;此时eax里为TEB的指针</span><br><span class="line">mov eax,dword ptr [eax + 30h] ;此时eax里为PEB的指针</span><br></pre></td></tr></table></figure></li></ul><p>此外,在内核结构对象EPROCESS结构中,同样记录了PEB结构的地址。因此,可以通过查看EPROCESS找到进程的PEB信息。</p><h3 id="PEB结构体"><a href="#PEB结构体" class="headerlink" title="PEB结构体"></a>PEB结构体</h3><p>与TEB一样,PEB也是随着Windows系统版本的变化而略有差异的结构。可以查阅MSDN或winternl.h,获取TEB结构定义。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">PEB</span> {</span></span><br><span class="line"> BYTE Reserved1[<span class="number">2</span>];</span><br><span class="line"> BYTE BeingDebugged;</span><br><span class="line"> BYTE Reserved2[<span class="number">1</span>];</span><br><span class="line"> PVOID Reserved3[<span class="number">2</span>];</span><br><span class="line"> PPEB_LDR_DATA Ldr;</span><br><span class="line"> PRTL_USER_PROCESS_PARAMETERS ProcessParameters;</span><br><span class="line"> PVOID Reserved4[<span class="number">3</span>];</span><br><span class="line"> PVOID AtlThunkSListPtr;</span><br><span class="line"> PVOID Reserved5;</span><br><span class="line"> ULONG Reserved6;</span><br><span class="line"> PVOID Reserved7;</span><br><span class="line"> ULONG Reserved8;</span><br><span class="line"> ULONG AtlThunkSListPtr32;</span><br><span class="line"> PVOID Reserved9[<span class="number">45</span>];</span><br><span class="line"> BYTE Reserved10[<span class="number">96</span>];</span><br><span class="line"> PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;</span><br><span class="line"> BYTE Reserved11[<span class="number">128</span>];</span><br><span class="line"> PVOID Reserved12[<span class="number">1</span>];</span><br><span class="line"> ULONG SessionId;</span><br><span class="line">} PEB, *PPEB;</span><br></pre></td></tr></table></figure><p>MSDN中定义的PEB是不完整的,微软隐藏了很多细节,其他结构也是一样,下面是windbg获得的结果</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">0:000> dt _PEB 0X0061d000</span><br><span class="line">ntdll!_PEB</span><br><span class="line"> +0x000 InheritedAddressSpace : 0 ''</span><br><span class="line"> +0x001 ReadImageFileExecOptions : 0 ''</span><br><span class="line"> +0x002 BeingDebugged : 0x1 ''</span><br><span class="line"> +0x003 BitField : 0x4 ''</span><br><span class="line"> +0x003 ImageUsesLargePages : 0y0</span><br><span class="line"> +0x003 IsProtectedProcess : 0y0</span><br><span class="line"> +0x003 IsImageDynamicallyRelocated : 0y1</span><br><span class="line"> +0x003 SkipPatchingUser32Forwarders : 0y0</span><br><span class="line"> +0x003 IsPackagedProcess : 0y0</span><br><span class="line"> +0x003 IsAppContainer : 0y0</span><br><span class="line"> +0x003 IsProtectedProcessLight : 0y0</span><br><span class="line"> +0x003 IsLongPathAwareProcess : 0y0</span><br><span class="line"> +0x004 Mutant : 0xffffffff Void</span><br><span class="line"> +0x008 ImageBaseAddress : 0x01140000 Void</span><br><span class="line"> +0x00c Ldr : 0x772c0c40 _PEB_LDR_DATA</span><br><span class="line"> +0x010 ProcessParameters : 0x009a2320 _RTL_USER_PROCESS_PARAMETERS</span><br><span class="line"> +0x014 SubSystemData : (null) </span><br><span class="line"> +0x018 ProcessHeap : 0x009a0000 Void</span><br><span class="line"> +0x01c FastPebLock : 0x772c09e0 _RTL_CRITICAL_SECTION</span><br><span class="line"> +0x020 AtlThunkSListPtr : (null) </span><br><span class="line"> +0x024 IFEOKey : (null) </span><br><span class="line"> +0x028 CrossProcessFlags : 2</span><br><span class="line"> //更多代码略</span><br></pre></td></tr></table></figure><p>其中,BeingDebugged成员适用于指定该进程是否处于被调试状态CheckRemoteDebuggerPrecset()函数用于判断进程是否处于调式状态。ProcessParameters是一个RTL_USER_PROCESS_PARAMMETERS,即用于记录进程的参数信息(例如命令行参数等)。</p><p>下图表示EPROCESS、ETHREAD、PEB、TEB的关系,可以看出,EPROCESS和ETHREAD结构处于内核空间中,它们分别拥有一个指针,指向处于应用层空间的PEB结构和TEB结构,而在TEB中也有一个指针指向PEB结构。</p><p><img src="http://imgset.gitee.io/img/1550668838988.png" alt="1550668838988"></p><p> EPROCESS、ETHREAD、TEB、PEB之间的关系</p><p><img src="http://imgset.gitee.io/img/1571159972513.png" alt="1571159972513"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>Window启动过程</title>
<link href="/2017/04/13/2017-04-13-Windows%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B/"/>
<url>/2017/04/13/2017-04-13-Windows%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h2 id="Windows启动包含下面几个阶段"><a href="#Windows启动包含下面几个阶段" class="headerlink" title="Windows启动包含下面几个阶段"></a>Windows启动包含下面几个阶段</h2><h3 id="1-启动自检阶段"><a href="#1-启动自检阶段" class="headerlink" title="1. 启动自检阶段"></a>1. 启动自检阶段</h3><p> 在打开电源时,计算机开始自检过程,从BIOS中载入必要的指令,然后进行一系列的自检操作,进行硬件的初始化检查(内存,硬盘,键盘等),同时在屏幕上显示信息。</p><h3 id="2-初始化启动阶段"><a href="#2-初始化启动阶段" class="headerlink" title="2. 初始化启动阶段"></a>2. 初始化启动阶段</h3><p> 自检完成后,根据CMOS的设置,BIOS加载启动盘,将主引导记录(MBR)中的引导代码载入内存。接着,启动过程由MBR来执行。启动代码搜索MBR中的分区表,找出活动分区,将第一个扇区中的引导代码载入内存。引导代码检测当前使用的文件系统,查找ntldr文件,找到之后将启动它。BIOS将控制权交给ntldr,由ntldr完成操作系统的启动工作(Windows7 与此不同,使用的时bootmgr)。</p><h3 id="3-Boot加载阶段"><a href="#3-Boot加载阶段" class="headerlink" title="3. Boot加载阶段"></a>3. Boot加载阶段</h3><p> 在这个阶段,先从启动分区加载ntldr,然后对ntldr进行如下配置。</p><p> 1.设置内存模式,如果是x86处理器,并且是32位操作系统,则设置为“32-bit flat memory mode”;如果64位操作系统,并且是64位处理器,则设置为64位内存模式。</p><p> 2.启动一个简单的文件系统,以定位boot.ini 、ntoskrnl、Hal等启动文件。</p><p> 3.读取boot.ini文件。</p><h3 id="4-检测和配置硬件阶段"><a href="#4-检测和配置硬件阶段" class="headerlink" title="4. 检测和配置硬件阶段"></a>4. 检测和配置硬件阶段</h3><p> 在这个阶段会检查和配置一些硬件设备,例如系统固件、总线和适配器、显示适配器、键盘、通信端口、磁盘、软盘、输入设备(鼠标等)、并口、ISA总线上运行的设备等。</p><h3 id="5-内核加载阶段"><a href="#5-内核加载阶段" class="headerlink" title="5.内核加载阶段"></a>5.内核加载阶段</h3><p> ntldr将首先加载Windows内核Ntoskrnl.exe和硬件抽象层(HAL)。HAL会对硬件底层的特性进行隔离,为操作系统提供统一的调用接口。接下来,ntldr从注册表的HKEY_LOCAL_MACHINE\System\CurrentControlSet键下读取这台机器安装的驱动程序,然后依次加载驱动程序。初始化底层设备驱动,在注册表的HKEY_LACAL_ACHINE\System\CurrentControlSet\Services键下查找Start键的值为0和1的设备驱动。</p><p> Start键的值可以为0、1、2、3、4,数值越小,启动越早。SERVICE_BOOT_START(0)表示内核刚刚初始化,此时加载的都是与系统核心有关的重要驱动程序,例如磁盘驱动;SERVICE_SYSTEM_START(1)稍晚一些;SERVICE_AUTO_START(2)是从登录界面出现的时候开始,如果登录速度较快,很可能驱动还没有加载就已经登录了;SERVICE_DMAND_START(3),表示在需要的时候手动加载;SERVICE_DISABLED(4)表示禁止加载。</p><h3 id="6-Windows的会话管理启动"><a href="#6-Windows的会话管理启动" class="headerlink" title="6. Windows的会话管理启动"></a>6. Windows的会话管理启动</h3><p> 驱动程序加载完成,内核会在启动会话管理器。这是一个名为smmss.exe的程序,是Windows系统中第一个创建的用户模式进程,其作用如下。</p><ul><li>创建系统环境变量</li><li>加载win32k.sys,它是Windows子系统的内核模式部分。</li><li>启动csrss.exe,它是Windows子系统的用户模式部分。</li><li>启动winnlogon.exe。</li><li>创建虚拟内存页面文件。</li><li>执行上次系统重启前未完成的重命名工作(PendingFileRename)。</li></ul><h3 id="7-登陆阶段"><a href="#7-登陆阶段" class="headerlink" title="7. 登陆阶段"></a>7. 登陆阶段</h3><p> Windows子系统启动的winlogon.exe系统服务提供对Windows用户的登录和注销的支持,可以完成如下工作。</p><ul><li>启动服务子系统(services.exe),也称服务控制管理器(SCM)。</li><li>启动本地安全授权(LSA)过程(lsass.exe)。</li><li>显示登录界面。</li></ul><p> 登录组件将用户的账号和密码安全的传送给LSA进行认证处理。如果用户提供的信息是正确的,能够通过认证,就允许用户对系统进行访问。</p><h3 id="8-Windows-7和-Windows-XP启动过程的区别"><a href="#8-Windows-7和-Windows-XP启动过程的区别" class="headerlink" title="8. Windows 7和 Windows XP启动过程的区别"></a>8. Windows 7和 Windows XP启动过程的区别</h3><ul><li>BIOS启动自检后,将MBR载入内存并执行,引导代码找到启动管理器Bootmgr。</li><li>Bootmgr 寻找活动分区boot文件夹中的启动配置数据BCD文件,读取并组成相应语言的启动菜单,然后在屏幕上显示多操作系统选择界面。</li><li>选择Windows7系统后,Bootmgr就会读取系统文件windows\system32\winload.exe,并将控制权交给winload.exe。</li><li>Winload.exe加载Windows 7的内核、硬件、服务等,然后加载桌面等信息,从而启动 整个Windows 7系统。</li></ul><h3 id="9-新一代引导方式UEFI与GPT"><a href="#9-新一代引导方式UEFI与GPT" class="headerlink" title="9. 新一代引导方式UEFI与GPT"></a>9. 新一代引导方式UEFI与GPT</h3><p> 以上说的是传统的系统引导核启动,这种方法主要借助BIOS和MBR完成系统的引导和启动。但是,这种方法有局限性,例如磁盘逻辑块地址(Logical Block Address,LBA)是32位的,最多表示2^32个扇区,而每个扇区的大小一般为512字节,所以最多支持2^32*512=2 * 2^40字节,即2TB.而且,MBR最多支持4个主分区或3个主分区和一个拓展分区,拓展分区下可以有多个逻辑分区。在BIOS中,启动操作系统之前必须从硬盘上的指定扇区中读取系统启动代码(包含在MBR中),然后从活动分区中引导并启动操作系统。对扇区的操作远比不上对分区中的文件的操作那样直观和简单。</p><p> 为了打破传统的BIOS与MBR引导系统的局限,新的系统引导方式(即UEFI结合GPT)已经出现并逐渐成为今后系统引导的主要解决方案。</p><p> UEFI(Unified Extensible Firware Interface,统一的可拓展固件接口)的出现主要用于替换BIOS。在UEFI中,用于LBA的地址是64位的,突破了在BIOS与MBR技术方案中分区容量2TB的限制。</p><p> UEFI本身已经相当与一个微型操作系统了,UEFI具备文件系统的支持能力,能够直接读取FAT分区中的文件。开发人员可以开发出直接在UEFI下运行的应用程序,这类程序文件通常以 “efi” 结尾。我们可以将Windows操作系统变得简单。而在UEFI下,这些统统都不需要–不需要主引导记录,不需要活动分区,不需要任何工具。只要将安装文件复制到一个FAT32(主)分区或U盘中,通过这个分区或U盘即可安装和启动Windows。</p><p> 与传统的MBR分区表相比,新型的GPT(GUID Partition Table,全局唯一标识分区表)对分区数量没有限制,但Windows在实现GPT的时候,将分区的个数限制在128个GPT分区以内,GPT可管理磁盘大小达到了18EB,因此,只有基于UEFI平台的主板才支持GPT分区引导启动。</p><h3 id="END"><a href="#END" class="headerlink" title="END"></a>END</h3><p><img src="http://imgset.gitee.io/img/1571159988223.png" alt="1571159988223"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>WinDbg & VMware 双机调试 查看内核数据结构</title>
<link href="/2017/01/13/2017-10-14-Windbg%E5%8F%8C%E6%9C%BA%E8%B0%83%E8%AF%95/"/>
<url>/2017/01/13/2017-10-14-Windbg%E5%8F%8C%E6%9C%BA%E8%B0%83%E8%AF%95/</url>
<content type="html"><![CDATA[<p>WinDbg 推荐微软商店安装,UI好看</p><p>WinDbg本地调试无法通过下断点来跟踪内核的执行,就需要采取特殊的方法,可以通过USB、1394火线等把两台机器连接起来,一台机器运行被调试的内核,另一台机器运行WinDbg,这样就可以调试内核了。不过两台机器的内核调试成本太高,实际上都是通过WinDbg结合虚拟机的方式来调试内核的。虚拟机平台自行选择。</p><span id="more"></span> <ol><li><p>安装好WinDbg后</p></li><li><p>在VMware上创建一个COM串口(COM1或COM2)。如下图,创建的串口是串口2,后文中Windows XP的 boot.ini或者 Windows Vista 以后版本命令行里的设置均填写 “com2”。创建的串口ID为 “com2”的原因是存在虚拟机。为了方便,也可以先将虚拟打印机删除,这样创建的串口ID就是“com1”了。</p></li></ol><p><img src="http://imgset.gitee.io/img/1550731479128.png" alt="1550731479128"></p><ol start="3"><li>设置虚拟机内部系统的调试环境。虚拟机内存操作系统根据不同的版本,设置方法略有差异。</li></ol><p>在Windows XP中是通过BOOT.ini进行配置的。在系统C:\目录下才可以找到boot.ini(注意:该文件为隐藏只读文件,须在文件夹选项中取消对系统文件隐藏才能看见)。在boot.ini文件的最后添加如下行,/debugport=取决于创建的串口ID(com1或com2)。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional-debug" /debug /debugport=com2 /baudrate=115200 /fastdetect</span><br></pre></td></tr></table></figure><p>在Windows Vista/7及以后的操作系统中,以管理员身份启动CMD,运行如下命令 。 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">bcdedit /dbgsettings serial baudrate:115200 debugport:2 </span><br><span class="line">//debugport取决于串口ID</span><br><span class="line">bcdedit /copy {current} /d DebugEntry</span><br><span class="line">bcdedit /displayorder {current} {GUID}</span><br><span class="line">//GUID 请执行第二步时输出的GUID替换</span><br><span class="line">bcdedit /debug {GUID}</span><br><span class="line">//GUID 请执行第二步时输出的GUID替换</span><br></pre></td></tr></table></figure><ol start="4"><li><p>在虚拟机外建立WinDbg快捷方式,命令如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">windbg.exe -k com:port=\\.\pipe\com_1,baud=11520,pipe</span><br></pre></td></tr></table></figure></li><li><p>启动虚拟机内的操作系统,并选择调试启动菜单。</p></li><li><p>通过虚拟机外的WinDbg快捷方式启动WinDbg。</p></li></ol><p>经过以上步骤,稍等片刻,WinDbg和虚拟机就会连接起来。如果长时间没连接上,可以点击WinDbg菜单项 Debug –> Kernel Connection –> Resynchronize,重新进行连接。</p><p>单击WinDbg菜单项Debug –> Break(或按 Crtl + Break快捷键)让系统中断,就可以向WinDbg下调试命令了。</p><p><img src="http://imgset.gitee.io/img/1571159965708.png" alt="1571159965708"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>Windows地址空间</title>
<link href="/2016/03/20/2016-03-20-Windows%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4/"/>
<url>/2016/03/20/2016-03-20-Windows%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4/</url>
<content type="html"><![CDATA[<h1 id="虚拟地址空间"><a href="#虚拟地址空间" class="headerlink" title="虚拟地址空间"></a>虚拟地址空间</h1><p>当处理器读取或写入存储器位置时,它使用虚拟地址。作为读或写操作的一部分,处理器将虚拟地址转换为物理地址。通过虚拟地址访问内存具有以下优势:</p><ul><li><p>程序可以使用连续范围的虚拟地址来访问在物理内存中不连续的大内存缓冲区。</p></li><li><p>程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。随着物理内存的供应变小,内存管理器将物理内存页(通常为4000 bytes)保存到磁盘文件中。根据需要,在物理内存和磁盘之间移动数据页或代码页。</p></li><li><p>不同进程使用的虚拟地址彼此隔离。一个进程中的代码不能改变另一个进程或操作系统正在使用的物理内存。</p><p>进程可用的虚拟地址范围称为进程的<em>虚拟地址空间</em>。每个用户模式进程都有自己的私有虚拟地址空间。对于32位进程,虚拟地址空间通常为2千兆字节范围0x00000000到0x7FFFFFFF。对于64位进程,虚拟地址空间为8 TB,范围为0x000’00000000到0x7FF’FFFFFFFF。一系列虚拟地址有时称为一系列<em>虚拟内存</em>。</p></li></ul><p>此图说明了虚拟地址空间的一些关键功能。</p><p><img src="http://imgset.gitee.io/img/virtualaddressspace01.png" alt="virtualaddressspace01"></p><p>该图显示了两个64位进程的虚拟地址空间:Notepad.exe和MyApp.exe。</p><p>每个进程都有自己的虚拟地址空间,从0x000’0000000到0x7FF’FFFFFFFF。每个阴影块表示一页(4千字节大小)的虚拟或物理内存。</p><p>注意,记事本进程使用三个连续的虚拟地址页面,从0x7F7’93950000开始。但是这三个连续的虚拟地址页面被映射到物理内存中的不连续页面。另请注意,两个进程都使用从0x7F7’93950000开始的虚拟内存页面,但这些虚拟页面映射到物理内存的不同页面。 </p><h2 id="用户空间和系统空间"><a href="#用户空间和系统空间" class="headerlink" title="用户空间和系统空间"></a>用户空间和系统空间</h2><p>Notepad.exe和MyApp.exe等进程在用户模式下运行。核心操作系统组件和许多驱动程序在更特权的内核模式下运行。每个用户模式进程都有自己的专用虚拟地址空间,但所有以内核模式运行的代码都共享一个称为<em>系统空间的</em>虚拟地址<em>空间</em>。用户模式进程的虚拟地址空间称为<em>用户空间</em>。</p><p>在32位Windows中,总可用虚拟地址空间为2 ^ 32bytes(4gigabytes 节)。通常,较低的2gigabytes 字节用于用户空间,而较高的2千兆字节用于系统空间。</p><p>![virtualaddressspace02]img\virtualaddressspace02.png)</p><p>在32位Windows中,您可以选择(在启动时)指定超过2千兆字节可用于用户空间。结果是可用于系统空间的虚拟地址更少。您可以将用户空间的大小增加到3千兆字节,在这种情况下,系统空间只有1千兆字节可用。 </p><p>在64位Windows中,理论上的虚拟地址空间量为2 ^ 64bytes(16exabytes ),但实际上只使用了16-exabyte范围的一小部分。从0x000’00000000到0x7FF’FFFFFFFF的8TB范围用于用户空间,并且从0xFFFF0800’00000000到0xFFFFFFFF’FFFFFFFF的248TB范围的部分用于系统空间。</p><p><img src="http://imgset.gitee.io/img/virtualaddressspace02.png" alt="virtualaddressspace02"></p><p>在用户模式下运行的代码可以访问用户空间,但无权访问系统空间。此限制可防止用户模式代码读取或更改受保护的操作系统数据结构。在内核模式下运行的代码可以访问用户空间和系统空间。也就是说,以内核模式运行的代码可以访问系统空间和当前用户模式进程的虚拟地址空间。</p><p>在内核模式下运行的驱动程序必须非常小心,直接读取或写入用户空间中的地址。这个场景说明了原因。</p><ol><li>用户模式程序启动从设备读取某些数据的请求。程序提供缓冲区的起始地址以接收数据。</li><li>在内核模式下运行的设备驱动程序例程启动读取操作并将控制权返回给其调用者。</li><li>稍后,设备会中断当前正在运行的任何线程,以表示读取操作已完成。中断由在此任意线程上运行的内核模式驱动程序例程处理,该线程属于任意进程。</li><li>此时,驱动程序不得将数据写入步骤1中提供的用户模式程序的起始地址。此地址位于发起请求的进程的虚拟地址空间中,这很可能与目前的进程。</li></ol><h2 id="分页池和非分页池"><a href="#分页池和非分页池" class="headerlink" title="分页池和非分页池"></a>分页池和非分页池</h2><p>在用户空间中,可以根据需要将所有物理内存页面分页到磁盘文件。在系统空间中,某些物理页面可以被分页,而其他物理页面则不能。系统空间有两个用于动态分配内存的区域:分页池和非分页池。</p><p>可以根据需要将页面缓冲池中分配的内存分页到磁盘文件中。在非分页池中分配的内存永远不能分页到磁盘文件。</p><p><img src="http://imgset.gitee.io/img/virtualaddressspace04.png" alt="virtualaddressspace04"> </p><p><img src="http://imgset.gitee.io/img/1571159991778.png" alt="1571159991778"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>Window开机过程</title>
<link href="/2016/03/13/2016-03-13-Windows%E5%BC%80%E6%9C%BA%E8%BF%87%E7%A8%8B/"/>
<url>/2016/03/13/2016-03-13-Windows%E5%BC%80%E6%9C%BA%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="一、第一阶段:BIOS"><a href="#一、第一阶段:BIOS" class="headerlink" title="一、第一阶段:BIOS"></a>一、第一阶段:BIOS</h1><p>上个世纪70年代初,”只读内存”(read-only memory,缩写为ROM)发明,开机程序被刷入ROM芯片,计算机通电后,第一件事就是读取它。</p><p>这块芯片里的程序叫做”基本輸出輸入系統”(Basic Input/Output System),简称为BIOS。</p><h2 id="1-1-硬件自检"><a href="#1-1-硬件自检" class="headerlink" title="1.1 硬件自检"></a>1.1 硬件自检</h2><p>BIOS程序首先检查,计算机硬件能否满足运行的基本条件,这叫做”硬件自检”(Power-On Self-Test),缩写为POST。</p><p>如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。如果没有问题,屏幕就会显示出CPU、内存、硬盘等信息。</p><h2 id="1-2-启动顺序"><a href="#1-2-启动顺序" class="headerlink" title="1.2 启动顺序"></a>1.2 启动顺序</h2><p>硬件自检完成后,BIOS把控制权转交给下一阶段的启动程序。</p><p>这时,BIOS需要知道,”下一阶段的启动程序”具体存放在哪一个设备。也就是说,BIOS需要有一个外部储存设备的排序,排在前面的设备就是优先转交控制权的设备。这种排序叫做”启动顺序”(Boot Sequence)。</p><p>打开BIOS的操作界面,里面有一项就是”设定启动顺序”。</p><h1 id="二、第二阶段:主引导记录"><a href="#二、第二阶段:主引导记录" class="headerlink" title="二、第二阶段:主引导记录"></a>二、第二阶段:主引导记录</h1><p>BIOS按照”启动顺序”,把控制权转交给排在第一位的储存设备。</p><p>这时,计算机读取该设备的第一个扇区,也就是读取最前面的512个字节。如果这512个字节的最后两个字节是0x55和0xAA,表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给”启动顺序”中的下一个设备。</p><p>这最前面的512个字节,就叫做”主引导记录”(Master boot record,缩写为MBR)。</p><h2 id="2-1-主引导记录的结构"><a href="#2-1-主引导记录的结构" class="headerlink" title="2.1 主引导记录的结构"></a>2.1 主引导记录的结构</h2><p>“主引导记录”只有512个字节,放不了太多东西。它的主要作用是,告诉计算机到硬盘的哪一个位置去找操作系统。</p><p>主引导记录由三个部分组成:</p><p>(1) 第1-446字节:调用操作系统的机器码。</p><p>(2) 第447-510字节:分区表(Partition table)。</p><p>(3) 第511-512字节:主引导记录签名(0x55和0xAA)。</p><p>其中,第二部分”分区表”的作用,是将硬盘分成若干个区。</p><h2 id="2-2-分区表"><a href="#2-2-分区表" class="headerlink" title="2.2 分区表"></a>2.2 分区表</h2><p>硬盘分区有很多好处。考虑到每个区可以安装不同的操作系统,”主引导记录”因此必须知道将控制权转交给哪个区。</p><p>分区表的长度只有64个字节,里面又分成四项,每项16个字节。所以,一个硬盘最多只能分四个一级分区,又叫做”主分区”。</p><p>每个主分区的16个字节,由6个部分组成:</p><p>(1) 第1个字节:如果为0x80,就表示该主分区是激活分区,控制权要转交给这个分区。四个主分区里面只能有一个是激活的。</p><p>(2) 第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)。</p><p>(3) 第5个字节:主分区类型。</p><p>(4) 第6-8个字节:主分区最后一个扇区的物理位置。</p><p>(5) 第9-12字节:该主分区第一个扇区的逻辑地址。</p><p>(6) 第13-16字节:主分区的扇区总数。</p><p>最后的四个字节(”主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。</p><p>如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。</p><h1 id="三、第三阶段:硬盘启动"><a href="#三、第三阶段:硬盘启动" class="headerlink" title="三、第三阶段:硬盘启动"></a>三、第三阶段:硬盘启动</h1><p>这时,计算机的控制权就要转交给硬盘的某个分区了,这里又分成三种情况。</p><h2 id="3-1-情况A:卷引导记录"><a href="#3-1-情况A:卷引导记录" class="headerlink" title="3.1 情况A:卷引导记录"></a>3.1 情况A:卷引导记录</h2><p>上一节提到,四个主分区里面,只有一个是激活的。计算机会读取激活分区的第一个扇区,叫做”卷引导记录”(Volume boot record,缩写为VBR)。</p><p>“卷引导记录”的主要作用是,告诉计算机,操作系统在这个分区里的位置。然后,计算机就会加载操作系统了。</p><h2 id="3-2-情况B:扩展分区和逻辑分区"><a href="#3-2-情况B:扩展分区和逻辑分区" class="headerlink" title="3.2 情况B:扩展分区和逻辑分区"></a>3.2 情况B:扩展分区和逻辑分区</h2><p>随着硬盘越来越大,四个主分区已经不够了,需要更多的分区。但是,分区表只有四项,因此规定有且仅有一个区可以被定义成”扩展分区”(Extended partition)。</p><p>所谓”扩展分区”,就是指这个区里面又分成多个区。这种分区里面的分区,就叫做”逻辑分区”(logical partition)。</p><p>计算机先读取扩展分区的第一个扇区,叫做”扩展引导记录”(Extended boot record,缩写为EBR)。它里面也包含一张64字节的分区表,但是最多只有两项(也就是两个逻辑分区)。</p><p>计算机接着读取第二个逻辑分区的第一个扇区,再从里面的分区表中找到第三个逻辑分区的位置,以此类推,直到某个逻辑分区的分区表只包含它自身为止(即只有一个分区项)。因此,扩展分区可以包含无数个逻辑分区。</p><p>但是,似乎很少通过这种方式启动操作系统。如果操作系统确实安装在扩展分区,一般采用下一种方式启动。</p><h2 id="3-3-情况C:启动管理器"><a href="#3-3-情况C:启动管理器" class="headerlink" title="3.3 情况C:启动管理器"></a>3.3 情况C:启动管理器</h2><p>在这种情况下,计算机读取”主引导记录”前面446字节的机器码之后,不再把控制权转交给某一个分区,而是运行事先安装的”启动管理器”(boot loader),由用户选择启动哪一个操作系统。</p><p>Linux环境中,目前最流行的启动管理器是Grub。</p><h1 id="四、第四阶段:操作系统"><a href="#四、第四阶段:操作系统" class="headerlink" title="四、第四阶段:操作系统"></a>四、第四阶段:操作系统</h1><p>控制权转交给操作系统后,操作系统的内核首先被载入内存。</p><p>以Linux系统为例,先载入/boot目录下面的kernel。内核加载成功后,第一个运行的程序是/sbin/init。它根据配置文件(Debian系统是/etc/initab)产生init进程。这是Linux启动后的第一个进程,pid进程编号为1,其他进程都是它的后代。</p><p>然后,init线程加载系统的各个模块,比如窗口程序和网络程序,直至执行/bin/login程序,跳出登录界面,等待用户输入用户名和密码。</p><p>至此,全部启动过程完成。 </p><p><img src="http://imgset.gitee.io/img/1571160002202.png" alt="1571160002202"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Windows </tag>
</tags>
</entry>
<entry>
<title>Linux内核(文件)</title>
<link href="/2016/03/12/2016-03-12-Linux%E7%AC%A6%E5%8F%B7%E6%96%87%E4%BB%B6/"/>
<url>/2016/03/12/2016-03-12-Linux%E7%AC%A6%E5%8F%B7%E6%96%87%E4%BB%B6/</url>
<content type="html"><![CDATA[<h1 id="链接文件"><a href="#链接文件" class="headerlink" title="链接文件"></a>链接文件</h1><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ln p1 p2</span></span><br></pre></td></tr></table></figure><p> 创建一个新的硬链接,由路径p1标识的文件创建一个路径名为p2的硬链接。</p><p>硬链接特性:</p><ul><li>不允许用户给目录创建硬链接。因为这可能把目录树变为环形图,从而就不能通过名字定位一个文件。</li><li>只有在同一个文件系统中的文件之间才能创建链接。这带来比较大的限制。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ln -s p1 p2</span></span><br></pre></td></tr></table></figure><p>创建一个软连接,也称符号链接。符号链接是段文件,这些文件包含有另一个文件的任意一个路径名。路径名可以指向2位于任意一个文件系统的人任意目录或文件,甚至包括不存在的文件。</p><h1 id="Linux中的所有文件类型"><a href="#Linux中的所有文件类型" class="headerlink" title="Linux中的所有文件类型"></a>Linux中的所有文件类型</h1><ul><li>普通文件</li><li>目录</li><li>符号链接</li><li>面向块的设备文件</li><li>面向字符的设备文件</li><li>管道和命名管道</li><li>套接字</li></ul><h1 id="Linux中的文件描述与索引"><a href="#Linux中的文件描述与索引" class="headerlink" title="Linux中的文件描述与索引"></a>Linux中的文件描述与索引</h1><p>文件系统处理文件需要所有的信息包含在一个名为索引节点的数据结构中。每个文件都有自己的索引节点 ,文件系统用索引节点来标识文件。</p><p>文件系统及内核函数对索引节点的处理可能随Unix西永的不同有很大的差异,但必须提供POSIX标准中指定如下属性:</p><ul><li>文件类型</li><li>与 文件相关的硬链接数量</li><li>以字节为单位的文件长度</li><li>设备标识符(即包含文件的设备的表示符)</li><li>在文件系统中表示文件的索引节点号</li><li>文件拥有者的UID</li><li>文件的用户组ID</li><li>几个时间戳,表示索引节点状态改变的事件最后访问事件与最后修改时间</li><li>访问权限和文件模式</li></ul><h1 id="Linux文件权限"><a href="#Linux文件权限" class="headerlink" title="Linux文件权限"></a>Linux文件权限</h1><p><img src="http://imgset.gitee.io/img/LinuxFile.png" alt="LinuxFile"></p><p>每三个为一组</p><p>第一组(文件所有者权限)</p><p>第二组(同组用户,不包括所有者权限)</p><p>第三组(其他用户权限)</p><p>可用chmod命令修改文件权限 例:chmod 777 <filename></p><h2 id="除此之外还有三种附加的标记"><a href="#除此之外还有三种附加的标记" class="headerlink" title="除此之外还有三种附加的标记"></a>除此之外还有三种附加的标记</h2><p>suid</p><p>进程执行一个文件时通常保持进程拥有者的UID,然而如果设置了可执行文件的suid的标志位,进程就获得了该文件拥有者的UID。</p><p>sgid </p><p>进程执行一个文件时保持进程组的用户组ID。然而设置了可执行sgid标志位,进程就获得了该文件用户组的ID。</p><p>sticky</p><p>实则知了sticky标志位的可执行文件相当于向内核发出一个请求,当程序执行结束后,依然将他保留在内存。这个标志现在已经使用基于代码页共享的其他方法替代。</p><p><img src="http://imgset.gitee.io/img/1571160010872.png" alt="1571160010872"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>Linux进程管理</title>
<link href="/2016/03/12/2016-03-12-Linux%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86/"/>
<url>/2016/03/12/2016-03-12-Linux%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86/</url>
<content type="html"><![CDATA[<p><img src="http://imgset.gitee.io/img/session-and-process.png" alt="âLinuxè¿ç¨âçå¾çæç´¢ç"æ"></p><h1 id="管理进程"><a href="#管理进程" class="headerlink" title="管理进程"></a>管理进程</h1><p>fork()创建一个新进程、_exit()终止一个进程、exec() 装入一个新程序</p><p>当一个系统调用执行以后,进程就在所装入的全新地址空间恢复运行。</p><p>调用fork() 的进程是父进程,新进程就是他的子进程,两个进程可以互相通信,在每个进程的数据结构中,包含两个指针,一个指向父进程,另一个指向子进程。</p><p>实现fork() ,可以把父进程的数据与代码复制到子进程,但这会很浪费时间。</p><p>他需要完成以下任务:</p><ul><li>为子进程的页表分配页面</li><li>为子进程的页分配页面</li><li>初始化子进程的页表</li><li>把父进程的页复制到子进程对应的页中</li></ul><p>Linux引进了(Copy-On-Write)写时复制的技术。</p><p>写时复制(copy-on-write)是一种可以推迟甚至避免复制数据的技术。内核此时并不是复制整个进程空间,而是让父进程和子进程共享同一个副本。只有在需要写入的时候,数据才会被复制,从而使父进程、子进程拥有各自的副本。也就是说,资源的复制只有在需要写入的时候才进行,在此之前以只读方式共享。这种技术使得对地址空间中的页的复制被推迟到实际发生写入的时候。有时共享页根本不会被写入,例如,fork()后立即调用exec(),就无需复制父进程的页了。fork()实际开销就是复制父进程的页表以及给子进程创建唯一的PCB。这种优化可以避免复制大量根本就不会使用的数据。</p><p>_exit()系统调用中止一个进程,内核对这个系统调用的处理是通过释放进程所拥有的资源,并向父进程发送SIGCHILD信号,来实现的。</p><h1 id="僵死进程"><a href="#僵死进程" class="headerlink" title="僵死进程"></a>僵死进程</h1><p>wait4()系统调用允许进程等待,直到其中的一个子进程结束,它返回已终止的进程PID</p><p>内核在执行这个系统调用时,检查子进程是否已经终止。引入僵死进程的特殊状态是为了表示终止的进程,父进程在执行完wait4()系统调用之前,进程就一直停留在那种状态。系统调用处理程序从进程描述符字段获取有关资源使用的数据,一旦得到数据,就可以释放进程描述符。若在执行时子进程尚未结束,内核就会把该进程设置为等待 状态,直到子进程结束。</p><p>waitpid(),允许等待一个特殊的子进程。</p><p><img src="http://imgset.gitee.io/img/1571160005426.png" alt="1571160005426"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>Linux 内存地址与分段机制</title>
<link href="/2016/03/12/2016-03-14-Linux%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80/"/>
<url>/2016/03/12/2016-03-14-Linux%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80/</url>
<content type="html"><![CDATA[<h1 id="Linux-内存地址与分段机制"><a href="#Linux-内存地址与分段机制" class="headerlink" title="Linux 内存地址与分段机制"></a>Linux 内存地址与分段机制</h1><p>基于80x86</p><h3 id="逻辑地址"><a href="#逻辑地址" class="headerlink" title="逻辑地址"></a>逻辑地址</h3><p>指定一个操作数或一条指令的地址,这种寻址方式,在80x86分段模式下表现的尤为得体,在Windows里二进制可执行文件会分为若干段。每一个逻辑地址都由一个段和偏移组成,偏移量指定了从段开始的地方到实际地址的距离。</p><h3 id="线性地址"><a href="#线性地址" class="headerlink" title="线性地址"></a>线性地址</h3><p>也可以叫做虚拟地址,是一个32位整数,最高可以表示到4294967296个内存单元。通常用16进制表示,值的范围0x00000000到0xFFFFFFFF</p><h3 id="物理地址"><a href="#物理地址" class="headerlink" title="物理地址"></a>物理地址</h3><p>用于内存芯片级内存单元寻址。他们从微处理器的地址引脚发送到内存总线上的电信号对应。物理地址由32位或36位unsigned int 表示</p><p>内存管理单元(MMU)通过一种称为分段单元的硬件电路,把一个逻辑地址转换为线性地址,接着,第二个称之为分页单元的硬件电路把线性地址转换为物理地址。</p><p><img src="http://imgset.gitee.io/img/1536740000646.png" alt="1536740000646"></p><p>在多处理器系统中,所有 CPU都共享同一内存,这意味着 RAM芯片可以由独立的 CPU并发地访问。因为在RAM芯片上的读或写操作必须串行地执行,因此一种所谓内存仲裁器的硬件电路插在总线和每个 RAM芯片之间,其作用是如果某个 RAM芯片空闲,就准予一个 CPU访问,如果该芯片忙于为另一个处理器提出的请求服务,就延迟这个 CPU的访问 。即使在单处理器上h也使用内存仲裁器,因为单处理器系统中包含一个叫做 DMA控制器的特殊处理器,在多处理器系统的情况下,因为仲裁器有多个输入端口,所以其结构更加复杂,例如,双Pentium在每个芯片的入口维持一个两端口仲裁器,并在试图使用公用总线前请求两个CPU交换同步信息.从编程观点看,因为仲栽器由硬件电路管理,因此它是隐藏的。</p><h1 id="系统分段"><a href="#系统分段" class="headerlink" title="系统分段"></a>系统分段</h1><p>一个逻辑地址由两部分组成:一个段标识符和一个指定段内先对地址的偏移量。段标识符是一个16位长的字段,称为段描述符。而偏移量是一个32位长的字段。</p><p><img src="http://imgset.gitee.io/img/1536740109090.png" alt="1536740109090"></p><p>处理器提供6个段寄存器,保存了段选择符,分别是:cs ss ds es fs gs,6个段寄存器。</p><p>cs 代码段寄存器</p><p>ss 栈段寄存器</p><p>ds 数据段寄存器 </p><p>cs 寄存器中,含有一个两位字段,用以指明CPU的当前CPL(Current Privilege Level),0代表R0级别,1代表R3级别。分别称为,内核态和用户态。</p><h3 id="段描述符"><a href="#段描述符" class="headerlink" title="段描述符"></a>段描述符</h3><p>每个段由一个8字节的段描述符表示,他描述了段的属性,段描述符放在GDT(Global Descriptor Table)或LDT(Local Description Table)中。</p><p>一般来说只定义一个GDT,而每个进程除了存放在GDT中的段之外如果还有附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在gdtr控制寄存器中,当前正在被使用的LDT地址和大小放在ldtr寄存器中。</p><h5 id="段描述符格式"><a href="#段描述符格式" class="headerlink" title="段描述符格式"></a>段描述符格式</h5><p><img src="http://imgset.gitee.io/img/1536740043063.png" alt="1536740043063"></p><p><strong>段描述符字段</strong>:</p><table><thead><tr><th>字段</th><th>描述</th></tr></thead><tbody><tr><td>BASE</td><td>包含段的首字节的线性地址</td></tr><tr><td>G</td><td>粒度标志:如果该位清0,、则段大小以字节为单位,否则以4096字节的倍数计算</td></tr><tr><td>Limit</td><td>存放段中最后一个内存单元的偏移量,从而决定段的长度,如果G被置为0,则一个段的大小在1个字节1 MB之间变化。否則,将在4 KB到 4 GB 之间变化</td></tr><tr><td>S</td><td>系统标志:如果它被清0,则这是一个系统段,存储诸如 LDT这种关鍵的数据结构,否則它是一个普通的代码段或数据段。</td></tr><tr><td>Type</td><td>描述了段的类型特征和它的存取权限。</td></tr><tr><td>DPL</td><td>描述符特权级字段,用于限制对这个段的存取 。 它表示为访问这个段而要求的 CPU最小的优先级 。因此, DPL设为0的段只能当 CPL设为0时(即在内核态)才垃可访问的,而 DPL设为3的段对任何CPL都是可访问的。</td></tr><tr><td>P</td><td>Segment-Present标志:等于0表示段当前不在主存中, Linux总是把这个标志(第47位)设为1。因为它从来不把整个段交换到磁盘上去。</td></tr><tr><td>D 或 B</td><td>称 为 D成 B的标志,取决于是代码段还是数据段 ,D或B的含义在两种情况下稍有所区别,但是如果段偏移量的地址是32位长,就基本上把它置为 1.如果这个偏移置是16位长,它被清0。</td></tr><tr><td>AVL</td><td>被Linux忽略。</td></tr></tbody></table><p> </p><p><strong>代码段描述符</strong></p><p> 表示这个段代表一个代码段,他可以放在GDT或LDT中,S标志位为1。</p><p><strong>数据段描述符</strong></p><p> 表示这个段代表一个数据段,他可以放在GDT或LDT中,S标志位为1。</p><p><strong>任务状态段描述符(TSSD)</strong></p><p> 表示这个段描述符代表一个任务状态段(Task State Segment)TSS,也就是说这个段用于保存处理器寄存器的内容它只能出现在GDT中。根据相应的进程是否正在CPU上运行,Type字段的值分别为11或9.这个描述符的S标志置0。</p><p><strong>局部描述符表描述符(LDTD)</strong></p><p> 表示这个段描述符代表一个包含LDT的段,它只出现在GDT中。相应的Type字段的值 为2,S标志置为0。</p><p><img src="http://imgset.gitee.io/img/1571159997314.png" alt="1571159997314"></p>]]></content>
<categories>
<category> OS </category>
</categories>
<tags>
<tag> Linux </tag>
</tags>
</entry>
<entry>
<title>Linux开机过程</title>
<link href="/2016/03/10/2016-03-10-Linux%20%E5%BC%80%E6%9C%BA%E8%BF%87%E7%A8%8B/"/>
<url>/2016/03/10/2016-03-10-Linux%20%E5%BC%80%E6%9C%BA%E8%BF%87%E7%A8%8B/</url>