-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1115 lines (537 loc) · 761 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>Qiling框架模拟运行固件配合IDA动态调试</title>
<link href="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/"/>
<url>/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/</url>
<content type="html"><![CDATA[<h3 id="前前言"><a href="#前前言" class="headerlink" title="前前言"></a>前前言</h3><ul><li>这个也是老早之前发在看雪上的帖子,原帖地址: <a href="https://bbs.kanxue.com/thread-274828.htm">https://bbs.kanxue.com/thread-274828.htm</a></li><li>以下开始为原贴内容</li></ul><span id="more"></span><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><ul><li><p>实际分析嵌入式固件过程中,经常会遇到各种非Linux和非常见系统的固件,这类固件往往就是一个"裸"的二进制程序,而不像ELF和PE这些有特定结构的可执行程序,对于这类固件往往很难对它进行动态调试,而只能静态分析。最近一段时间,学习了很强大的Qiling框架,它除了可以模拟运行各种可执行程序,还提供了Debugger接口,于是乎我就想着能不能用Qiling配合IDA动态调试固件。遗憾的是网上关于Qiling的教程大部分都是模拟ELF和PE这些相对来说比较标准的程序,而模拟像这种就一个"裸"的二进制程序的资料却很少,这里分享下我的摸索过程</p></li><li><p>关于Qiling和Unicorn这里就不多介绍了,详见官网</p><ul><li><p>Qiling官网: <a href="https://qiling.io/">https://qiling.io/</a></p></li><li><p>Unicorn官网: <a href="https://www.unicorn-engine.org/">https://www.unicorn-engine.org/</a></p></li></ul></li><li><p>下面以我上个帖子 <a href="https://bbs.pediy.com/thread-274788.htm">一个简单的STM32固件分析</a> 用到的固件stm32f103RCT6.bin来介绍如何用Qiling框架模拟运行指定函数并启用Debugger,然后使用IDA进行动态调试</p></li></ul><h3 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h3><ul><li><p>首先时qiling官方github的有一个<a href="https://github.com/qilingframework/qiling/tree/master/examples">example</a>引起了我的注意,如下图,qiling的示例有一个模拟arm下的uboot。uboot据我所知是一个bootloader,它在固件中就是一个"裸"的二进制程序,于是这里面可能就有我想要的样例</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221021210511630.png" alt="image-20221021210511630"></p></li><li><p>查看hello_arm_uboot.py代码,一直拉到最后,如下图,这部分代码就是模拟运行"裸"的二进制程序的一个例子</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221021211024326.png" alt="image-20221021211024326"></p></li><li><p>接下来就是照猫画虎,仿照着这个来尝试模拟运行stm32f103RCT6.bin固件中的XTEA函数。这里简单介绍下这个stm32f103RCT6.bin固件,它的加载基地址为0x8000000,从前一篇的分析可以知道它在地址0x800E288有个XTEA函数,这个函数就是接下来需要模拟调试运行的函数</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221021211526970.png" alt="image-20221021211526970"></p></li><li><p>首先是导入qiling和unicorn的包</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> qiling.core <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> qiling.const <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> qiling.os.const <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> unicorn.arm64_const <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> unicorn <span class="keyword">import</span> *</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">filepath=<span class="string">'stm32f103RCT6.bin'</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(filepath, <span class="string">'rb'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> fw = fp.read()</span><br></pre></td></tr></table></figure></li><li><p>接下来看原代码的Qiling对象生成方式,第一个code=uboot_code[0x40:],剔除了前0x40字节的原因应该是,uboot固件的前0x40字节不加载进内存,这里的stm32固件是整个都加载进内存的,所以可以直接传整个读入的fw。有个参数需要注意的是profile="uboot_bin.ql",看起来是还有一个配置文件"uboot_bin.ql"</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221021212040773.png" alt="image-20221021212040773"></p></li><li><p>在同级目录下,可以找到这个uboot_bin.ql,那么接下来,需要简单理解下这个配置文件的各个参数的意义</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221021212457187.png" alt="image-20221021212457187"></p></li><li><p>源码中搜索"heap_size",如下图,可以在<a href="https://github.com/qilingframework/qiling/blob/master/qiling/loader/blob.py">qiling/loader/blob.py</a>中找到关于这几个参数的含义</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023093614044.png" alt="image-20221023093614044"></p></li><li><p>根据上面代码可画出下图内存映射,"entry_point"这里为内存加载地址"load_address"而不是代码入口点,Qiling会根据entry_point和ram_size大小分配一块内存,然后将代码code写入,需要注意的是默认初始栈寄存器SP指向这块内存end_address - 0x1000的位置,如果模拟运行前不做修改,需要将ram_size预留出一定的栈空间的大小,不然往栈内存写数据时会覆盖code内存数据。堆内存heap的起始地址就是entry_point + ram_size,下图虚拟线表示默认不会直接映射堆内存,如果需要使用这块内存,需要先执行<code>ql.os.heap.alloc(size)</code>来使用</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023095235044.png" alt="image-20221023095235044"></p></li><li><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><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="comment"># 加载地址</span></span><br><span class="line">load_addr=<span class="number">0x8000000</span> </span><br><span class="line"></span><br><span class="line"><span class="comment"># 栈大小</span></span><br><span class="line">stack_size = <span class="number">0x20000</span> </span><br><span class="line"></span><br><span class="line"><span class="comment"># 堆大小</span></span><br><span class="line">heap_size = <span class="number">0x20000</span> </span><br><span class="line"></span><br><span class="line"><span class="comment"># 计算固件大小 0x1000对齐</span></span><br><span class="line">fw_size = math.ceil(<span class="built_in">len</span>(fw)/<span class="number">0x1000</span>)*<span class="number">0x1000</span> </span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始分配的内存大小包括 固件和栈空间大小,+0x1000是为了使可用的栈大小与stack_size保持一致</span></span><br><span class="line">ram_size = fw_size + stack_size + <span class="number">0x1000</span> </span><br><span class="line"></span><br><span class="line">cfg_str = <span class="string">f"""</span></span><br><span class="line"><span class="string">[CODE]</span></span><br><span class="line"><span class="string">ram_size = <span class="subst">{ram_size}</span></span></span><br><span class="line"><span class="string">entry_point = <span class="subst">{load_addr}</span></span></span><br><span class="line"><span class="string">heap_size = <span class="subst">{heap_size}</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">[MISC]</span></span><br><span class="line"><span class="string">current_path = /</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 保存配置文件</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">'ql-config.ql'</span>, <span class="string">'w'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> fp.write(cfg_str)</span><br></pre></td></tr></table></figure></li><li><p>接着,仿照样例生成Qiling对象,代码如下,因为这里模拟运行的用到了thumb指令,所以需要指定参数thumb=True。因为这里默认模拟的是小端序,而固件恰好是小端序固件,所以可以不指定端序,但是如果模拟的为大端序的固件,则需要指定参数endian=QL_ENDIAN.EB。更多参数用法详见官方文档</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ql = Qiling(code=fw, archtype=<span class="string">"arm"</span>, ostype=<span class="string">"blob"</span>, profile=<span class="string">"ql-config.ql"</span>, thumb=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure></li><li><p>定义模拟运行的起始地址和终止地址,这里因为只模拟运行sub_800E288函数,所以设为sub_800E288函数起始地址和终止地址即可</p> <figure class="highlight python"><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">begin =<span class="number">0x800E288</span></span><br><span class="line">end = <span class="number">0x800E296</span></span><br></pre></td></tr></table></figure><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023101808829.png" alt="image-20221023101808829"></p></li><li><p>因为模拟的sub_800E288函数有3个参数,所以还需要给函数传参</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023102108181.png" alt="image-20221023102108181"></p></li><li><p>这里简单介绍下ARM中常见的函数传参规范:对于函数参数不超过4个参数时用r0,r1,r2,r3寄存器来传参,对于函数参数超过4个参数时,前4个依旧用r0,r1,r2,r3寄存器传参,往后的参数以压入栈的方式传参。类似地函数如果有返回值,约定以r0寄存器返回。这些规范主要是为了不同程序或模块之间相互调用各自的函数而不出错,因为是规范,所以也就可以不遵循这个规范,比如说你自己用汇编写的程序的话,想怎么传参就怎么传参,只要你自己不限入混乱,程序不出错即可。</p></li><li><p>废话不多说,回到正题,这里获取unicorn对象来为函数传参(unicorn对象可以读写各个寄存器)。从上面可知,需要模拟的函数的3个参数都是指针,所以需要给r0,r1,r2 这3个寄存器写入3个内存地址,而初始SP寄存器(栈寄存器)和heap之间有块0x1000的内存没有用到,因此这里可以用sp+0x100,sp+0x200,sp+0x300,这3个地址作为函数参数传入。不过单单将3个地址写入r0,r1,r2寄存器还不够,还要在相应地内存写入数据,这样才是完整的传参过程。因为第3个参数是加密结果的输出,所以可以不往该地址写数据。如下代码,为r0传入密钥"BA 2F 96 A9 BA 2F 96 A9 BA 2F 96 A9 BA 2F 96 A9",为r1传入明文"10 BE 62 F8 E8 DC 34 46"</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></pre></td><td class="code"><pre><span class="line">sp = ql.arch.regs.sp</span><br><span class="line"></span><br><span class="line">uc=ql.uc</span><br><span class="line"></span><br><span class="line"><span class="comment">#为第1个参数key传参</span></span><br><span class="line">uc.reg_write(UC_ARM_REG_R0, sp+<span class="number">0x100</span>) </span><br><span class="line">uc.mem_write(sp+<span class="number">0x100</span>, <span class="built_in">bytes</span>.fromhex(<span class="string">"BA 2F 96 A9 BA 2F 96 A9 BA 2F 96 A9 BA 2F 96 A9"</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">#为第2个参数in传参</span></span><br><span class="line">uc.reg_write(UC_ARM_REG_R1, sp+<span class="number">0x200</span>)</span><br><span class="line">uc.mem_write(sp+<span class="number">0x200</span>, <span class="built_in">bytes</span>.fromhex(<span class="string">"10 BE 62 F8 E8 DC 34 46"</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">#为第3个参数out传参</span></span><br><span class="line">uc.reg_write(UC_ARM_REG_R2, sp+<span class="number">0x300</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>然后是启用debugger,Qiling默认是不启用debugger的,如下代码,启用gdb debugger并监听相应IP和端口。详细说明见官方文档:<a href="https://docs.qiling.io/en/latest/debugger/">https://docs.qiling.io/en/latest/debugger/</a></p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ql.debugger = <span class="string">'gdb:0.0.0.0:9999'</span></span><br></pre></td></tr></table></figure></li><li><p>在qiling-1.4.4中,设置了debugger后且ostype为"blog"的情况下,直接运行ql.run会报"AttributeError: 'QlOsBlob' object has no attribute 'fs_mapper'"错</p></li></ul><p><img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023113044571.png" alt="image-20221023113044571"></p><ul><li><p>在源码中找到了相应的描述,如下图,QlOsBlob类中有几个函数还没有实现,详见:<a href="https://github.com/qilingframework/qiling/blob/master/qiling/os/blob/blob.py">qiling/os/blob/blob.py</a></p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023113249473.png" alt="image-20221023113249473"></p></li><li><p>不过好在,经过测试,在执行ql.run之前,可以按以下方式,规避下这个问题。这段代码作用很简单,就是给个空壳给ql.os.fs_mapper。Python一个非常好的特性就是可以像下面这样,可以很容易地对各种库进行动态修改,而不用去修改库的原文件。下面这部份代码,等以后Qiling有相应的函数实现后就不在需要了</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyMapper</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">add_fs_mapping</span>(<span class="params">self, ql_path, real_dest</span>):</span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"> </span><br><span class="line">ql.os.fs_mapper = MyMapper()</span><br></pre></td></tr></table></figure></li><li><p>重新运行后,出现如下信息,说明成功模拟运行,并启用了debugger并监听了相应的IP和端口。需要注意的是,如果在生成Qiling对象是指定了参数verbose=QL_VERBOSE.OFF,那么运行时不会有任何log信息</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023114304961.png" alt="image-20221023114304961"></p></li><li><p>接下来,介绍IDA中如何连接到这个server进行动态调试</p></li><li><p>IDA中选择Debugger > Select debugger或者快捷键F9,选择Remote GDB debugger</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023122125572.png" alt="image-20221023122125572"></p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023122428343.png" alt="image-20221023122428343"></p></li><li><p>选择Debugger > Process options</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023122518931.png" alt="image-20221023122518931"></p></li><li><p>输入运行Qiling机器的IP和端口,如果运行Qiling和IDA是同一机器同一系统内,则IP填127.0.0.1即可</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023122623653.png" alt="image-20221023122623653"></p></li><li><p>接着选择Debugger > Debugger options</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023122900288.png" alt="image-20221023122900288"></p></li><li><p>勾选如下两个即可</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023123136389.png" alt="image-20221023123136389"></p></li><li><p>然后选择Debugger > Manual memory regions</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023123178231.png" alt="image-20221023123436544"></p></li><li><p>按照下图,新建几个内存映射,否则调试时,IDA可能不能跳转到栈内存中,也不能查看栈内存的数据</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023095235044.png" alt="image-20221023095235044"></p></li><li><p>这里选择添加两个映射分别是栈内存和栈内存到堆内存之间的一小部分,堆内存(heap)因为这里没有用到,所以可以省略,code因为IDA分析的固件就是这部分内存,所以也可以省略</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023123852414.png" alt="image-20221023123852414"></p></li><li><p>最后选择Debugger > Attach to process,会出现一个PID为0的进程,点击OK即可</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023123204494.png" alt="image-20221023123204494"></p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023123252377.png" alt="image-20221023123252377"></p></li><li><p>最后如下图可以看到,IDA进入了调试模式,且停在sub_800E28函数开始的位置,接下来就可以使用IDA进行动态调试了</p><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023124128868.png" alt="image-20221023124128868"></p></li><li><p>调试结束后,可以回到Qiling中,读取相应地址的结果。从我上一个帖子可知预期的密文为"8C 79 F5 D1 5E A9 46 2D",如下图,输出与预期一致</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ql.mem.read(sp+<span class="number">0x300</span>, <span class="number">8</span>).<span class="built_in">hex</span>()</span><br></pre></td></tr></table></figure><p> <img src="/article/2024/10/Qiling%E6%A1%86%E6%9E%B6%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C%E5%9B%BA%E4%BB%B6%E9%85%8D%E5%90%88IDA%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95/image-20221023130922847.png" alt="image-20221023130922847"></p></li></ul><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><ul><li>本文只是简单介绍了qiling的一些基本用法,关于qiling更多用法详见官方文档:<a href="https://docs.qiling.io/en/latest/">https://docs.qiling.io/en/latest/</a></li><li>本文并没有过多介绍unicorn的用法,关于unicorn的用法可以参看官方文档:<a href="https://www.unicorn-engine.org/docs/">https://www.unicorn-engine.org/docs/</a>,还有论坛的这两个帖子:<a href="https://bbs.pediy.com/thread-267153.htm">利用unicorn分析固件中的算法 </a>,<a href="https://bbs.pediy.com/thread-253868.htm">Unicorn 在 Android 的应用 </a></li><li>实际上对于STM32 mcu的模拟可能并不需要那么繁琐,官方有相应的模拟示例,详见:<a href="https://github.com/qilingframework/qiling/tree/master/examples/mcu">https://github.com/qilingframework/qiling/tree/master/examples/mcu</a>,本文的这种方法主要是用来模拟那些qiling官方还不支持的固件,只不过例子是stm32的固件而已</li><li>本文完整代码包含在附件之中</li></ul><hr><h3 id="2022-10-27更新"><a href="#2022-10-27更新" class="headerlink" title="2022-10-27更新"></a>2022-10-27更新</h3><ul><li><p>qiling模拟thumb时,IDA下断点,停不下来,原因是IDA下的断点地址没有+1,但是qiling判断时却将当前地址+1了</p></li><li><p>执行ql.run之前加入以下代码,可以动态解决这个问题,下面代码作用相当于注释掉qiling/debugger/gdb/utils.py中dbg_hook函数的前两行代码。同样地,待qiling后续版本修复这个问题后就不再需要这部分代码了</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> types</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">dbg_hook</span>(<span class="params">self, ql, address: <span class="built_in">int</span>, size: <span class="built_in">int</span></span>):</span><br><span class="line"> <span class="comment">#if ql.arch.type == QL_ARCH.ARM and ql.arch.is_thumb:</span></span><br><span class="line"> <span class="comment"># address += 1</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># resuming emulation after hitting a breakpoint will re-enter this hook.</span></span><br><span class="line"> <span class="comment"># avoid an endless hooking loop by detecting and skipping this case</span></span><br><span class="line"> <span class="keyword">if</span> address == self.last_bp:</span><br><span class="line"> self.last_bp = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">elif</span> address <span class="keyword">in</span> self.bp_list:</span><br><span class="line"> self.last_bp = address</span><br><span class="line"></span><br><span class="line"> ql.log.info(<span class="string">f'gdb> breakpoint hit, stopped at <span class="subst">{address:#x}</span>'</span>)</span><br><span class="line"> ql.stop()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># # <span class="doctag">TODO:</span> not sure what this is about</span></span><br><span class="line"> <span class="comment"># if address + size == self.exit_point:</span></span><br><span class="line"> <span class="comment"># ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}')</span></span><br><span class="line"> <span class="comment"># ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}')</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self, begin: <span class="type">Optional</span>[<span class="built_in">int</span>] = <span class="literal">None</span>, end: <span class="type">Optional</span>[<span class="built_in">int</span>] = <span class="literal">None</span>, timeout: <span class="built_in">int</span> = <span class="number">0</span>, count: <span class="built_in">int</span> = <span class="number">0</span></span>):</span><br><span class="line"> <span class="string">"""Start binary emulation.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> Args:</span></span><br><span class="line"><span class="string"> begin : emulation starting address</span></span><br><span class="line"><span class="string"> end : emulation ending address</span></span><br><span class="line"><span class="string"> timeout : limit emulation to a specific amount of time (microseconds); unlimited by default</span></span><br><span class="line"><span class="string"> count : limit emulation to a specific amount of instructions; unlimited by default</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># replace the original entry point, exit point, timeout and count</span></span><br><span class="line"> self.entry_point = begin</span><br><span class="line"> self.exit_point = end</span><br><span class="line"> self.timeout = timeout</span><br><span class="line"> self.count = count</span><br><span class="line"></span><br><span class="line"> <span class="comment"># init debugger (if set)</span></span><br><span class="line"> debugger = select_debugger(self._debugger)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> debugger:</span><br><span class="line"> debugger = debugger(self)</span><br><span class="line"> debugger.gdb.dbg_hook = types.MethodType(dbg_hook, debugger.gdb)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># patch binary</span></span><br><span class="line"> self.do_bin_patch()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> self.baremetal:</span><br><span class="line"> <span class="keyword">if</span> self.count <= <span class="number">0</span>:</span><br><span class="line"> self.count = -<span class="number">1</span></span><br><span class="line"></span><br><span class="line"> self.arch.run(count=self.count, end=self.exit_point)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.write_exit_trap()</span><br><span class="line"> <span class="comment"># emulate the binary</span></span><br><span class="line"> self.os.run()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># run debugger</span></span><br><span class="line"> <span class="keyword">if</span> debugger <span class="keyword">and</span> self.debugger:</span><br><span class="line"> debugger.run()</span><br></pre></td></tr></table></figure></li></ul><hr><h3 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h3><ul><li><a href="qiling-example.zip">qiling-example.zip</a></li></ul>]]></content>
<categories>
<category> article </category>
</categories>
<tags>
<tag> 逆向 </tag>
<tag> Qiling </tag>
</tags>
</entry>
<entry>
<title>一个简单的STM32固件分析</title>
<link href="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/"/>
<url>/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h3 id="前前言"><a href="#前前言" class="headerlink" title="前前言"></a>前前言</h3><ul><li>这个是老早之前发在看雪上的帖子,原帖地址: <a href="https://bbs.kanxue.com/thread-274788.htm">https://bbs.kanxue.com/thread-274788.htm</a></li><li>以下开始为原贴内容</li></ul><span id="more"></span><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><ul><li><p>看完论坛的 <a href="https://bbs.pediy.com/thread-272811.htm">STM32固件逆向</a> 帖子后,又在评论区发现这个求助贴 <a href="https://bbs.pediy.com/thread-272872.htm">stm32芯片程序有xtea加密算法,但是数据排序的问题研究不明白</a> ,于是想着分析分析练练手</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019212949107.png" alt="image-20221019212949107"></p></li><li><p>根据求助贴内容,概括下有用的信息</p><ul><li><p>一个名为stm32f103RCT6.bin的固件,从固件名可以得知MCU型号为stm32f103RCT6</p></li><li><p>MCU与设备A通信时用到了XTEA加密</p></li><li><p>MCU注册时用到的密钥为 BA2F96A9</p></li><li><p>MCU与设备A通信时的两个加密样例如下</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">明文1</span><br><span class="line">10 BE 62 F8 E8 DC 34 46</span><br><span class="line">密文1</span><br><span class="line">8C 79 F5 D1 5E A9 46 2D</span><br><span class="line"></span><br><span class="line">明文2</span><br><span class="line">0E 77 50 C8 C6 27 E1 BF</span><br><span class="line">密文2</span><br><span class="line">36 0A 1A 6E 6E FE F0 84</span><br></pre></td></tr></table></figure></li></ul></li><li><p>接下来目标就是根据上面信息,找到加密函数,还原加密过程</p></li></ul><h3 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h3><ul><li><p>首先用IDA打开stm32f103RCT6.bin文件,选择ARM小端序,然OK进入即可</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019214524365.png" alt="image-20221019214524365"></p></li><li><p>直接以Binary file打开,可以看到,IDA没有识别出任何函数</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019214706672.png" alt="image-20221019214706672"></p></li><li><p>习惯性的把前几个字节"D"三下,如下图,可以大胆的猜测加载基地址为0x8000000,事实上也确实如此,当然可以根据MCU型号stm32f103RCT6去查datasheet。不过更多确定基地址的方法请参考论坛的这个帖子 <a href="https://bbs.pediy.com/thread-267719.htm">固件安全之加载地址分析</a> ,里面详细介绍了多种方法来确定基地址,这里就不再赘述了</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019214831722.png" alt="image-20221019214831722"></p></li><li><p>Edit > Segments > Rebase program... 重设基地址为0x8000000,设置好基地址后,就已经往成功路上迈向一大步了</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019215646779.png" alt="image-20221019215646779"></p></li><li><p>接下来,Alt + L从前面几个地址之后开始选择,一直到末尾,右键选择"Analyze selected area"</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019215922602.png" alt="image-20221019215922602"></p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220040812.png" alt="image-20221019220040812"></p></li><li><p>出现下面提示框,选择Analyze,然后静待IDA分析过程结束</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220121203.png" alt="image-20221019220121203"></p></li><li><p>这时候,可以看到,IDA已经识别出许多函数了</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220226469.png" alt="image-20221019220226469"></p></li><li><p>接下来使用Findcrypt插件搜索加密常量,看看有没有什么发现,Search > Find crypto constants,如下图,可以看到,有个TEA的delta加密常量,这正好对应前面提到的MCU通信时使用了XTEA加密</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220515688.png" alt="image-20221019220515688"></p></li><li><p>跟踪查看引用该常量的sub_800E288函数,F5结果如下图所示</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220734506.png" alt="image-20221019220734506"></p></li><li><p>对比网上的XTEA加密C代码,可以知道,sub_800E288函数的参数从左到右为,加密密钥、加密输入、加密输出</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019220905530.png" alt="image-20221019220905530"></p></li><li><p>对照上面XTEA加密C代码,就可以对一些变量重命名了,如下图所示,可以看到,加密时的密钥输入并不直接作为XTEA加密的密钥,而是经过了一些运算进行了变换</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019221229600.png" alt="image-20221019221229600"></p></li><li><p>接下来详细分析加密过程,首先时最前面的这部分,存在大量的 左移24、左移16和左移8,熟悉的朋友可能很快就反应过来,这个是大小端转换。所谓大小端指的是"大端序"(BigEndian)和"小端序"(LittleEndian)。用一句话来说"大端序"就是高位在前,低位在后,"小端序"就是低位在前,高位在后。举个简单例子对于同一个4字节的byte数组"12 34 56 78",在大端序里面把它当成4字节的int的话它就是0x12345678,而在小端序里面把它当成4字节的int的话它就是0x78563412了。下面的"<<24 <<16 <<8"作用就是用来将4个byte的数组转为大端序的整数(MCU是小端序的,直接用*(int *)类型强转的话得到的结果就是一个小端序的int)</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019221450090.png" alt="image-20221019221450090"></p></li><li><p>忽略左移24、左移16和左移8这些端序转换的部分,我们能发现,其实对于输入in在正式XTEA加密前没有做任何特殊处理,而对于密钥的每一个字节则与一些常量进行了异或处理。同样地,看到"0x66、0x6F ...",这些值熟悉的情况下,很容易反应过来,这些可能是ASCII码</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019221450090.png" alt="image-20221019221450090"></p></li><li><p>TAB键切换查看反汇编代码,如下图,可以看到这些常量都是基于地址0x8030A30开始的偏移量</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019223224183.png" alt="image-20221019223224183"></p></li><li><p>查看0x8030A30地址,"A"一下,可以得到一个字符串,如下图所示,看得出来这是个有故事的字符串</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019223350235.png" alt="image-20221019223350235"></p></li><li><p>回到反编译代码窗口,重新F5一下,可以看到,反编译代码更清晰明了了,密钥在用于XTEA加密之前,每个字节会与上面的字符串相对应的字节进行异或处理</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019223537966.png" alt="image-20221019223537966"></p></li><li><p>再来看后面这部分,如下图,后面的其实就是标准XTEA加密,只不过对于加密结果的输出,同样有大小端的转换,"HIBYTE(x) BYTE2(x) BYTE1(x)"与上面的"<<24 <<16 <<8"一样,常见于数据大小端转换</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019223757828.png" alt="image-20221019223757828"></p></li><li><p>到这里就结束了吗?- 答案是还没有,求助贴里面提到的MCU注册密钥是"BA2F96A9",只有4个字节大小的密钥,但是这个加密函数的密钥输入却有16个字节,所以从MCU注册密钥"BA2F96A9"到加密密钥userKey中间还有一些过程需要去分析</p></li><li><p>对 sub_800E288 "X"一下,查看sub_800E288函数的交叉引用,如下图,可以看到只有一处调用</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019224422289.png" alt="image-20221019224422289"></p></li><li><p>查看该调用,如下图,可以看到第一个参数加密密钥是以地址0x20000104传入的,所以加密密钥就储存在0x20000104地址处,接下来只要查看哪些函数有往0x20000104地址写数据,即可找到密钥变换的函数</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019224607801.png" alt="image-20221019224607801"></p></li><li><p>接下来Text Serach搜索0x20000104,看哪些函数使用了0x20000104地址</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019224814544.png" alt="image-20221019224814544"></p></li><li><p>如下图,搜索的结果并不多,挨个查看后,可以看到只有sub_800F3C8函数中有往0x20000104地址写数据的代码,而且sub_800F3C8函数参数为unsigned int类型(刚好是4个字节大小),所以基本上可以确定这个函数就是MCU注册时的密钥变换为加密密钥的函数</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019224916234.png" alt="image-20221019224916234"></p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019225002719.png" alt="image-20221019225002719"></p></li><li><p>接下来将sub_800F3C8和sub_800E288这两个函数反编译伪代码提取出来,用VS Code简单写个程序验证下,代码如下</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"defs.h"</span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">keyExpand</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> key, _BYTE *outKey)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">XTEA</span><span class="params">(_BYTE *userkey, _BYTE *in, _BYTE *out)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">keyExpand</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> key, _BYTE *outKey)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> i; <span class="comment">// r0</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> ( i = <span class="number">0</span>; i < <span class="number">16</span>; i = (i + <span class="number">1</span>) )</span><br><span class="line"> *(i + outKey) = key >> (<span class="number">8</span> * (<span class="number">3</span> - i % <span class="number">4</span>));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">XTEA</span><span class="params">(_BYTE *userkey, _BYTE *in, _BYTE *out)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> v3; <span class="comment">// r3</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> v4; <span class="comment">// r4</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> v5; <span class="comment">// r5</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> i; <span class="comment">// r6</span></span><br><span class="line"> <span class="type">int</span> v7[<span class="number">4</span>]; <span class="comment">// [sp+0h] [bp-34h]</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> v8; <span class="comment">// [sp+10h] [bp-24h]</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> v9; <span class="comment">// [sp+14h] [bp-20h]</span></span><br><span class="line"></span><br><span class="line"> <span class="type">char</span> aStefanlovesmay[] = <span class="string">"StefanLovesMaya!"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( userkey !=<span class="number">0</span> && in!=<span class="number">0</span> && out!=<span class="number">0</span> )</span><br><span class="line"> {</span><br><span class="line"> v8 = (*in << <span class="number">24</span>) + (in[<span class="number">1</span>] << <span class="number">16</span>) + (in[<span class="number">2</span>] << <span class="number">8</span>) + in[<span class="number">3</span>];</span><br><span class="line"> v9 = (in[<span class="number">4</span>] << <span class="number">24</span>) + (in[<span class="number">5</span>] << <span class="number">16</span>) + (in[<span class="number">6</span>] << <span class="number">8</span>) + in[<span class="number">7</span>];</span><br><span class="line"> v7[<span class="number">0</span>] = (userkey[<span class="number">3</span>] ^ aStefanlovesmay[<span class="number">3</span>])</span><br><span class="line"> + ((*userkey ^ aStefanlovesmay[<span class="number">0</span>]) << <span class="number">24</span>)</span><br><span class="line"> + ((userkey[<span class="number">1</span>] ^ aStefanlovesmay[<span class="number">1</span>]) << <span class="number">16</span>)</span><br><span class="line"> + ((userkey[<span class="number">2</span>] ^ aStefanlovesmay[<span class="number">2</span>]) << <span class="number">8</span>);</span><br><span class="line"> v7[<span class="number">1</span>] = (userkey[<span class="number">7</span>] ^ aStefanlovesmay[<span class="number">7</span>])</span><br><span class="line"> + ((userkey[<span class="number">4</span>] ^ aStefanlovesmay[<span class="number">4</span>]) << <span class="number">24</span>)</span><br><span class="line"> + ((userkey[<span class="number">5</span>] ^ aStefanlovesmay[<span class="number">5</span>]) << <span class="number">16</span>)</span><br><span class="line"> + ((userkey[<span class="number">6</span>] ^ aStefanlovesmay[<span class="number">6</span>]) << <span class="number">8</span>);</span><br><span class="line"> v7[<span class="number">2</span>] = (userkey[<span class="number">11</span>] ^ aStefanlovesmay[<span class="number">11</span>])</span><br><span class="line"> + ((userkey[<span class="number">8</span>] ^ aStefanlovesmay[<span class="number">8</span>]) << <span class="number">24</span>)</span><br><span class="line"> + ((userkey[<span class="number">9</span>] ^ aStefanlovesmay[<span class="number">9</span>]) << <span class="number">16</span>)</span><br><span class="line"> + ((userkey[<span class="number">10</span>] ^ aStefanlovesmay[<span class="number">10</span>]) << <span class="number">8</span>);</span><br><span class="line"> v7[<span class="number">3</span>] = (userkey[<span class="number">15</span>] ^ aStefanlovesmay[<span class="number">15</span>])</span><br><span class="line"> + ((userkey[<span class="number">12</span>] ^ aStefanlovesmay[<span class="number">12</span>]) << <span class="number">24</span>)</span><br><span class="line"> + ((userkey[<span class="number">13</span>] ^ aStefanlovesmay[<span class="number">13</span>]) << <span class="number">16</span>)</span><br><span class="line"> + ((userkey[<span class="number">14</span>] ^ aStefanlovesmay[<span class="number">14</span>]) << <span class="number">8</span>);</span><br><span class="line"> v3 = v8;</span><br><span class="line"> v4 = v9;</span><br><span class="line"> v5 = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> ( i = <span class="number">0</span>; i < <span class="number">0x20</span>; ++i )</span><br><span class="line"> {</span><br><span class="line"> v3 += (((<span class="number">16</span> * v4) ^ (v4 >> <span class="number">5</span>)) + v4) ^ (v7[v5 & <span class="number">3</span>] + v5);</span><br><span class="line"> v5 -= <span class="number">-0x9E3779B9</span>;</span><br><span class="line"> v4 += (((<span class="number">16</span> * v3) ^ (v3 >> <span class="number">5</span>)) + v3) ^ (v7[(v5 >> <span class="number">11</span>) & <span class="number">3</span>] + v5);</span><br><span class="line"> }</span><br><span class="line"> *out = HIBYTE(v3);</span><br><span class="line"> out[<span class="number">1</span>] = BYTE2(v3);</span><br><span class="line"> out[<span class="number">2</span>] = BYTE1(v3);</span><br><span class="line"> out[<span class="number">3</span>] = v3;</span><br><span class="line"> out[<span class="number">4</span>] = HIBYTE(v4);</span><br><span class="line"> out[<span class="number">5</span>] = BYTE2(v4);</span><br><span class="line"> out[<span class="number">6</span>] = BYTE1(v4);</span><br><span class="line"> out[<span class="number">7</span>] = v4;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> key=<span class="number">0xBA2F96A9</span>;</span><br><span class="line"> _BYTE outKey[<span class="number">16</span>];</span><br><span class="line"> </span><br><span class="line"> keyExpand(key, outKey);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"outKey: "</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<<span class="number">16</span>; i++) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%02x "</span>, outKey[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line"> <span class="comment">// _BYTE in[] = {0x10, 0xBE, 0x62, 0xF8, 0xE8, 0xDC, 0x34, 0x46};</span></span><br><span class="line"> _BYTE in[] ={<span class="number">0x0E</span>, <span class="number">0x77</span>, <span class="number">0x50</span>, <span class="number">0xC8</span>, <span class="number">0xC6</span>, <span class="number">0x27</span>, <span class="number">0xE1</span>, <span class="number">0xBF</span>};</span><br><span class="line"> _BYTE out[<span class="number">8</span>];</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"in: "</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<<span class="number">8</span>; i++) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%02x "</span>, in[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line"> XTEA(outKey, in, out);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"out: "</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<<span class="number">8</span>; i++) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%02x "</span>, out[i]);</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></li><li><p>输出结果,如下图所示,下面输出结果用到例子为上面样例的第2组数据(使用第1组效果也一样),可以看到加密结果与预期一致,至此,成功还原了整个XTEA加密过程</p><p> <img src="/article/2024/10/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84STM32%E5%9B%BA%E4%BB%B6%E5%88%86%E6%9E%90/image-20221019225959702.png" alt="image-20221019225959702"></p></li></ul><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><ul><li>如您觉得本文内容侵犯了您的利益,请联系作者或者管理删除!</li></ul><hr><h3 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h3><ul><li><a href="stm32-re.zip">stm32-re.zip</a></li></ul>]]></content>
<categories>
<category> article </category>
</categories>
<tags>
<tag> 逆向 </tag>
<tag> STM32 </tag>
</tags>
</entry>
<entry>
<title>CW308T-AVR使用笔记</title>
<link href="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/"/>
<url>/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>最近入手了ChipWhisperer的CW308 的底板,方便进一步学习侧信道攻击,详见官网介绍:<a href="https://rtfm.newae.com/Targets/CW308%20UFO/">https://rtfm.newae.com/Targets/CW308 UFO/</a></p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416125221810.png" alt="image-20230416125221810"></p></li><li><p>有了底板之后就可以自从官方的github仓库中 <a href="https://github.com/newaetech/chipwhisperer-target-cw308t">https://github.com/newaetech/chipwhisperer-target-cw308t</a> 获取pcb源码,自己打板制作更多的目标板了,打板pcb可以白嫖嘉立创,每月两次免费打板机会(规则貌似是:如果上月消费<20,那么两次免费打板仅限于立创EDA的工程文件,上月消费>=20,那么免费打板没有这个限制,具体以官方说明为准)</p></li><li><p>虽然嘉立创免费打板可能要受限于要立创EDA设计,但是基于ChipWhisperer开源的Altium Designer的工程文件和原理图,还是可以自己将AD工程移植为立创EDA工程</p></li></ul><span id="more"></span><h2 id="CW308T-AVR使用"><a href="#CW308T-AVR使用" class="headerlink" title="CW308T_AVR使用"></a>CW308T_AVR使用</h2><ul><li><p>照着官方的原理图和实物图 <a href="https://github.com/newaetech/chipwhisperer-target-cw308t/tree/main/CW308T_AVR">https://github.com/newaetech/chipwhisperer-target-cw308t/tree/main/CW308T_AVR</a>,自己用立创EDA也画了个CW308T_AVR,然后白嫖了一波嘉立创。之所以选AVR这个,是因为这个工程足够简单,元器件和导线数就没多少,适合像我这样不懂pcb设计的萌新。此外,还有另外一个原因是:之前用面包板和atmega 328p搭了个arduino最小系统去解一道硬件CTF题,虽然当时成功了,但是抓取的波形属实难看的离谱,想看看用CW308T_AVR去解那道题会有什么效果</p></li><li><p>下图为官方CW308T_AVR实物图,基本上没多少元器件,也就4个电阻和3个电容还有一个atmega328p</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/cw308t_avr.jpg" alt="cw308t_avr"></p></li><li><p>在移植CW308T_AVR过程中,才发现原来atmega328p 3.3v供电也可以正常运行,只不过时钟频率需要变为8mhz,而16mhz对应的为5v电压</p></li><li><p>在反复查看官方原理图过程中,也终于明白,在给Vcc串联一个电阻后是怎么测电压的,实际上也并没有直接测串联的小电阻两端的电压,而是相当于测atmega328p两端的电压,这样也就避免了因为共地直接测电阻两端电压会导致短路的问题,官方原理图如下</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416140330629.png" alt="image-20230416140330629"></p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416140359733.png" alt="image-20230416140359733"></p></li><li><p>接下来以官方的这个jupyter notebook <a href="https://github.com/newaetech/chipwhisperer-jupyter/blob/master/courses/sca101/Lab%205_1%20-%20ChipWhisperer%20CPA%20Attacks%20in%20Practice.ipynb">https://github.com/newaetech/chipwhisperer-jupyter/blob/master/courses/sca101/Lab%205_1%20-%20ChipWhisperer%20CPA%20Attacks%20in%20Practice.ipynb</a> 为例,摸索CW308T_AVR的使用方法</p></li><li><p>首先是PLATFORM选择问题,经过测试应该选'CW301_AVR',而不是'CW308_AVR'(没有这个)</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></pre></td><td class="code"><pre><span class="line">SCOPETYPE = <span class="string">'OPENADC'</span></span><br><span class="line">PLATFORM = <span class="string">'CW301_AVR'</span></span><br><span class="line">CRYPTO_TARGET=<span class="string">'TINYAES128C'</span></span><br><span class="line">SS_VER=<span class="string">'SS_VER_1_1'</span></span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">%%bash -s <span class="string">"$PLATFORM"</span> <span class="string">"$CRYPTO_TARGET"</span> <span class="string">"$SS_VER"</span></span><br><span class="line">cd ../../../hardware/victims/firmware/simpleserial-aes</span><br><span class="line">make PLATFORM=$<span class="number">1</span> CRYPTO_TARGET=$<span class="number">2</span> SS_VER=$<span class="number">3</span></span><br></pre></td></tr></table></figure></li><li><p>然后就是第一个坑了,这里不能再选<code>%run "../../Setup_Scripts/Setup_Generic.ipynb"</code>,而是应该选<code>%run "../../Setup_Scripts/Setup_Notduino.ipynb"</code>,原因是Setup_Generic.ipynb中没有关于AVR的配置,如下图</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416141803733.png" alt="image-20230416141803733"></p></li><li><p>实际上Setup_Notduino.ipynb中主要起作用的就是下图部分</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416141904996.png" alt="image-20230416141904996"></p></li><li><p>接着添加以下代码(也可添加在Setup_Notduino.ipynb中),不然,后面烧录编译后的文件到目标板时,会问题</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">prog = cw.programmers.AVRProgrammer</span><br></pre></td></tr></table></figure></li><li><p>烧录编译的固件,出现Verified flash OK则为正常</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># If you need to program - run this</span></span><br><span class="line">fw_path = <span class="string">'../../../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex'</span>.<span class="built_in">format</span>(PLATFORM)</span><br><span class="line">cw.program_target(scope, prog, fw_path)</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416142618454.png" alt="image-20230416142618454"></p></li><li><p>接着修改,samples数和偏移如下</p> <figure class="highlight python"><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">scope.adc.samples=<span class="number">4000</span></span><br><span class="line">scope.adc.offset=<span class="number">0</span></span><br></pre></td></tr></table></figure></li><li><p>动态画图</p> <figure class="highlight python"><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">%run <span class="string">"../../Helper_Scripts/plot.ipynb"</span></span><br><span class="line">plot = real_time_plot(plot_len=<span class="number">24000</span>)</span><br></pre></td></tr></table></figure></li><li><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tnrange</span><br><span class="line">ktp = cw.ktp.Basic()</span><br><span class="line"></span><br><span class="line"><span class="comment">#Set your project name here</span></span><br><span class="line">project = cw.create_project(<span class="string">"projects/lab51_examplecpa"</span>, overwrite = <span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#Set your number of traces here</span></span><br><span class="line">num_traces = <span class="number">250</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> tnrange(num_traces, desc=<span class="string">'Capturing traces'</span>):</span><br><span class="line"> key, text = ktp.<span class="built_in">next</span>() <span class="comment"># manual creation of a key, text pair can be substituted here</span></span><br><span class="line"> trace = cw.capture_trace(scope, target, text, key)</span><br><span class="line"> <span class="keyword">if</span> trace <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> project.traces.append(trace)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Send every 10th trace?</span></span><br><span class="line"> <span class="keyword">if</span> i % <span class="number">10</span> == <span class="number">0</span>:</span><br><span class="line"> plot.send(trace)</span><br><span class="line"> time.sleep(<span class="number">0.1</span>)</span><br><span class="line"> </span><br><span class="line">project.save()</span><br></pre></td></tr></table></figure></li><li><p>如下图,可以看到,官方的例子,抓取的波形还是很清晰明了的</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416143436456.png" alt="image-20230416143436456"></p></li><li><p>最后也成功恢复出了AES密钥,实际上这里只需抓取25条能量迹就可以恢复出AES密钥而不用250条</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> chipwhisperer.analyzer <span class="keyword">as</span> cwa</span><br><span class="line"><span class="comment">#pick right leakage model for your attack</span></span><br><span class="line">leak_model = cwa.leakage_models.sbox_output</span><br><span class="line">attack = cwa.cpa(project, leak_model)</span><br><span class="line">results = attack.run(cwa.get_jupyter_callback(attack))</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416143610401.png" alt="image-20230416143610401"></p></li><li><p>用官方例子验证完目标板没有问题后,就尝试用来解之前硬件CTF Rhme-2016的piece_of_scake题,详见:<a href="https://github.com/Riscure/Rhme-2016">https://github.com/Riscure/Rhme-2016</a></p></li><li><p>烧录piece_of_scake固件</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># If you need to program - run this</span></span><br><span class="line">fw_path = <span class="string">'Rhme-2016/challenges/binaries/piece_of_scake/piece_of_scake.hex'</span></span><br><span class="line">cw.program_target(scope, prog, fw_path)</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416145625000.png" alt="image-20230416145625000"></p></li><li><p>设置串口波特率,貌似piece_of_scake.hex固件中是按16mhz编译的,而实际在ChipWhisperer中,时钟频率为7384615,然后就导致串口波特率还有休眠函数delay变慢了,需要重新计算波特率,公式:7.38mhz/16mhz * orgin_baud</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">target.baud=<span class="number">7384615.384615385</span>/<span class="number">16000000</span>*<span class="number">19200</span></span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416145654325.png" alt="image-20230416145654325"></p></li><li><p>事实上实际波特率,还可以先烧一个不断往外发串口信息的固件,然后用逻辑分析仪,抓取波形,计算得到。下图为烧录一个以19200波特率不断往外发数据的arduino程序,实际波特率计算方式,寻找间距最小的峰,下图最小间距的峰为112.667us,所以频率为1 / 112.667 * 1e6 = 8875.7 bps</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416150445490.png" alt="image-20230416150445490"></p></li><li><p>chipwhisperer官方中也有提到类似的问题 <a href="https://forum.newae.com/t/cw1173-errortarget-did-not-ack/1757/3">https://forum.newae.com/t/cw1173-errortarget-did-not-ack/1757/3</a></p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416151249144.png" alt="image-20230416151249144"></p></li><li><p>设置好正确波特率后,就可以与atmega328p正常通信了,按找'e'+16个字节数据的方式即可进行加密,这里可以用chipwhipserer的target.write函数来发送串口信息,不过有个问题是chipwhisperer的target.read函数返回的为str类型而不是bytes类型,导致乱码,直接用encode也不能得到正确的bytes数组,所以最后用一个usb转串口来配合串口通信</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416151734753.png" alt="image-20230416151734753"></p></li><li><p>同样波特率需要重新计算,将usb转串口的Rx、Tx接到CW308T底板上的Rx和Tx即可</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> serial <span class="keyword">import</span> Serial</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line">s=Serial(<span class="string">'/dev/cu.wchusbserial1410'</span>, baudrate=<span class="number">7384615.384615385</span>/<span class="number">16000000</span>*<span class="number">19200</span>)</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416152210307.png" alt="image-20230416152210307"></p></li><li><p>此时,可以正确获得加密后的结果,<del>这里之所以不用usb转串口发送数据,而是还是使用chipwhisperer发送数据的原因是,usb转串口发送的数据,atmega328p没有返回(原因未知),所以这里一个发一个接收正好互补</del>(原因应该是Rx和Tx接反了,CW308T底板的Rx Tx是atmega328p的Rx Tx而不是cwlite攻击板的Rx Tx,所以正确接法是usb转串口的Rx接CW308T底板的Tx,Tx接Rx)</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416152742451.png" alt="image-20230416152742451"></p></li><li><p>接着设置采样samples数,还有触发信号引脚设为tio4,同时:chipwhisperer中用一根杜邦线将GPIO4(10)和SCK(12)连在一起,这样原因是SCK(12)连接的是atmega328p的19引脚,而之前的分析中,AES加密前会在19引脚有信号输出</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">scope.adc.samples=24000</span><br><span class="line">scope.clock.adc_src='clkgen_x4'</span><br><span class="line">scope.adc.offset=0</span><br><span class="line">scope.adc.presamples=0</span><br><span class="line">scope.trigger.triggers = "tio4"</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416153251978.png" alt="image-20230416153251978"></p></li><li><p>动态画图</p> <figure class="highlight python"><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">%run <span class="string">"../../Helper_Scripts/plot.ipynb"</span></span><br><span class="line">plot = real_time_plot(plot_len=<span class="number">24000</span>)</span><br></pre></td></tr></table></figure></li><li><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><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="keyword">from</span> tqdm <span class="keyword">import</span> tnrange</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">ktp = cw.ktp.Basic()</span><br><span class="line">key, text = ktp.<span class="built_in">next</span>()</span><br><span class="line"><span class="comment"># key=bytes([0]*16)</span></span><br><span class="line">key=<span class="built_in">bytes</span>([<span class="number">0xaf</span>, <span class="number">0x23</span>, <span class="number">0xd5</span>, <span class="number">0x45</span>, <span class="number">0xa0</span>, <span class="number">0xea</span>, <span class="number">0xe6</span>, <span class="number">0xa0</span>, <span class="number">0x74</span>, <span class="number">0x65</span>, <span class="number">0x96</span>, <span class="number">0xca</span>, <span class="number">0xce</span>, <span class="number">0x51</span>, <span class="number">0xf0</span>, <span class="number">0xf7</span>])</span><br><span class="line">target.simpleserial_write(<span class="string">'k'</span>, key)</span><br><span class="line">trace_array = []</span><br><span class="line">textin_array = []</span><br><span class="line">textout_array=[]</span><br><span class="line">proj = cw.create_project(<span class="string">"projects/piece_of_scake"</span>, overwrite=<span class="literal">True</span>)</span><br><span class="line">N = <span class="number">50</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> tnrange(N, desc=<span class="string">'Capturing traces'</span>):</span><br><span class="line"> </span><br><span class="line"> scope.arm()</span><br><span class="line"> target.write(<span class="string">b"e"</span>+<span class="built_in">bytes</span>(text))</span><br><span class="line"> ret = scope.capture()</span><br><span class="line"> r=s.read_all()</span><br><span class="line"> <span class="keyword">while</span> <span class="built_in">len</span>(r)==<span class="number">0</span>:</span><br><span class="line"> r=s.read_all()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Target timed out!"</span>)</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> trace = scope.get_last_trace()</span><br><span class="line"> <span class="keyword">if</span> i % <span class="number">10</span> == <span class="number">0</span>:</span><br><span class="line"> plot.send(trace)</span><br><span class="line"> <span class="built_in">print</span>(r.<span class="built_in">hex</span>())</span><br><span class="line"> trace_array.append(trace)</span><br><span class="line"> proj.traces.append(cw.common.traces.Trace(trace, text, r,key))</span><br><span class="line"> textin_array.append(text)</span><br><span class="line"> key, text = ktp.<span class="built_in">next</span>()</span><br><span class="line">proj.save() </span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>效果如下,这里波形中不能很明显看出AES的位置,可能还是时钟频率不一致导致的</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416153741880.png" alt="image-20230416153741880"></p></li><li><p>按如下更改采样参数后,可以抓到如下的波形图,可以看到有几个差不多的峰形,推测就是AES的每轮循环</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">scope.adc.samples=24000</span><br><span class="line">scope.clock.adc_src='clkgen_x1'</span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416155013977.png" alt="image-20230416155013977"></p></li><li><p>放大第一个峰形状的结果如下,看起来效果还不错</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416155052561.png" alt="image-20230416155052561"></p></li><li><p>最后按如下修改参数,因为第一个峰结束位置差不多就在6-7000附近,当然保持24000也可,但计算会变慢,而且效果会变差些</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scope.adc.samples=<span class="number">8000</span></span><br></pre></td></tr></table></figure><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416155700555.png" alt="image-20230416155700555"></p></li><li><p>最后,如下图,可以看到只需很少能量迹,实际上25条就可以恢复出AES密钥,比之前在面包板上的方式(500条)要少得多</p><p> <img src="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/image-20230416155631724.png" alt="image-20230416155631724"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>通过移植CW308T_AVR和使用CW308T_AVR解CTF题过程中,对侧信道攻击有了进一步了解,也对ChipWhisperer的用法有了进一步了解</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
<tag> CW308T </tag>
</tags>
</entry>
<entry>
<title>Python实现AES加解密</title>
<link href="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
<url>/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>最近实际逆向过程中,经常会遇到AES算法,而且有时也会遇到一些非标准AES或者魔改的AES</p></li><li><p>本着进一步了解AES算法的态度,决定结合网上各种资料,自己动手实现一次AES加解密,加深自己对AES的了解</p></li><li><p>当然网上有很多优秀的AES实现了,其中最著名的当属:<a href="https://github.com/openssl/openssl">openssl</a>,openssl包含非常多的密码学算法的实现,而且很多都是性能优化后的实现。一个比较小巧的c语言实现是:<a href="https://github.com/kokke/tiny-AES-c">https://github.com/kokke/tiny-AES-c</a></p></li><li><p>python的一些AES实现:</p><ul><li><p><a href="https://gist.github.com/raullenchai/2920069">https://gist.github.com/raullenchai/2920069</a></p></li><li><p><a href="https://github.com/hlilje/aes-python.git">https://github.com/hlilje/aes-python.git</a></p></li></ul></li><li><p>这里再介绍一下python的:<a href="https://github.com/Legrandin/pycryptodome.git">pycryptodome</a>库,这个虽然是python库,但底层算法是c写的,所以性能上是非常不错的,可以安装这个库替换python的Crypto库,也可以与Crypto共存。这个库可以作为python中各种加密算法的标准库来使用。</p></li><li><p>因为这里是以学习为目的,所以本文不考虑任何性能上的问题</p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>AES算法总体上可以分为两部分去实现,一部分是:密钥扩展,另一部分是:加解密逻辑</p></li><li><p>下面首先来实现密钥扩展,这个连接有AES密钥扩展的详细介绍<a href="https://www.brainkart.com/article/AES-Key-Expansion_8410/">https://www.brainkart.com/article/AES-Key-Expansion_8410/</a></p></li><li><p>下面盗用上面提到链接中的两张图,第一张图是AES密钥扩展的实现伪代码实现,第二张图为AES密钥扩展的流程图</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917152134364.png" alt="image-20220917152134364"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917152200906.png" alt="image-20220917152200906"></p></li><li><p>下面来解析这两张图,首先看图中的这一部分,这张图什么意思呢?这张图表示,将密钥划分为4x4的矩阵,需要注意的点是这个密钥是以列为主顺序(<strong>Column-major order</strong>)的矩阵,而不是以行为主顺序(<strong>Row-major order</strong>)的矩阵,这两种矩阵相互转化的实际上就是矩阵转置。密钥的每一列记为w0,w1, w2, w3(w代表word,32位即4字节整数,w0 ... w3视为一个word类型数组)</p><ul><li>题外话:像c语言中的二维数组就是以行为主顺序的,不注意这个的话,可能最后实现的"AES"是非标准的AES。实际逆向工作中就有遇到过,密钥和数据输入输出都没转置的情况,如果遇到这种"AES",如何使用标准AES去实现呢?很简单,只需将密钥、输入、输出都进行矩阵转置即可</li></ul><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917152503403.png" alt="image-20220917152503403"></p></li><li><p>接着来看这部分,这张图又代表什么含义呢?先不看w4,只看w5,w6,w7,可以看到w5 = w1 xor w4,w6 = w2 xor w5, w7 = w3 xor w6 即(wi = wi-4 xor wi-1)。再来看w4部分,下图w3指出的g代表使用g函数处理w3,所以w4 = w0 xor g(w3)。就这样依次类推,以后的w8,w9 ... wi都是这么生成的(其中如果i为4的倍数,则wi-1需要先经过g函数处理后再与wi-4进行异或)</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917154217910.png" alt="image-20220917154217910"></p></li><li><p>接着看下面这部分,下图即为上面提到的g函数,这个g函数可以拆分成3部分来看</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917155050764.png" alt="image-20220917155050764"></p></li><li><p>首先是下图这个,下图这个代表将word类型循环左移1位如:第0个字节移到最后一位,其余3字节往左移动1位</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917155512887.png" alt="image-20220917155512887"></p></li><li><p>接着来看这个,下图B1,B2,B3,B0经过S后得到B1',B2',B3',B0',S代表S-Box,也就是说这一步操作是将word的每个字节分别经过S-Box替换得到新的word</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917155804412.png" alt="image-20220917155804412"></p></li><li><p>最后是这个B1',B2',B3',B0'与RCj,0,0,0异或得到g函数最终输出w',这个RCj代表什么呢?这个代表着AES的密钥扩展的轮常量Rcon(这些值好像与有限域GF(2^8)上的乘法有关),这个AES轮常量也是word类型数据,而且每个Rcon的右边3个字节总为0,只有第一个字节会随着循环发生改变。Rcon的值为"01 02 04 08 10 20 40 80 1B 36"(有些AES实现中Rcon常量很多,但实际上AES密钥扩展用到的就只有这些)</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917160008208.png" alt="image-20220917160008208"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917160335145.png" alt="image-20220917160335145"></p></li><li><p>回过头来再看密钥扩展函数的为代码就很容易理解了,第一个循环作用实际上就是按列取密钥数据得到wi,第二个循环就是密钥扩展的主逻辑了,RotWord代表将word的4个字节循环左移1位,SubWord代表将word的每个字节进行S-Box替换,Rcon代表着轮常量数组</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917152134364.png" alt="image-20220917152134364"></p></li><li><p>接下来使用python实现AES密钥扩展了,首先定义sbox和i_sbox。sbox和i_sbox相当于互为逆运算,一个字节使用sbox进行字节替换后再用i_sbox进行字节替换得到的还是它本身,反过来也一样。密钥扩展算法里面只用到sbox,加密算法也用到了sbox,解密则用的为i_sbox</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917190656328.png" alt="image-20220917190656328"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917190808814.png" alt="image-20220917190808814"></p></li><li><p>接着定义轮常量Rcon,注意第0个0x8d实际上并没有用到,这里加上这个主要是后面取值方便,参考:<a href="https://github.com/kokke/tiny-AES-c">https://github.com/kokke/tiny-AES-c</a>的密钥扩展实现,对于AES 128用到了10个Rcon值,192只用到了其中的8个,256则只用到了7个,Rcon[0]在AES中没有被使用</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917191346001.png" alt="image-20220917191346001"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917191553869.png" alt="image-20220917191553869"></p></li><li><p>接下来编写sub_word函数,这里为了方便将word直接视为4字节的数组,而不是视为一个32位的整数,代码很简单,就是将word的每个字节通过sbox查表方式替换得到新的字节,最后返回替换后的结果</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917191830108.png" alt="image-20220917191830108"></p></li><li><p>类似地可以按以下方式实现rot_word,循环左移1个字节,可以通过取余数的方式来实现</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917192242734.png" alt="image-20220917192242734"></p></li><li><p>最后是异或函数xor,将a,b中的每个字节异或得到输出</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917192445315.png" alt="image-20220917192445315"></p></li><li><p>定义了上面的运算函数后,就可以编写密钥扩展函数了,总体函数如下</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917192603229.png" alt="image-20220917192603229"></p></li><li><p>首先解释下前面部分的作用,因为AES有128、192、256位数之分,所以先根据输入参数判断选用的是哪种,如果为128则定义密钥长度为klen=16字节,nk=4(nk代表原始密钥有多少个4字节的word,nk与轮常量Rcon取值有关),扩展后的密钥长度为176(16+10*16)。因为标准AES的密钥扩展后的密钥前klen字节为原AES密钥,所以轮密钥前klen字节只需复制原来的密钥即可</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917192723809.png" alt="image-20220917192723809"></p></li><li><p>接下来看下面部分,"//"代表只保留整数的除法(python中单"/"除法,如果有小数会保留小数),temp按照前面的先取wi-1,如果i为nk的倍数,那么就进行先rot_word,再sub_word,最后再与轮常量rcon_t异或,从前面可知rcon_t数组后3个元素固定为0,第0个元素通过i//nk来确定。最后wi-nk与wi-1异或(因为要扩展到192和256,所以不是wi-4而是wi-nk)</p><ul><li>这里需要注意的是如果是AES 256,那么当i%nk == 4时,temp还要再进行一次sub_word运算,这个也是从tiny-AES-c中的实现得知,实际上最前面的密钥扩展描述的是AES 128的密钥扩展,所以这里需要补充一下AES 256密钥扩展</li></ul><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917193444822.png" alt="image-20220917193444822"></p></li><li><p>接下来测试AES 128密钥扩展,以16个00字节为例,密钥扩展后结果如下,这个00得到的密钥扩展结果还是挺有意义的,有时判断一个算法是否为标准AES实现,可以首先通过对比同样为16个00字节密钥时,密钥扩展输出是否与标准AES一致。在实现AES时,也可以通过这种方式,来对比标准AES的结果,看自己实现的是否正确。</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917195219892.png" alt="image-20220917195219892"></p></li><li><p>接着来看AES加密部分,同样先来看张图,图来自<a href="https://www.brainkart.com/article/The-AES-Encryption-Algorithm_9558/">https://www.brainkart.com/article/The-AES-Encryption-Algorithm_9558/</a></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917210513080.png" alt="image-20220917210513080"></p></li><li><p>AES加密每轮循环可以分为SubBytes、ShiftRows、MixColumns、AddRoundKey四个部分(除最后一轮外,最后一轮没有MixColumns)</p></li><li><p>先来看前两个SubBytes和ShiftRows,这两个顺序其实可以对调(结果一样),SubBytes就是将输入的16个字节每个字节经过sbox替换得到新的16字节</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917210840858.png" alt="image-20220917210840858"></p></li><li><p>ShifRows有个形象的图,如下图所示,ShifRows就是第0行不变,第1行循环左移1字节,第2行循环左移2字节,第3行循环左移3字节,参考:<a href="http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html">http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html</a></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917212201020.png" alt="image-20220917212201020"></p></li><li><p>接着是MixColumns,列混淆有点复杂,右乘一个固定矩阵,其中的运算与有限域GF(2^8)的四则运算有关,参考:<a href="https://www.brainkart.com/article/Advanced-Encryption-Standard(AES)-Transformation-Functions_8409/">https://www.brainkart.com/article/Advanced-Encryption-Standard(AES)-Transformation-Functions_8409/</a></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917232817574.png" alt="image-20220917232817574"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917232910940.png" alt="image-20220917232910940"></p></li><li><p>最后是AddRoundKey,AddRoundKey比较容易理解,就是将当前状态state每个字节与下一轮密钥的每个字节异或</p></li><li><p>AES解密的话则是所有操作反过来,每轮循环先AddRoundKey(因为是异或,所以可以用同一个函数),再InvMixColumns,再InvShifRows,最后InvSubBytes,这三个都是与加密的3个变换是相反的</p></li><li><p>接着可以编写Python代码了,首先是最容易实现的SubBytes和InvSubBytes,只需将每个字节通过查表替换即可</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917233759724.png" alt="image-20220917233759724"></p></li><li><p>这里再定义一个矩阵转置函数transpose,之所以要这个函数,就是前面所提到的AES的矩阵是<strong>Column-major order</strong>,而C语言、Python这些编程语言的数组其实是<strong>Row-major order</strong>的,所以需要对密钥、输入输出都进行矩阵转置才能与标准AES一致,这个transpose函数用了个最简单粗暴的方法实现</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917234022205.png" alt="image-20220917234022205"></p></li><li><p>接着是AddRoundKey,这里接收3个参数,分别为当前循环数cur_round,加解密状态state,轮密钥数组round_key,每次AddRoundKey只需将state与相对应的轮密钥异或即可</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917233901866.png" alt="image-20220917233901866"></p></li><li><p>接着是ShiftRows和InvShiftRows,这里同样选择最简单粗暴的方式来实现</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917234537749.png" alt="image-20220917234537749"></p></li><li><p>接着是最个人认为AES最难以理解的就是MixColumns和InvMixColumns,这里有限域GF(2^8)乘法直接抄<a href="https://gist.github.com/raullenchai/2920069">https://gist.github.com/raullenchai/2920069</a>的实现</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917234654838.png" alt="image-20220917234654838"></p></li><li><p>MixColumns,按照下图的右乘固定矩阵方式,实现如下</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917234947434.png" alt="image-20220917234947434"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917234916382.png" alt="image-20220917234916382"></p></li><li><p>InvMixColumns,按照下图的右乘另一个固定矩阵方式,实现如下</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917235133997.png" alt="image-20220917235133997"></p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917235225560.png" alt="image-20220917235225560"></p></li><li><p>从下图可以看出,所用到的两个固定矩阵是互为逆矩阵,所以可以用来加解密</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220917235258295.png" alt="image-20220917235258295"></p></li><li><p>有了上面这些就可以实现AES加密函数了,如下图所示</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220918110420942.png" alt="image-20220918110420942"></p></li><li><p>首先来解释前面部分,首先判断key是否为字符串,如果是那么将之转化为bytes类型。然后是判断是否有bits参数,如果没有则根据密钥key长度自动判断bits的值,然后就是调用前面实现的密钥扩展函数key_expansion来生成轮密钥数组,并计算加密循环次数</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220918110440201.png" alt="image-20220918110440201"></p></li><li><p>接着同样先判断输入是否为字符串,如果是那么先将之转化为bytes类型。然后是将输入进行矩阵每16个字节进行转置,输出也是进行转置,然后就是按每轮循环SubBytes、ShiftRows、MixColumns、AddRoundKey进行(最后一轮循环没有MixColumns)</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220918110457960.png" alt="image-20220918110457960"></p></li><li><p>AES解密跟加密的过程刚好反过来,AddRoundKey的初始轮数是最后一轮,每轮循环的顺序是AddRoundKey、InvMixColumns、InvShiftRows、InvSubBytes</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220918111212449.png" alt="image-20220918111212449"></p></li><li><p>测试加解密,并与pycryptodome库的结果相对比来判断是否正确实现</p><p> <img src="/note/2022/09/Python%E5%AE%9E%E7%8E%B0AES%E5%8A%A0%E8%A7%A3%E5%AF%86/image-20220918111630976.png" alt="image-20220918111630976"></p></li><li><p>至此,成功用Python实现了AES,本文完整代码:<a href="AES.ipynb">AES.ipynb</a></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> AES </tag>
</tags>
</entry>
<entry>
<title>解决Switch-Tinfoil下载提示-SSL时间不对问题</title>
<link href="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/"/>
<url>/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>switch 硬破后,一般都把任天堂相关的域名IP屏蔽了,所以就不能自动联网校准时间</p></li><li><p>时间久了,switch的时间就会与标准时间偏差很大,使用Tinfoil下载就有可能会出现下面的错误,这种情况手动设置时间一般与无济于事</p> <figure class="highlight text"><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">SSL Connection failed.</span><br><span class="line">Your system clock is likely incorrect.</span><br><span class="line">Please ensure your system date time is set correctly.</span><br></pre></td></tr></table></figure></li><li><p>这时候就可以使用一个插件来同步时间:<a href="https://github.com/nedex/QuickNTP.git">https://github.com/nedex/QuickNTP.git</a></p></li><li><p>但QuickNTP默认的几个服务器,有时候也会出现连不上情况</p></li><li><p>接下来介绍,如何将QuickNTP默认的NTP服务器替换为国内NTP服务器并重新编译.ovl文件</p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>首先直接git clone下来</p> <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">git clone https://github.com/nedex/QuickNTP.git</span><br></pre></td></tr></table></figure></li><li><p>需要注意的是,QuickNTP的libs目录下引用了另一个github仓库,默认是不会被一起clone</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/1.png" alt="1"></p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911134147384.png" alt="image-20220911134147384"></p></li><li><p>所以需要手动clone <a href="https://github.com/WerWolv/libtesla.git">https://github.com/WerWolv/libtesla.git</a>并替换libs目录下的libtesla</p> <figure class="highlight shell"><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">cd libs</span><br><span class="line">rm -r libtesla</span><br><span class="line">git clone https://github.com/WerWolv/libtesla.git</span><br></pre></td></tr></table></figure></li><li><p>clone好后,直接make,会报下面的错,原因是缺少<a href="https://github.com/devkitPro">devkitPro</a></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">Makefile:6: *** "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro". Stop.</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911134402537.png" alt="image-20220911134402537"></p></li><li><p>安装<a href="https://github.com/devkitPro">devkitPro</a>有多种方式,这里选择最省事的,用docker拉取官方镜像,官方docker镜像链接<a href="https://hub.docker.com/u/devkitpro/">https://hub.docker.com/u/devkitpro/</a></p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911134654028.png" alt="image-20220911134654028"></p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911134753022.png" alt="image-20220911134753022"></p></li><li><p>可以点击查看每个镜像的用途,可以看到,devkitpro/devkita64是用于Nintendo Switch的开发环境,所以这里直接选这个即可</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911134947979.png" alt="image-20220911134947979"></p></li><li><p>拉取docker镜像</p> <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">docker pull devkitpro/devkita64</span><br></pre></td></tr></table></figure></li><li><p>运行镜像,使用-v参数,将主机的QuickNTP所在目录映射到docker容器的/data目录</p> <figure class="highlight shell"><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"><span class="meta prompt_"># </span><span class="language-bash">将/Users/cc/github替换为QuickNTP所在目录,可将/data替换为你想要的目录</span></span><br><span class="line">docker run -itd --name devkita64 -v /Users/cc/github:/data devkitpro/devkita64</span><br></pre></td></tr></table></figure></li><li><p>进入容器shell</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911135447465.png" alt="image-20220911135447465"></p> <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">docker exec -it devkita64 /bin/bash</span><br></pre></td></tr></table></figure></li><li><p>进入/data/QuickNTP目录</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911135526102.png" alt="image-20220911135526102"></p></li><li><p>运行make,即可编译得到QuickNTP.ovl</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911135646255.png" alt="image-20220911135646255"></p></li><li><p>如果make报没有"tesla.hpp"文件错误,那么就需要检查QuickNTP/libs/libtesla/目录下是否为空,若为空,说明是前面说的clone没有把github的子仓库一起clone下来的问题,只需要clone下libtesla即可</p> <figure class="highlight text"><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">/data/QuickNTP/source/main.cpp:2:10: fatal error: tesla.hpp: No such file or directory</span><br><span class="line"> 2 | #include <tesla.hpp></span><br><span class="line"> | ^~~~~~~~~~~</span><br><span class="line">compilation terminated.</span><br><span class="line">make[1]: *** [/opt/devkitpro/devkitA64/base_rules:14: main.o] Error 1</span><br><span class="line">make: *** [Makefile:170: build] Error 2</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911135732599.png" alt="image-20220911135732599"></p></li><li><p>可以正常编译了,那么接下来就是修改默认的NTP服务器,替换为国内的NTP服务器</p></li><li><p>QuickNTP的服务器列表在servers.hpp中,如下图,两个字符串数组,分别为Title和域名的列表</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911140412095.png" alt="image-20220911140412095"></p></li><li><p>按照格式,替换为国内常见的NTP服务器,如下图所示:</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="meta">#<span class="keyword">include</span> <span class="string"><initializer_list></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string></span></span></span><br><span class="line"></span><br><span class="line">std::initializer_list<std::string> NTPSERVERS[<span class="number">2</span>] = {</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"China NTP 1"</span>,</span><br><span class="line"> <span class="string">"China NTP 2"</span>,</span><br><span class="line"> <span class="string">"China NTSC"</span>,</span><br><span class="line"> <span class="string">"China Edu"</span>,</span><br><span class="line"> <span class="string">"Aliyun"</span>,</span><br><span class="line"> <span class="string">"Tencent"</span>,</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> <span class="string">"cn.pool.ntp.org"</span>,</span><br><span class="line"> <span class="string">"cn.ntp.org.cn"</span>,</span><br><span class="line"> <span class="string">"ntp.ntsc.ac.cn"</span>,</span><br><span class="line"> <span class="string">"edu.ntp.org.cn"</span>,</span><br><span class="line"> <span class="string">"ntp1.aliyun.com"</span>,</span><br><span class="line"> <span class="string">"time1.cloud.tencent.com"</span>,</span><br><span class="line"> }};</span><br><span class="line"></span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911140629632.png" alt="image-20220911140629632"></p></li><li><p>保存,然后重新make下,即可生成修改后的QuickNTP.ovl文件</p></li><li><p>使用QuickNTP.ovl插件,需要<a href="https://github.com/WerWolv/Tesla-Menu">https://github.com/WerWolv/Tesla-Menu</a>,下载最新Tesla-Menu的releases并复制到switch SD卡的/switch/.overlays目录下即可</p></li><li><p>再将编译好的QuickNTP.ovl也复制到switch SD卡的/switch/.overlays目录下</p></li><li><p>同时按住switch 的" L" 和 "▼" 然后往下戳右摇杆,即可调出Tesla-Menu,然后选择QuickNTP即可开始设置时间</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911142923485.png" alt="image-20220911142923485"></p></li><li><p>如下图所示,选择一个服务器,并点击Sync time即可同步时间,并且可以看到服务器列表成功替换为自定义的列表,出现Syced with xxx后就可以重启switch,这样tinfoil就不会报SSL 时间错误</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3Switch-Tinfoil%E4%B8%8B%E8%BD%BD%E6%8F%90%E7%A4%BA-SSL%E6%97%B6%E9%97%B4%E4%B8%8D%E5%AF%B9%E9%97%AE%E9%A2%98/image-20220911142849430.png" alt="image-20220911142849430"></p></li><li><p>当然也可以通过QuickNTP的User-set time把自己switch系统设置中手动设置的时间当作标准时间</p></li><li><p>本文编译的QuickNTP.ovl文件下载链接:<a href="QuickNTP.ovl">QuickNTP.ovl</a></p></li><li><p>QuickNTP原版下载链接:<a href="https://github.com/nedex/QuickNTP/releases/latest">https://github.com/nedex/QuickNTP/releases/latest</a></p></li><li><p>如果只是替换QuickNTP默认服务器列表,其实不用编译也可以,直接使用16进制编辑器打开QuickNTP.ovl,搜索并将域名字符串替换掉即可,这里主要是想了解下如何编译switch的一些插件</p></li></ul><hr><ul><li>新版QuickNTP修改服务器列表方法与上面差不多,修改servers.hpp即可,更新一个最新编译的文件:<a href="QuickNTP_1.2.8.ovl">QuickNTP_1.2.8.ovl</a></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 教程 </tag>
<tag> Switch </tag>
<tag> 杂项 </tag>
</tags>
</entry>
<entry>
<title>解决ChipWhisperer-Unable to import LASCAR错误</title>
<link href="/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/"/>
<url>/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/</url>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><ul><li><p>最近学习chipwhisperer时,遇到了点问题,运行<code>import chipwhisperer.common.api.lascar as cw_lascar</code>会报两个错误"Unable to import LASCAR"和"NameError: name 'Container' is not defined"</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/image-20220910102604114.png" alt="image-20220910102604114"></p></li></ul><span id="more"></span><ul><li><p>一顿搜索后,按照这个链接找到了解决方法:<a href="https://forum.newae.com/t/lascar-lab5-1-sca101-cpa-attacks-in-practice/2816">https://forum.newae.com/t/lascar-lab5-1-sca101-cpa-attacks-in-practice/2816</a></p></li><li><p>可以看到官方说需要去clone<a href="https://github.com/Ledger-Donjon/lascar">https://github.com/Ledger-Donjon/lascar</a>,并手动安装。LASCAR跟ChipWhisperer Analyzer功能是一样的,官方推荐在分析大的数据集时使用LASCAR,因为它的实现比ChipWhisperer Analyzer更快</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/image-20220910102930832.png" alt="image-20220910102930832"></p></li><li><p>github上查看lascar官方安装教程,可以看到很简单clone下来后运行<code>python3 setup.py install --user</code>即可,"--user"参数是给当前用户安装。</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/image-20220910103522849.png" alt="image-20220910103522849"></p></li><li><p>所以解决上述错误,只需执行如下命令即可</p><pre><code class="shell">git clone https://github.com/Ledger-Donjon/lascarcd lascarpython3 setup.py install --user</code></pre></li><li><p>如下图,重启jupyter notebook后,重新运行不再报错</p><p> <img src="/tutorial/2022/09/%E8%A7%A3%E5%86%B3ChipWhisperer-Unable-to-import-LASCAR%E9%94%99%E8%AF%AF/image-20220910104124103.png" alt="image-20220910104124103"></p></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://forum.newae.com/t/lascar-lab5-1-sca101-cpa-attacks-in-practice/2816">https://forum.newae.com/t/lascar-lab5-1-sca101-cpa-attacks-in-practice/2816</a></li><li><a href="https://github.com/Ledger-Donjon/lascar">https://github.com/Ledger-Donjon/lascar</a></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> chipwhisperer </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击伪实战-分析一道硬件CTF题</title>
<link href="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/"/>
<url>/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>使用前面学习了那么多侧信道攻击的内容,是时候实战一波了</p></li><li><p>本文这道题来自<a href="https://github.com/Riscure/Rhme-2016">https://github.com/Riscure/Rhme-2016</a>的<strong>piece_of_scake</strong>,下图为这题的提示信息</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220903214212850.png" alt="image-20220903214212850"></p></li><li><p>本文大部分内容基于这个视频中对这道题的解法,很不错的视频,值得一看:<a href="https://www.youtube.com/watch?v=FktI4qSjzaE&list=PLhixgUqwRTjwNaT40TqIIagv3b4_bfB7M&index=14">https://www.youtube.com/watch?v=FktI4qSjzaE&list=PLhixgUqwRTjwNaT40TqIIagv3b4_bfB7M&index=14</a></p></li><li><p>与上面提到的视频不同,这里为了方便起见使用的硬件为用面包板构造的Arduino最小系统,这样做的好处在于接电阻、接线和拆器件方便</p></li><li><p>最近更新了关于这道CTF题的另一篇文章,可供参考:<a href="/note/2023/04/CW308T-AVR%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/" title="CW308T-AVR使用笔记">CW308T-AVR使用笔记</a></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>将上一篇中构造的Arduino直接拿过来用,但有个地方需要稍作调整,将一个20Ω的电阻与给Atmega328P 供电的GND串联,用于给ChipWhisperer-Lite测电压</p></li><li><p>为什么不连Vcc而连GND?因为实际使用中发现貌似无法避免ChipWhisperer-Lite GND与供电端的GND相连,这样相连的情况下把20Ω电阻接Vcc上,再用ChipWhisperer-Lite测电压,就会出现短路,这种情况貌似有个专业术语叫"Ground Loop"</p></li><li><p>如下图,把ChipWhisperer-Lite当作电压表,把Atmega328P和其它器件整体看成一个大电阻,如果把20Ω接在Vcc端,并且ChipWhisperer-Lite又与Atmega328P共地的情况下,就会使Atmega328P被短路了。实测按下图接法,芯片无法正常工作且20Ω电阻将发烫。</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/1.png" alt="1"></p></li><li><p>因为找不到好的解决方法,于是改为20Ω电阻接在电源的GND出,更改后的电路示意图,如下图所示,这样就避免了将Atmega328P芯片短路,电阻也不会发烫</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220903222608291.png" alt="image-20220903222608291"></p></li><li><p>下图为实际电路接线图,chipwhisperer-lite用一个sma转鳄鱼夹将一个20Ω来测电阻两端电压(试过用sma转bnc连接示波器的x1探头,但是效果不太行,可能是阻抗的问题)</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/3.png" alt="3"></p></li><li><p>下图为chipwhipserer-lite中引出来的线,这里只引出来了TIO4(chipwhipserer-lite正面朝上的情况下,20PIN下面的那排左数第3针脚即为TIO4),用于设置触发信号的探针</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/4.png" alt="4"></p></li><li><p>什么是TIO4?- 这得跟chipwhisperer的官方文档有关,chipwhisperer-lite主要有TIO1、TIO2、TIO3、TIO4这四个针脚可用于设置触发信号(其中TIO1和TIO2默认为Rx和Tx,TIO4默认为触发信号的输入),更多细节请看官方文档<a href="https://chipwhisperer.readthedocs.io/en/latest/scope-api.html">https://chipwhisperer.readthedocs.io/en/latest/scope-api.html</a></p></li><li><p>如下图所示,chipwhisperer-lite的背面有TIO1、TIO2、TIO3、TIO4的定义,所以只有用万用表量一下,看这4个针脚跟20PIN座子的哪几个针脚相连即可知道,哪个是Rx、哪个是Tx和哪个是触发信号的输入</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/5.png" alt="5"></p></li><li><p>接下来为Atmega328p刷入硬件CTF的题目,如果搭建的最新系统可以自动Reset并正常通过USB转UART上传程序,那么可以用下面的命令上传(只需修改USB转UART设备)</p> <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">avrdude -v -c arduino -p atmega328p -P /dev/cu.wchusbserial1410 -b115200 -u -V -U flash:w:piece_of_scake.hex</span><br></pre></td></tr></table></figure></li><li><p>如果不能通过USB转UART上传程序,那么可以用上一篇的Arduino Uno当作ISP编程器,为Atmega328p烧录固件</p> <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">avrdude -p m328p -P /dev/cu.usbmodem14201 -c avrisp -b 19200 -U flash:w:piece_of_scake.hex</span><br></pre></td></tr></table></figure></li><li><p>上传好固件后,就可以先来测试,这里使用python的pyserial包来使用串口</p> <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">pip install pyserial</span><br></pre></td></tr></table></figure></li><li><p>跟据题目提示,使用"e"+ 16 bytes即可加密,</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220903214212850.png" alt="image-20220903214212850"></p></li><li><p>如下图所示,设置好选用的设备名称和波特率即可用write和read方法来与芯片进行串口通信,可以看到使用"e"+16个0x01加密返回结果为"5cf473073f0a0940be290224ca495b90"。如果格式不对将没有任何响应</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904105456498.png" alt="image-20220904105456498"></p></li><li><p>实际使用过程中可以发现连接在Atmega329P的19引脚和22引脚的二极管会在正确发送指令后亮一下,因此可以用chipwhipserer的TIO4与Atmega329P19引脚相连来作为触发信号的输入</p></li><li><p>设置好触发信号后,就可以开始写代码,用chipwhipserer-lite来抓取能量迹,正式开始之前,建议把面包板上的电容都去掉(因为电容有滤波效果,所以会影响抓取的能量迹波形)。而且建议给Arduino最小系统独立供电(串联的电阻较大时,靠笔记本供电,会电压不足),笔记本电脑拔掉充电头(电脑充电状态会有杂峰影响)</p></li><li><p>接下来照猫画虎,将之前侧信道攻击学习的前面部分的代码复制过来,并稍作修改,如下图所示,这里还是使用USB转UART作为串口通信(虽然chipwhisperer-lite有Rx和Tx,但是chipwhisperer-lite是3.3v的,而atmega328p是5v的,所以两者不能直接通信)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> serial</span><br><span class="line"></span><br><span class="line">SCOPE=<span class="string">"OPENADC"</span></span><br><span class="line">PLATFORM=<span class="string">"CWLITEXMEGA"</span></span><br><span class="line">CRYPTO_TARGET=<span class="string">"TINYAES128C"</span></span><br><span class="line">VERSION=<span class="string">"HARDWARE"</span></span><br><span class="line"></span><br><span class="line">%run <span class="string">"../../Setup_Scripts/Setup_Generic.ipynb"</span></span><br><span class="line"></span><br><span class="line">s=serial.Serial(<span class="string">'/dev/cu.wchusbserial1410'</span>, <span class="number">19200</span>, timeout=<span class="number">0.5</span>)</span><br><span class="line"></span><br><span class="line">proj = cw.create_project(<span class="string">"test"</span>, overwrite=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904141218312.png" alt="image-20220904141218312"></p></li><li><p>接着设置chipwhisperer-lite的触发信号输入引脚,触发模式,采样频率等,具体可查看官方文档,下面的设置为使用TIO4引脚作为触发信号,单次采样5000个点的数据,还有采样频率"clkgen_x4"(如果为"clkgen_x1",那么将会用更低的频率采样)</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># scope.adc.basic_mode = "falling_edge"</span></span><br><span class="line"><span class="comment"># scope.adc.basic_mode = "high"</span></span><br><span class="line"></span><br><span class="line">scope.adc.samples = <span class="number">5000</span></span><br><span class="line">scope.clock.adc_src = <span class="string">"clkgen_x4"</span></span><br><span class="line"></span><br><span class="line">scope.trigger.triggers = <span class="string">"tio4"</span></span><br><span class="line"></span><br><span class="line">scope.adc.offset=<span class="number">0</span></span><br><span class="line"></span><br><span class="line">scope.adc.presamples=<span class="number">0</span></span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904141520510.png" alt="image-20220904141520510"></p></li><li><p>接着是,开始抓取能量迹,第一个key可以随意设置(主要用于后面画图高亮显示,如果一开始不知道真正key可以用16个00代替)</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><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="keyword">import</span> chipwhisperer</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tnrange</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">reset_target(scope)</span><br><span class="line"></span><br><span class="line">ktp = cw.ktp.Basic()</span><br><span class="line">key, text = ktp.<span class="built_in">next</span>()</span><br><span class="line"><span class="comment"># key=bytes([0]*16)</span></span><br><span class="line">key=<span class="built_in">bytes</span>([<span class="number">0xaf</span>, <span class="number">0x23</span>, <span class="number">0xd5</span>, <span class="number">0x45</span>, <span class="number">0xa0</span>, <span class="number">0xea</span>, <span class="number">0xe6</span>, <span class="number">0xa0</span>, <span class="number">0x74</span>, <span class="number">0x65</span>, <span class="number">0x96</span>, <span class="number">0xca</span>, <span class="number">0xce</span>, <span class="number">0x51</span>, <span class="number">0xf0</span>, <span class="number">0xf7</span>])</span><br><span class="line">target.simpleserial_write(<span class="string">'k'</span>, key)</span><br><span class="line">trace_array = []</span><br><span class="line">textin_array = []</span><br><span class="line">textout_array=[]</span><br><span class="line"></span><br><span class="line">N = <span class="number">500</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> tnrange(N, desc=<span class="string">'Capturing traces'</span>):</span><br><span class="line"> </span><br><span class="line"> scope.arm()</span><br><span class="line"> s.write(<span class="string">b"e"</span>+<span class="built_in">bytes</span>(text))</span><br><span class="line"> ret = scope.capture()</span><br><span class="line"> r=s.read_all()</span><br><span class="line"> <span class="keyword">while</span> <span class="built_in">len</span>(r)==<span class="number">0</span>:</span><br><span class="line"> r=s.read_all()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ret:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Target timed out!"</span>)</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> trace = scope.get_last_trace()</span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(r.<span class="built_in">hex</span>())</span><br><span class="line"> trace_array.append(trace)</span><br><span class="line"> proj.traces.append(chipwhisperer.common.traces.Trace(trace, text, r,key))</span><br><span class="line"> textin_array.append(text)</span><br><span class="line"> key, text = ktp.<span class="built_in">next</span>()</span><br><span class="line">proj.save() </span><br><span class="line"></span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904141951653.png" alt="image-20220904141951653"></p></li><li><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></pre></td><td class="code"><pre><span class="line">%matplotlib notebook</span><br><span class="line"><span class="keyword">import</span> matplotlib.pylab <span class="keyword">as</span> plt</span><br><span class="line"></span><br><span class="line">plt.figure()</span><br><span class="line">plt.plot(trace_array[<span class="number">0</span>])</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904142218949.png" alt="image-20220904142218949"></p></li><li><p>最后就是执行CPA攻击,可以看到最终结果为"0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7"</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> chipwhisperer <span class="keyword">as</span> cw</span><br><span class="line"><span class="keyword">import</span> chipwhisperer.analyzer <span class="keyword">as</span> cwa</span><br><span class="line"></span><br><span class="line">leak_model = cwa.leakage_models.sbox_output</span><br><span class="line">attack = cwa.cpa(proj, leak_model)</span><br><span class="line">cb = cwa.get_jupyter_callback(attack)</span><br><span class="line">results = attack.run(cb, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904142343167.png" alt="image-20220904142343167"></p></li><li><p>验证密钥是否正确,如下图所示,使用标准AES,用侧信道攻击得到的密钥,加密同样的数据[1]*16得到的结果同样为"5cf473073f0a0940be290224ca495b90",说明"0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7"是正确的AES密钥</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"></span><br><span class="line">key=<span class="built_in">bytes</span>([<span class="number">0xaf</span>, <span class="number">0x23</span>, <span class="number">0xd5</span>, <span class="number">0x45</span>, <span class="number">0xa0</span>, <span class="number">0xea</span>, <span class="number">0xe6</span>, <span class="number">0xa0</span>, <span class="number">0x74</span>, <span class="number">0x65</span>, <span class="number">0x96</span>, <span class="number">0xca</span>, <span class="number">0xce</span>, <span class="number">0x51</span>, <span class="number">0xf0</span>, <span class="number">0xf7</span>])</span><br><span class="line">aes=AES.new(key, AES.MODE_ECB)</span><br><span class="line"></span><br><span class="line">data=<span class="built_in">bytes</span>([<span class="number">1</span>]*<span class="number">16</span>)</span><br><span class="line">aes.encrypt(data).<span class="built_in">hex</span>()</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904142623815.png" alt="image-20220904142623815"></p></li><li><p>至此成功利用侧信道攻击解决了这道CTF</p></li></ul><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ul><li><p>如果不去掉电容会怎样?- 如下图,抓取的波形的振幅会减小,恢复正确的密钥需要的能量迹更多,去掉电容只需200条左右能量迹即可恢复正确密钥,但不去掉密钥则需要500条左右甚至更多</p><p> <img src="/tutorial/2022/09/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E4%BC%AA%E5%AE%9E%E6%88%98-%E5%88%86%E6%9E%90%E4%B8%80%E9%81%93%E7%A1%AC%E4%BB%B6CTF%E9%A2%98/image-20220904143319351.png" alt="image-20220904143319351"></p></li><li><p>为什么串联20Ω大小的阻值,其它大小可以吗?- 理论上可以,试过1Ω的电阻(但效果不行),试过50Ω(分压太明显,需要输入更多的电压,大概6.8v输入才能稳定正常工作,20Ω则只需5.5v输入)</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>利用侧信道攻击成功解了一道硬件CTF题</li><li>通过自己构建电路,才知道实施一次侧信道攻击不是一件容易的事</li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 侧信道攻击 </tag>
<tag> DPA </tag>
<tag> CPA </tag>
<tag> CTF </tag>
</tags>
</entry>
<entry>
<title>使用Atmega328P搭建Arduino最小系统</title>
<link href="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/"/>
<url>/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li>最近为了方便折腾硬件CTF,于是准备用Atmega328P-PU搭建Arduino最小系统</li><li>需要用到的器件<ul><li>面包板 x1</li><li>Atmega328P-PU x 1</li><li>USB转UART x1(建议有DTR引脚的,可以用来自动Reset)</li><li>22pF 陶瓷电容 x2</li><li>16 MHz晶振</li><li>Arduino uno或者Arduino mini x1(用于给Atmega328P-PU芯片烧录bootloader)</li><li>导线(杜邦线,面包板线)</li><li>1µF (100nF) 陶瓷电容x2(可选)</li><li>10 kΩ电阻(可选)</li><li>二极管(可选)</li></ul></li></ul><p><img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/arduino.gif" alt="arduino"></p><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>Atmega328P-PU的引脚定义如下图所示:</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/2022-08-29.9.25.35-2174441.png" alt="2022-08-29.9.25.35"></p></li><li><p>Arduino最小系统电路图</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903105923715.png" alt="image-20220903105923715"></p></li><li><p>接下来根据上面的电路图来构造arduino最小系统,下图为使用的配件</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903135808882.png" alt="image-20220903135808882"></p></li><li><p>首先,如下图所示,将16MHz的晶振连接atmega328p的第9和第10引脚</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-1.png" alt="step-1"></p></li><li><p>接着,如下图所示,用两个22 pF的陶瓷电容分别连接atmega328p的8-9和8-10(其中8脚为GND)</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-2.png" alt="step-2"></p></li><li><p>接着,如下图所示,将二极管的正极连接atmega328p的19引脚,负极连接22引脚(GND)</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-3.png" alt="step-3"></p></li><li><p>接着,如下图所示,将7脚(Vcc)和8脚(GND)引出,以便后面接入电源供电</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-4.png" alt="step-4"></p></li><li><p>接着如下图所示,在7脚(Vcc)和8脚(GND)之间连接一个1µF(100nF)的陶瓷电容</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-5.png" alt="step-5"></p></li><li><p>接着如下图所示,用10kΩ的电阻将1脚(Reset)与7脚(Vcc)相连,用一个1µF电容将1脚引出(用于后面与USB转UART串口的DTR引脚相连)</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-6.png" alt="step-6"></p></li><li><p>如下图所示,将USB转UART的GND和Vcc分别与atmega328p的GND和Vcc相连(注意需要5v输出,如果串口输出电压不对,可以选择给atmega328p独立供电,uart与atmega328p只连GND)</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-7.png" alt="step-7"></p></li><li><p>接着如下图所示,将UART的Tx与atmega328p的2脚(Rx)相连,UART的Rx与atmega328p的3脚(Tx)相连</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-8.png" alt="step-8"></p></li><li><p>接着如下图所示,将UART的DTR引脚与上面的1µF陶瓷电容与1脚串联</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-9.png" alt="step-9"></p></li><li><p>最小系统的电路至此构建完毕,但是由于此时atmega328p芯片没有bootloader程序,所以不能直接跑任何东西,需要先刷入bootloader程序</p></li><li><p>下面以Arduino Uno为例(Arduino mini等其它设备也可以),为atmega328p烧录bootloader</p></li><li><p>如下图所示,将Arduino Uno的10脚直接与atmega328p的1脚相连(这里不需要串联电容),将Arduino uno的11、12、13引脚依次与atmega328p的17、18、19引脚相连</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903144913266.png" alt="image-20220903144913266"></p></li><li><p>将Arduino Uno和USB转UART连接到电脑(为atmega328p供电,可以独立供电)</p></li><li><p>如下图所示,打开ArduinoIDE,选择 文件 > 示例 > ArduinoISP,打开一个示例文件</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903133709896.png" alt="image-20220903133709896"></p></li><li><p>接着选择 工具 > 开发板 > Arduino Uno</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903133759177.png" alt="image-20220903133759177"></p></li><li><p>接着选择 工具 > 端口 > xxxx (Arduino Uno),选中Arduino Uno设备</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903133842275.png" alt="image-20220903133842275"></p></li><li><p>选择 项目 > 上传(或者左上角上传按钮上传),将ArduinoISP上传到Arduino Uno,这样就可以用Arduino Uno来当作编程器为atmega328p烧录bootloader了</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903133936606.png" alt="image-20220903133936606"></p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134000547.png" alt="image-20220903134000547"></p></li><li><p>接着选择 工具 > 编程器 > Arduino as ISP</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134054119.png" alt="image-20220903134054119"></p></li><li><p>最后,如下图选择 工具烧录引导程序,等待烧录完成,即可为atmega328p刷入bootloader</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134114418.png" alt="image-20220903134114418"></p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134135909.png" alt="image-20220903134135909"></p></li><li><p>断开Arduino Uno与电脑连接,拔掉Arduino Uno在面包板上的线恢复至最小系统电路图状态,如下图所示</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/step-9.png" alt="step-9"></p></li><li><p>ArduinoIDE中重新选择端口,选中你的USB转UART设备(不同USB转UART设备显示不一样),如下图</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134224235.png" alt="image-20220903134224235"></p></li><li><p>选择 文件 > 示例 > Basics > Blink打开一个示例项目</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134251325.png" alt="image-20220903134251325"></p></li><li><p>选择上传,此时不出意外,即可使用USB转串口给构造的Arduino最小系统上传一个程序</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903134320460.png" alt="image-20220903134320460"></p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/image-20220903150322243.png" alt="image-20220903150322243"></p></li><li><p>最终运行效果,如下图所示,二极管将每隔一段时间亮一次</p><p> <img src="/note/2022/09/%E4%BD%BF%E7%94%A8Atmega328P%E6%90%AD%E5%BB%BAArduino%E6%9C%80%E5%B0%8F%E7%B3%BB%E7%BB%9F/arduino.gif" alt="arduino"></p></li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><ul><li>网上搜索了很多Arduino最小系统的相关教程,折腾了挺久,这里将我个人觉得比较好的方案记录下来</li><li>下一篇将基于这个最小系统,来解一道硬件CTF的题</li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://b23.tv/AGT8p7z">【arduino!】给你的ATmega328P烧录引导程序</a></li><li><a href="https://www.geek-workshop.com/thread-136-1-1.html">https://www.geek-workshop.com/thread-136-1-1.html</a></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> Arduino </tag>
<tag> Arduino最小系统 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记8-ChipWhisperer Analyzer 相关性能量分析攻击</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将继续跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习使用ChipWhisperer Analyzer提供的API来进行相关性能量分析(CPA)攻击</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/cpa2.gif" alt="cpa2"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>最开始的代码,作用依旧是编译目标板固件并刷入,然后抓取能量迹</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828160903549.png" alt="image-20220828160903549"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161019273.png" alt="image-20220828161019273"></p></li><li><p>直接进入正题,python中使用ChipWhisperer Analyzer需要先导入包</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> chipwhisperer.analyzer <span class="keyword">as</span> cwa</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161222474.png" alt="image-20220828161222474"></p></li><li><p>然后,这个analyzer的方便之处在于,像之前分析AES时用到的SBox模型,analyzer都给封装好了,只需在analyzer的leakage_models模块下选择一个模型即可,一行代码搞定(非常方便)</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">leak_model = cwa.leakage_models.sbox_output</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161449730.png" alt="image-20220828161449730"></p></li><li><p>同样地,进行CPA攻击,也只需一行代码初始化</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">attack = cwa.cpa(proj, leak_model)</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161605590.png" alt="image-20220828161605590"></p></li><li><p>可以通过print(attack)来查看设置信息</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161754626.png" alt="image-20220828161754626"></p></li><li><p>同样地,开始执行CPA攻击也是执行一行代码即可</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">results = attack.run()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828161918862.png" alt="image-20220828161918862"></p></li><li><p>查看结果,如下图所示,可以看到,利用chipwhipserer analyzer提供的API,大大减少了代码量,非常地方便</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828162005247.png" alt="image-20220828162005247"></p></li><li><p>接着的提示信息是说,CPA攻击的返回结果可以调用find_maximums函数来获得全部信息</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828162240542.png" alt="image-20220828162240542"></p></li><li><p>可以利用pandas的dataframe来查看find_maximums的结果</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line">stat_data = results.find_maximums()</span><br><span class="line">df = pd.DataFrame(stat_data).transpose()</span><br><span class="line"><span class="built_in">print</span>(df.head())</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828162409967.png" alt="image-20220828162409967"></p></li><li><p>甚至可以dataframe的.style方法来自定义显示数据,如下图,可以将真实的Key用红色高亮显示</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></pre></td><td class="code"><pre><span class="line">key = proj.keys[<span class="number">0</span>]</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">format_stat</span>(<span class="params">stat</span>):</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">str</span>(<span class="string">"{:02X}<br>{:.3f}"</span>.<span class="built_in">format</span>(stat[<span class="number">0</span>], stat[<span class="number">2</span>]))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">color_corr_key</span>(<span class="params">row</span>):</span><br><span class="line"> <span class="keyword">global</span> key</span><br><span class="line"> ret = [<span class="string">""</span>] * <span class="number">16</span></span><br><span class="line"> <span class="keyword">for</span> i,bnum <span class="keyword">in</span> <span class="built_in">enumerate</span>(row):</span><br><span class="line"> <span class="keyword">if</span> bnum[<span class="number">0</span>] == key[i]:</span><br><span class="line"> ret[i] = <span class="string">"color: red"</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> ret[i] = <span class="string">""</span></span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line">df.head().style.<span class="built_in">format</span>(format_stat).apply(color_corr_key, axis=<span class="number">1</span>)</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828162509523.png" alt="image-20220828162509523"></p></li><li><p>接着的提示信息是说,ChipWhisperer Analyzer使用上一篇中提到的“可以实时更新的”相关性计算,这意味着,我们可以从攻击过程获取反馈信息。可以通过定义一个callback函数,并将callback函数传递给attack.run函数,以及传递一个数(每隔多少能量迹反馈一次)来实现实时反馈信息。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> IPython.display <span class="keyword">import</span> clear_output</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"> </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">stats_callback</span>():</span><br><span class="line"> results = attack.results</span><br><span class="line"> results.set_known_key(key)</span><br><span class="line"> stat_data = results.find_maximums()</span><br><span class="line"> df = pd.DataFrame(stat_data).transpose()</span><br><span class="line"> clear_output(wait=<span class="literal">True</span>)</span><br><span class="line"> display(df.head().style.<span class="built_in">format</span>(format_stat).apply(color_corr_key,axis=<span class="number">1</span>))</span><br><span class="line"> </span><br><span class="line">results = attack.run(stats_callback, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828162828677.png" alt="image-20220828162828677"></p></li><li><p>运行后,可以看到整个cpa攻击过程中排在前5的相关系数最大的key的变化,非常地直观!</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/cpa.gif" alt="cpa"></p></li><li><p>另外analyzer还提供了一个默认的jupyter的callback,只需要3行代码就可以实现跟上面一样的效果</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> chipwhisperer <span class="keyword">as</span> cw</span><br><span class="line">cb = cwa.get_jupyter_callback(attack)</span><br><span class="line">results = attack.run(cb, <span class="number">10</span>)</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/cpa2.gif" alt="cpa2"></p></li><li><p>analyzer还提供了一个生成可以用于画图数据的函数analyzer_plots</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08-ChipWhisperer-Analyzer-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB/image-20220828170355213.png" alt="image-20220828170355213"></p></li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li>ChipWhisperer Analyzer是一个可用于侧信道攻击非常强大的python库,通过ChipWhisperer Analyzer可以极大方便我们完成侧信道攻击</li><li>ChipWhisperer Analyzer还有很多API需要去学习研究</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
<tag> 相关性能量分析 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记7-相关性能量分析攻击固件中的AES实现</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将继续跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习比差分能量分析(Differential Power Analysis, DPA)更强大的相关性能量分析(Correlation Power Analysis, CPA)</p></li><li><p>在之前的DPA学习中为了恢复完整的AES密钥,需要抓取了大量的能量迹(几千条),而这里使用CPA只需要50条能量迹即可恢复完整的AES密钥,如此足矣可见这个方法的强大之处</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827165052510.png" alt="image-20220827165052510"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>老规矩,将硬件的部分复制到MAIN中执行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827165705764.png" alt="image-20220827165705764"></p></li><li><p>抓取50条能量迹,如果是NANO则需要200条</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827165802817.png" alt="image-20220827165802817"></p></li><li><p>画出单条能量迹,应该可以看到形如下图的峰形</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827165853858.png" alt="image-20220827165853858"></p></li><li><p>这里提示我们复制前几篇用到的AES模型代码和计算Hamming Weight的代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827165942823.png" alt="image-20220827165942823"></p></li><li><p>按照提示,复制并运行即可</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827170029060.png" alt="image-20220827170029060"></p></li><li><p>接下来的提示是我们开发自己的相关系数算法(虽然有很多库可以直接计算,但是用代码实现数学算法是一个实用的技能)</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827170103826.png" alt="image-20220827170103826"></p></li><li><p>对于长度为N的数据集X和Y,其相关系数的计算公式为$$r = \frac{cov(X, Y)}{\sigma_X \sigma_Y}$$</p><ul><li><p>$cov(X, Y)$ 为X和Y的协方差,可按以下公式计算:</p><p> $$cov(X, Y) = \sum_{n=1}^{N}[(Y_n - \bar{Y})(X_n - \bar{X})]$$</p></li><li><p>$\sigma_X$ 和 $\sigma_Y$ 为两个数据集的标准差,可按以下公式计算:</p><p> $$\sigma_X = \sqrt{\sum_{n=1}^{N}(X_n - \bar{X})^2}$$</p></li></ul></li><li><p>为了实现代码计算相关系数,首先可以分模块实现,如:实现计算平均值的函数mean、实现计算标准差的函数std_dev、实现计算协方差函数cov。可以用**<code>np.sum(X, axis=0)</code>**来替换所有求和符号$\sum$</p></li><li><p>按照提示,可以实现以下3个函数</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827171159050.png" alt="image-20220827171159050"></p></li><li><p>接着运行检查函数,查看mean、std_dev、cov三个函数是否正确实现</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827171246643.png" alt="image-20220827171246643"></p></li><li><p>紧接着往下运行可以看到很多提示信息</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827184411711.png" alt="image-20220827184411711"></p></li><li><p>根据提示,补全代码后运行,可以看到AES密钥第一个字节的猜测结果为"0x2b",相关系数为0.82</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></pre></td><td class="code"><pre><span class="line">maxcpa = [<span class="number">0</span>] * <span class="number">256</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># we don't need to redo the mean and std dev calculations </span></span><br><span class="line"><span class="comment"># for each key guess</span></span><br><span class="line">t_bar = mean(trace_array) </span><br><span class="line">o_t = std_dev(trace_array, t_bar)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> kguess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line"> hws = np.array([[HW[aes_internal(textin[<span class="number">0</span>],kguess)] <span class="keyword">for</span> textin <span class="keyword">in</span> textin_array]]).transpose()</span><br><span class="line"> hw_bar = mean(hws)</span><br><span class="line"> o_hw = std_dev(hws, hw_bar)</span><br><span class="line"> hw_cov = cov(trace_array,t_bar, hws, hw_bar)</span><br><span class="line"> </span><br><span class="line"> cpaoutput = hw_cov/(o_t*o_hw)</span><br><span class="line"> maxcpa[kguess] = <span class="built_in">max</span>(<span class="built_in">abs</span>(cpaoutput))</span><br><span class="line"> </span><br><span class="line">guess=np.argmax(maxcpa)</span><br><span class="line">guess_corr=maxcpa[guess]</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Key guess: "</span>, <span class="built_in">hex</span>(guess))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Correlation: "</span>, guess_corr)</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827184544861.png" alt="image-20220827184544861"></p></li><li><p>如下图,可以看到,有个判断是否正确猜测AES密钥第一个字节的代码块,"0x2b"为实际AES密钥的第一个字节</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827184712313.png" alt="image-20220827184712313"></p></li><li><p>接着根据提示补全代码,自动判断AES密钥所有16个字节</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></pre></td><td class="code"><pre><span class="line">t_bar = np.<span class="built_in">sum</span>(trace_array, axis=<span class="number">0</span>)/<span class="built_in">len</span>(trace_array)</span><br><span class="line">o_t = np.sqrt(np.<span class="built_in">sum</span>((trace_array - t_bar)**<span class="number">2</span>, axis=<span class="number">0</span>))</span><br><span class="line"></span><br><span class="line">cparefs = [<span class="number">0</span>] * <span class="number">16</span> <span class="comment">#put your key byte guess correlations here</span></span><br><span class="line">bestguess = [<span class="number">0</span>] * <span class="number">16</span> <span class="comment">#put your key byte guesses here</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> bnum <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">16</span>):</span><br><span class="line"> maxcpa = [<span class="number">0</span>] * <span class="number">256</span></span><br><span class="line"> <span class="keyword">for</span> kguess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line"> hws = np.array([[HW[aes_internal(textin[bnum],kguess)] <span class="keyword">for</span> textin <span class="keyword">in</span> textin_array]]).transpose()</span><br><span class="line"> hw_bar = mean(hws)</span><br><span class="line"> o_hw = std_dev(hws, hw_bar)</span><br><span class="line"> hw_cov = cov(trace_array,t_bar, hws, hw_bar)</span><br><span class="line"></span><br><span class="line"> cpaoutput = hw_cov/(o_t*o_hw)</span><br><span class="line"> maxcpa[kguess] = <span class="built_in">max</span>(<span class="built_in">abs</span>(cpaoutput))</span><br><span class="line"> </span><br><span class="line"> guess=np.argmax(maxcpa)</span><br><span class="line"> guess_corr=maxcpa[guess]</span><br><span class="line"> bestguess[bnum]=guess</span><br><span class="line"> cparefs[bnum]=guess_corr</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Best Key Guess: "</span>, end=<span class="string">""</span>)</span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> bestguess: <span class="built_in">print</span>(<span class="string">"%02x "</span> % b, end=<span class="string">""</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"\n"</span>, cparefs)</span><br></pre></td></tr></table></figure></li><li><p>运行结果如下图所示,使用CPA成功恢复了完整的AES密钥"2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c"</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827184938968.png" alt="image-20220827184938968"></p></li><li><p>最后可以看到如下提示信息,大意是有个新公式可以用来实时的计算更新相关系数,这个方法会在下一个教程中使用</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07-%E7%9B%B8%E5%85%B3%E6%80%A7%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220827185057076.png" alt="image-20220827185057076"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>学习了如何简单用python代码实现相关系数的计算</li><li>学习了如何利用相关系数能量分析和少量能量迹来恢复AES密钥</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
<tag> 相关性能量分析 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记6-汉明重量和能耗之间的关系</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将继续跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习,汉明重量和能量消耗之间的关系</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111132577.png" alt="image-20220827111132577"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>同样Lab 4_1 - Power and Hamming Weight Relationship有硬件和模拟之分,这里同样选择将HARDWARE的内容复制到MAIN中执行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827102559595.png" alt="image-20220827102559595"></p></li><li><p>这里根据提示只修改PLATFORM为"CWLITEXMEGA"(当然根据提示"CWLITEXMEGA"还可以修改CRYPTO_TARGET)</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827102956854.png" alt="image-20220827102956854"></p></li><li><p>前面的代码作用依旧是编译目标板的固件并刷入,之间运行即可</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827103310892.png" alt="image-20220827103310892"></p></li><li><p>接着代码是抓取1000条能量迹,同样运行即可</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827103404110.png" alt="image-20220827103404110"></p></li><li><p>接着根据提示,复制前两篇用到的AES模型到此处执行即可</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827103514707.png" alt="image-20220827103514707"></p></li><li><p>接下来看到如下提示,大意是:“回想一下,功耗与微控制器之间存在关系的原因是,设置数据需要消耗能量。我们还看到,设置的数据越多,平均功耗越大。事实上,能量消耗与设置为1的bit的个数(汉明重量)存在一定的联系。虽然汉明重量是一个非常简单的概念,但是它实际上不容易计算。但在python中有更简单的方式,将待计算的数转换为二进制的字符串,然后计算1的个数即可”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827103623918.png" alt="image-20220827103623918"></p></li><li><p>接着的代码是,制作一个256字节对应的汉明重量的表,后续可直接通过查表来获得汉明重量</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827105445103.png" alt="image-20220827105445103"></p></li><li><p>接下来的提示,大意是"我们第一个问题是我们不知道SBox操作发生在哪?它应该发生在非常接近起点的位置(让我们猜测它发生在最开始的2000个样本中)。一个想法是我们可以根据汉明重量将能量迹分组然后给每组分配一个颜色。如果我们画出图,我们也许就可以找到一种模式。"</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827105757507.png" alt="image-20220827105757507"></p></li><li><p>运行截图如下图所示:</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111006600.png" alt="image-20220827111006600"></p></li><li><p>接着可以看到如下提示信息,大意就是,从上图看不出什么来,那是因为SBox只是很小一部分,有许多噪声影响了观察,所以需要重新处理数据,先按汉明重量分组后求平均值,再将每组平均值减去总体的平均值,然后再重新画图</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111343954.png" alt="image-20220827111343954"></p></li><li><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><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">hw_groups=[[] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line">bnum = <span class="number">0</span></span><br><span class="line"><span class="keyword">for</span> tnum <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(trace_array)):</span><br><span class="line"> hw_of_byte = HW[aes_internal(textin_array[tnum][bnum], key[bnum])]</span><br><span class="line"> hw_groups[hw_of_byte].append(trace_array[tnum])</span><br><span class="line">hw_averages = [np.mean(np.array(each), axis=<span class="number">0</span>) <span class="keyword">for</span> each <span class="keyword">in</span> hw_groups]</span><br><span class="line">avg_trace=np.mean(trace_array, axis=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> bokeh.plotting <span class="keyword">import</span> figure, show</span><br><span class="line"><span class="keyword">from</span> bokeh.io <span class="keyword">import</span> output_notebook</span><br><span class="line"><span class="keyword">from</span> bokeh.palettes <span class="keyword">import</span> brewer</span><br><span class="line"></span><br><span class="line">output_notebook()</span><br><span class="line">p = figure()</span><br><span class="line"></span><br><span class="line">plot_start = <span class="number">0</span></span><br><span class="line">plot_end = <span class="number">2000</span></span><br><span class="line"></span><br><span class="line">xrange = <span class="built_in">range</span>(<span class="built_in">len</span>(trace_array[<span class="number">0</span>]))[plot_start:plot_end]</span><br><span class="line">color_mapper = brewer[<span class="string">'PRGn'</span>][<span class="number">9</span>]</span><br><span class="line"><span class="keyword">for</span> hw <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">9</span>): </span><br><span class="line"> p.line(xrange, (hw_averages[hw]-avg_trace)[plot_start:plot_end], line_color=color_mapper[hw])</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111756582.png" alt="image-20220827111756582"></p></li><li><p>重新画图结果如下图所示</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111924024.png" alt="image-20220827111924024"></p></li><li><p>根据如下提示,运行代码,可以找到图中差异最大的地方</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827112034201.png" alt="image-20220827112034201"></p></li><li><p>接着有如下提示,大意是“现在可以知道SBox操作发生的地方了,用上面的sbox_loc来重新画图,要使用**<code>hw_averages[:,sbox_loc]</code>**来取值需要将hw_averages转换为numpy array”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827112132384.png" alt="image-20220827112132384"></p></li><li><p>最终结果如下图所示,可以看到汉明重量与能量消耗呈现出线性关系</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F%E5%92%8C%E8%83%BD%E8%80%97%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/image-20220827111132577.png" alt="image-20220827111132577"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>学习了汉明重量与能耗之间的关系,通过画图知道两者是线性相关的关系</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记5-差分能量分析攻击固件中的AES实现</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习DPA(Differential Power Analysis,差分能量分析)</p></li><li><p>与上一篇不同,本篇将攻击真正的AES算法</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821104901460.png" alt="image-20220821104901460"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>这次选择的是Lab3_3,可以看到同样有MAIN、HARDWARE和SIMULATED三种ipynb,这里同样选择将硬件的内容复制到MAIN中执行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220820223116137.png" alt="image-20220820223116137"></p></li><li><p>首先可以看到选择版本号的提示如下图所示,这里提到对于'CWLITEXMEGA'的加密模块可以选择'AVRCRYPTOLIB',但这里不选择'AVRCRYPTOLIB',因为测试选用这个,效果太好了,可以相对比较轻松的恢复整个AES密钥,基于学习目的,所以这里保持默认,选择'TINYAES128C'</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220820223434595.png" alt="image-20220820223434595"></p></li><li><p>前几行代码,跟之前的作用一样,初始化chipwhisperer,编译目标板固件并上传</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220820223931846.png" alt="image-20220820223931846"></p></li><li><p>接下来的代码,就是抓取2500条能量迹</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220820224044381.png" alt="image-20220820224044381"></p></li><li><p>接下来查看MAIN中的Summary,大意是:“在上一个的教程中,你知道了如何利用一个位的信息去恢复一个AES密钥的byte。需要注意的是,这个方法之所以能生效是因为我们攻击的数据流中存在那个S-Box。接下来我们将学习如何用能量分析而不是一个实际的bit去恢复AES密钥。这项技术的目标是利用S-Box输出结果的一个bit(无论哪个bit都行)将能量迹分成两部分。如果那个bit的值是1,那么对应的那组数据的能耗将比另一组的要高。这些都基于之前的教程:数据总线中数据的值与能量消耗之间有一定的联系”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821102023449.png" alt="image-20220821102023449"></p></li><li><p>接下来看到如下提示,大意是说:“不需要记住AES复杂的模型-我们可以直接将上一篇的AES模型复制过来”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821103417466.png" alt="image-20220821103417466"></p></li><li><p>如下图,这部分代码确实是上一篇中的AES简单模型。这里之所以能用这个模型是因为,真正的AES模型也包含了这个流程,只是多了其它几个流程。DPA分析AES时,不需要知道这个流程具体发生在能量迹的哪个时间点,但是整个能量迹的时间段需要包含这个流程发生的时间段,不然就无法分析(都没抓取到,当然无法分析)。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821103614196.png" alt="image-20220821103614196"></p></li><li><p>接下来的提示,是说完成生成能量迹的代码,可以选择模拟或者硬件的代码复制过来。这里上面已经复制了硬件的代码运行了,所以直接跳过到下一个节点</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821104401058.png" alt="image-20220821104401058"></p></li><li><p>往下掉提示是完成画图的代码,画出一到两条能量迹</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821104723654.png" alt="image-20220821104723654"></p></li><li><p>接着是查看输入数据是什么样的</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821104821718.png" alt="image-20220821104821718"></p></li><li><p>接下来的提示是说:这个攻击需要用一个方法将能量迹分成两组,分组的依据是我们猜测的值中的一个bit。这里为了简单起见,先猜测一个byte的AES密钥。开始之前-需要定义能量迹的数量和每条能量迹的点的数量。可以直接运行下面的代码例子来定义这两个变量</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821105014483.png" alt="image-20220821105014483"></p></li><li><p>接下来的提示信息,大意是:让我们补全代码,将能量迹分成两组,并将两组求平均值,再取两者差的绝对值,最后得到两组中的最大差值。有个需要注意的是使用numpy的mean计算平均值时,需要指定axis=0。为了方便测试,还给出了密钥key第一个byte的真实值0x2b。如果遇到问题,往后还有提示。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821111905404.png" alt="image-20220821111905404"></p></li><li><p>根据上面信息补全代码,运行后如下图所示,可以看到只有0x2b拥有最大的差异值,与上面描述的一致,这样就恢复了key的第一个byte:"0x2b"</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/2022-08-21.11.31.11.png" alt="2022-08-21.11.31.11"></p></li><li><p>接下来思考,这个方法为什么生效,为什么可以用aes_internal这个函数来划分两个组?</p><ul><li>可以用aes_internal函数来分组,是因为真正的AES拥有与aes_internal类似的代码片段SubBytes,只不过真正AES还有AddRoundKey、ShiftRows、MixColumns三种操作</li><li>现在假设用相同密钥key加密2500数据样本,并根据其某一次SubBytes的输出的第0个byte的第0位的值来分成''0"和"1"两组,那么这两组数据所对应的能量迹会有一定的差异(这是前面的知识,指令相同情况下,数据也会对能量消耗产生影响)</li><li>现在使用aes_internal来对某个key加密的能量迹进行分组,如果猜测的密钥guess是真实值,那么分组结果就恰好是某一次SubBytes的输出的第0个byte的第0位的值'0'和'1'的分组,两组数据会有1个bit的明显差异,那么两者的能量消耗会有比较明显的差异,如果guess不是真实值,那么分组结果的'0'和'1'的两组的数据是随机的,两组数据没有明显差异,两组的能量消耗也就不会有太大差异,这就是这个方法可以生效的原因</li></ul></li><li><p>接着往下运行,可以看到如下的信息,大意是说:你想要对猜测结果进行排序,这样可以帮助确定最有可能的值。一个最好的方式就是用一个list保存每个猜测的key的最大差异。然后可以使用numpy的argsort来实现排序。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821131907341.png" alt="image-20220821131907341"></p></li><li><p>按照上述信息,修改代码后,可以把差异最大的前5的key打印出来,如下图所示</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821132918276.png" alt="image-20220821132918276"></p></li><li><p>接着可以看到,用来计算差异的函数calculate_diffs</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821133033977.png" alt="image-20220821133033977"></p></li><li><p>接着按照提示,运行代码,查看画出不同key对应的差异图,这个图的作用就是:既可以直观的看出不同猜测的key造成的差异,又可以看到哪些位置会出现尖峰</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821133137066.png" alt="image-20220821133137066"></p></li><li><p>接下来就是根据提示信息,完成自动猜测AES密钥所有字节的代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821133523599.png" alt="image-20220821133523599"></p></li><li><p>补全代码后,运行结果如下图所示,可以看到,猜测正确的寥寥无几(只有第0、5、10个字节这三个是正确),造成这个原因有不少(比如算法实现方式也有关系,如果硬件是CWLITEXMEGA,并且算法选的是AVRCRYPTOLIB,那么这里将直接得到16正确的结果)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tnrange</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment">#Store your key_guess here, compare to known_key</span></span><br><span class="line">key_guess = []</span><br><span class="line">known_key = [<span class="number">0x2b</span>, <span class="number">0x7e</span>, <span class="number">0x15</span>, <span class="number">0x16</span>, <span class="number">0x28</span>, <span class="number">0xae</span>, <span class="number">0xd2</span>, <span class="number">0xa6</span>, <span class="number">0xab</span>, <span class="number">0xf7</span>, <span class="number">0x15</span>, <span class="number">0x88</span>, <span class="number">0x09</span>, <span class="number">0xcf</span>, <span class="number">0x4f</span>, <span class="number">0x3c</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> subkey <span class="keyword">in</span> tnrange(<span class="number">0</span>, <span class="number">16</span>, desc=<span class="string">"Attacking Subkey"</span>):</span><br><span class="line"> max_list=[]</span><br><span class="line"> <span class="keyword">for</span> guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">256</span>):</span><br><span class="line"> max_diff_value = <span class="built_in">max</span>(calculate_diffs(guess, subkey))</span><br><span class="line"> max_list.append(max_diff_value)</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Subkey {} - most likelv {:02x} (actual {:02x})'</span>.<span class="built_in">format</span>(subkey,np.argsort(max_list)[::-<span class="number">1</span>][<span class="number">0</span>], known_key[subkey]))</span><br><span class="line"> </span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821134846876.png" alt="image-20220821134846876"></p></li><li><p>接下来看看教程怎么说这个现象,如下图所示,大意是说:你可能在前面并没有恢复整个密钥?不用担心-造成这个现象是因为有一些其它因素导致的。有一篇关于DPA攻击的文章提到的观点是“你获取了另一个不是正确密钥的强峰(它可能是幽灵峰)”。后面将会有更有效的攻击方法,但是,对于现在,让我们来看一些解决方案:</p><ul><li>增加抓取的能量迹数量(如将2500变成5000或者10000),增加数据有时就可以解决这个问题了,但有时候真正的幽灵峰可能不会消失,这种情况就需要用其它方法。</li><li>修改攻击的目标位数(如第'0'位改为第'3'位)或者综合多个位的结果</li><li>截取数据</li></ul><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821135230198.png" alt="image-20220821135230198"></p></li><li><p>接下来就是按照提示信息,运行一个准备好的DPA攻击代码块</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821140652892.png" alt="image-20220821140652892"></p></li><li><p>接着就看到如下提示信息,大意是让我们画出猜测错误key的前几个可能值和真实key的差异图</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821140828304.png" alt="image-20220821140828304"></p></li><li><p>这里因为第5个subkey猜测恰好是正确的(与教程的例子不同),所以选择第1个subkey画图</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821141253823.png" alt="image-20220821141253823"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821141405339.png" alt="image-20220821141405339"></p></li><li><p>接着看到如下提示信息,大意是说,通过放大你应该可以看到正确key的峰形会在某些地方拥有比错误key的峰形高。从上图看确实如此,在3000和5000附近的位置,正确key的尖峰(绿色)很明显,另外两个的则只在2000附近有一个大的尖峰</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821141504265.png" alt="image-20220821141504265"></p></li><li><p>然后可以看到如下提示信息,大意是说:取决于你的硬件设备,上图中你会看到一个不错的大尖峰或者多个尖峰。如果有幽灵峰的问题,那么很可能就会有多个尖峰。错误的峰可能与正确的峰错开--所以我们首先可以把所有正确key对应的峰形画出来</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821142643906.png" alt="image-20220821142643906"></p></li><li><p>接下来的提示信息是,大意是说:最后一个技巧是 -- 截取数据的一些片段会很有用。例如,从上图可以看到,正确的峰图总是每隔60个周期出现,第一个峰出现在附近1100(这个对于你的硬件可能有所不同)。然后就是提示如何修改代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821143256050.png" alt="image-20220821143256050"></p></li><li><p>最后按照提示做了如下修改,从3220出开始,截取能量迹的数据,这里可以不按每个subkey隔60时钟周期取值,因为实测这样效果更好</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tnrange</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment">#Store your key_guess here, compare to known_key</span></span><br><span class="line">key_guess = []</span><br><span class="line">known_key = [<span class="number">0x2b</span>, <span class="number">0x7e</span>, <span class="number">0x15</span>, <span class="number">0x16</span>, <span class="number">0x28</span>, <span class="number">0xae</span>, <span class="number">0xd2</span>, <span class="number">0xa6</span>, <span class="number">0xab</span>, <span class="number">0xf7</span>, <span class="number">0x15</span>, <span class="number">0x88</span>, <span class="number">0x09</span>, <span class="number">0xcf</span>, <span class="number">0x4f</span>, <span class="number">0x3c</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">#Which bit to target</span></span><br><span class="line">bitnum = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">full_diffs_list = []</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> subkey <span class="keyword">in</span> tnrange(<span class="number">0</span>, <span class="number">16</span>, desc=<span class="string">"Attacking Subkey"</span>):</span><br><span class="line"> </span><br><span class="line"> max_diffs = [<span class="number">0</span>]*<span class="number">256</span></span><br><span class="line"> full_diffs = [<span class="number">0</span>]*<span class="number">256</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line"> full_diff_trace = calculate_diffs(guess, subkey, bitnum)</span><br><span class="line"> full_diff_trace = full_diff_trace[(<span class="number">3220</span> + subkey*<span class="number">0</span>):]</span><br><span class="line"> max_diffs[guess] = np.<span class="built_in">max</span>(full_diff_trace)</span><br><span class="line"> full_diffs[guess] = full_diff_trace</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Make copy of the list</span></span><br><span class="line"> full_diffs_list.append(full_diffs[:])</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Get argument sort, as each index is the actual key guess.</span></span><br><span class="line"> sorted_args = np.argsort(max_diffs)[::-<span class="number">1</span>]</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Keep most likely</span></span><br><span class="line"> key_guess.append(sorted_args[<span class="number">0</span>])</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Print results</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Subkey %2d - most likely %02X (actual %02X)"</span>%(subkey, key_guess[subkey], known_key[subkey]))</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Print other top guesses</span></span><br><span class="line"><span class="comment"># print(" Top 5 guesses: ")</span></span><br><span class="line"><span class="comment"># for i in range(0, 5):</span></span><br><span class="line"><span class="comment"># g = sorted_args[i]</span></span><br><span class="line"><span class="comment"># print(" %02X - Diff = %f"%(g, max_diffs[g]))</span></span><br><span class="line"> </span><br><span class="line"><span class="comment"># print("\n")</span></span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821145417898.png" alt="image-20220821145417898"></p></li><li><p>最后运行结果,如下图所示,猜测准确的字节数由原来的3个字节提升到全部字节都猜测正确,不得不说,这个提升很大</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821145256193.png" alt="image-20220821145256193"></p></li><li><p>这里解释下为什么从3220的位置开始截取能量迹数据而不是2000附近的位置开始?-- 如下图,因为2000附近的位置除了正确的key会出现尖峰外,错误的key也会出现尖峰,所以需要寻找一个只有正确的key会出现明显尖峰的地方作为数据截取的起始点</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-%E5%B7%AE%E5%88%86%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90%E6%94%BB%E5%87%BB%E5%9B%BA%E4%BB%B6%E4%B8%AD%E7%9A%84AES%E5%AE%9E%E7%8E%B0/image-20220821141405339.png" alt="image-20220821141405339"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>学习了如何利用差分能量分析(DPA)来恢复整个AES密钥</li><li>了解了幽灵峰的现象,以及一些解决方法</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
<tag> 差分能量分析 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记4-利用1个Bit恢复数据</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将继续跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习从一个Bit的信息来恢复"AES"的密钥,这里的"AES"当然不是标准的AES,但是也挺接近了,为后面能量分析真正的AES提供理论基础</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820104253537.png" alt="image-20220820104253537"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>首先打开Lab 3_2 - Recovering Data from a Single Bit.ipynb,与前面不同,这个notebook不需要硬件就可以完整运行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820104552900.png" alt="image-20220820104552900"></p></li><li><p>可以到如下提示,大意是说:“你可以把"AES"看成是密钥key和输入数据input异或(XOR)后,再将结果通过S-Box进行字节替换得到输出”。虽然这个"AES"比真正的AES要简单的多,但真正AES的确包含类似的方式,所以通过这个例子来学习确实很适合新手。后面用到的加密模型就是基于下图所提到的XOR + SubBytes</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820104848010.png" alt="image-20220820104848010"></p></li><li><p>接着就是给出了AES的S-Box,然后提示根据sbox补全接下来的函数</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820110342505.png" alt="image-20220820110342505"></p></li><li><p>补全aes_internal函数,根据提示描述可知实现该函数的要点:1. 输入和输出相异或 2.根据异或结果从sbox中查表获得最后输出,总结起来就是<code>sbox[inputdata ^ key]</code></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820110501571.png" alt="image-20220820110501571"></p></li><li><p>紧接着有个简单检验前面的aes_internal函数是否正确实现的代码块,执行后出现'OK to continue!'就说明应该没什么问题</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820110811543.png" alt="image-20220820110811543"></p></li><li><p>接着可以看到如下提示信息和代码,大意就是:“定义一个新的函数aes_secret,不对外暴露key参数,而是内置一个固定的key。后面的攻击就是攻击这个函数,并且假设我们不能直接获得这个预设的固定key是多少但是能获得1个bit的信息”</p></li></ul><p><img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820111056601.png" alt="image-20220820111056601"></p><ul><li><p>紧接着看到如下提示信息和代码块,这段信息目的就是通过这个例子引导我们如何用python生成随机数,并将生成数据通过一个加密函数得到输出</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820112007105.png" alt="image-20220820112007105"></p></li><li><p>接着就是根据提示,补全生成随机数据input_data的代码,并且同样有一个简单判断代码实现是否正确</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820112324740.png" alt="image-20220820112324740"></p></li><li><p>接着是根据提示信息生成泄漏数据(模拟每个byte泄漏一个bit),"& 0x01"的作用就是取最低位,也就是这里取的是每个byte的第0位,另外可以看到,生成的泄漏数据全为'0'和'1'</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820112548952.png" alt="image-20220820112548952"></p></li><li><p>接着有个提示,可以通过画图来直观查看泄漏数据leaked_data的内容,然后有个提问是:“你认为我们将能从这些数据中获取一些有用的东西吗?让我们攻击它来找到答案”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820112959057.png" alt="image-20220820112959057"></p></li><li><p>接着看到如下提示信息,大意是:“让我们攻击之前的模型(知道算法等信息,只是不知道密钥key),然后用所有可能的key获取一系列的数据,再跟上面泄漏的信息做对比,根据有多少相同来判断猜测是否正确。首先,需要补全一个函数,让该函数返回两个lis之间t有多少相同元素的数量”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820140859283.png" alt="image-20220820140859283"></p></li><li><p>如下图,获得两个list相同元素的数量代码可以用numpy array来实现,先转为numpy array,再让两个array用'=='比较,最后sum就可以计算得到'True'(1)的数量</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">num_same</span>(<span class="params">a, b</span>):</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(a) != <span class="built_in">len</span>(b):</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Arrays must be same length!"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">max</span>(a) != <span class="built_in">max</span>(b):</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Arrays max() should be the same!"</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Count how many list items match up</span></span><br><span class="line"> _a=np.array(a)</span><br><span class="line"> _b=np.array(b)</span><br><span class="line"> <span class="keyword">return</span> (_a==_b).<span class="built_in">sum</span>()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820141619791.png" alt="image-20220820141619791"></p></li><li><p>同样地,有个简单判断我们代码是否正确实现地代码块</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820141834436.png" alt="image-20220820141834436"></p></li><li><p>然后可以看到如下提示信息,大意就是:补齐代码,调用aes_internal函数,用key每个可能值[0x00,0x01,...,0xff]加密相同的input_data得到key_guess,再将结果与泄漏数据相比较获得两个list相同的值</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820141943793.png" alt="image-20220820141943793"></p></li><li><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><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>): </span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Get a hypothetical leakage list - use aes_internal(guess, input_byte) and mask off to only get value of lowest bit</span></span><br><span class="line"> hypothetical_leakage = [(aes_internal(a,guess) & <span class="number">0x01</span>) <span class="keyword">for</span> a <span class="keyword">in</span> input_data]</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Use our function</span></span><br><span class="line"> same_count = num_same(hypothetical_leakage, leaked_data) </span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Print for debug</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Guess {:02X}: {:4d} bits same"</span>.<span class="built_in">format</span>(guess, same_count))</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820142345382.png" alt="image-20220820142345382"></p></li><li><p>结合上图的输出信息,正如下图的提示信息一样,大部分猜测的'byte'输出相同数目都是500左右,只有'0xEF'是1000(前提是前面的内置key没有设置为其他),所以'0xEF'是固定密钥key的唯一可能值</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820142548417.png" alt="image-20220820142548417"></p></li><li><p>接下来是介绍numpy的argsort,如下图np.argsort()可以从小到大排序一个list(注意这里argsort结果是输出list下标index的排序结果,也就是最小数据所在index到最大数据所在index),要从大到小排序可以在结果加[::-1]或者np.argsort(-np.array(list()))</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820142841758.png" alt="image-20220820142841758"></p></li><li><p>接下来的提示信息就是,利用argsort来补全代码,让代码输出相同数前5的猜测key</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820143458506.png" alt="image-20220820143458506"></p></li><li><p>按照提示可以补全代码,如下图,可以看到,代码成功输出相同数量排在前5的猜测值</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">guess_list = [<span class="number">0</span>] * <span class="number">256</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>): </span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Get a hypothetical leakage list - use aes_internal(guess, input_byte) and mask off to only get value of lowest bit</span></span><br><span class="line"> hypothetical_leakage = [(aes_internal(a,guess) & <span class="number">0x01</span>) <span class="keyword">for</span> a <span class="keyword">in</span> input_data]</span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Use our function</span></span><br><span class="line"> same_count = num_same(hypothetical_leakage, leaked_data) </span><br><span class="line"> </span><br><span class="line"> <span class="comment">#Track the number of correct bits</span></span><br><span class="line"> guess_list[guess] = same_count</span><br><span class="line"></span><br><span class="line"><span class="comment">#Use np.argsort to generate a list of indicies from low to high, then [::-1] to reverse the list to get high to low.</span></span><br><span class="line">sorted_list = np.argsort(guess_list)[::-<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">#Print top 5 only</span></span><br><span class="line"><span class="keyword">for</span> guess <span class="keyword">in</span> sorted_list[<span class="number">0</span>:<span class="number">5</span>]:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Key Guess {:02X} = {:04d} matches"</span>.<span class="built_in">format</span>(guess, guess_list[guess]))</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820143653346.png" alt="image-20220820143653346"></p></li><li><p>接下来可以看到如下提示信息,大意是说:“这个例子是我们知道第'0'位就是泄漏数据所在的位,但如果我们不知道泄漏的数据是哪一位信息呢?有个简单的方法解决这个问题就是修改上面的代码让它猜测每个可能byte中8个可能的bit,要做到这点,首先要做的是写一个函数来返回一个byte指定位的值”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820143819301.png" alt="image-20220820143819301"></p></li><li><p>前面可以知道"& 1"可以获得第0位的值,那么获取指定位值就可以配合移位运算来实现。首先将需要获得的位右移到第0位,再用"& 1"即可。如下图get_bit函数如下,同样有个简单判断代码实现是否正确的代码块</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820144556584.png" alt="image-20220820144556584"></p></li><li><p>然后就是补全aes_leakage_guess代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820144715156.png" alt="image-20220820144715156"></p></li><li><p>最后可以看到如下提示信息,大意就是:用上面的aes_leakage_guess函数补全新的循环,获得每个可能byte每个可能的bit与泄漏数据相比相同的数量。而且还提到,将可以看到只有第'0'位的是有'0xEF'=1000,其余位都是500左右的其他结果。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820144830653.png" alt="image-20220820144830653"></p></li><li><p>补全代码如下,结果也确实跟预期一样,只有第0位的才猜测出正确结果</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> bit_guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">8</span>):</span><br><span class="line"> guess_list = [<span class="number">0</span>] * <span class="number">256</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Checking bit {:d}"</span>.<span class="built_in">format</span>(bit_guess))</span><br><span class="line"> <span class="keyword">for</span> guess <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">256</span>):</span><br><span class="line"></span><br><span class="line"> <span class="comment">#Get a hypothetical leakage for guessed bit (ensure returns 1/0 only)</span></span><br><span class="line"> <span class="comment">#Use bit_guess as the bit number, guess as the key guess, and data from input_data</span></span><br><span class="line"> hypothetical_leakage = [aes_leakage_guess(a, guess, bit_guess) <span class="keyword">for</span> a <span class="keyword">in</span> input_data]</span><br><span class="line"></span><br><span class="line"> <span class="comment">#Use our function</span></span><br><span class="line"> same_count = num_same(hypothetical_leakage, leaked_data) </span><br><span class="line"></span><br><span class="line"> <span class="comment">#Track the number of correct bits</span></span><br><span class="line"> guess_list[guess] = same_count</span><br><span class="line"></span><br><span class="line"> sorted_list = np.argsort(guess_list)[::-<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"> <span class="comment">#Print top 5 only</span></span><br><span class="line"> <span class="keyword">for</span> guess <span class="keyword">in</span> sorted_list[<span class="number">0</span>:<span class="number">5</span>]:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Key Guess {:02X} = {:04d} matches"</span>.<span class="built_in">format</span>(guess, guess_list[guess]))</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820145249765.png" alt="image-20220820145249765"></p></li><li><p>修改前面的leaked_data的代码,可以按如下图修改,这样泄漏数据就是第'3'位的泄漏数据了</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820145518040.png" alt="image-20220820145518040"></p></li><li><p>重新往下运行,可以看到变成第'3'位猜测才有1000 matches了,与预期一致</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820145740656.png" alt="image-20220820145740656"></p></li><li><p>接下来,可以看到如下提示信息,大意就是:给泄漏数据的函数加点“料”,修改aes_secret函数,让它根据随机数来决定返回正确加密结果还是0,以此来模拟噪声</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820145907896.png" alt="image-20220820145907896"></p></li><li><p>然后可以看到如下提示信息,大意就是:加料后会发生什么?-会影响猜测的key的准确率,而且事实上,将返回正确加密后的值的概率和需要多少观察数据来恢复正确的key画图,可以得到如下图所示的结果,从图可知70%以上正确率的基本上都只需要少量数据就可以恢复出正确的key。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820150106933.png" alt="image-20220820150106933"></p></li><li><p>接下来测试下,让aes_secret有70%概率返回正确结果,并且为了避免混淆,将leaked_data重新改为,获取第'0'位的数据</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820151437119.png" alt="image-20220820151437119"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820150909975.png" alt="image-20220820150909975"></p></li><li><p>最后结果如下图所示:可以看到,虽然同样可以得到正确的'0xEF',但是matches值已经变小了很多,但是与其他相比还是有明显的大小区别</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820151505947.png" alt="image-20220820151505947"></p></li><li><p>再来看看10%正确返回的结果,可以看到,虽然同样排在第一的是'0xEF',但是'0xEF'对应的matches数量与排在第2位的0x'E2'差异不大,所以不能只通过一组数据来确定正确密钥是'0xEF'</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820151621619.png" alt="image-20220820151621619"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04-%E5%88%A9%E7%94%A81%E4%B8%AABit%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE/image-20220820151658074.png" alt="image-20220820151658074"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>学习了如何利用一位的泄漏数据来恢复"AES"的密钥</li><li>在实际情况中因为存在噪声等因素地干扰,往往需要更多地数据才能正确恢复密钥</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
</tags>
</entry>
<entry>
<title>解决hexo-asset-image图片相对路径替换问题</title>
<link href="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/"/>
<url>/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>最近手贱删除了hexo 项目中的node_modules重新安装hexo的插件</p></li><li><p>然后出问题了,博客的所有图片不显示了,全变下面这种情况了</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820100246072.png" alt="image-20220820100246072"></p></li><li><p>一开始以为是_config.yml配置问题,但是查看配置文件发现post_asset_folder是设置为true,然后就是各种查找原因</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820100445218.png" alt="image-20220820100445218"></p></li><li><p>最后终于找到解决方法,这里记录一下方法</p></li></ul><span id="more"></span><ul><li><p>首先查看执行<code>hexo clean && hexo g</code>的log输出,可以看到其实图片的相对路径有被替换,只不过替换的有些莫名其妙,不是预期的效果</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820100817548.png" alt="image-20220820100817548"></p></li><li><p>经过一番搜索,发现"update link as"是hexo-asset-image的index.js文件输出,于是文本编辑器打开node_modules/hexo-asset-image/index.js,查看下代码,如下图所示,在index.js文件后面可以看到相应的console打印信息</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820101153986.png" alt="image-20220820101153986"></p></li><li><p>往上看可以看到用于链接替换的"link"变量的是怎么来的,问题在于if语句中的endPos的获得方式,如下图可知,第一种方式是根据原url是否包含"/index.html"来选择最终substring的结束位置是最后一个'/'还是'.'</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820101340070.png" alt="image-20220820101340070"></p></li><li><p>这里因为我hexo设置的是既没有index.html又没有.html只是文件路径形式的url,即:"<a href="https://xxx.com/a/b/c/%22%E8%BF%99%E7%A7%8D%E5%BD%A2%E5%BC%8F">https://xxx.com/a/b/c/"这种形式</a></p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820101918701.png" alt="image-20220820101918701"></p></li><li><p>所以因为不符合第一个if语句的条件,就会用url中最后一个'.'所在index作为substring结束位置,知道问题原因就好办了,添加如下代码,重新保存index.js即可</p> <figure class="highlight js"><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">if</span>(<span class="regexp">/.*\/index\.html$/</span>.<span class="title function_">test</span>(link)) {</span><br><span class="line"> <span class="comment">// when permalink is end with index.html, for example 2019/02/20/xxtitle/index.html</span></span><br><span class="line"> <span class="comment">// image in xxtitle/ will go to xxtitle/index/</span></span><br><span class="line"> appendLink = <span class="string">'index/'</span>;</span><br><span class="line"> <span class="keyword">var</span> endPos = link.<span class="title function_">lastIndexOf</span>(<span class="string">'/'</span>);</span><br><span class="line"> }</span><br><span class="line"><span class="comment">// add</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="regexp">/.*\/$/</span>.<span class="title function_">test</span>(link)) {</span><br><span class="line"> <span class="keyword">var</span> endPos = link.<span class="title function_">lastIndexOf</span>(<span class="string">'/'</span>);</span><br><span class="line"> }</span><br><span class="line"><span class="comment">// add end</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">var</span> endPos = link.<span class="title function_">lastIndexOf</span>(<span class="string">'.'</span>);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820102236695.png" alt="image-20220820102236695"></p></li><li><p>新增加的else if语句作用就是简单判断url是否以'/'结尾,是的话直接用'/'作为substring结束位置</p></li><li><p>重新运行<code>hexo clean && hexo g</code>可以看到url替换已经正常了,图片终于又可以正常显示了</p><p> <img src="/tutorial/2022/08/%E8%A7%A3%E5%86%B3hexo-asset-image%E5%9B%BE%E7%89%87%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E6%9B%BF%E6%8D%A2%E9%97%AE%E9%A2%98/image-20220820102627104.png" alt="image-20220820102627104"></p></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p><a href="https://hexo.io/zh-cn/docs/asset-folders#%E7%9B%B8%E5%AF%B9%E8%B7%AF%E5%BE%84%E5%BC%95%E7%94%A8%E7%9A%84%E6%A0%87%E7%AD%BE%E6%8F%92%E4%BB%B6">https://hexo.io/zh-cn/docs/asset-folders#相对路径引用的标签插件</a></p>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 杂项 </tag>
<tag> hexo </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记3-汉明重量</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li>本篇将继续跟随<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的教程来学习了解能量分析中的常用模型 汉明重量</li><li>汉明重量:简单来说就是一个数的二进制包含'1'的个数,如0x00的HM为0,0xFF的HM为8,0x1和0x2的HM都为1</li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>如下图,同样选择复制HARDWARE的代码至MAIN中</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814160505408.png" alt="image-20220814160505408"></p></li><li><p>同样地根据提示信息选择平台,这里需要注意的是:XMEGA平台的CRYPTO_TARGET既可以使用默认值'TINYAES128C'(全平台通用)也可以使用'AVRCRYPTOLIB'(仅限XMEGA)</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814160747457.png" alt="image-20220814160747457"></p></li><li><p>前几行代码,依旧是初始化一些参数和编译并上传合适的固件到目标板中</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814161509014.png" alt="image-20220814161509014"></p></li><li><p>接着就是提示信息和相应的代码,大意是说为了查看不同汉明重量的数据对能量消耗的影响,这里生成两种极端数据'0x00'(00000000)和'0xff'(11111111),100个traces就足够查看两者的差异</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814161708972.png" alt="image-20220814161708972"></p></li><li><p>接着往下运行,可以看到如下提示信息,大意是指:查看HM大的0xFF和HM小的0x00数据对运行AES时的能量消耗差异,由于这些数据是混合在一起的,所以首先需要对抓取的数据进行分组,分为one_list和zero_list</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814181733701.png" alt="image-20220814181733701"></p></li><li><p>按照提示修改代码将数据分为两组,并将结果转为numpy array类型</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">zero_list=[]</span><br><span class="line">one_list=[]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(trace_array)):</span><br><span class="line"> <span class="keyword">if</span> textin_array[i][<span class="number">0</span>] == <span class="number">0x00</span>:</span><br><span class="line"> zero_list.append(trace_array[i])</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> one_list.append(trace_array[i])</span><br><span class="line"> </span><br></pre></td></tr></table></figure> <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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">one_list=np.array(one_list)</span><br><span class="line">zero_list=np.array(zero_list)</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(one_list) > <span class="built_in">len</span>(zero_list)/<span class="number">2</span></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(zero_list) > <span class="built_in">len</span>(one_list)/<span class="number">2</span></span><br></pre></td></tr></table></figure></li><li><p>接着可以看到如下提示信息,大意是:使用np.mean分别对上面两组数据的trace求平均值,得到zero_avg和one_avg的数据,其中'axis'参数可用来指定求均值的方向(按行或者按列求)</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814182345329.png" alt="image-20220814182345329"></p></li><li><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></pre></td><td class="code"><pre><span class="line">trace_length = <span class="built_in">len</span>(one_list[<span class="number">0</span>])</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Traces had original sample length of %d"</span>%trace_length)</span><br><span class="line"></span><br><span class="line">one_avg=np.mean(one_list, axis=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(one_avg) != trace_length:</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Average length is only %d - check you did correct dimensions!"</span>%one_avg)</span><br></pre></td></tr></table></figure> <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></pre></td><td class="code"><pre><span class="line">trace_length = <span class="built_in">len</span>(zero_list[<span class="number">0</span>])</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Traces had original sample length of %d"</span>%trace_length)</span><br><span class="line"></span><br><span class="line">zero_avg=np.mean(zero_list, axis=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(zero_avg) != trace_length:</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"Average length is only %d - check you did correct dimensions!"</span>%zero_avg)</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">%matplotlib notebook</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"></span><br><span class="line">diff = one_avg[:<span class="number">1000</span>] - zero_avg[:<span class="number">1000</span>]</span><br><span class="line"></span><br><span class="line">plt.plot(<span class="built_in">abs</span>(diff))</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814183217485.png" alt="image-20220814183217485"></p></li><li><p>往下可以看到如下提示信息,大意是说:在前面的位置你可以看到一个明显的差异,这个代表着数据的差异也可以在能量图中体现出来。对比上图确实如此,在200的位置可以看到一个明显的峰值,其余部分的差异都非常小</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814183255221.png" alt="image-20220814183255221"></p></li><li><p>接下来尝试不同的数据与'0x00'的对比,下图'0x01'与'0x00'的对比,可以看到,两者差异并不明显,不能识别出差异数据所在位置</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814194658702.png" alt="image-20220814194658702"></p></li><li><p>出现上图原因是,抓取的数据太少,将前面的N=100改为N=1000,重新运行,并画图,可以看到同样可以看到一个明显的</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814194940481.png" alt="image-20220814194940481"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03-%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F/image-20220814194555668.png" alt="image-20220814194555668"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>除了代码会对能量消耗产生影响外,代码所处理的数据同样会对能量消耗产生影响</li><li>有时为了区分出数据的细微差异,往往需要抓取更多的数据才能保证出现预期的结果</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记2-密码验证的能量分析</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li><p>本篇将基于<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a> sca101的Lab 2_1B来学习如何通过能量分析得出简单密码验证系统的正确密码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814093545869.png" alt="image-20220814093545869"></p></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>可以看到同样有MAIN、HARDWARE和SIMULATED,跟之前一样,这里选择把HARDWARE的代码复制到MAIN执行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814093852193.png" alt="image-20220814093852193"></p></li><li><p>开始同样是选择合适的平台,这里因为目标板是XMEGA,所以只需改PLATFORM为'CWLITEXMEGA'即可</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814094220508.png" alt="image-20220814094220508"></p></li><li><p>前几行代码其实作用就是初始化一些参数和编译合适的目标板固件并上传至目标板</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814094442129.png" alt="image-20220814094442129"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814094607213.png" alt="image-20220814094607213"></p></li><li><p>接着运行,可以看到'OK to continue!',说明前面的配置没有问题,可以继续往下运行</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814094715395.png" alt="image-20220814094715395"></p></li><li><p>接着看到如下提示信息,大意是:“所以我们能做些什么呢?首先我要作弊并且告诉你我们拥有一个以'h'开头的预设密码,并且密码长度是5位,但是这是唯一的提示-你能用这些信息来干什么?”,然后便是让我们画一个发送'h'作为输入密码和另外一个值的能量消耗图</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814094852208.png" alt="image-20220814094852208"></p></li><li><p>根据提示,补全代码,运行即可看到,输入'h'和'r'作为密码时,微控制器的能量消耗波形</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></pre></td><td class="code"><pre><span class="line"><span class="comment">#Example - capture 'h' - end with newline '\n' as serial protocol expects that</span></span><br><span class="line">trace_h = cap_pass_trace(<span class="string">"h\n"</span>)</span><br><span class="line">trace_r = cap_pass_trace(<span class="string">"r\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(trace_h)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ###################</span></span><br><span class="line"><span class="comment"># Add your code here</span></span><br><span class="line"><span class="comment"># ###################</span></span><br><span class="line">%matplotlib notebook</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line">plt.plot(trace_h)</span><br><span class="line">plt.plot(trace_r)</span><br><span class="line"></span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814095717311.png" alt="image-20220814095717311"></p></li><li><p>往下有以下提示信息,意思就是说:应该看到与下图1类似的结果,并且如果是使用了<code>%matplotlib notebook</code>魔法值,那么可以放大缩小查看图片</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814095940428.png" alt="image-20220814095940428"></p></li><li><p>这里要放大缩小图片,需要点击左下角保存图标左边的一个控件,点击后,右下角出现'zoom rect'字样即可开始放大图片,如果还需要移动,则点击移动的图标即可。</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814100239285.png" alt="image-20220814100239285"></p></li><li><p>接着往下运行,可以看到以下提示信息,大意是:接下来画所有可能的密码对应的能量消耗图,这个密码的实现只在'abcdefghijklmnopqrstuvwxyz0123456789'之中。提示信息还提到上面的波形图太长了,不好看,可以将scope.adc.samples设为500</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814100703015.png" alt="image-20220814100703015"></p></li><li><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><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">%matplotlib notebook</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"></span><br><span class="line">scope.adc.samples = <span class="number">500</span></span><br><span class="line"></span><br><span class="line">plt.figure()</span><br><span class="line"></span><br><span class="line">LIST_OF_VALID_CHARACTERS=<span class="string">'abcdefghijklmnopqrstuvwxyz0123456789'</span></span><br><span class="line"><span class="keyword">for</span> CHARACTER <span class="keyword">in</span> LIST_OF_VALID_CHARACTERS:</span><br><span class="line"> trace = cap_pass_trace(CHARACTER + <span class="string">"\n"</span>)</span><br><span class="line"> plt.plot(trace, label=CHARACTER)</span><br><span class="line">plt.legend()</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814102041713.png" alt="image-20220814102041713"></p></li><li><p>往下可以看到提示信息说,图片放大后应该可以看到有一条曲线不与其他曲线重叠在一起</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814102326595.png" alt="image-20220814102326595"></p></li><li><p>事实也是如此,如下图所示,有一条曲线明显与其他曲线偏离开,这条曲线其实就意味着输入了一位正确密码,根据其颜色与旁边的图例可知改峰图可能为'h'或者'r'(这里图中两者颜色一样不好区分),但结合已知第一位是'h',所以可知实际代表的就是'h'</p><p> ![截屏2022-08-14 上午10.21.00](侧信道攻击学习笔记2-密码验证的能量分析/截屏2022-08-14 上午10.21.00.png)</p></li><li><p>接着可以看到如下提示信息,让我们进行猜测第二位密码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814103137749.png" alt="image-20220814103137749"></p></li><li><p>按提示信息修改密码后,再次画图,如下图,可以看到有一条粉丝曲线明显与其他曲线不重叠,但由于图例中'g'、'q'、'0'都是粉色,所以不能直接判断是哪个,这是matplotlib的缺陷</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将循环的trace = cap_pass_trace(CHARACTER + "\n")改为以下代码即可猜测第二位</span></span><br><span class="line">trace = cap_pass_trace(<span class="string">'h'</span>+CHARACTER + <span class="string">"\n"</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814103428749.png" alt="image-20220814103428749"></p></li><li><p>改为用plotly画图,plotly同样为python画图的库,而且对比matplotlib,plotly有个优点就是,当你鼠标悬浮在某个位置时,它会显示该点对应的曲线信息,这样一来就容易区分是哪个字符了。如下图,图中明显与其他曲线分离的是字符'0'所对应的曲线,因此,可以推断第二位密码为'0'</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"><span class="keyword">import</span> plotly.graph_objects <span class="keyword">as</span> go</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line">fig = go.Figure()</span><br><span class="line"></span><br><span class="line">LIST_OF_VALID_CHARACTERS=<span class="string">'abcdefghijklmnopqrstuvwxyz0123456789'</span></span><br><span class="line"><span class="keyword">for</span> CHARACTER <span class="keyword">in</span> LIST_OF_VALID_CHARACTERS:</span><br><span class="line"> trace = cap_pass_trace(<span class="string">'h'</span>+CHARACTER + <span class="string">"\n"</span>)</span><br><span class="line"> fig.add_trace(go.Scatter(x=np.array(<span class="built_in">range</span>(trace.shape[<span class="number">0</span>])), y=trace, mode=<span class="string">'lines'</span>, name=CHARACTER,text=CHARACTER))</span><br><span class="line">fig.show()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814105345076.png" alt="image-20220814105345076"></p></li></ul><h2 id="自动化判断"><a href="#自动化判断" class="headerlink" title="自动化判断"></a>自动化判断</h2><ul><li><p>再往下就可以看到如下信息,大意是:假定现在不知道任何密码,要自动化判断正确密码的一个简单方法是,用'0x00'即空密码和其他输入做比较</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814110009573.png" alt="image-20220814110009573"></p></li><li><p>按提示,运行后,如下图,图中的两条曲线基本上重叠在一起了,因为'c'同样不是正确密码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814111242308.png" alt="image-20220814111242308"></p></li><li><p>接下来的提示信息,就是让我们画出每个可能密码减去以'0x00'作为基线后的曲线图</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814111431278.png" alt="image-20220814111431278"></p></li><li><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><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">%matplotlib notebook</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"></span><br><span class="line">plt.figure()</span><br><span class="line"></span><br><span class="line">LIST_OF_VALID_CHARACTERS=<span class="string">'abcdefghijklmnopqrstuvwxyz0123456789'</span></span><br><span class="line">ref_trace = cap_pass_trace( <span class="string">"\x00\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> CHARACTER <span class="keyword">in</span> LIST_OF_VALID_CHARACTERS:</span><br><span class="line"> trace = cap_pass_trace(CHARACTER + <span class="string">"\n"</span>)</span><br><span class="line"> plt.plot(trace - ref_trace, label=CHARACTER)</span><br><span class="line">plt.legend()</span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814111650382.png" alt="image-20220814111650382"></p></li><li><p>可以看到上图跟下面提示信息一致,有一条图曲线比较突出</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814111746779.png" alt="image-20220814111746779"></p></li><li><p>接下来提示信息就是说:让我们利用numpy的sum和abs来计算差异</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814111914894.png" alt="image-20220814111914894"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814112046626.png" alt="image-20220814112046626"></p></li><li><p>按提示修改代码,计算每种可能密码的能量消耗数据减去'0x00'为基准的数据,最后求和得出差异值。如下图所示,字符'h'拥有最大的差异值,也就是说正确密码拥有最大的差异值</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"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">ref_trace = cap_pass_trace( <span class="string">"\x00\n"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> CHARACTER <span class="keyword">in</span> LIST_OF_VALID_CHARACTERS:</span><br><span class="line"> trace = cap_pass_trace(CHARACTER + <span class="string">"\n"</span>)</span><br><span class="line"> diff = np.<span class="built_in">sum</span>(np.<span class="built_in">abs</span>(trace - ref_trace))</span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"{:1} diff = {:2}"</span>.<span class="built_in">format</span>(CHARACTER, diff))</span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814112307120.png" alt="image-20220814112307120"></p></li><li><p>接下来的提示信息,就是让我们修改代码,自动化判断出每一位正确密码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814112437023.png" alt="image-20220814112437023"></p></li><li><p>如下图,按提示信息修改代码运行后,最终得出正确密码'h0px3',如果结果是'hhhhh'或者其他则需要检查代码是否有问题</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">LIST_OF_VALID_CHARACTERS=<span class="string">'abcdefghijklmnopqrstuvwxyz0123456789'</span></span><br><span class="line">guessed_pwd = <span class="string">""</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line"></span><br><span class="line"> ref_trace = cap_pass_trace(guessed_pwd + <span class="string">"\x00\n"</span>)</span><br><span class="line"> m=<span class="number">0</span></span><br><span class="line"> c=<span class="string">'\x00'</span></span><br><span class="line"> <span class="keyword">for</span> CHARACTER <span class="keyword">in</span> LIST_OF_VALID_CHARACTERS:</span><br><span class="line"> trace = cap_pass_trace(guessed_pwd + CHARACTER + <span class="string">'\x00\n'</span>)</span><br><span class="line"> diff = np.<span class="built_in">sum</span>(np.<span class="built_in">abs</span>(trace - ref_trace))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> diff > <span class="number">25</span>:</span><br><span class="line"></span><br><span class="line"> guessed_pwd += CHARACTER</span><br><span class="line"> <span class="built_in">print</span>(guessed_pwd)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">break</span></span><br></pre></td></tr></table></figure><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814112717402.png" alt="image-20220814112717402"></p></li><li><p>接下来看看目标板固件的源码实现,在hardware/victims/firmware/basic-passwdcheck目录下的basic-passwdcheck.c文件中,我们可以看到,main函数中定义的代码的确就是"h0px3"。从下面源码,可以推测,正确密码与错误密码会有较大的差异主要在于,正确的密码需要执行更多的循环语句</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814112913830.png" alt="image-20220814112913830"></p></li><li><p>那么问题来了:把错误密码的<code>break</code>语句注释掉,还能用这个方法判断正确密码吗?</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814113850610.png" alt="image-20220814113850610"></p></li><li><p>按上图中修改basic-passwdcheck.c代码,重新编译并上传至目标板</p></li><li><p>再次运行画图和计算差异的代码,可以看到已经不能判断出,哪个是正确代码了</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814114004783.png" alt="image-20220814114004783"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E7%9A%84%E8%83%BD%E9%87%8F%E5%88%86%E6%9E%90/image-20220814114031310.png" alt="image-20220814114031310"></p></li><li><p>所以如果要简单防止这种攻击,一个简单方法就是即使已经判断出密码错误,也让它跑完整个循环而不是跳出循环,虽然这会造成一定性能上的损失,但是这是个简单有效的方法</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>学习了如何利用能量分析来判断出简单密码验证系统中的正确密码</li><li>学习了如何简单地防范这种能量分析攻击</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
</tags>
</entry>
<entry>
<title>侧信道攻击学习笔记1</title>
<link href="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/"/>
<url>/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/</url>
<content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><ul><li>最近入手了块Chipwhisperer-lite,决定先跟着官方教程学习了解侧信道攻击</li><li>New-AE官网:<a href="https://www.newae.com/">https://www.newae.com/</a></li><li>Chipwhisperer官方GitHub仓库:<a href="https://github.com/newaetech/chipwhisperer">https://github.com/newaetech/chipwhisperer</a></li><li>Chipwhisperer软件环境官方安装教程:<a href="https://chipwhisperer.readthedocs.io/en/latest/index.html">https://chipwhisperer.readthedocs.io/en/latest/index.html</a></li></ul><span id="more"></span><h2 id="指令能耗差异"><a href="#指令能耗差异" class="headerlink" title="指令能耗差异"></a>指令能耗差异</h2><ul><li><p>本篇将跟着官方的<a href="https://github.com/newaetech/chipwhisperer-jupyter">chipwhisperer-jupyter</a>教程sca101的**Lab 2_1A - Instruction Power Differences **学习CPU运行不同指令时消耗的能量差异</p></li><li><p>首先可以看到sca101目录下有三个Lab 2_1A - Instruction Power Differences 的.ipynb文件,其实从后缀就可以看出,第二个是主程序的代码,第一个对应的是拥有Chipwhisperer硬件时可运行的代码,第三个是没有硬件时模拟运行的代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813175523509.png" alt="image-20220813175523509"></p></li><li><p>打开MAIN的ipynb,可以看到,最前面有提示选择模拟的代码块或者硬件的代码块复制到这个notebook代码中</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813175941844.png" alt="image-20220813175941844"></p></li><li><p>打开HARDWARE的ipynb,可以看到最前面有以下提示,意思是:需要根据你的硬件设备先设置相应平台类型,CWLite和CW1200的SCOPETYPE选择'OPENADC',CWNANO则选择'CWNANO';如果目标板为STM32(如:CWLITEARM的目标板就是这个)PLATFORM选择'CWLITE',如果目标板为XMEGA(如:CWLITEXMEGA的目标板就是这个)则选择'CWLITEXMEGA'</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813180223510.png" alt="image-20220813180223510"></p></li><li><p>这里我的硬件是CWLITEXMEGA所以选择</p> <figure class="highlight python"><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">SCOPETYPE = <span class="string">'OPENADC'</span></span><br><span class="line">PLATFORM = <span class="string">'CWLITEXMEGA'</span></span><br></pre></td></tr></table></figure></li><li><p>设置好后,把所有HARDWARE的ipynb的代码复制到MAIN的ipynb中执行</p></li><li><p>编译目标板固件,上面的设置就是为了编译输出合适的目标板固件</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190104862.png" alt="image-20220813190104862"></p></li><li><p>用USB连接设备,继续往下运行,正常运行会输出Found ChipWhisperer</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190238161.png" alt="image-20220813190238161"></p></li><li><p>给目标板烧录新编译的固件</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190406861.png" alt="image-20220813190406861"></p></li><li><p>再往下就是MAIN ipynb的代码了</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190456892.png" alt="image-20220813190456892"></p></li><li><p>接着可以看到如下提示,意思是,这里只需要关注单个指令的能量消耗,而不关注串口通信的能量消耗,所以需要去'hardware/victims/firmware/simpleserial-base-lab2'目录下修改simpleserial-base.c的代码,找到get_pt函数,删除或者注释调末尾的simpleserial_put调用语句,保存后重新编译并上传至目标板</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190633871.png" alt="image-20220813190633871"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813190913530.png" alt="image-20220813190913530"></p></li><li><p>编译,只需重新执行前面make命令的代码块,即可</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">%%bash -s "$PLATFORM"</span><br><span class="line">cd ../../../hardware/victims/firmware/simpleserial-base-lab2</span><br><span class="line">make PLATFORM=$1 CRYPTO_TARGET=NONE</span><br></pre></td></tr></table></figure></li><li><p>然后再执行前面的给目标板烧录固件的代码即可</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cw.program_target(scope, prog, <span class="string">"../../../hardware/victims/firmware/simpleserial-base-lab2/simpleserial-base-{}.hex"</span>.<span class="built_in">format</span>(PLATFORM))</span><br></pre></td></tr></table></figure></li><li><p>接着往下执行,可以查看目标板的能量消耗曲线</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813200838496.png" alt="image-20220813200838496"></p></li><li><p>接着往下可以看到如下提示,大意是修改simpleserial-base.c中的代码,让CPU重复执行单一命令20次,看能否从能量消耗曲线中看出该命令所对应的位置,其中需要用"volatile"关键字修饰变量,避免编译器对该代码进行优化(将这些指令直接替换为这些运算的最终结果)。对于不同的目标板,建议选则的指令不一样,如:ARM(STM32)的目标板建议测试乘法运算,XMEGA和AVR则建议测试加法运算(因为这类设备乘法运算消耗更大)</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813201148039.png" alt="image-20220813201148039"></p></li><li><p>按照上面提示在simpleserial-base.c的get_pt函数中添加测试的加法代码</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813202109830.png" alt="image-20220813202109830"></p></li><li><p>重新编译并烧录进目标板后,再次画能量消耗图,可以看到如下结果</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813202453123.png" alt="image-20220813202453123"></p></li><li><p>对比上图和一开始的图片,可以看到,上面前面部分明显出现20个尖峰,结合代码可知,这20个尖峰其实就是目标板运算20次A+=2所对应的能量消耗</p></li><li><p>再往下可以看到如下提示,大意是,“你可能觉得很奇怪,为什么我们不用一个循环而是复制粘贴一行代码20次。将这个替换为循环你就知道为什么了,<strong>需要注意的是循环的变量同样要用volatile修饰</strong>”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813202903264.png" alt="image-20220813202903264"></p></li><li><p>按照上述提示,将该代码改写为for 循环并用volatile修饰循环计数 i,保存并重新编译上传至目标板</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813203442632.png" alt="image-20220813203442632"></p></li><li><p>再次画出能量消耗图,如下图,可以看到,虽然同样出现20个周期性的峰型,但是对比复制粘贴执行的能量消耗,下图耗时更久,波形更复杂些</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813203554149.png" alt="image-20220813203554149"></p></li><li><p>往下可以看到提示,大意是“你可能觉得很奇怪,为什么循环执行更复制粘贴执行的能量消耗结果不一样?如果你熟悉汇编,你可以去查看simpleserial-base-lab2文件夹下的.lss(编译输出的中间文件)。或者看下面这个C伪代码。正如你所看到的,微控制器比起执行重复粘贴的代码,循环需要做更多的事情(比较大小,循环体,循环计数+1)。如果i的变量不用volatile修饰,那么通常会将循环展开(即将循环优化为复制粘贴的代码),有volatile可以避免编译器进行这项优化。当然展开循环会导致代码块大小增大,所以有时编译器为了优化代码块大小而避免展开循环”</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813203853260.png" alt="image-20220813203853260"></p></li><li><p>再往下可以看到如下提示信息,大意是让我们测试高消耗的运算,对于ARM建议测试除法运算,对于AVR/XMEGA则建议测试乘法运算</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813213322870.png" alt="image-20220813213322870"></p></li><li><p>但是按照提示将+=2改为*=2后,重新编译并上传目标板后,抓取的能量消耗图与前面相比却并没有太大区别</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813213516138.png" alt="image-20220813213516138"></p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813213643346.png" alt="image-20220813213643346"></p></li><li><p>按下面的提示,可以知道,改为复杂运算后,应该会花更多的时间运算而且消耗更多的能量,但是上面*=2的与前面+=2却并没有太大区别</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813213827759.png" alt="image-20220813213827759"></p></li><li><p>不过想到编译器可能对2的倍数的乘法运算有优化,将乘法优化为移位运算了(如*2优化为<<1),于是把2改为3,重新编译测试,结果一出来,果然乘法消耗明显更大</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813214350672.png" alt="image-20220813214350672"></p></li><li><p>再来试试除法,根据提示的意思是XMEGA中的除法开销会巨大,如下图,下图这其实只是1次除法运算的能量消耗图而不是20次,如此可见XMEGA中的除法消耗的确非常大。同样从单次除法运算中出现多个尖峰可以推测,可以推测XMEGA的除法是循环运算一些简单指令得到的</p><p> <img src="/note/2022/08/%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20220813214930241.png" alt="image-20220813214930241"></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>CPU/微控制器执行不同指令会有不同能量消耗和时间消耗</li><li>编译器可能会在编译时对一些代码做一些优化,如:把简单的运算之间替换为运算结果,循环展开等,所以有时候测试某些运算/指令的能量消耗时,需要特别留意最终的可执行程序是否被编译器优化过的</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 侧信道攻击 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记14</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第二十五章"><a href="#逆向工程核心原理第二十五章" class="headerlink" title="逆向工程核心原理第二十五章"></a>逆向工程核心原理第二十五章</h3><h4 id="通过修改PE文件加载DLL"><a href="#通过修改PE文件加载DLL" class="headerlink" title="通过修改PE文件加载DLL"></a>通过修改PE文件加载DLL</h4><h5 id="练习文件"><a href="#练习文件" class="headerlink" title="练习文件"></a>练习文件</h5><ul><li>从<a href="https://github.com/reversecore/book">https://github.com/reversecore/book</a>下载对应的章节的可执行文件TextView.exe和myhack3.dll,当然也可以选择下载两者的源码,用vs重新编译,这里直接下载可执行文件进行练习</li></ul><span id="more"></span><h5 id="TextView-exe"><a href="#TextView-exe" class="headerlink" title="TextView.exe"></a>TextView.exe</h5><ul><li><p>使用PEView工具查看TextView.exe可执行文件的IDT(Import Directory Table,导入目录表),如下图,可以看到TextView.exe直接导入的DLL文件为KERNEL32.dll、USER32.dll、GDI32.dll、SHELL32.dll</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20211223165038607.png" alt="image-20211223165038607"></p></li><li><p>修改思路:如上图所示,PE文件导入的DLL信息以结构体列表的形式存在IDT中。只要将myhack3.dll添加到列表尾部就可以了。当然在此之前需要确认一下IDT中有无足够空间。</p></li><li><p>查看IDT是否有足够空间:首先使用PEView查看TextView.exe的IDT地址(PE文件头的IMAGE_OPTIONAL_HEADER结构体中导入表RVA即为IDT的RVA),可以看到IDT的地址(RVA)为84CC,大小为64h</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20211224100929941.png" alt="image-20211224100929941"></p></li><li><p>接下来在PEView中切换RVA为基址视图,查看RVA 84CC的IDT,如下图,可以看到,TextView.exe存在于.rdata节区,</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20211224104149591.png" alt="image-20211224104149591"></p></li><li><p>在PEView工具栏中将视图改为File Offset,可以看到IDT的文件偏移为76CC</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220117234418434.png" alt="image-20220117234418434"></p></li><li><p>用HEdit找到76CC地址处,如下图所示,IDT的文件偏移为76CC~772F,整个大小为64字节,共有5个结构体,其中最后一个为NULL结构体。IDT尾部存在其他数据,没有足够空间来添加myhack3.dll的IID结构体</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118001933169.png" alt="image-20220118001933169"></p></li></ul><h5 id="移动IDT"><a href="#移动IDT" class="headerlink" title="移动IDT"></a>移动IDT</h5><ul><li><p>这种情况,我们可以把整个IDT转移到其他更广阔的位置,然后再添加新的IID。确定移动的目标位置时,可以使用下面的三种方式:</p><ul><li>查找文件中的空白区域</li><li>增加文件最后一个节区的大小</li><li>在文件末尾添加新的节区</li></ul></li><li><p>首先尝试第一种方法,即查找文件中的空白区域。如下图所示,可以看到.rdata节区尾部恰好存在大片空白区域(一般来说,节区或文件末尾都存在空白区域,PE文件中这种空白区域称为Null-Padding区域)</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118002541976.png" alt="image-20220118002541976"></p></li><li><p>接下来只要把原IDT移动到该Null-Padding区域(RVA:8C60~8DFF)中合适位置即可。在此之前,先要确认一下该区域(RVA:8C60~8DFF)是否全是空白可用区域(Null-Padding)。如下图所示:从节区头信息中可知,.rdata在磁盘文件与内存中的大小是不同的,.rdata在磁盘文件中的大小为2E00,而文件执行后加载到内存时,程序实际使用的数据大小仅为2C56,剩余未被使用的区域大小为1AA(2E00-2C56)。在这段空白区域创建IDT不会有什么问题。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118153343389.png" alt="image-20220118153343389"></p></li><li><p>接下来,我们将在RVA:8C60(RAW: 7E60)位置创建新的IDT</p></li></ul><h4 id="修改TextView-exe"><a href="#修改TextView-exe" class="headerlink" title="修改TextView.exe"></a>修改TextView.exe</h4><ul><li><p>修改导入表的RVA值,IMAGE_OPTIONAL_HEADER的导入表结构体成员用来指出IDT的位置(RVA)与大小,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118160616195.png" alt="image-20220118160616195"></p></li><li><p>使用HEdit将原RVA:84CC改为新的RVA:8C60,并将Size(IDT结构体大小)的64改为78</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118161019282.png" alt="image-20220118161019282"></p></li></ul><h5 id="删除绑定导入表"><a href="#删除绑定导入表" class="headerlink" title="删除绑定导入表"></a>删除绑定导入表</h5><ul><li><p>BOUND IMPORT TABLE(绑定导入表)是一种提高DLL加载速度的技术,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118161414168.png" alt="image-20220118161414168"></p></li><li><p>若想要正常导入myhack3.dll,需要向绑定导入表添加信息。但这个绑定导入表是个可选项,不是必须存在的,所以可删除(修改其值为0即可),而且绑定导入表完全不存在也没关系,但若存在,且其信息记录错误,则会在程序运行时引发错误。</p></li></ul><h5 id="创建新的IDT"><a href="#创建新的IDT" class="headerlink" title="创建新的IDT"></a>创建新的IDT</h5><ul><li><p>先使用HEdit复制原IDT(RVW: 76CC~772F),然后粘贴到IDT的新位置(RAW:7E80),如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118162149707.png" alt="image-20220118162149707"></p></li><li><p>在新IDT尾部(RAW:7EB0)添加与myhack3.dll对应的IID</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118163832853.png" alt="image-20220118163832853"></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="keyword">struct</span> <span class="title class_">_IMAGE_IMPORT_DESCRIPTOR</span> {</span><br><span class="line"> <span class="keyword">union</span> {</span><br><span class="line"> DWORD Characteristics; <span class="comment">// </span></span><br><span class="line"> DWORD OriginalFirstThunk; <span class="comment">// 00008D00 => RVA to INT</span></span><br><span class="line"> };</span><br><span class="line"> DWORD TimeDateStamp; <span class="comment">// 0</span></span><br><span class="line"></span><br><span class="line"> DWORD ForwarderChain; <span class="comment">// 0</span></span><br><span class="line"> DWORD Name; <span class="comment">// 00008D10 => RVA,指向dll名字,该名字以0结尾</span></span><br><span class="line"> DWORD FirstThunk; <span class="comment">// 00008D20 => RVA,指向IMAGE_THUNK_DATA结构数组</span></span><br><span class="line">} IMAGE_IMPORT_DESCRIPTOR;</span><br></pre></td></tr></table></figure></li></ul><h5 id="设置Name、INT、IAT"><a href="#设置Name、INT、IAT" class="headerlink" title="设置Name、INT、IAT"></a>设置Name、INT、IAT</h5><ul><li><p>前面添加的IID结构体成员拥有指向其他数据结构(INT、Name、IAT)的RVA值。因此,必须准确设置这些数据才能保证修改后的TextView.exe文件正常运行。由前面设置可知INT、Name、IAT的RVA/RAW的值,整理如下表所示</p><table><thead><tr><th align="center"></th><th align="center">RVA</th><th align="center">RAW</th></tr></thead><tbody><tr><td align="center">INT</td><td align="center">8D00</td><td align="center">7F00</td></tr><tr><td align="center">Name</td><td align="center">8D10</td><td align="center">7F10</td></tr><tr><td align="center">IAT</td><td align="center">8D20</td><td align="center">7F20</td></tr></tbody></table></li><li><p>这些地址(RVA:8D00,8D10,8D20)就位于新的IDT下方(这里这些RVA可以选择在其他位置)。如下图,用HEdit在7F00、7F10、7F20、7F30输入相应的值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118172035434.png" alt="image-20220118172035434"></p></li><li><p>用PEView打开查看该区域,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118172249101.png" alt="image-20220118172249101"></p></li><li><p>下面讲解显示的各值意义<img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/2022-01-18.5.23.56.png" alt="2022-01-18.5.23.56"></p></li><li><p>8CB0地址处存在着myhack3.dll的IID结构体,其中3个主要成员(RVA of INT、RVA of Name、RVA of IAT)的值分别是实际INT、Name、IAT的指针</p></li><li><p>INT(Import Name Table,导入名称表)是RVA数组,数组的各个元素都是一个RVA地址,该地址由导入函数的Ordinal(2个字节)+ 函数名结构体组成,数组的末尾为NULL。上图中有一个元素,其值为8D30,该地址处是要导入的函数的Ordinal(2个字节)与函数名称字符串("dummy")</p></li><li><p>Name是包含导入函数的DLL文件名称字符串,在8D10地址处可以看到"myhack3.dll"字符串</p></li><li><p>IAT也是RVA数组,各元素既可以拥有与INT相同的值,也可以拥有其他不同值(若INT数据准确,IAT也可以拥有其他不同值)。实际运行时,PE装载器会将虚拟内存中的IAT替换为实际函数的地址。</p></li></ul><h5 id="修改IAT节区的属性值"><a href="#修改IAT节区的属性值" class="headerlink" title="修改IAT节区的属性值"></a>修改IAT节区的属性值</h5><ul><li><p>加载PE文件到内存时,PE装载器会修改IAT,写入函数的实际地址,所以相关节区一定要拥有WRITE(可写)属性。只有这样,PE装载器才能正常进行写入操作。使用PE View查看.rdata节区头,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118174350752.png" alt="image-20220118174350752"></p></li><li><p>向原属性值(Characteristics)40000040添加IMAGE_SCN_MEM_WRITE(80000000)属性值。执行bit OR运算,最终属性值变为C0000040。如下图所示,使用HEdit修改该处的值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118174728225.png" alt="image-20220118174728225"></p></li><li><p>至此完成了所有修改,打开TextView.exe可以看到TextView.exe成功加载myhack3.dll,myhack3.dll被加载后会自动下载指定网站的index.html,然后由TextView.exe显示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B014/image-20220118174933883.png" alt="image-20220118174933883"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记13</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第二十三章"><a href="#逆向工程核心原理第二十三章" class="headerlink" title="逆向工程核心原理第二十三章"></a>逆向工程核心原理第二十三章</h3><h4 id="DLL注入"><a href="#DLL注入" class="headerlink" title="DLL注入"></a>DLL注入</h4><ul><li><p>DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载用户指定的DLL文件。DLL注入与一般DLL加载的区别在于,加载的目标进程是其自身或其他进程。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211211141246744.png" alt="image-20211211141246744"></p></li></ul><span id="more"></span><ul><li><p>从上图可以看到,myhack.dll已被强制插入notepad.exe进程(本来notepad.exe并不会加载myhack.dll)。加载到notepad.exe进程中的myhack.dll与已经被加载到notepad.exe进程中的DLL(kernel32.dll、user32.dll)一样,拥有访问notepad.exe进程内存的(正当的)权限,这样用户就可以做任何想做的事了(添加一些新功能,或者修复bug等)</p></li><li><p>DLL(Dynamic Linked Library,动态链接库):DLL被加载到进程后会自动运行DllMain()函数,用户可以把想执行的代码放到DllMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。利用该特性可修复程序Bug,或向程序添加新功能。</p></li></ul><h5 id="DLL注入的实现方法"><a href="#DLL注入的实现方法" class="headerlink" title="DLL注入的实现方法"></a>DLL注入的实现方法</h5><ul><li>向某个进程注入DLL时主要使用以下三种方法<ul><li>创建远程线程(CreateRemoteThread() API)</li><li>使用注册表(AppInit_DLLs值)</li><li>消息钩取(SetWindowsHookEx() API)</li></ul></li></ul><h5 id="CreateRemoteThread"><a href="#CreateRemoteThread" class="headerlink" title="CreateRemoteThread()"></a>CreateRemoteThread()</h5><ul><li><p>因为这里用的是64位win10,所以作者提供的32位InjectDll.exe和myhack.dll文件不能生效。需要使用作者提供的源码编译成64位的PE文件</p></li><li><p>把重新编译生成的InjectDll.exe和myhack.dll放在一个文件夹下,以管理员身份运行cmd(命令行)</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212110926185.png" alt="image-20211212110926185"></p></li><li><p>运行notepad.exe,然后使用Process Explorer获取notepad.exe的进程的PID值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212111007361.png" alt="image-20211212111007361"></p></li><li><p>运行<a href="https://docs.microsoft.com/zh-cn/sysinternals/downloads/debugview">DebugView</a></p></li><li><p>命令行中运行一下命令注入myhack.dll,注意myhack.dll的路径必须位绝对路径,其中第一个参数6940即位notepad.exe此时的pid值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212111403845.png" alt="image-20211212111403845"></p></li><li><p>查看DebugView,可以看到myhack.dll的日志输出</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212111508904.png" alt="image-20211212111508904"></p></li><li><p>myhack.dll所在路径也静默下载了指定网站的index.html</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212111604749.png" alt="image-20211212111604749"></p></li><li><p>Process View查看notepad.exe的DLL,可以看到,myhack.dll已经被加载到notepad.exe进程中</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211212111715978.png" alt="image-20211212111715978"></p></li></ul><h5 id="分析示例源代码"><a href="#分析示例源代码" class="headerlink" title="分析示例源代码"></a>分析示例源代码</h5><ul><li><p>先分析一下myhack.dll源代码(myhack.cpp)</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tchar.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> comment(lib, <span class="string">"urlmon.lib"</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_URL (<span class="string">L"http://www.naver.com/index.html"</span>)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_FILE_NAME (<span class="string">L"index.html"</span>)</span></span><br><span class="line"></span><br><span class="line">HMODULE g_hMod = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">DWORD WINAPI <span class="title">ThreadProc</span><span class="params">(LPVOID lParam)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TCHAR szPath[_MAX_PATH] = {<span class="number">0</span>,};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">GetModuleFileName</span>( g_hMod, szPath, MAX_PATH ) )</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"></span><br><span class="line"> TCHAR *p = _tcsrchr( szPath, <span class="string">'\\'</span> );</span><br><span class="line"> <span class="keyword">if</span>( !p )</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"></span><br><span class="line"> _tcscpy_s(p+<span class="number">1</span>, _MAX_PATH, DEF_FILE_NAME);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">URLDownloadToFile</span>(<span class="literal">NULL</span>, DEF_URL, szPath, <span class="number">0</span>, <span class="literal">NULL</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><span class="line"></span><br><span class="line"><span class="function">BOOL WINAPI <span class="title">DllMain</span><span class="params">(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> HANDLE hThread = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"> g_hMod = (HMODULE)hinstDLL;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span>( fdwReason )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> DLL_PROCESS_ATTACH : </span><br><span class="line"> <span class="built_in">OutputDebugString</span>(<span class="string">L"<myhack.dll> Injection!!!"</span>);</span><br><span class="line"> hThread = <span class="built_in">CreateThread</span>(<span class="literal">NULL</span>, <span class="number">0</span>, ThreadProc, <span class="literal">NULL</span>, <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="built_in">CloseHandle</span>(hThread);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</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></li><li><p>在DllMain()函数中可以看到,该DLL被加载(DLL_PROCESS_ATTACH)时,先输入一个调试字符串("<myhack.dll> Injection!!!"),然后创建线程调用函数(ThreadProc)。在ThreadProc()函数中通过调用urlmon!URLDownloadToFile() API来下载指定网站的index.html文件。向进程注入DLL后就会自动调用执行该DLL的DLLMain()函数。所以当my hack.dll注入notepad.exe进程后,最终会调用执行URLDownloadToFile() API</p></li><li><p>接下来查看InjectDll.exe的源码InjectDll.cpp</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tchar.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">BOOL <span class="title">SetPrivilege</span><span class="params">(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)</span> </span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TOKEN_PRIVILEGES tp;</span><br><span class="line"> HANDLE hToken;</span><br><span class="line"> LUID luid;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">OpenProcessToken</span>(<span class="built_in">GetCurrentProcess</span>(),</span><br><span class="line"> TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, </span><br><span class="line"> &hToken) )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"OpenProcessToken error: %u\n"</span>, <span class="built_in">GetLastError</span>());</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">LookupPrivilegeValue</span>(<span class="literal">NULL</span>, <span class="comment">// lookup privilege on local system</span></span><br><span class="line"> lpszPrivilege, <span class="comment">// privilege to lookup </span></span><br><span class="line"> &luid) ) <span class="comment">// receives LUID of privilege</span></span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"LookupPrivilegeValue error: %u\n"</span>, <span class="built_in">GetLastError</span>() ); </span><br><span class="line"> <span class="keyword">return</span> FALSE; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> tp.PrivilegeCount = <span class="number">1</span>;</span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Luid = luid;</span><br><span class="line"> <span class="keyword">if</span>( bEnablePrivilege )</span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Attributes = SE_PRIVILEGE_ENABLED;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Attributes = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Enable the privilege or disable all privileges.</span></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">AdjustTokenPrivileges</span>(hToken, </span><br><span class="line"> FALSE, </span><br><span class="line"> &tp, </span><br><span class="line"> <span class="built_in">sizeof</span>(TOKEN_PRIVILEGES), </span><br><span class="line"> (PTOKEN_PRIVILEGES) <span class="literal">NULL</span>, </span><br><span class="line"> (PDWORD) <span class="literal">NULL</span>) )</span><br><span class="line"> { </span><br><span class="line"> _tprintf(<span class="string">L"AdjustTokenPrivileges error: %u\n"</span>, <span class="built_in">GetLastError</span>() ); </span><br><span class="line"> <span class="keyword">return</span> FALSE; </span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">GetLastError</span>() == ERROR_NOT_ALL_ASSIGNED )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"The token does not have the specified privilege. \n"</span>);</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> } </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><br><span class="line"><span class="function">BOOL <span class="title">InjectDll</span><span class="params">(DWORD dwPID, LPCTSTR szDllPath)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> HANDLE hProcess = <span class="literal">NULL</span>, hThread = <span class="literal">NULL</span>;</span><br><span class="line"> HMODULE hMod = <span class="literal">NULL</span>;</span><br><span class="line"> LPVOID pRemoteBuf = <span class="literal">NULL</span>;</span><br><span class="line"> DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + <span class="number">1</span>) * <span class="built_in">sizeof</span>(TCHAR);</span><br><span class="line"> LPTHREAD_START_ROUTINE pThreadProc;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// #1.使用dwPID获取目标进程(notepad.exe)句柄</span></span><br><span class="line"> <span class="keyword">if</span> ( !(hProcess = <span class="built_in">OpenProcess</span>(PROCESS_ALL_ACCESS, FALSE, dwPID)) )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"OpenProcess(%d) failed!!! [%d]\n"</span>, dwPID, <span class="built_in">GetLastError</span>());</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// #2. 在目标程序(notepad.exe)内存中分配szDllName大小的内存</span></span><br><span class="line"> pRemoteBuf = <span class="built_in">VirtualAllocEx</span>(hProcess, <span class="literal">NULL</span>, dwBufSize, MEM_COMMIT, PAGE_READWRITE);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// #3. 将myhack.dll路径写入分配的内存</span></span><br><span class="line"> <span class="built_in">WriteProcessMemory</span>(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// #4. 获取LoadLibraryW() API的地址</span></span><br><span class="line"> hMod = <span class="built_in">GetModuleHandle</span>(<span class="string">L"kernel32.dll"</span>);</span><br><span class="line"> pThreadProc = (LPTHREAD_START_ROUTINE)<span class="built_in">GetProcAddress</span>(hMod, <span class="string">"LoadLibraryW"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// #5. 在notepad.exe进程中运行线程</span></span><br><span class="line"> hThread = <span class="built_in">CreateRemoteThread</span>(hProcess, <span class="literal">NULL</span>, <span class="number">0</span>, pThreadProc, pRemoteBuf, <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="built_in">WaitForSingleObject</span>(hThread, INFINITE);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">CloseHandle</span>(hThread);</span><br><span class="line"> <span class="built_in">CloseHandle</span>(hProcess);</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><br><span class="line"><span class="type">int</span> _tmain(<span class="type">int</span> argc, TCHAR *argv[])</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span>( argc != <span class="number">3</span>)</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"USAGE : %s <pid> <dll_path>\n"</span>, argv[<span class="number">0</span>]);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// change privilege</span></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">SetPrivilege</span>(SE_DEBUG_NAME, TRUE) )</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// inject dll</span></span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">InjectDll</span>((DWORD)_tstol(argv[<span class="number">1</span>]), argv[<span class="number">2</span>]) )</span><br><span class="line"> _tprintf(<span class="string">L"InjectDll(\"%s\") success!!!\n"</span>, argv[<span class="number">2</span>]);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> _tprintf(<span class="string">L"InjectDll(\"%s\") failed!!!\n"</span>, argv[<span class="number">2</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></li><li><p>main()函数的主要功能是检查输入程序的参数,然后先调用SetPrivilege()函数获取权限,再调用InjectDll()函数。InjectDll()函数是用来实施DLL注入的核心函数,其功能是命令目标进程自行调用LoadLibrary("myhack.dll") API。下面分析InjectDll()函数</p><ul><li><p><code>hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)</code>,获取目标进程句柄,调用OpenProcess() API,借助程序运行时以参数形式传递过来的dwPID值,获取notepad.exe进程的句柄(PROCESS_ALL_ACCESS权限)。得到PROCESS_ALL_ACCESS权限后,就可以使用获取的句柄(hProcess)控制对应的进程(notepad.exe)</p></li><li><p><code>pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);</code>,将要注入的DLL路径写入目标进程内存。需要把即将加载的DLL文件的路径(字符串)告知目标进程(notepad.exe)。因为任何内存空间都无法进行写入操作,故先使用VirtualAllocEx() API在目标进程(notepad.exe)的内存空间中分配一块缓冲区,且指定该缓冲区的大小为DLL文件路径字符串的长度(含Terminating NULL即'\0')即可</p></li><li><p><code>WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL)</code>,使用WriteProcessMemory() API将DLL路径字符串("C:\xxxx\myhack.dll")写入分配所得缓冲区(pRemoteBuf)地址。WriteProcessMemory() API所写的内存空间也是hProcess句柄所指的目标进程(notepad.exe)的内存空间。这样,要注入的DLL文件的路径就被写入目标进程(notepad.exe)的内存空间</p></li><li><pre><code class="c++"> /** * 获取LoadLibraryW() API地址,调用LoadLibrary() API前先要获取其地址 *(LoadLibraryW()是LoadLibrary()的Unicode字符串版本) * 这里需要注意的是,我们的目标是获取加载到notepad.exe进程的kernel32.dll的LoadLibraryW() API * 的起始地址 * 但下面的代码切是用来获取加载到InjectDll.exe进程的kernel32.dll的LoadLibraryW()的起始地址 * 之所以可以这样是因为,在Windows系统中,kernel32.dll在每个进程中的加载地址都是相同的 */ hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); <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">- `hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);`,在目标进程中运行远程线程(Remote Thread)。pThreadProc = notepad.exe进程内存中的LoadLibraryW() 地址,pRemoteBuf = notepad.exe 进程内存中的"C:\xxxx\myhack.dll"字符串地址。一切准备就绪后,最后向notepad.exe发送一个命令,让其调用LoadLibraryW() API函数加载指定的DLL即可,但是Windows并未直接提供执行这一命令的API。不过,可以另辟蹊径,使用CreateRemoteThread()这个API(在DLL注入是几乎总会用到)</span><br><span class="line"></span><br><span class="line">- CreateRemoteThread() API函数原型如下</span><br><span class="line"></span><br><span class="line"> ```c++</span><br><span class="line"> HANDLE CreateRemoteThread(</span><br><span class="line"> [in] HANDLE hProcess, // 目标进程</span><br><span class="line"> [in] LPSECURITY_ATTRIBUTES lpThreadAttributes,</span><br><span class="line"> [in] SIZE_T dwStackSize,</span><br><span class="line"> [in] LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数地址</span><br><span class="line"> [in] LPVOID lpParameter,// 线程参数地址</span><br><span class="line"> [in] DWORD dwCreationFlags,</span><br><span class="line"> [out] LPDWORD lpThreadId</span><br><span class="line"> );</span><br></pre></td></tr></table></figure></code></pre></li><li><p>除了第一个参数hProcess外,其他参数与CreateThread()函数完全一样。hProcess参数是要执行目标线程的目标进程的句柄。lpStartAddress与lpParameter参数分别给出线程函数地址与线程参数地址。需要注意的是,这两个地址都应该在目标进程虚拟内存空间中</p></li><li><p>这里可以用LoadLibrary作为第四个参数的原因是LoadLibrary函数原型与线程函数原型一样,两者都接受一个4字节参数并返回一个4字节的值,LoadLibrary和ThreadProc函数比较如下</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">DWORD WINAPI <span class="title">ThreadProc</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">[in]LPVOIDlpParameter</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="function">DWORD WINAPI <span class="title">LoadLibrary</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">[in]LPCTSTRlpFileName</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span></span><br></pre></td></tr></table></figure></li><li><p>CreateRemoteThread()函数的最主要功能就是驱使目标进程调用LoadLibrary()函数,进而加载指定的DLL文件</p></li></ul></li></ul><h5 id="AppInit-DLLs"><a href="#AppInit-DLLs" class="headerlink" title="AppInit_DLLs"></a>AppInit_DLLs</h5><ul><li><p>进行DLL注入的第二种方法是使用注册表。Windows操作系统的注册表中默认提供了AppInit_DLLs与LoadAppInit_DLLs两个注册表项,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211216155611730.png" alt="image-20211216155611730"></p></li><li><p>在注册表编辑器中,将要注入的DLL的路径字符串写入AppInit_DLLs项目,然后把LoadAppInit_DLLs的项目值设置为1。重启后,指定DLL会注入所有运行进程。该方法操作非常简单,但功能非常强大。</p></li><li><p>同上编译生成x64的myhack2.dll,然后修改注册表AppInit_DLLs和LoadAppInit_DLLs的值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211216162458027.png" alt="image-20211216162458027"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211216162516337.png" alt="image-20211216162516337"></p></li><li><p>保存后,重启电脑,用Process Explorer查找加载了myhack2.dll的进程,可以看到,myhack2.dll成功被注入进程</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211216162700276.png" alt="image-20211216162700276"></p></li><li><p>接下来分析myhack2.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><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="comment">// myhack2.cpp</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tchar.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_CMD <span class="string">L"c:\\Program Files\\Internet Explorer\\iexplore.exe"</span> </span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_ADDR <span class="string">L"http://www.naver.com"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_DST_PROC <span class="string">L"notepad.exe"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">BOOL WINAPI <span class="title">DllMain</span><span class="params">(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TCHAR szCmd[MAX_PATH] = {<span class="number">0</span>,};</span><br><span class="line"> TCHAR szPath[MAX_PATH] = {<span class="number">0</span>,};</span><br><span class="line"> TCHAR *p = <span class="literal">NULL</span>;</span><br><span class="line"> STARTUPINFO si = {<span class="number">0</span>,};</span><br><span class="line"> PROCESS_INFORMATION pi = {<span class="number">0</span>,};</span><br><span class="line"></span><br><span class="line"> si.cb = <span class="built_in">sizeof</span>(STARTUPINFO);</span><br><span class="line"> si.dwFlags = STARTF_USESHOWWINDOW;</span><br><span class="line"> si.wShowWindow = SW_HIDE;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span>( fdwReason )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> DLL_PROCESS_ATTACH : </span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">GetModuleFileName</span>( <span class="literal">NULL</span>, szPath, MAX_PATH ) )</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span>( !(p = _tcsrchr(szPath, <span class="string">'\\'</span>)) )</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( _tcsicmp(p+<span class="number">1</span>, DEF_DST_PROC) )</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">wsprintf</span>(szCmd, <span class="string">L"%s %s"</span>, DEF_CMD, DEF_ADDR);</span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">CreateProcess</span>(<span class="literal">NULL</span>, (LPTSTR)(LPCTSTR)szCmd, </span><br><span class="line"> <span class="literal">NULL</span>, <span class="literal">NULL</span>, FALSE, </span><br><span class="line"> NORMAL_PRIORITY_CLASS, </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">break</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( pi.hProcess != <span class="literal">NULL</span> )</span><br><span class="line"> <span class="built_in">CloseHandle</span>(pi.hProcess);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</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><br></pre></td></tr></table></figure></li><li><p>myhack2.dll的源代码很简单,若当前加载自己的进程为"notepad.exe",则以使用IE打开连接指定网站。</p></li><li><p>AppInit_DLLs注册表键非常强大,通过它几乎可以向所有进程注入DLL文件。若被注入的DLL出现问题(Bug),则有可能导致Windows无法正常启动,所以修改AppInit_DLLs前务必彻查</p></li></ul><h5 id="SetWindowsHookEx"><a href="#SetWindowsHookEx" class="headerlink" title="SetWindowsHookEx()"></a>SetWindowsHookEx()</h5><ul><li>注入DLL的第三个方法就是消息钩取,即用SetWindowsHookEx() API安装好消息"钩子",然后由OS将指定DLL(含有"钩子"过程)强制注入相应(带窗口的)进程。</li></ul><hr><h3 id="逆向工程核心原理第二十四章"><a href="#逆向工程核心原理第二十四章" class="headerlink" title="逆向工程核心原理第二十四章"></a>逆向工程核心原理第二十四章</h3><h3 id="DLL卸载"><a href="#DLL卸载" class="headerlink" title="DLL卸载"></a>DLL卸载</h3><ul><li>DLL卸载是将强制插入进程的DLL弹出的一种技术,其基本原理与用CreateRemoteThread API进行DLL注入的原理类似</li></ul><h4 id="DLL卸载的工作原理"><a href="#DLL卸载的工作原理" class="headerlink" title="DLL卸载的工作原理"></a>DLL卸载的工作原理</h4><ul><li>前面利用CreateRemoteThread() API进行DLL注入的工作原理:驱使目标进程调用LoadLibrary() API;同样DLL卸载工作原理也与之类似:驱使目标进程调用FreeLibrary() API</li><li>也就是说,将FreeLibrary() API的地址传递给CreateRemoteThread()的lpStartAddress参数,并把要卸载的DLL的句柄传递给lpParameter参数</li></ul><h4 id="实现DLL卸载"><a href="#实现DLL卸载" class="headerlink" title="实现DLL卸载"></a>实现DLL卸载</h4><ul><li><p>首先分析一下EjectDll.exe源码,它用来从目标进程卸载指定的DLL文件(myhack.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><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><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// EjectDll.exe</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlhelp32.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tchar.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_PROC_NAME(<span class="string">L"notepad.exe"</span>)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_DLL_NAME(<span class="string">L"myhack.dll"</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="function">DWORD <span class="title">FindProcessID</span><span class="params">(LPCTSTR szProcessName)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> DWORD dwPID = <span class="number">0xFFFFFFFF</span>;</span><br><span class="line"> HANDLE hSnapShot = INVALID_HANDLE_VALUE;</span><br><span class="line"> PROCESSENTRY32 pe;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Get the snapshot of the system</span></span><br><span class="line"> pe.dwSize = <span class="built_in">sizeof</span>( PROCESSENTRY32 );</span><br><span class="line"> hSnapShot = <span class="built_in">CreateToolhelp32Snapshot</span>( TH32CS_SNAPALL, <span class="literal">NULL</span> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// find process</span></span><br><span class="line"> <span class="built_in">Process32First</span>(hSnapShot, &pe);</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>(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))</span><br><span class="line"> {</span><br><span class="line"> dwPID = pe.th32ProcessID;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span>(<span class="built_in">Process32Next</span>(hSnapShot, &pe));</span><br><span class="line"></span><br><span class="line"> <span class="built_in">CloseHandle</span>(hSnapShot);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dwPID;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">BOOL <span class="title">SetPrivilege</span><span class="params">(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)</span> </span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TOKEN_PRIVILEGES tp;</span><br><span class="line"> HANDLE hToken;</span><br><span class="line"> LUID luid;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">OpenProcessToken</span>(<span class="built_in">GetCurrentProcess</span>(),</span><br><span class="line"> TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, </span><br><span class="line"> &hToken) )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"OpenProcessToken error: %u\n"</span>, <span class="built_in">GetLastError</span>());</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">LookupPrivilegeValue</span>(<span class="literal">NULL</span>, <span class="comment">// lookup privilege on local system</span></span><br><span class="line"> lpszPrivilege, <span class="comment">// privilege to lookup </span></span><br><span class="line"> &luid) ) <span class="comment">// receives LUID of privilege</span></span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"LookupPrivilegeValue error: %u\n"</span>, <span class="built_in">GetLastError</span>() ); </span><br><span class="line"> <span class="keyword">return</span> FALSE; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> tp.PrivilegeCount = <span class="number">1</span>;</span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Luid = luid;</span><br><span class="line"> <span class="keyword">if</span>( bEnablePrivilege )</span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Attributes = SE_PRIVILEGE_ENABLED;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> tp.Privileges[<span class="number">0</span>].Attributes = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Enable the privilege or disable all privileges.</span></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">AdjustTokenPrivileges</span>(hToken, </span><br><span class="line"> FALSE, </span><br><span class="line"> &tp, </span><br><span class="line"> <span class="built_in">sizeof</span>(TOKEN_PRIVILEGES), </span><br><span class="line"> (PTOKEN_PRIVILEGES) <span class="literal">NULL</span>, </span><br><span class="line"> (PDWORD) <span class="literal">NULL</span>) )</span><br><span class="line"> { </span><br><span class="line"> _tprintf(<span class="string">L"AdjustTokenPrivileges error: %u\n"</span>, <span class="built_in">GetLastError</span>() ); </span><br><span class="line"> <span class="keyword">return</span> FALSE; </span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">GetLastError</span>() == ERROR_NOT_ALL_ASSIGNED )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"The token does not have the specified privilege. \n"</span>);</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> } </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><br><span class="line"><span class="function">BOOL <span class="title">EjectDll</span><span class="params">(DWORD dwPID, LPCTSTR szDllName)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> BOOL bMore = FALSE, bFound = FALSE;</span><br><span class="line"> HANDLE hSnapshot, hProcess, hThread;</span><br><span class="line"> HMODULE hModule = <span class="literal">NULL</span>;</span><br><span class="line"> MODULEENTRY32 me = { <span class="built_in">sizeof</span>(me) };</span><br><span class="line"> LPTHREAD_START_ROUTINE pThreadProc;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// dwPID = notepad 进程 ID</span></span><br><span class="line"> <span class="comment">// 使用 TH32CS_SNAPMODULE 参数,获取加载到notepad.exe进程的DLL名称</span></span><br><span class="line"> hSnapshot = <span class="built_in">CreateToolhelp32Snapshot</span>(TH32CS_SNAPMODULE, dwPID);</span><br><span class="line"></span><br><span class="line"> bMore = <span class="built_in">Module32First</span>(hSnapshot, &me);</span><br><span class="line"> <span class="keyword">for</span>( ; bMore ; bMore = <span class="built_in">Module32Next</span>(hSnapshot, &me) )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span>( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || </span><br><span class="line"> !_tcsicmp((LPCTSTR)me.szExePath, szDllName) )</span><br><span class="line"> {</span><br><span class="line"> bFound = TRUE;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>( !bFound )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">CloseHandle</span>(hSnapshot);</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( !(hProcess = <span class="built_in">OpenProcess</span>(PROCESS_ALL_ACCESS, FALSE, dwPID)) )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"OpenProcess(%d) failed!!! [%d]\n"</span>, dwPID, <span class="built_in">GetLastError</span>());</span><br><span class="line"> <span class="keyword">return</span> FALSE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> hModule = <span class="built_in">GetModuleHandle</span>(<span class="string">L"kernel32.dll"</span>);</span><br><span class="line"> pThreadProc = (LPTHREAD_START_ROUTINE)<span class="built_in">GetProcAddress</span>(hModule, <span class="string">"FreeLibrary"</span>);</span><br><span class="line"> hThread = <span class="built_in">CreateRemoteThread</span>(hProcess, <span class="literal">NULL</span>, <span class="number">0</span>, </span><br><span class="line"> pThreadProc, me.modBaseAddr, </span><br><span class="line"> <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="built_in">WaitForSingleObject</span>(hThread, INFINITE);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">CloseHandle</span>(hThread);</span><br><span class="line"> <span class="built_in">CloseHandle</span>(hProcess);</span><br><span class="line"> <span class="built_in">CloseHandle</span>(hSnapshot);</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><br><span class="line"><span class="type">int</span> _tmain(<span class="type">int</span> argc, TCHAR* argv[])</span><br><span class="line">{</span><br><span class="line"> DWORD dwPID = <span class="number">0xFFFFFFFF</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// find process</span></span><br><span class="line"> dwPID = <span class="built_in">FindProcessID</span>(DEF_PROC_NAME);</span><br><span class="line"> <span class="keyword">if</span>( dwPID == <span class="number">0xFFFFFFFF</span> )</span><br><span class="line"> {</span><br><span class="line"> _tprintf(<span class="string">L"There is no <%s> process!\n"</span>, DEF_PROC_NAME);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> _tprintf(<span class="string">L"PID of \"%s\" is %d\n"</span>, DEF_PROC_NAME, dwPID);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// change privilege</span></span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">SetPrivilege</span>(SE_DEBUG_NAME, TRUE) )</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// eject dll</span></span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">EjectDll</span>(dwPID, DEF_DLL_NAME) )</span><br><span class="line"> _tprintf(<span class="string">L"EjectDll(%d, \"%s\") success!!!\n"</span>, dwPID, DEF_DLL_NAME);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> _tprintf(<span class="string">L"EjectDll(%d, \"%s\") failed!!!\n"</span>, dwPID, DEF_DLL_NAME);</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></pre></td></tr></table></figure></li></ul><h5 id="获取进程中加载的DLL信息"><a href="#获取进程中加载的DLL信息" class="headerlink" title="获取进程中加载的DLL信息"></a>获取进程中加载的DLL信息</h5><ul><li><p><code>hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);</code></p></li><li><p>使用CreateToolhelp32Snapshot() API可以获取加载到进程的模块(DLL)信息。将获取的hSnapshot句柄传递给Module32First()/Module32Next()函数后,即可设置与MODULEENTRY32结构体相关的模块信息。</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="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">tagMODULEENTRY32</span> {</span><br><span class="line"> DWORD dwSize;</span><br><span class="line"> DWORD th32ModuleID;</span><br><span class="line"> DWORD th32ProcessID;</span><br><span class="line"> DWORD GlblcntUsage;</span><br><span class="line"> DWORD ProccntUsage;</span><br><span class="line"> BYTE *modBaseAddr;</span><br><span class="line"> DWORD modBaseSize;</span><br><span class="line"> HMODULE hModule;</span><br><span class="line"> <span class="type">char</span> szModule[MAX_MODULE_NAME32 + <span class="number">1</span>];</span><br><span class="line"> <span class="type">char</span> szExePath[MAX_PATH];</span><br><span class="line">} MODULEENTRY32;</span><br></pre></td></tr></table></figure></li><li><p>szModule成员表示DLL的名称,modBaseAddr成员表示相应DLL被加载的地址(进程虚拟内存)。在EjectDll()函数的for循环中比较szModule与希望卸载的DLL文件名称,能够准确查找到相应模块的信息</p></li></ul><h5 id="DLL卸载练习"><a href="#DLL卸载练习" class="headerlink" title="DLL卸载练习"></a>DLL卸载练习</h5><ul><li><p>同样用vs将EjectDll.cpp编译生成64位的可执行文件来测试DLL卸载</p></li><li><p>将EjectDll.exe复制到之前InjectDll.exe和myhack.dll一个目录下</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211223111415107.png" alt="image-20211223111415107"></p></li><li><p>用Process Explorer查看notepad.exe的PID,然后使用InjectDll注入myhack.dll</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/2021-12-23.11.16.57.png" alt="2021-12-23.11.16.57"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211223112244725.png" alt="image-20211223112244725"></p></li><li><p>可以看到myhack.dll已成功注入到notepad.exe的进程中</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211223112329148.png" alt="image-20211223112329148"></p></li><li><p>接下来卸载myhack.dll,使用命令行运行EjectDll.exe,然后用Process Explorer验证是否成功卸载</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211223112452247.png" alt="image-20211223112452247"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B013/image-20211223112626396.png" alt="image-20211223112626396"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记12</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第二十一章"><a href="#逆向工程核心原理第二十一章" class="headerlink" title="逆向工程核心原理第二十一章"></a>逆向工程核心原理第二十一章</h3><h4 id="Windows消息钩取"><a href="#Windows消息钩取" class="headerlink" title="Windows消息钩取"></a>Windows消息钩取</h4><h5 id="钩子(Hook)"><a href="#钩子(Hook)" class="headerlink" title="钩子(Hook)"></a>钩子(Hook)</h5><ul><li><p>英文Hook一词,翻成中文是"钩子"、"鱼钩"的意思,泛指勾取所需东西而使用的一切工具。"钩子"这一基本含义被延伸发展为"偷看或截取信息时所用的手段或工具"</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211208144510124.png" alt="image-20211208144510124"></p></li></ul><span id="more"></span><h5 id="消息钩子"><a href="#消息钩子" class="headerlink" title="消息钩子"></a>消息钩子</h5><ul><li><p>Windows操作系统向用户提供GUI(Graphic User Interface,图形用户界面),它事件驱动(Event Driven)方式工作。在操作系统中借助键盘、鼠标,选择菜单、按钮,以及移动鼠标、改遍窗口大小与位置等都是事件(Event)。发生这样的事件时,OS会把事先定义好的消息发送给相应的应用程序,应用程序分析收到的信息后执行相应的动作。也就是说,敲击键盘时,消息会从OS移动到应用程序。所谓的"消息钩子"就在此间偷看这些信息。</p></li><li><p>常规Windows消息流</p><ul><li>发生键盘输入事件时,WM_KEYDOWN消息被添加到[OS message queue]</li><li>OS判断哪个应用程序中发生了事件,然后从[OS message queue]取出消息,添加到相应应用程序的[application message queue]中</li><li>应用程序(如记事本)监视自身的[application message queue],发现新添加的WM_KEYDOWN消息后,调用相应的事件处理程序处理</li></ul></li><li><p>如下图所示,OS消息队列与应用程序消息队列之间存在一条"钩链"(Hook Chain),设置好键盘消息钩子之后,处于"钩链"中的键盘消息钩子会比应用程序先看到相应信息。在键盘消息钩子函数内部,除了可以查看消息之外,还可以修改消息本身,而且还能对消息实施拦截,阻止消息传递。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211208144510124.png" alt="image-20211208144510124"></p></li><li><p>像这样的消息钩子功能是Windows操作系统提供的基本功能,其中最具代表性的是MS Visual Studio中提供的SPY++,它是一个功能十分强大的消息钩起程序,能够查看操作系统中来往的所有消息。</p></li></ul><h5 id="SetWindowsHookEx"><a href="#SetWindowsHookEx" class="headerlink" title="SetWindowsHookEx()"></a>SetWindowsHookEx()</h5><ul><li><p>使用SetWindowsHookEx() API可轻松实现消息钩子,SetWindowsHookEx() API的定义如下</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">HHOOK <span class="title">SetWindowsHookEx</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="type">int</span> idHook;<span class="comment">// hook type</span></span></span></span><br><span class="line"><span class="params"><span class="function"> HOOKPROC lpfn;<span class="comment">// hook procedure</span></span></span></span><br><span class="line"><span class="params"><span class="function"> HINSTANCE hMod;<span class="comment">// hook procedure所属的DLL句柄</span></span></span></span><br><span class="line"><span class="params"><span class="function"> DWORD dwThreadID<span class="comment">// 想要挂钩的线程ID</span></span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>; </span><br></pre></td></tr></table></figure></li><li><p>钩子过程(hook procedure)是由操作系统调用的回调函数。安装消息"钩子"时,"钩子"过程需要存在于某个DLL内部,且该DLL的实例句柄(instance handle)即是hMod。</p><blockquote><p>若dwThreadID参数被设置为0,则安装的钩子为"全局钩子"(Global Hook),它会影响到运行中的(以及以后要运行的)所有进程</p></blockquote></li></ul><h5 id="键盘消息钩取练习"><a href="#键盘消息钩取练习" class="headerlink" title="键盘消息钩取练习"></a>键盘消息钩取练习</h5><ul><li><p>下面做一个简单的键盘消息钩取练习,以进一步加深对前面内容的理解</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210095813040.png" alt="image-20211210095813040"></p></li><li><p>KeyHook.dll文件是一个含有钩子的过程(KeyboardProc)的DLL文件。HooMain.exe是最先加载KeyHook.dll并安装键盘钩子的程序。HookMain.exe加载KeyHook.dll文件后使用SetWindowsHookEx()安装键盘钩子(KeyboardProc)。若其他进程(explore.exe、iexplore.exe、notepad.exe等)中发生键盘输入事件,OS就会强制将KeyHook.dll加载到相应进程的内存,然后调用KeyboardProc()函数。</p></li></ul><h5 id="练习示例HooKMain-exe"><a href="#练习示例HooKMain-exe" class="headerlink" title="练习示例HooKMain.exe"></a>练习示例HooKMain.exe</h5><ul><li><p>从《逆向工程核心原理》书的官方github<a href="https://github.com/reversecore/book.git">https://github.com/reversecore/book.git</a>,下载HookMain的源代码(有可执行程序,但是64位系统的不能使用,64位系统使用32位的Hook程序会直接卡死,所以需要自己编译测试)</p><blockquote><p>一开始直接用32位的编译程序在64位的win10上运行,发现一直卡死。搜索后,在这篇文章找到了原因<a href="https://blog.csdn.net/qq_39627797/article/details/79495500">windows10 记事本进程 键盘消息钩子 dll注入</a>,原因是:<a href="https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexa?redirectedfrom=MSDN">SetWindowsHookEx</a>的官方文档提到了这个API只能用于32位程序注入32位程序或者64位注入64位程序,否则会直接卡死。</p></blockquote></li><li><p>下载源代码后,用visual studio打开HookMain和KeyHook项目后,新建一个x64平台的活动解决方案,然后生成即可</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210111753611.png" alt="image-20211210111753611"></p></li><li><p>编译生成好HookMain.exe和KeyHook.dll后,将两者放在一个文件夹下,运行HookMain.exe,安装键盘钩子</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210112040141.png" alt="image-20211210112040141"></p></li><li><p>接着运行notepad.exe,使用Process Explore查看notepad.exe进程,在Process Explore选项卡中选择View DLLs(Ctrl + D)即可查看进程加载了哪些DLL文件,如下图所示,可以看到KeyHook.dll已经注入到notepad.exe进程中</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210112350891.png" alt="image-20211210112350891"></p></li><li><p>在Process Explorer中检索(Find Handle or DLL ... Ctrl + Shift + F)注入KeyHook.dll的所有进程,如下图所示,一个进程开始运行并发生键盘事件时,KeyHook.dll就会注入其中(但忽视键盘事件的仅有notepad.exe进程,其他进程会正常处理键盘事件)</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210112903242.png" alt="image-20211210112903242"></p></li><li><p>按"q",拆除键盘钩子,在notepad.exe中使用键盘输入,可以发现又能正常输入了。在Process Explorer中检索KeyHook会发现,没有进程加载KeyHook.dll</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210113257063.png" alt="image-20211210113257063"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210113541186.png" alt="image-20211210113541186"></p></li><li><p>拆除键盘钩子后,相关进程就会将KeyHook.dll文件全部卸载</p></li></ul><h5 id="分析源代码"><a href="#分析源代码" class="headerlink" title="分析源代码"></a>分析源代码</h5><ul><li><p>下载书中的源代码,首先看一下HookMain.cpp的源代码</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"stdio.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"conio.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>DEF_DLL_NAME<span class="string">"KeyHook.dll"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>DEF_HOOKSTART<span class="string">"HookStart"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>DEF_HOOKSTOP<span class="string">"HookStop"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(*PFN_HOOKSTART)</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(*PFN_HOOKSTOP)</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">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">HMODULEhDll = <span class="literal">NULL</span>;</span><br><span class="line">PFN_HOOKSTARTHookStart = <span class="literal">NULL</span>;</span><br><span class="line">PFN_HOOKSTOPHookStop = <span class="literal">NULL</span>;</span><br><span class="line"><span class="type">char</span>ch = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 加载 KeyHook.dll </span></span><br><span class="line">hDll = <span class="built_in">LoadLibraryA</span>(DEF_DLL_NAME);</span><br><span class="line"> <span class="keyword">if</span>( hDll == <span class="literal">NULL</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"LoadLibrary(%s) failed!!! [%d]"</span>, DEF_DLL_NAME, <span class="built_in">GetLastError</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"> <span class="comment">// 获取导出函数地址</span></span><br><span class="line">HookStart = (PFN_HOOKSTART)<span class="built_in">GetProcAddress</span>(hDll, DEF_HOOKSTART);</span><br><span class="line">HookStop = (PFN_HOOKSTOP)<span class="built_in">GetProcAddress</span>(hDll, DEF_HOOKSTOP);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 启动Hook</span></span><br><span class="line"><span class="built_in">HookStart</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待用户输入"q"</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"press 'q' to quit!\n"</span>);</span><br><span class="line"><span class="keyword">while</span>( _getch() != <span class="string">'q'</span> );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 停止Hook</span></span><br><span class="line"><span class="built_in">HookStop</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 卸载KeyHook.dll</span></span><br><span class="line"><span class="built_in">FreeLibrary</span>(hDll);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>HookMain.exe的源代码非常简单,先加载KeyHook.dll文件,然后调用HookStart()函数开始钩取,用户输入"q"时,调用HookStop()函数终止钩取。</p></li><li><p>接下来看看KeyHook.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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"stdio.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DEF_PROCESS_NAME<span class="string">"notepad.exe"</span></span></span><br><span class="line"></span><br><span class="line">HINSTANCE g_hInstance = <span class="literal">NULL</span>;</span><br><span class="line">HHOOK g_hHook = <span class="literal">NULL</span>;</span><br><span class="line">HWND g_hWnd = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="function">BOOL WINAPI <span class="title">DllMain</span><span class="params">(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">switch</span>( dwReason )</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">case</span> DLL_PROCESS_ATTACH:</span><br><span class="line">g_hInstance = hinstDLL;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"></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><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="function">LRESULT CALLBACK <span class="title">KeyboardProc</span><span class="params">(<span class="type">int</span> nCode, WPARAM wParam, LPARAM lParam)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="type">char</span> szPath[MAX_PATH] = {<span class="number">0</span>,};</span><br><span class="line"><span class="type">char</span> *p = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>( nCode >= <span class="number">0</span> )</span><br><span class="line">{</span><br><span class="line"><span class="comment">// bit 31 : 0 => press, 1 => release</span></span><br><span class="line"><span class="keyword">if</span>( !(lParam & <span class="number">0x80000000</span>) ) <span class="comment">// 释放键盘按键时</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">GetModuleFileNameA</span>(<span class="literal">NULL</span>, szPath, MAX_PATH);</span><br><span class="line">p = <span class="built_in">strrchr</span>(szPath, <span class="string">'\\'</span>);</span><br><span class="line"> <span class="comment">// 比较当前进程名称,若为notepad.exe则消息不会传递给应用程序(或下一个"钩子")</span></span><br><span class="line"><span class="keyword">if</span>( !_stricmp(p + <span class="number">1</span>, DEF_PROCESS_NAME) )</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 若非notepad.exe,则调用CallNextHooxEx()函数,将消息传递给应用程序(或下一个"钩子")</span></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">CallNextHookEx</span>(g_hHook, nCode, wParam, lParam);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">__declspec(dllexport) <span class="function"><span class="type">void</span> <span class="title">HookStart</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">g_hHook = <span class="built_in">SetWindowsHookEx</span>(WH_KEYBOARD, KeyboardProc, g_hInstance, <span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">__declspec(dllexport) <span class="function"><span class="type">void</span> <span class="title">HookStop</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">if</span>( g_hHook )</span><br><span class="line">{</span><br><span class="line"><span class="built_in">UnhookWindowsHookEx</span>(g_hHook);</span><br><span class="line">g_hHook = <span class="literal">NULL</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> __cplusplus</span></span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure></li><li><p>DLL代码部分,调用导出函数HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链</p></li><li><p>MSDN中对KeyboardProc函数的定义如下:</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">LRESULT CALLBACK <span class="title">KeyboardProc</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="type">int</span> code,<span class="comment">// HC_ACTION(0), HC_NOREMOVE(3)</span></span></span></span><br><span class="line"><span class="params"><span class="function"> WPARAM wParam, <span class="comment">// virtual-key code</span></span></span></span><br><span class="line"><span class="params"><span class="function"> LPARAM lParam <span class="comment">// extra information</span></span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span></span><br></pre></td></tr></table></figure></li><li><p>安装好键盘"钩子"后,无论哪个进程,只要发生键盘输入事件,OS就会强制将KeyHook.dll注入相应进程。加载了KeyHook.dll的进程中,发生键盘事件时,就会首先调用执行KeyHook.KeyboardProc()</p></li><li><p>KeyboardProc()函数作用,判断当前发生键盘事件的进程是否为notepad.exe,如果是notepad.exe,那么返回1(拦截键盘事件,不往下传递),否则将键盘事件原封不动地传递给应用程序(或者下一个钩链)</p></li></ul><h5 id="调试练习"><a href="#调试练习" class="headerlink" title="调试练习"></a>调试练习</h5><ul><li><p>因为上面编译的HookMain.exe和KeyHook.dll都是64位的,Ollydbg不支持64可执行文件,所以这里使用<a href="https://github.com/x64dbg/x64dbg/">x64dbg</a>进行调试</p></li><li><p>因为HookMain.exe有字符串"press 'q' to quit!",字符串,所以这里可以使用搜索字符串定位到核心代码,如下图所示,图中代码就是HookMain.exe到main()函数</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210164854939.png" alt="image-20211210164854939"></p></li><li><p>可以看到64位程序的寄存器名称都变成了R开头,但跟32位的寄存器大同小异。在"140069910"地址处下断点,然后运行程序,到断点处停下来,开始调试。从断点处开始依次跟踪调试代码,可以了解main()中的主要代码流。</p></li><li><p>先在"14006994E"地址处调用LoadLibrary("KeyHook.dll"),然后在"1400699A7"地址处调用HookStart()函数,该处执行后notepad.exe的键盘事件就会被拦截</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210171134527.png" alt="image-20211210171134527"></p></li><li><p>F7步入查看HookStart()函数内容,可以看到在"1800698F1"调用了SetWindowsHookExW()函数</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210171339846.png" alt="image-20211210171339846"></p></li></ul><h5 id="调试notepad-exe进程内的KeyHook-dll"><a href="#调试notepad-exe进程内的KeyHook-dll" class="headerlink" title="调试notepad.exe进程内的KeyHook.dll"></a>调试notepad.exe进程内的KeyHook.dll</h5><ul><li><p>先启动HookMain.exe</p></li><li><p>用x64dbg调试notepad.exe,F9让notepad.exe正常运行,然后在x64dbg的选项中勾选DLL入口,这样当新的DLL装入时,调试器会在DLL入口处停止(调试完后再取消勾选)。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210172730064.png" alt="image-20211210172730064"></p></li><li><p>在notepad.exe界面中,键盘输入触发KeyHook.dll装载。x64dbg中选择内存布局选项卡,可以看到keyhook.dll被装载在180000000地址处</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210173108633.png" alt="image-20211210173108633"></p></li><li><p>点击CPU选项卡,回到主界面,可以看到,此时调试器停在KeyHook.dll的EP,</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210173314132.png" alt="image-20211210173314132"></p></li><li><p>如下图所示,F7跟进"18006A41C"处的调用dllmain_dispatch,从函数名来看,这个函数就是DLLMain函数的转发的函数</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210173443709.png" alt="image-20211210173443709"></p></li><li><p>继续调试,可以找到DLLMain函数调用的地方,如下图所示,在"18006A243"处调用了KeyHook.dll的DLLMain函数</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210192921720.png" alt="image-20211210192921720"></p></li><li><p>F7步入"18006A243"该处的调用,就可以看到DLLMain函数,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B012/image-20211210193300985.png" alt="image-20211210193300985"></p></li><li><p>KeyHook.dll的KeyboardProc函数则可以通过搜索字符串"notepad.exe"来定位</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记11</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第二十章"><a href="#逆向工程核心原理第二十章" class="headerlink" title="逆向工程核心原理第二十章"></a>逆向工程核心原理第二十章</h3><h4 id="内嵌补丁-练习"><a href="#内嵌补丁-练习" class="headerlink" title=""内嵌补丁"练习"></a>"内嵌补丁"练习</h4><ul><li><p>"内嵌补丁"是"内嵌代码补丁"(Inline Code Patch)的简称,难以直接修改指定代码时,插入并运行被称为"洞穴代码"(Code Cave)的补丁代码后,对程序打补丁。常用于运行时压缩(或加密处理)而难以直接修改的情况。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211206113450362.png" alt="image-20211206113450362"></p></li></ul><span id="more"></span><ul><li>上图左边描述的是典型的运行时压缩代码(或者加密代码)。EP代码先将加密的OEP代码解密,然后再跳转到OEP处。若要打补丁的代码存在于经过加密的OEP区域是很难打补丁的(即使知道代码所在位置也是如此),因为解码过程中可能会解出完全不同的结果。</li><li>解决上述问题的简单方法就是,在文件中加一层中间层的补丁代码,让EP代码解密后跳转至中间层。在中间层执行补丁代码后(由于已经解密OEP,所以可以这么修改)再跳转到OEP处。</li></ul><h4 id="练习:Patchme"><a href="#练习:Patchme" class="headerlink" title="练习:Patchme"></a>练习:Patchme</h4><ul><li><p>《逆向工程核心原理》作者的github上可以下载本章的patchme程序,<a href="https://github.com/reversecore/book">https://github.com/reversecore/book</a></p></li><li><p>运行程序,可以看到程序非常简单,只有两个弹窗。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211206114417214.png" alt="image-20211206114417214"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211206114436112.png" alt="image-20211206114436112"></p></li><li><p>这个patchme比较简单,只要修改上面两处字符串即可。</p></li></ul><h5 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h5><ul><li><p>用Ollydbg打开unpackme#1.aC.exe,可以看到EP非常简单</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161105395.png" alt="image-20211207161105395"></p></li><li><p>跟踪调试,很快可以看到第一个解密循环,如下图所示,第一个解密循环作用是将"004010F5"~"00401248"(004010F5+154-1)处的数据XOR(异或) 44</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161254808.png" alt="image-20211207161254808"></p></li><li><p>知道第一个循环作用后,我们跳过该循环,继续往下调试,很快碰见第二个解密循环,将"00401007"~"00401085"处的数据XOR(异或)7,第二个解密循环的下面是第三个解密循环,第三个解密循环,解密的区域与第一个相同,只是XOR的值为11,说明"004010F5"~"00401249"区域经过了双重XOR加密</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161343930.png" alt="image-20211207161343930"></p></li><li><p>跳过第二、三个解密循环后,继续调试,就能发现"00401038"处的校验函数,该函数循环从通"004010F5"~"00401248"区域读取4个字节数据累加得到校验和,再将结果与"31EB8D80"比较,判断程序是否被修改。若校验失败会弹出"CrC of this file has been modified !!!"的错误提示。0040105D处的CALL 0040108A命令是调用另一解密循环,用来解密0040124A~00401279处的数据</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161420463.png" alt="image-20211207161420463"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207163811971.png" alt="image-20211207163811971"></p><blockquote><p>EDX寄存器为4个字节大小,像这样向其中不断加上4个字节的值,就会发生溢出(overflow)问题。一般的校验和计算中常常忽略该溢出问题,使用最后一个保存在EDX的值</p></blockquote></li><li><p>跳过校验和部分,继续调试,很快就会跳转至OEP处,如下图所示,OEP所在地址为0040121E</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161733025.png" alt="image-20211207161733025"></p></li><li><p>如果解密后,Ollydbg在0040121E~00401248没有显示为汇编指令,可以先选中0040121E~00401248这片区域,然后右键选择=>分析=>分析代码,然后Ollydbg就会把该处数据视为汇编代码展示出来</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207162101974.png" alt="image-20211207162101974"></p></li><li><p>OEP代码用来运行对话框,执行位于0040123E地址处的CALL DialogBoxParamA()命令后即弹出对话框。下面是DialogBoxParamA() 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">INT_PTR WINAPI DialogBoxParamA(</span><br><span class="line">__in_optHINSTANCE hInstance,</span><br><span class="line">__inLPCTSTR lpTemplateName,</span><br><span class="line">__in_optHWND hWndParent,</span><br><span class="line">__in_optDLGPROC lpDialogFunc,</span><br><span class="line">__inLPARAM dwInitParam</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p>DialogBoxParamA() API的第四个参数lpDialogFunc用来指出Dialog Box Procedure的地址。地址40122C处有条push 4010F5命令,由此可见,函数第四个参数的地址为4010F5(栈先进后出,所以第二个进栈即位倒数第二个出栈)。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207161733025.png" alt="image-20211207161733025"></p></li><li><p>下图为004010F5处的代码,可以看到我们要修改的字符串都在里面</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207162243371.png" alt="image-20211207162243371"></p></li><li><p>根据上面的分析,可以总结出下面的,代码结构图。如图所示,[EP Code]只是用来调用[Decoding Code]的,实际的解密处理是由[Decoding Code]完成的。按照[B]-[A]-[B]的顺序解码,运行解密后的[A]区代码,在[A]区代码会求得[B]区的校验和,并据此判断[B]区是否发生过改变。然后对[C]区解码,最后跳转至OEP处(0040121E)</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207164129987.png" alt="image-20211207164129987"></p></li></ul><h5 id="内嵌补丁-练习-1"><a href="#内嵌补丁-练习-1" class="headerlink" title=""内嵌补丁"练习"></a>"内嵌补丁"练习</h5><ul><li>操作顺序:首先向文件合适位置插入用于修改字符串的代码,然后在上图的[A]区域将JMP OEP命令修改为JMP补丁代码(修改时要充分考虑文件中的[A]区域处于加密状态)。在补丁代码中更改字符串,然后再通过JMP命令跳转至OEP处,这样整个内嵌补丁过程就完成了。</li></ul><h5 id="补丁代码要设置在何处"><a href="#补丁代码要设置在何处" class="headerlink" title="补丁代码要设置在何处"></a>补丁代码要设置在何处</h5><ul><li><p>这个问题在进行内嵌补丁的过程中非常重要。有如下3种设置方法:</p><ol><li>设置到文件的空白区域</li><li>扩展最后节区后设置</li><li>添加新节区后设置</li></ol></li><li><p>补丁代码较少时,使用方法1,其他情况使用方法2或方法3。首先尝试方法1,使用PE View查看示例文件的第一个节区(.text)头,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207165423151.png" alt="image-20211207165423151"></p></li><li><p>接着查看Section Alignment的值</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207165827158.png" alt="image-20211207165827158"></p></li><li><p>可以看到第一个节区Virtual Size为280,但是由于Section Alignment为1000,所以第一个节区后面RVA 1280之后存在大量NULL填充的空白区域,我们可以在该区域添加补丁代码。</p></li><li><p>用Ollydbg查看00401280地址,如下图所示,可以看到,该处之后确实存在大量空白区域</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207172959638.png" alt="image-20211207172959638"></p></li><li><p>我们开始添加补丁代码,首先添加我们需要修改的字符串。鼠标选中00401280地址处,右键选择二进制编辑</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207173214791.png" alt="image-20211207173214791"></p></li><li><p>取消勾选保持大小,然后在ASCII中输入所需字符串,这里为"blog.iz4.cc",然后点确定即可</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207173250338.png" alt="image-20211207173250338"></p></li><li><p>同样的在0040128C处添加字符串"Unpacked!"</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207173547014.png" alt="image-20211207173547014"></p></li><li><p>如果添加字符串后,该部分数据被Ollydbg识别为汇编指令的话,可以鼠标选中该区域,右键选择 分析=>分析代码,然后OD就能将该部分识别为ASCII字符串了。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207173624240.png" alt="image-20211207173624240"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207173746325.png" alt="image-20211207173746325"></p></li><li><p>添加好字符串后,接着我们添加补丁代码的汇编指令,在00401296处按空格即可编辑汇编指令</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">mov ecx, 0xC; 0xC为要拷贝的字符串的字符长度(包括'\0')==> blog.iz4.cc\0</span><br><span class="line">mov esi, 00401280; 让ESI指向第一个需要拷贝的字符串所在地址</span><br><span class="line">mov edi, 00401123; 修改的目标字符串所在地址</span><br><span class="line">rep movsb; <==> rep movs byte ptr es:[edi],byte ptr ds:[esi]</span><br><span class="line">;这个指令意思就是将ESI指向的地址的值以1字节方式拷贝到EDI指向的地址中</span><br><span class="line">;重复执行ECX次,每次执行后ESI+1,EDI+1,ECX-1</span><br><span class="line"></span><br><span class="line">mov ecx, 0xA; 0xA为要拷贝的字符串的字符串长度(包括'\0')==> Unpacked\0</span><br><span class="line">mov esi, 0040128C; 让ESI指向第二个需要拷贝的字符串所在地址</span><br><span class="line">mov edi, 0040110A; 修改的目标字符串所在地址</span><br><span class="line">rep movsb; <==> rep movs byte ptr es:[edi],byte ptr ds:[esi] ; 作用同上 </span><br><span class="line"></span><br><span class="line">jmp 0040121E; 跳转至OEP</span><br></pre></td></tr></table></figure><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207175428169.png" alt="image-20211207175428169"></p></li><li><p>添加完,指令后,我们先修改地址00401083处的jmp命令跳转至我们的补丁代码位置,测试效果</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207175604997.png" alt="image-20211207175604997"></p></li><li><p>如下图所示,可以看到,两个字符串成功被修改,接下来就是保存修改到可执行文件</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207180207186.png" alt="image-20211207180207186"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207180221420.png" alt="image-20211207180221420"></p></li><li><p>在左下角的数据窗口中,选中修改的00401280~004012BC区域,然后右键选择复制到可执行文件,再保存即可</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207182713918.png" alt="image-20211207182713918"></p></li><li><p>这里不直接保存00401083处的jmp命令修改,因为该区域是加密的,所以文件中修改该处的jmp指令也应该替换为加密后的数据</p></li><li><p>由00401083 => RVA 1083 => RAW: 483,用HEdit查看该处偏移。从文件偏移看加密只到485,后面的0000并不是加密区域。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207184027437.png" alt="image-20211207184027437"></p></li><li><p>如下图所示,OD中<code>jmp 00401296</code>对应的机器码为E90E020000,从上面可知,后面两个00不用加密。因为是XOR(异或)加密,所以加解密都是对同一个key进行XOR运算即可。所以应该将"E90E02"加密后为"EE0905",所以文件483偏移处的"EE9106"应该修改为"EE0905"</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207184447970.png" alt="image-20211207184447970"></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">E9 xor 07 = EE</span><br><span class="line">0E xor 07 = 09</span><br><span class="line">02 xor 07 = 05</span><br></pre></td></tr></table></figure></li><li><p>最后使用HEdit将文件483偏移处的"EE9106"应该修改为"EE0905"保存即可,运行可以发现程序成功跳转至补丁代码。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207180207186.png" alt="image-20211207180207186"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B011/image-20211207180221420.png" alt="image-20211207180221420"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记10</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第十八章"><a href="#逆向工程核心原理第十八章" class="headerlink" title="逆向工程核心原理第十八章"></a>逆向工程核心原理第十八章</h3><h4 id="UPack-PE文件头分析"><a href="#UPack-PE文件头分析" class="headerlink" title="UPack PE文件头分析"></a>UPack PE文件头分析</h4><h5 id="使用UPack压缩abexcm1-exe"><a href="#使用UPack压缩abexcm1-exe" class="headerlink" title="使用UPack压缩abexcm1.exe"></a>使用UPack压缩abexcm1.exe</h5><ul><li><p>使用WinUpack 0.39 Final版本压缩abexcm1.exe。直接拖拽abexcm1.exe到WinUpack,按以下勾选参数进行压缩,其中默认是没有清除重定位表的,这里勾选上以便复现原文。将压缩后abexcm1.exe重命名为abexcm1_upack.exe</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204162922497.png" alt="image-20211204162922497"></p></li></ul><span id="more"></span><ul><li><p>下图为使用PE View查看的结果,这里用的是PE View 0.9.9版,这个版本仍无法正常读取PE文件</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204163155007.png" alt="image-20211204163155007"></p></li></ul><h5 id="使用Stud-PE工具"><a href="#使用Stud-PE工具" class="headerlink" title="使用Stud_PE工具"></a>使用Stud_PE工具</h5><ul><li><p>下面使用Stud_PE查看abexcm1_upack.exe</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204163506287.png" alt="image-20211204163506287"></p></li><li><p>可以看到Stud_PE界面要比PE View界面复杂一些,但它拥有其他工具无法比拟的众多独特优点(也能更好地显示Upack)</p></li></ul><h5 id="比较PE文件头"><a href="#比较PE文件头" class="headerlink" title="比较PE文件头"></a>比较PE文件头</h5><ul><li><p>使用HEdit打开abexcm1.exe和abexcm1_upack.exe,再比较它们PE头部分</p></li><li><p>下图为原abexcm1.exe的PE文件头,其中数据按照IMAGE_DOS_HEADER、DOS Stub、IMAGE_NT_HEADERS、IMAGE_SECTIONS_HEADER顺序排列</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204164022657.png" alt="image-20211204164022657"></p></li><li><p>下图为abexcm1_upack.exe的PE文件头,可以看到abexcm1_upack.exe的PE头看上去有些奇怪。MZ与PE签名贴得太近了,并且没有DOS存根,出现了大量字符串,中间好像还夹杂着代码</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204164721595.png" alt="image-20211204164721595"></p></li></ul><h4 id="分析UPack的PE文件头"><a href="#分析UPack的PE文件头" class="headerlink" title="分析UPack的PE文件头"></a>分析UPack的PE文件头</h4><h5 id="重叠文件头"><a href="#重叠文件头" class="headerlink" title="重叠文件头"></a>重叠文件头</h5><ul><li><p>重叠文件头也是其他压缩器经常使用的技法,借助该方法可以把MZ文件头(IMAGE_DOS_HEADER)与PE文件头(IMAGE_NT_HEADERS)巧妙重叠在一起,并可有效节约文件头空间。</p></li><li><p>下面使用Stud_PE看一下MZ文件头部分,在文件头选项卡中,选择"在十六进制编辑器中以树形结构查看文件头"</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/4.55.56.png" alt="4.55.56"></p></li><li><p>MZ文件头(IMAGE_DOS_HEADER)中有以下2个重要成员,其余成员都不怎么重要(对程序运行没有任何意义)</p><ul><li>e_magic:Magic number = 4D5A("MZ")</li><li>e_lfanew:File address of new exe header</li></ul></li><li><p>问题在于,根据PE文件格式规范,IMAGE_NT_HEADERS的起始位置是"可变的"。换言之,IMAGE_NT_HEADERS的起始位置是由e_lfanew的值决定的。一般在一个正常程序中,e_lfanew拥有如下所示的值(不同的构建环境会有不同)</p><blockquote><p>e_lfanew = MZ文件头大小 (40) + DOS存根大小 (可变: VC++下为A0) = E0</p></blockquote></li><li><p>UPack中的e_lfanew的值为10,这并不违反PE规范,只是钻了规范的空子。像这样就可以把MZ文件头和PE文件头重叠在一起</p></li></ul><h5 id="IMAGE-FILE-HEADER-SizeOfOptionalHeader"><a href="#IMAGE-FILE-HEADER-SizeOfOptionalHeader" class="headerlink" title="IMAGE_FILE_HEADER.SizeOfOptionalHeader"></a>IMAGE_FILE_HEADER.SizeOfOptionalHeader</h5><ul><li><p>修改IMAFE_FILE_HEADER.SizeOfOptionalHeader的值,可以向文件头插入解码代码。</p></li><li><p>SizeOfOptionalHeader表示PE文件头中紧接在IMAGE_FILE_HEADER下的IMAGE_OPTIONAL_HEADER结构体的长度(E0)。如下图所示,Upack将该值更改为148</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204171427997.png" alt="image-20211204171427997"></p></li><li><p>这里会产生一个疑问,IMAGE_OPTIONAL_HEADER是结构体,PE32文件格式中其大小已经被确定为E0。既然如此,PE文件格式的设计者们为何还要另外输入IMAGE_OPTIONAL_HEADER结构体的大小呢?原本的设计意图是,根据PE文件形态分别更换并插入其他IMAGE_OPTIONAL_HEADER形态的结构体。简单来说,就是由于IMAGE_OPTIONAL_HEADER的种类很多,所以需要另外输入结构体大小(例如:64位PE32+的IMAGE_OPTIONAL_HEADER结构体的大小为F0)</p></li><li><p>SizeOfOptionalHeader的另一层含义是确定节区头(IMAGE_SECTION_HEADER)的起始偏移</p></li><li><p>仅从PE文件头来看,紧接着IMAGE_OPTIONAL_HEADER的好像就是IMAGE_SECTION_HEADER。但实际上(更准确地说),从IMAGE_OPTIONAL_HEADER的起始偏移加上SizeOfOptionalHeader值后的位置开始才是IMAGE_SECTION_HEADER</p></li><li><p>Upack把SizeOfOptionalHeader的值设置为148,比正常值(E0或F0)要更大一些。所以IMAGE_SECTION_HEADER是从偏移170开始的(IMAGE_OPTIONAL_HEADER起始偏移(28)+SizeOfOptionalHeader(148)=170)</p></li><li><p>Upack这么修改的原因是:把PE文件头变形,向文件头适当插入解码需要的代码,增大逆向分析的难度</p></li><li><p>增大SizeOfOptionalHeader的值后,就在IMAGE_OPTIONAL_HEADER与IMAGE_SECTION_HEADER之间添加了额外空间,Upack就向这个区域添加解码代码。</p></li><li><p>下面查看该区域,IMAGE_OPTIONAL_HEADER结束位置为D7,IMAGE_SECTION_HEADER的起始位置为170。使用HEdit查看中间区域,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204202928417.png" alt="image-20211204202928417"></p></li><li><p>使用Ollydbg查看反汇编代码,如下图所示,这部分信息不是PE头文件中的信息,而是UPack中使用的代码。若PE相关实用工具将其识别为PE文件头信息,就会引发错误,导致程序无法正常运行</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204203319376.png" alt="image-20211204203319376"></p></li></ul><h5 id="IMAGE-OPTIONAL-HEADER-NumberOfRvaAndSizes"><a href="#IMAGE-OPTIONAL-HEADER-NumberOfRvaAndSizes" class="headerlink" title="IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes"></a>IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes</h5><ul><li><p>从IMAGE_OPTIONAL_HEADER结构体中可以看到,其NumberOfRvaAndSizes的值也发生了改变,这样做的目的也是为了向文件头插入自身代码</p></li><li><p>NumberOfRvaAndSizes值用来指出紧接在后面的IMAGE_DATA_DIRECTORY结构体数组的元素个数。正常文件中IMAGE_DATA_DIRECTORY数组元素的个数为10h,但在UPack中将其更改为了Ah个。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204204439471.png" alt="image-20211204204439471"></p></li><li><p>IMAGE_DATA_DIRECTORY结构体数组元素的个数已经被确定为10,但PE规范将NumberOfRvaSizes值作为数组元素的个数。所以UPack中IMAGE_DATA_DIRECTORY结构体数组的后6个元素被忽略。</p></li><li><p>下表对IMAGE_DATA_DIRECTORY的各项进行了说明,其中粗体的项如果更改不正确就会引发运行错误</p><table><thead><tr><th align="center">索 引</th><th align="center">内 容</th><th align="center">索 引</th><th align="center">内 容</th></tr></thead><tbody><tr><td align="center">0</td><td align="center">EXPORT Directory</td><td align="center">8</td><td align="center">GLOBALPTR Directory</td></tr><tr><td align="center"><strong>1</strong></td><td align="center"><strong>IMPORT Directory</strong></td><td align="center"><strong>9</strong></td><td align="center"><strong>TLS Directory</strong></td></tr><tr><td align="center"><strong>2</strong></td><td align="center"><strong>RESOURCE Directory</strong></td><td align="center"><strong>A</strong></td><td align="center"><strong>LOAD_CONFIG Directory</strong></td></tr><tr><td align="center"><strong>3</strong></td><td align="center"><strong>EXCEPTION Directory</strong></td><td align="center"><strong>B</strong></td><td align="center"><strong>BOUND_IMPORT Directory</strong></td></tr><tr><td align="center">4</td><td align="center">SECURITY Directory</td><td align="center"><strong>C</strong></td><td align="center"><strong>IAT Directory</strong></td></tr><tr><td align="center">5</td><td align="center">BASERELOC Directory</td><td align="center">D</td><td align="center">DELAY_IMPORT Directory</td></tr><tr><td align="center"><strong>6</strong></td><td align="center"><strong>DEBUG Directory</strong></td><td align="center"><strong>E</strong></td><td align="center"><strong>COM_DESCRIPTOR Directory</strong></td></tr><tr><td align="center">7</td><td align="center">COPYRIGHT Directory</td><td align="center">F</td><td align="center">Reserved Directory</td></tr></tbody></table></li><li><p>Upack将IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes的值更改为A,从LOAD_CONFIG Directory项(文件偏移D8以后)开始不再使用。UPack就在这块被忽视的IMAGE_DATA_DIRECTORY区域中覆写自己的代码。</p></li><li><p>接下来使用HEdit查看IMAGE_OPTIONAL_HEADER结构体数组区域</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204214502804.png" alt="image-20211204214502804"></p></li></ul><h5 id="IMAGE-SECTION-HEADER"><a href="#IMAGE-SECTION-HEADER" class="headerlink" title="IMAGE_SECTION_HEADER"></a>IMAGE_SECTION_HEADER</h5><ul><li><p>IMAGE_SECTION_HEADER结构体中,Upack会把自身数据记录到程序运行不需要的项目。这与UPack向PE文件头中不使用的区域覆写自身代码与数据的方法是一样的</p></li><li><p>下面使用HEdit查看IMAGE_SECTION_HEADER结构体,UPack把代码数据存放在IMAGE_SECTION_HEADER结构体的offset to relocations、offset to line numbers、number of relocations、number of line numbers这四个成员中,因为这四个成员本身对程序运行没有任何意义</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204215046678.png" alt="image-20211204215046678"></p></li></ul><h5 id="重叠节区"><a href="#重叠节区" class="headerlink" title="重叠节区"></a>重叠节区</h5><ul><li><p>UPack的主要特征之一就是可以随意重叠PE节区与文件头,下面通过Stud_PE查看UPack的IMAGE_SECTION_HEADER</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204215948031.png" alt="image-20211204215948031"></p></li><li><p>从图可以看到,某些部分看上去比较奇怪。首先是第一个节区与第三个节区的文件起始偏移(RawOffset)值都为10。偏移10是文件头区域,UPack中该位置起即为节区部分。</p></li><li><p>另一奇怪的地方是,第一个节区和第三个节区的文件起始偏移与在文件中的大小(RawSize)是完全一致的。但是,节区内存的起始RVA(VirtualOffset)项与内存大小(VirtualSize)值是彼此不同的。根据PE规范,这样做不会有什么问题。</p></li><li><p>综合以上两点可知,UPack会对PE文件头、第一个节区、第三个节区进行重叠,三者重叠示意图如下。根据节区头(IMAGE_SECTION_HEADER)中的定义,PE装载器会将文件偏移0~1FF的区域分别映射到3个不同的内存位置(文件头、第一个节区、第三个节区)。也就是说,用相同的文件映像可以分别创建出处于不同位置的、不同大小的内存映像</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204222232165.png" alt="image-20211204222232165"></p></li><li><p>文件的头(第一/第三节区)区域的大小为200,第二个节区大小为5A0(占据了文件的大部分区域),原文件(abexcm1.exe)就压缩于此</p></li><li><p>另一点需要注意的是内存中的第一个节区区域,它的内存尺寸为6000,与原文件(abexcm1.exe)的Size of Image具有相同的值。也就是说,压缩在第二个节区中的文件映像会被原样解压缩到第一个节区(abexcm1的内存映像)。另外,原abexcm1.exe拥有5个节区,它们被解压到一个节区。</p></li></ul><h5 id="RVA-to-RAW"><a href="#RVA-to-RAW" class="headerlink" title="RVA to RAW"></a>RVA to RAW</h5><ul><li><p>各种PE实用程序对Upack束手无策的原因就是无法正确进行RVA=>RAW的变换。</p></li><li><p>首先复习下常规的RVA=>RAW方法</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">RAW - PointerToRawData = RVA - VirtualAddress</span><br><span class="line">RAW = RVA - VirtualAddress + PointerToRawData</span><br><span class="line">VirtualAddress、PointerToRawData是从RVA所在节区头中获取的值,它们都是已知值</span><br></pre></td></tr></table></figure></li><li><p>根据上述公式,算一下EP的文件偏移量(RAW)。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204223804768.png" alt="image-20211204223804768"></p></li><li><p>UPack的EP是RVA 1018,RVA 1018位于第一个节区,将其代入公式,换算如下</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">RAW = 1018 - 1000 + 10 = 28</span><br></pre></td></tr></table></figure></li><li><p>用HEdit查看文件偏移28处,如下图所示,可以看到RAW 28不是代码区域,而是(ordinal:010B)"LoadLibraryA"字符串区域。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204224202028.png" alt="image-20211204224202028"></p></li><li><p>造成上面这个问题原因在于第一个节区的PointerToRawData值10。一般而言,指向节区开始的文件偏移的PointerToRawData值应该是FileAlignment的整数倍。UPack的FileAlignment为200,故PointerToRawData值应为0、200、400、600等值。PE装载器发现第一个节区的PointerToRawData(10)不是FileAlignment(200)的整数倍时,它会强制将其识别为整数倍(该情况下为0)。这使UPack文件能够正常运行,但是许多PE相关实用程序都会发生错误。正确的RVA=>RAW变换如下</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">RAW = 1018 - 1000 + 0 = 18</span><br><span class="line">PointerToRawData倍识别为0</span><br></pre></td></tr></table></figure></li><li><p>使用Ollydbg查看相应区域的代码,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204225058145.png" alt="image-20211204225058145"></p></li></ul><h5 id="导入表(IMAGE-IMPORT-DESCRIPTOR-array)"><a href="#导入表(IMAGE-IMPORT-DESCRIPTOR-array)" class="headerlink" title="导入表(IMAGE_IMPORT_DESCRIPTOR array)"></a>导入表(IMAGE_IMPORT_DESCRIPTOR array)</h5><ul><li><p>UPack的导入表(Import Table)组织结构相当独特(暗藏玄机)</p></li><li><p>下面使用HEdit查看IMAGE_IMPORT_DESCRIPTOR结构体。首先要从Directory Table中获取IDT(IMAGE_IMPORT_DESCRIPTOR结构体数组)的地址,如下图所示,图中框选的8个字节大小的数据就是指向导入表的IMAGE_IMPORT_DESCRIPTOR结构体。前四个字节为导入表地址(RVA),后面四个字节为导入表的大小(Size)。从图中可知导入表的RVA为F1EE</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204225454371.png" alt="image-20211204225454371"></p></li><li><p>使用HEdit查看之前,需要先进行RVA=>RAW变换。首先确定该RVA值属于哪个节区,内存地址F1EE在内存中是第三个节区,参考下图</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204225842802.png" alt="image-20211204225842802"></p></li><li><p>进行RVA=>RAW变换,如下所示</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">RAW = RVA(F1EE) - VirtualOffset(F000) + RawOffset(0) = 1EE</span><br><span class="line">这里同样地第三个节区的RawOffset不是10,而是被强制变换为0</span><br></pre></td></tr></table></figure></li><li><p>使用HEdit查看偏移1EE中的数据,如下图所示,该处就是UPack暗藏玄机的地方</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204230434611.png" alt="image-20211204230434611"></p></li><li><p>首先看一下IMAGE_IMPORT_DESCRIPTOR结构体的定义,再继续分析(结构体的大小为20字节)</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="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_IMPORT_DESCRIPTOR</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">union</span> {</span><br><span class="line"> DWORD Characteristics; </span><br><span class="line"></span><br><span class="line"> DWORD OriginalFirstThunk; <span class="comment">// INT(Import Name Table) address (RVA)</span></span><br><span class="line"></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> DWORD TimeDateStamp; </span><br><span class="line"></span><br><span class="line"> DWORD ForwarderChain;</span><br><span class="line"></span><br><span class="line"> DWORD Name; <span class="comment">// library name string address (RVA) </span></span><br><span class="line"></span><br><span class="line"> DWORD FirstThunk; <span class="comment">// IAT(Import Address Table) address (RVA)</span></span><br><span class="line"></span><br><span class="line">} IMAGE_IMPORT_DESCRIPTOR;</span><br></pre></td></tr></table></figure></li><li><p>根据PE规范,导入表是由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成的数组,最后以一个内容为NULL的结构体结束。</p></li><li><p>文件偏移1EE~201为第一个结构体,其后既不是第二个结构体,也不是(表示导入表结束的)NULL结构体。乍一看这种做法分明是违反PE规范的。但是偏移1FF处为第三个节区的结束,因此文件偏移在200以后的部分不会映射到第三个节区内存。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204232357659.png" alt="image-20211204232357659"></p></li><li><p>第三个节区加载到内存时,文件偏移0~1FF到区域映射到内存的F000~F1FF区域,而(第三个节区其余内存区域)F200~10000区域全部填充为NULL。使用调试器查看相同区域,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204233029629.png" alt="image-20211204233029629"></p></li><li><p>准确地说,只映射到0040F1FF,从0040F200开始全部填充为NULL值。</p></li><li><p>再次返回PE规范的导入表条件,0040F202地址以后出现NULL结构体,这并不算违反PE规范。这就是UPack使用节区的玄机。从文件看导入表好像是损坏了,但是其实它已在内存中准确表现出来。</p></li></ul><h5 id="导入地址表"><a href="#导入地址表" class="headerlink" title="导入地址表"></a>导入地址表</h5><ul><li><p>UPack都输入了哪些DLL中的哪些API呢?下面通过分析IAT查看</p><table><thead><tr><th align="center">偏 移</th><th align="center">成 员</th><th align="center">RVA</th></tr></thead><tbody><tr><td align="center">1EE</td><td align="center">OriginalFirstThunk(INT)</td><td align="center">0</td></tr><tr><td align="center">1FA</td><td align="center">Name</td><td align="center">2</td></tr><tr><td align="center">1FE</td><td align="center">FirstThunk(IAT)</td><td align="center">11E8</td></tr></tbody></table></li><li><p>首先Name的RVA值为2,它属于Header区域(因为第一个节区是从RVA 1000开始的)</p></li><li><p>Header区域中RVA值与RAW值是一样的,故使用Hex Editor查看文件中偏移(RAW)为2的区域,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204234101448.png" alt="image-20211204234101448"></p></li><li><p>在偏移为2的区域中可以看到字符串KERNEL32.DLL。该位置原本是DOS头部分(IAMGE_DOS_HEADER),属于不使用区域,UPack将ImportDLL名称写入该处。得到DLL名称后,再看一下从中导入了哪些API函数</p></li><li><p>一般而言,跟踪OriginalFirstThunk(INT)能够发现API名称字符串,但是像UPack这样,OriginalFirstThunk(INT)为0时,跟踪FirstThunk(IAT)也是可以的(只要INT、IAT其中一个有API名称字符串即可)。IAT值为11E8,属于第一个节区,故RVA=>RAW换算如下</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">RAW = RVA(11E8) - VirtualOffset(1000) + RawOffset(0) = 1E8</span><br></pre></td></tr></table></figure></li><li><p>用HEdit查看文件偏移1E8,如下图所示,图中高亮部分就是IAT区域,同时也作为INT来使用。也就是说,该处是Name Pointer(RVA)数组,其结束是NULL。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204235022350.png" alt="image-20211204235022350"></p></li><li><p>此外可以看到导入了2个API,分别为RVA 28与BE。RVA位置上存在着导入函数的[ordinal+名称字符串],由于都是header区域,所以RVA与RAW值是一样的</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204235326466.png" alt="image-20211204235326466"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211204235354500.png" alt="image-20211204235354500"></p></li><li><p>从图中可以看到导入的2个API函数分别为LoadLibrary和GetProcAddress,它们在形成原文件的IAT时非常方便,所以普通压缩器也常常导入使用。</p></li></ul><hr><h3 id="逆向工程核心原理第十九章"><a href="#逆向工程核心原理第十九章" class="headerlink" title="逆向工程核心原理第十九章"></a>逆向工程核心原理第十九章</h3><h4 id="UPack调试-查找OEP"><a href="#UPack调试-查找OEP" class="headerlink" title="UPack调试 - 查找OEP"></a>UPack调试 - 查找OEP</h4><h5 id="使用x64dbg调试"><a href="#使用x64dbg调试" class="headerlink" title="使用x64dbg调试"></a>使用x64dbg调试</h5><ul><li><p><a href="https://github.com/x64dbg/x64dbg/">x64dbg</a>是类似于Ollydbg的开源调试器,而且由于Ollydbg早已停止更新,所以x64dbg也是Ollydbg的替代品</p></li><li><p>下载最新版<a href="https://github.com/x64dbg/x64dbg/">x64dbg</a>,因为调试的是32位程序,所以运行时应该选择x86dbg.exe来调试</p></li><li><p>下图为x86dbg运行界面,可以看到软件界面跟Ollydbg非常相似,下面就用x86dbg来调试UPack压缩的abexcm1_upack.exe</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205112358070.png" alt="image-20211205112358070"></p></li></ul><h5 id="EntryPoint"><a href="#EntryPoint" class="headerlink" title="EntryPoint"></a>EntryPoint</h5><ul><li><p>x86dbg默认停住运行的地方不是EntryPoint,但是x86dbg能识别出程序的EntryPoint(UPack的也能正确识别)并设置断点,点击断点选项卡,查看设置的断点</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205112848841.png" alt="image-20211205112848841"></p></li><li><p>从图可以看到x86dbg默认在EP上设置了一个一次性的断点,并且准确识别了UPack的EP "00401018"</p></li><li><p>接着,我们F9让程序运行到EP处,可以看到程序在"00401018"处停下</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205113109965.png" alt="image-20211205113109965"></p></li></ul><h5 id="解码循环"><a href="#解码循环" class="headerlink" title="解码循环"></a>解码循环</h5><ul><li><p>所有压缩器中都存在解码循环(Decoding Loop)。如果明白压缩/解压算法本身就是由许多条分支语句和循环构成的,那么就能理解为何解码循环看上去如此复杂。</p></li><li><p>调试这样的循环时,应该适当跳过条件分支语句以跳出某个循环。</p></li><li><p>UPack把压缩后的数据放到第二个节区,再运行解码循环将这些数据解压缩后放到第一个节区。下面从EP代码开始调试,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205114733058.png" alt="image-20211205114733058"></p></li><li><p>前两条指令用于从004011B0处读取4个字节,然后保存到EAX寄存器。EAX拥有的值为"00401000",它就是原本abexcm1.exe的OEP(lodsd指令,相当于这2句汇编代码mov eax,[esi],esi=esi+4;该指令从esi所指的地址处读取4字节储存到EAX寄存器)。这里可以直接设置硬件断点,然后F9运行,程序就会在OEP处暂停。</p></li><li><p>我们的目标时提高调试水平,所以这里继续调试,经过一阵调试后,会出现如下图所示的函数调用代码</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205170248915.png" alt="image-20211205170248915"></p></li><li><p>此时ESI的值为00407443,该地址就是decode()函数的地址,后面会反复调用执行该函数。F7跟进查看该函数代码</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205170523733.png" alt="image-20211205170523733"></p></li><li><p>仅从这部分来看,还搞不清楚这段代码的用途。继续调试遇到如下图所示的代码</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205172256256.png" alt="image-20211205172256256"></p></li><li><p>004075CF与004075D5地址处有"向EDI所指位置写入内容"的指令。此时EDI值指向第一个节区中的地址。也就是说,这些命令会先执行解压缩操作,然后写入实际内存。在004075D6处与004075D9地址处通过CMP/JB指令继续执行循环,直到EDI值为0040604C([ESI+34]=0040604C)</p></li></ul><h5 id="设置IAT"><a href="#设置IAT" class="headerlink" title="设置IAT"></a>设置IAT</h5><ul><li><p>一般而言,压缩器执行完解码循环后会根据原文件重新组织IAT。UPack也有类似的过程,如下图所示,UPack会使用导入的2个函数(LoadLibrary和GetProcAddress),边执行循环边构建原abexcm1.exe的IAT(先获取abexcm1.exe导入函数的实际内存地址,再写入原IAT区域)。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205173322197.png" alt="image-20211205173322197"></p></li><li><p>该过程结束后,由00407627地址处的RETN命令将运行转到OEP处,如下图所示</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B010/image-20211205173733473.png" alt="image-20211205173733473"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记9</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/</url>
<content type="html"><![CDATA[<h3 id="《逆向工程核心原理》第十六章"><a href="#《逆向工程核心原理》第十六章" class="headerlink" title="《逆向工程核心原理》第十六章"></a>《逆向工程核心原理》第十六章</h3><h4 id="基址重定位表"><a href="#基址重定位表" class="headerlink" title="基址重定位表"></a>基址重定位表</h4><ul><li>PE在重定位过程会用到基址重定位表(Base Relocation Table)</li></ul><h5 id="PE重定位"><a href="#PE重定位" class="headerlink" title="PE重定位"></a>PE重定位</h5><ul><li><p>向进程的虚拟内存加载PE文件(EXE/DLL/SYS)时,文件会被加载到PE头的ImageBase所指的地址处。若加载的是DLL(SYS)文件,且在ImageBase位置处已经加载了其他DLL(SYS)文件,那么PE装载器就会将其加载到其他未被占用的空间。这就涉及到PE文件重定位的问题,PE重定位是指PE文件无法加载到ImageBase所指位置,而是被加载到其他地址时,发生的一系列的处理问题。</p><blockquote><p>使用SDK(Software Development Kit,软件开发工具包)或Visual C++创建的PE文件时,EXE默认的ImageBase为00400000,DLL默认的为ImageBase为10000000。此外使用DDK(Driver Development Kit,驱动开发工具包)创建的SYS文件默认的ImageBase为10000。</p></blockquote></li><li><p>下图为DLL重定位示意图,A.DLL被加载到TEXT的10000000地址处。此后,B.DLL试图加载到相同地址(10000000)时,PE装载器将B.DLL加载到另一个尚未被占用的地址(3C000000)处。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203093559819.png" alt="image-20211203093559819"></p></li></ul><span id="more"></span><ul><li><p>创建好进程后,EXE文件会首先加载到内存,所以在EXE中无须考虑重定位的问题。但是Windows Vista之后的版本引入了ASLR安全机制,每次运行EXE文件都会被加载到随机地址,这样大大增加了系统安全性。下图为win10中三次用Ollydbg打开notepad.exe(win10 测试ASLR可能需要每次重启电脑,才会改变地址),可以看到EP起始地址不一样,说明运行时程序被随机加载到不同地址。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203165924999.png" alt="image-20211203165924999"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203170131068.png" alt="image-20211203170131068"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203170641907.png" alt="image-20211203170641907"></p></li></ul><h5 id="PE重定位时执行的操作"><a href="#PE重定位时执行的操作" class="headerlink" title="PE重定位时执行的操作"></a>PE重定位时执行的操作</h5><ul><li><p>下面以win10的notepad.exe程序为例,看看PE重定位时都发生了什么。如下图所示,notepad.exe的ImageBase.exe为"00400000"</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203171330884.png" alt="image-20211203171330884"></p></li><li><p>下图为Ollydbg查看的notepad.exe EP代码部分,从图中指令可以看到,方框中的进程地址都是以硬编码形式存在。这些地址值随加载地址的不同而改变,像这样,使硬编码在程序中的内存地址随当前加载地址变化而改变的处理过程就是PE重定位。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/5.08.18.png" alt="5.08.18"></p></li><li><p>无法加载到ImageBase地址时,若未进行过PE重定位处理,应用程序就不能正常运行(因发生"内存地址引用错误",程序异常终止)</p></li></ul><h5 id="PE重定位操作原理"><a href="#PE重定位操作原理" class="headerlink" title="PE重定位操作原理"></a>PE重定位操作原理</h5><ul><li><p>Windows的PE装载器进行PE重定位处理时,基本的操作原理很简单</p><ul><li>在应用程序中查找硬编码的地址位置</li><li>读取值后,减去ImageBase(VA=>RVA)</li><li>加上实际加载地址(RVA=>VA)</li></ul></li><li><p>最关键的是查找硬编码地址的位置。查找过程会用到PE文件内部的RelocationTable(重定位表),它是记录硬编码地址偏移(位置)的列表(重定位表是在PE文件构建过程(编译/链接)中提供的)。通过重定位表查找,其实就是指根据PE头的"基址重定位表"项进行的查找。</p></li></ul><h5 id="基址重定位表-1"><a href="#基址重定位表-1" class="headerlink" title="基址重定位表"></a>基址重定位表</h5><ul><li><p>基址重定位表位于PE头的DataDirectory数组的第六个元素(数组索引为5),在PE View中查看notepad.exe的基址重定位表地址。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203172950018.png" alt="image-20211203172950018"></p></li><li><p>基址重定位表的地址为RVA 0002B000。使用PE View查看该地址,如下图所示设置以RVA地址查看</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203173426537.png" alt="image-20211203173426537"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203173512764.png" alt="image-20211203173512764"></p></li></ul><h5 id="IMAGE-BASE-RELOCATION结构体"><a href="#IMAGE-BASE-RELOCATION结构体" class="headerlink" title="IMAGE_BASE_RELOCATION结构体"></a>IMAGE_BASE_RELOCATION结构体</h5><ul><li><p>上图的基址重定位表中罗列了硬编码地址的偏移(位置)。读取这张表就能获得准确的硬编码地址偏移。基址重定位表是IMAGE_BASE_RELOCATION结构体数组。IMAGE_BASE_RELOCATION结构体的定义如下</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="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_BASE_RELOCATION</span> {</span><br><span class="line"> DWORD VirtualAddress;</span><br><span class="line"> DWORD SizeOfBlock;</span><br><span class="line"><span class="comment">// WORD TypeOffset[1];</span></span><br><span class="line">} IMAGE_BASE_RELOCATION;</span><br><span class="line"><span class="keyword">typedef</span> IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;</span><br></pre></td></tr></table></figure></li><li><p>IMAGE_BASE_RELOCATION结构体的第一个成员为VirtualAddress,它是一个基准地址(Base Address),实际是RVA值。第二个成员为SizeOfBlock,指重定位快的大小。最后一位TypeOffset数组不是结构体成员,而是以注释的形式存在的,表示在该结构体之下会出现WORD类型的数组,并且该数组元素的值就是硬编码在程序中的地址偏移。</p></li></ul><h5 id="基址重定位表的分析方法"><a href="#基址重定位表的分析方法" class="headerlink" title="基址重定位表的分析方法"></a>基址重定位表的分析方法</h5><ul><li><p>下表列出了notepad.exe部分基址重定位表的部分内容</p><table><thead><tr><th align="center">RVA</th><th align="center">数 据</th><th align="center">注释</th></tr></thead><tbody><tr><td align="center">0002B000</td><td align="center">00001000</td><td align="center">VirtualAddress</td></tr><tr><td align="center">0002B004</td><td align="center">00000218</td><td align="center">SizeOfBlock</td></tr><tr><td align="center">0002B008</td><td align="center">3000</td><td align="center">TypeOffset</td></tr><tr><td align="center">0002B00A</td><td align="center">3004</td><td align="center">TypeOffset</td></tr><tr><td align="center">0002B00C</td><td align="center">3008</td><td align="center">TypeOffset</td></tr><tr><td align="center">...</td><td align="center">...</td><td align="center">...</td></tr></tbody></table></li><li><p>由IMAGE_BASE_RELOCATION结构体的定义可知,VirtualAddress成员(基准地址)的值为1000,SizeOfBlock成员的值为218。也就是说,上表显示的TypeOffset数组的基准地址(起始地址)为RVA 1000,块的总大小为150(这些块按照基准地址分类,以数组形式存在),块的末端显示为0。TypeOffset值为2个字节(16位)大小,是由4位的Type与12位的Offset合成的。比如TypeOffset值为3000,解析表如下</p><table><thead><tr><th align="center">类型(4位)</th><th align="center">偏移(12位)</th></tr></thead><tbody><tr><td align="center">3</td><td align="center">000</td></tr></tbody></table></li><li><p>高4位用作Type,PE文件中常见的值为3(IMAGE_REL_BASED_HIGHLOW),64位的PE+文件中常见值为A(IMAGE_REL_BASED_DIR64)。</p><blockquote><p>有时为了略去PE装载器的重定位过程,常常把Type值修改为0(IMAGE_REL_BASED_ABSOLUTE)</p></blockquote></li><li><p>TypeOffset的低12位才是真正的偏移,该偏移值基于VirtualAddress的偏移。所以程序中的硬编码地址的偏移使用以下公式转换</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">VirtualAddress (1000) + Offset(0000) = 1000(RVA)</span><br></pre></td></tr></table></figure></li><li><p>下面看一下RVA 1000处是否实际存在要执行PE重定位操作的硬编码地址,notepad.exe被加载到004E0000 <code>EP(VA) - EP(RVA)</code>地址处,故RVA 1000即为004E1000(VA),该地址储存着地址005048D8,并且该值经过PE重定位发生了变化。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203190923550.png" alt="image-20211203190923550"></p></li></ul><hr><h3 id="逆向工程核心原理第十七章"><a href="#逆向工程核心原理第十七章" class="headerlink" title="逆向工程核心原理第十七章"></a>逆向工程核心原理第十七章</h3><h4 id="从可执行文件中删除-reloc节区"><a href="#从可执行文件中删除-reloc节区" class="headerlink" title="从可执行文件中删除.reloc节区"></a>从可执行文件中删除.reloc节区</h4><h5 id="reloc节区"><a href="#reloc节区" class="headerlink" title=".reloc节区"></a>.reloc节区</h5><ul><li><p>EXE形式的PE文件中,"基址重定位表"项对运行没什么影响。实际上将其删除后程序仍能正常运行(基址重定位表对DLL/SYS形式的文件来说几乎是必需的)</p></li><li><p>VC++中生成的PE文件的重定位节区名为.reloc,删除该节区后文件照常运行,且文件大小将缩减(实际上存在这种实用小程序)。.reloc节区一般位于所有节区的最后。</p></li><li><p>删除.relco节区需要按照以下4个步骤</p><ul><li>整理.reloc节区头</li><li>删除.reloc节区</li><li>修改IMAGE_FILE_HEADER</li><li>修改IMAGE_OPTIONAL_HEADER</li></ul></li><li><p>下面以前面用过的Tut.ReverseMe1.exe为例,删除.reloc节区,为了避免删除数据使文件偏移发生变化进而导致修改了错误的地址处数据,这里建议修改顺序按文件偏移低地址到高地址的顺序</p></li></ul><h5 id="修改IMAGE-FILE-HEADER"><a href="#修改IMAGE-FILE-HEADER" class="headerlink" title="修改IMAGE_FILE_HEADER"></a>修改IMAGE_FILE_HEADER</h5><ul><li><p>删除1个节区,需要首先修改IMAGE_FILE_HEADER的Number of Sections成员,可以看到该处偏移为86,用HEdit修改该处值为4</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203214240651.png" alt="image-20211203214240651"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203214917343.png" alt="image-20211203214917343"></p></li></ul><h5 id="修改IMAGE-OPTIONAL-HEADER"><a href="#修改IMAGE-OPTIONAL-HEADER" class="headerlink" title="修改IMAGE_OPTIONAL_HEADER"></a>修改IMAGE_OPTIONAL_HEADER</h5><ul><li><p>删除一个节区,(进程虚拟内存中)整个映像就随之减少相应大小。映像大小值储存在IMAGE_OPTIONAL_HEADER的Size of Image成员中,从下图可以看到Size of Image值为8000。问题在于,要计算减去多少才能让程序正常运行。根据下面的.reloc节区头的截图可以得知.reloc Virtual Size为272,但是由于Section Alignment值为1000,所以应该减去Section Alignment的整数倍(向上取整)。因此应修改Size of Image为7000,即用HEdit修改D0偏移处为7000即可。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203214306702.png" alt="image-20211203214306702"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203214321918.png" alt="image-20211203214321918"></p></li><li><p>HEdit修改Size of Image</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203215505418.png" alt="image-20211203215505418"></p></li></ul><h5 id="删除-reloc节区头"><a href="#删除-reloc节区头" class="headerlink" title="删除.reloc节区头"></a>删除.reloc节区头</h5><ul><li><p>下图为原exe文件的.reloc节区头截图,由图可知.reloc节区头从文件偏移218处开始,大小为28。使用Hex Editor将区域(218~239)全部用0覆盖,之所以用0覆盖而不直接删除是因为,如果直接删除,那么239之后的偏移都会发生改变,这样以来就需要修改更多的地方来解决这一问题。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203214321918.png" alt="image-20211203214321918"></p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203220045686.png" alt="image-20211203220045686"></p></li></ul><h5 id="删除-reloc节区"><a href="#删除-reloc节区" class="headerlink" title="删除.reloc节区"></a>删除.reloc节区</h5><ul><li><p>从上面的.reloc节区头截图可知.reloc节区文件的起始偏移为3600,用HEdit从3600偏移处到文件末端所有数据删除,这里因为是最后一个节区,所以直接删除对其他节区的文件偏移没有影响。</p></li><li><p>按以上4个步骤删除.reloc节区后,双击Tut.ReverseMe1.exe,可以发现程序仍能正常运行。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B09/image-20211203220542470.png" alt="image-20211203220542470"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记8</title>
<link href="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/"/>
<url>/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第十四章"><a href="#逆向工程核心原理第十四章" class="headerlink" title="逆向工程核心原理第十四章"></a>逆向工程核心原理第十四章</h3><h4 id="运行时压缩"><a href="#运行时压缩" class="headerlink" title="运行时压缩"></a>运行时压缩</h4><ul><li>运行时压缩器(Run-TimePacker)是软件逆向分析学的常见主题。为了理解好它,需要掌握有关PE文件格式、操作系统的基本知识(进程、内存、DLLL等),同时也要了解有关压缩/解压缩算法的基本内容。</li></ul><h4 id="数据压缩"><a href="#数据压缩" class="headerlink" title="数据压缩"></a>数据压缩</h4><ul><li>计算机中文件(数据)都是由二进制(0或1)组成的,只要使用合适的压缩算法,就能缩减其大小。经过压缩的文件若能100%恢复,则称该压缩为"无损压缩"(Lossless Data Compression);若不能恢复原状,则称该压缩为"有损压缩"(Loss Data Compression)。</li></ul><span id="more"></span><h5 id="无损压缩"><a href="#无损压缩" class="headerlink" title="无损压缩"></a>无损压缩</h5><ul><li>最具代表性的无损压缩算法有Run-Length、Lempel-Ziv、Huffman等,此外还有许多其他算法,它们都是在上面3种压缩算法等基础上改造而成的。只要准确理解了上面3种,就能轻松掌握其他各种压缩算法。</li></ul><h5 id="有损压缩"><a href="#有损压缩" class="headerlink" title="有损压缩"></a>有损压缩</h5><ul><li>有损压缩允许压缩文件(数据)时损失一定信息,以换取高压缩率。压缩多媒体文件(jpg、mp3、mp4)时,大部分都使用这种有损压缩方式。从压缩特性来看,有损压缩的数据解压后不能完全恢复原始数据。人的肉眼与听觉几乎无法察觉到这些多媒体文件在压缩中损失的数据。</li></ul><h5 id="运行时压缩器"><a href="#运行时压缩器" class="headerlink" title="运行时压缩器"></a>运行时压缩器</h5><ul><li><p>运行时压缩器是针对PE(Protable Executable)文件而言的,可执行文件内部含有解压缩代码,文件在运行瞬间于内存中解压缩后执行。</p></li><li><p>运行时压缩文件也是PE文件,内部含有原PE文件与解码程序。在程序的EP代码中执行解码程序,同时在内存中解压缩后执行。下表为运行时压缩与普通zip压缩的不同点。</p><table><thead><tr><th align="center">项 目</th><th align="center">普通压缩</th><th align="center">运行时压缩</th></tr></thead><tbody><tr><td align="center">对象文件</td><td align="center">所有文件</td><td align="center">PE文件(exe、dll、sys)</td></tr><tr><td align="center">压缩结果</td><td align="center">压缩文件(.zip、.rar等)</td><td align="center">PE文件(exe、dll、sys)</td></tr><tr><td align="center">解压缩方式</td><td align="center">使用专门解压缩程序</td><td align="center">内部含有解码程序</td></tr><tr><td align="center">文件是否可执行</td><td align="center">本身不可执行</td><td align="center">本身可执行</td></tr><tr><td align="center">优点</td><td align="center">可以对所有文件高压缩率压缩</td><td align="center">无须专门解压程序便可直接运行</td></tr><tr><td align="center">缺点</td><td align="center">若无专门解压缩软件则无法使用压缩文件</td><td align="center">每次运行均需调用解码程序导致运行时间过长</td></tr></tbody></table></li><li><p>把普通PE文件创建成运行时压缩文件的使用程序称为"压缩器"(Packer),经过反逆向(Anti-Reversing)技术特别处理的压缩器称为保护器(Protector)。</p></li></ul><h5 id="压缩器"><a href="#压缩器" class="headerlink" title="压缩器"></a>压缩器</h5><ul><li>使用目的:缩减PE文件大小、隐藏PE文件内部代码与资源</li><li>压缩器种类:大致分为两类:一类是单纯用于压缩普通PE文件的压缩器,常见有:UPX、ASPack等;另一类是对源文件进行较大变形、严重破坏PE头、意图不纯的压缩器(专门用于恶意程序),常见有UPack、PESpin、NSAnti等。</li></ul><h5 id="保护器"><a href="#保护器" class="headerlink" title="保护器"></a>保护器</h5><ul><li>使用目的:防止破解,没人愿意自己编写的程序被非法破解使用,此时使用保护器可有效保护PE文件;保护代码与资源,保护器不仅可以保护PE文件本身,还可以在文件运行时,保护进程内存,防止打开Dump窗口。因此,使用保护器可以比较安全地保护程序自身的代码与资源。</li><li>保护器种类:有商业程序和公用程序,商业保护器:ASProtect、Themida、SVKP等;公用保护器:UltraProtect、Morphine等。</li></ul><h4 id="运行时压缩测试"><a href="#运行时压缩测试" class="headerlink" title="运行时压缩测试"></a>运行时压缩测试</h4><ul><li><p>下面以前面用到的abexcm1.exe为例进行运行时压缩测试。</p></li><li><p>测试使用的压缩器为UPX:<a href="https://github.com/upx/upx">https://github.com/upx/upx</a>,下载后在cmd中运行<code>upx.exe -o abexcm1_upx.exe abexcm1.exe</code>即可</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211201172402003.png" alt="image-20211201172402003"></p></li><li><p>从运行结果的输出可以看到,文件大小从8192变为了6656,压缩率为81.25%。</p></li></ul><h5 id="比较abexcm1-exe和abexcm1-upx-exe"><a href="#比较abexcm1-exe和abexcm1-upx-exe" class="headerlink" title="比较abexcm1.exe和abexcm1_upx.exe"></a>比较abexcm1.exe和abexcm1_upx.exe</h5><ul><li><p>下图是从PE文件视觉比较两个文件的示意图,很好地反映了UPX压缩器的特点</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211201181531561.png" alt="image-20211201181531561"></p></li><li><p>abexcm1.exe与abexcm1_upx.exe比较项目</p><ul><li>PE头大小一样(0~400h)</li><li>节区名称和数量改变</li><li>abexcm1_upx.exe第一个节区的RawDataSize=0(文件中的大小为0)</li><li>abexcm1_upx.exe的EP位于第二个节区(原文件的EP在第一个节区)</li><li>资源节区(.rsrc)大小几乎无变化</li></ul></li><li><p>需要注意的是abexcm1_upx.exe第一个节区(UPX0)的RawDataSize为0,即第一个节区在磁盘文件中是不存在的,用PEView查看该节区头,可以看到VirtualSize为"00006000",这是为abexcm1_upx.exe运行时解压预留的空间。也就是说,程序运行时将(文件中的)压缩的代码解压到(内存中的)第一个节区,更详细点来说,解压缩代码与被压缩的代码都在第二个节区。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211201181743885.png" alt="image-20211201181743885"></p></li></ul><hr><h3 id="逆向工程核心原理第十五章"><a href="#逆向工程核心原理第十五章" class="headerlink" title="逆向工程核心原理第十五章"></a>逆向工程核心原理第十五章</h3><h4 id="调试UPX压缩的abexcm1程序"><a href="#调试UPX压缩的abexcm1程序" class="headerlink" title="调试UPX压缩的abexcm1程序"></a>调试UPX压缩的abexcm1程序</h4><h5 id="abexcm1-exe的EP代码"><a href="#abexcm1-exe的EP代码" class="headerlink" title="abexcm1.exe的EP代码"></a>abexcm1.exe的EP代码</h5><ul><li><p>先用Ollydbg查看原abexcm1.exe的EP代码,如下图所示,可以看到代码量很少,因为这是汇编编写的程序</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202154618660.png" alt="image-20211202154618660"></p></li></ul><h5 id="abexcm1-upx-exe的EP代码"><a href="#abexcm1-upx-exe的EP代码" class="headerlink" title="abexcm1_upx.exe的EP代码"></a>abexcm1_upx.exe的EP代码</h5><ul><li><p>再用Ollydbg打开abexcm1_upx.exe,查看相应的EP代码,如下图所示,可以看到代码发生了明显变化</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202155000431.png" alt="image-20211202155000431"></p></li><li><p>EP地址为"004071B0",该处即为第二个节区"UPX1"的末端部分。实际压缩的abexcm1.exe代码存在于EP地址"004071B0"上方。</p></li><li><p>下面查看代码开始的部分("004071B0")</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">004071B0 > $ 60 pushad</span><br><span class="line">004071B1 . BE 00704000 mov esi,abexcm1_.00407000</span><br><span class="line">004071B6 . 8DBE 00A0FFFF lea edi,dword ptr ds:[esi-0x6000]</span><br></pre></td></tr></table></figure></li><li><p>首先使用PUSHAD命令将寄存器EAX~EDI的值保存到栈,然后分别把第二个节区的起始地址"00407000"与第一个节区地址"00401000"设置到ESI和EDI寄存器。UPX文件的第一个节区仅存在于内存,该处即是解压缩后保存源文件代码的地方。</p></li><li><p>调试时,遇到这样同时设置ESI与EDI,就能预见从ESI所指缓冲区到EDI所指缓冲区到内存发生了复制。此时从Source(ESI)读取数据,解压缩后保存到Destination(EDI)。我们的目标是跟踪全部UPX EP代码,并最终找到原abexcm1的EP代码。</p></li></ul><h5 id="跟踪UPX文件"><a href="#跟踪UPX文件" class="headerlink" title="跟踪UPX文件"></a>跟踪UPX文件</h5><ul><li><p>下面开始跟踪代码,跟踪数量庞大的代码时,请遵循如下法则:"遇到循环时,先了解作用再跳出"</p></li><li><p>Ollydbg的跟踪命令:跟踪数量庞大的代码时,通常不会使用Step Into(F7)命令,而使用Ollydbg中另外提供的跟踪调试命令,如下表所示:</p><table><thead><tr><th align="center">命 令</th><th align="center">快捷键</th><th align="center">说 明</th></tr></thead><tbody><tr><td align="center">自动步入</td><td align="center">Ctrl+F7</td><td align="center">自动执行StepInto命令(画面显示)</td></tr><tr><td align="center">自动步过</td><td align="center">Ctrl+F8</td><td align="center">自动执行StepOver命令(画面显示)</td></tr><tr><td align="center">跟踪步入</td><td align="center">Ctrl+F11</td><td align="center">自动执行StepInto命令(画面不显示)</td></tr><tr><td align="center">跟踪步过</td><td align="center">Ctrl+F12</td><td align="center">自动执行StepOver命令(画面不显示)</td></tr></tbody></table></li><li><p>除了画面显示之外,自动命令与跟踪命令是类似的,由于自动命令要把跟踪过程显示在画面中,所以执行速度略微慢一些。两者最大差别在于,跟踪命令会自动在事先设置的跟踪条件处停下来,并生成日志文件。</p></li></ul><h5 id="循环-1"><a href="#循环-1" class="headerlink" title="循环#1"></a>循环#1</h5><ul><li><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">004071BD . /EB 0B jmp short abexcm1_.004071CA</span><br><span class="line">004071BF |90 nop</span><br><span class="line">004071C0 > |8A06 mov al,byte ptr ds:[esi]</span><br><span class="line">004071C2 . |46 inc esi ; abexcm1_.00407004</span><br><span class="line">004071C3 . |8807 mov byte ptr ds:[edi],al</span><br><span class="line">004071C5 . |47 inc edi ; abexcm1_.00401000</span><br><span class="line">004071C6 > |01DB add ebx,ebx</span><br><span class="line">004071C8 . |75 07 jnz short abexcm1_.004071D1</span><br><span class="line">004071CA > \8B1E mov ebx,dword ptr ds:[esi]</span><br><span class="line">004071CC . 83EE FC sub esi,-0x4</span><br><span class="line">004071CF 11DB adc ebx,ebx</span><br><span class="line">004071D1 ^ 72 ED jb short abexcm1_.004071C0</span><br></pre></td></tr></table></figure></li><li><p>循环开始前先从读取ESI"00407000"地址处的值并复制给EBX寄存器,然后将ESI寄存器的值减去-0x4(加0x4)得到新值"00407004",再执行<code>adc ebx ebx</code>=>"操作对象1 = 操作对象1 + 操作对象2 + CF",<code>jb short abexcm1_.004071C0</code>然后判断CF是否等于"1",是则跳转。然后进入"004071C0"开始循环,循环内容为从ESI("00407004")中读取一个字节写入EDI("00401000")中,然后分别增加ESI和EDI的值,在让EBX加上自己,直到执行<code>add ebx, ebx</code>指令无溢出时即CF=0循环终止。</p></li><li><p>调试遇到这样的循环应该跳出来,在"004071D3"地址处下断点,F9跳出循环</p></li></ul><h5 id="循环-2"><a href="#循环-2" class="headerlink" title="循环#2"></a>循环#2</h5><ul><li><p>在断点处再次使用自动步过(Ctrl+F8)命令继续跟踪代码,不久后遇到下图所示的循环,(比之前的循环要大,包含了第一个循环的代码),这个循环是正式解码循环(解压缩循环),不断从ESI所指的第二个节区(UPX1)地址中依次读取值,经过适当的运算解压后,将值写入EDI所指的第一个节区(UPX0)。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202195945064.png" alt="image-20211202195945064"></p></li><li><p>在"0040727A"处设置断点可跳过第二个循环,运行到"0040727A"时,左下角数据窗口Ctrl+G跳转至"00401000"地址,可以看到相应的代码已经解压第一个节区(UPX0)了,原来都是NULL填充的</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202202338099.png" alt="image-20211202202338099"></p></li></ul><h5 id="循环-3"><a href="#循环-3" class="headerlink" title="循环#3"></a>循环#3</h5><ul><li><p>重新跟踪代码,稍后会遇到如下图所示的第三个循环</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202201356737.png" alt="image-20211202201356737"></p></li><li><p>这部分代码用于恢复源代码的CALL/JMP指令(操作码:E8/E9)的目标地址。在"004072AE处"下断点运行后可以跳出该循环。到此接近尾声了,只要再设置好IAT,UPX解压缩代码就结束了。</p></li></ul><h5 id="循环-4"><a href="#循环-4" class="headerlink" title="循环#4"></a>循环#4</h5><ul><li><p>重新跟踪代码,再稍微进行一段,遇到第四个循环,该循环即为设置IAT的循环。在地址"004072AE"处设置EDI=00406000,它指向第二个节区(UPX1)区域,该区域保存着原abexcm1.exe调用的API函数名称的字符串。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202202130380.png" alt="image-20211202202130380"><br> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202202541268.png" alt="image-20211202202541268"></p></li><li><p>UPX压缩原abexcm1.exe文件时,它会分析其IAT,提取出程序中调用的API名称列表,形成API名称字符串。利用这些API名称字符串调用"004072DF"处的GetProcAddress()函数,获取API的起始地址,然后把API地址输入EBX寄存器所指的原abexcm1.exe的IAT区域。这个过程会反复执行,直到恢复所有原abexcm1.exe的IAT。</p></li><li><p>abexcm1.exe全部解压缩完成后,应该将程序的控制返回到OEP处,下图显示的就是跳转到OEP的代码。另外"00407325"处的POPAD命令与UPX代码的第一条PUSHAD命令对应,用来把当前寄存器恢复原状。</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202203827648.png" alt="image-20211202203827648"></p></li></ul><h5 id="快速查找UPX-OEP的方法"><a href="#快速查找UPX-OEP的方法" class="headerlink" title="快速查找UPX OEP的方法"></a>快速查找UPX OEP的方法</h5><ul><li><p>在POPAD指令后的JMP指令处设置断点:UPX压缩器的特征之一是,其EP代码被包含在PUSHAD/POPAD之间。并且,跳转到OEP代码的JMP指令紧接着出现在POPAD指令之后。只要在JMP指令处设置好断点,运行就能直接找到OEP了。</p><blockquote><p>PUSHAD:将8个通用寄存器(EAX~EDX)的值保存到栈</p><p>POPAD:把PUSHAD命令储存在栈的值再次恢复到各个寄存器</p></blockquote></li></ul><h5 id="在栈中设置硬件断点"><a href="#在栈中设置硬件断点" class="headerlink" title="在栈中设置硬件断点"></a>在栈中设置硬件断点</h5><ul><li><p>这个方法同样利用UPX的PUSHAD/POPAD指令的特点。在执行"004071B0"地址处的PUSHAD命令后,查看栈</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202205233698.png" alt="image-20211202205233698"></p></li><li><p>EAX到EDX寄存器的值依次被储存到栈。从Ollydbg的Dump窗口进入栈地址"0019FF54",将光标定位到19FF54地址,右键按下图所示设置硬件断点</p><p> <img src="/note/2021/12/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B08/image-20211202205604651.png" alt="image-20211202205604651"></p></li><li><p>硬件断点是CPU支持的断点,最多可以设置4个。与普通断点不同的是,硬件断点的指令执行完成后才暂停调试。在这种状态下,程序会边解压缩边执行代码,在执行到POPAD的瞬间访问设置有硬件断点的0019FF54地址,然后暂停调试。其下方即是跳转至OEP的JMP指令。</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记7</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第十三章"><a href="#逆向工程核心原理第十三章" class="headerlink" title="逆向工程核心原理第十三章"></a>逆向工程核心原理第十三章</h3><h4 id="PE文件格式"><a href="#PE文件格式" class="headerlink" title="PE文件格式"></a>PE文件格式</h4><h5 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h5><ul><li>PE(Portable Executable)是Windows下的可执行文件,是微软在UNIX平台的COFF(Common Object File Format)基础上制作的。最初(正如Portable这个单词所代表那样)设计用来提高程序在不同操作系统的移植性,但实际上这种文件格式只有在Windows系列的操作系统能运行</li></ul><span id="more"></span><h5 id="PE文件格式-1"><a href="#PE文件格式-1" class="headerlink" title="PE文件格式"></a>PE文件格式</h5><table><thead><tr><th align="center">种类</th><th align="center">主扩展名</th><th align="center">种类</th><th align="center">主扩展名</th></tr></thead><tbody><tr><td align="center">可执行系列</td><td align="center">EXE、SRC</td><td align="center">驱动程序系列</td><td align="center">SYS、VXD</td></tr><tr><td align="center">库系列</td><td align="center">DLL、OCX、CPL、DRV</td><td align="center">对象文件系列</td><td align="center">OBJ</td></tr></tbody></table><ul><li><p>除OBJ(对象)文件外,所有文件都是可执行的,DLL、SYS等虽然不能直接在Shell(Explorer.exe)运行,但可以使用其他方法(调试器、服务等)执行。</p></li><li><p>用十六进制编辑器打开win10的notepad.exe,查看PE文件的特征,下图为PE文件的头部分(PE header),notepad.exe运行所需要的DLL有哪些、需要多大的栈/堆内存这些信息就包含在这个PE头中</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/2021-11-26.11.20.31.png" alt="2021-11-26.11.20.31"></p></li><li><p>从DOS头(DOS header)到节区头(Section header)是PE头部分,其余的节区合称PE体(PE body)。文件中使用偏移(offset),内存中使用VA(Virtual Address,虚拟地址)来表示位置。文件加载到内存时,情况就会发生变化(节区的大小、位置等)。文件等内容一般可分为(.text)、数据(.data)、资源(.rsrc)节,分别保存。</p></li><li><p>各节区头定义了各节区在文件或内存中的大小、位置、属性等。PE头与各节区尾部存在一个区域,称为NULL填充(NULL padding)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211129162653285.png" alt="image-20211129162653285"></p></li></ul><h5 id="VA与RVA"><a href="#VA与RVA" class="headerlink" title="VA与RVA"></a>VA与RVA</h5><ul><li><p>VA指的是进程虚拟内存的绝对地址,RVA(Relative Virtual Address,相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址,VA与RVA满足以下换算关系</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">RVA + ImageBase = VA</span><br></pre></td></tr></table></figure></li></ul><h5 id="PE头"><a href="#PE头" class="headerlink" title="PE头"></a>PE头</h5><ul><li><p>DOS头:为了兼容DOS,PE头最前面有个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><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_DOS_HEADER</span></span><br><span class="line">{</span><br><span class="line"> WORD e_magic;<span class="comment">// DOS signature: 4D5A("MZ")</span></span><br><span class="line"> WORD e_cblp;</span><br><span class="line"> WORD e_cp;</span><br><span class="line"> WORD e_crlc;</span><br><span class="line"> WORD e_cparhdr;</span><br><span class="line"> WORD e_minalloc;</span><br><span class="line"> WORD e_maxalloc;</span><br><span class="line"> WORD e_ss;</span><br><span class="line"> WORD e_sp;</span><br><span class="line"> WORD e_csum;</span><br><span class="line"> WORD e_ip;</span><br><span class="line"> WORD e_cs;</span><br><span class="line"> WORD e_lfarlc;</span><br><span class="line"> WORD e_ovno;</span><br><span class="line"> WORD e_res[<span class="number">4</span>];</span><br><span class="line"> WORD e_oemid;</span><br><span class="line"> WORD e_oeminfo;</span><br><span class="line"> WORD e_res2[<span class="number">10</span>];</span><br><span class="line"> LONG e_lfanew;<span class="comment">// offset to NT header</span></span><br><span class="line">} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>IMAGE_DOS_HEADER结构体大小为64个字节,其中有两个比较重要的成员e_magic和e_lfanew</p><ul><li>e_magic: DOS签名(固定值,"4D5A"=>ASCII值"MZ"=>"Mark Zbikowski"首字母)</li><li>e_lfanew: 指示NT头的偏移(根据不同文件拥有可变值)</li></ul></li><li><p>用Hex Editor打开notepad.exe查看IMAGE_DOS_HEADER结构体,根据PE规范文件开始的2个字节为"4D5A",e_lfanew值为"00000100"而不是"00010000"(小端序存储,低位在前)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126153219527.png" alt="image-20211126153219527"></p></li><li><p>这里有个奇怪的地方,是这样的,同一个notepad.exe用010 editor和HexEdit打开,e_lfanew的值是不一样的,但两者都指向了正确位置,从下图可以看到010 editor的e_lfanew值为"000000F8",对比010 editor和Hex Editor两者,发现两者主要区别是从地址"000000E4"开始,010 editor要少一些00,从这点上来推测是跟NULL填充有关。另外试了WinHex,结果跟HexEdit一样;试了BeyondCompare,结果跟010 editor一样;所以这并不能推断出两者谁对谁错或者都是对的,这里记下问题,待后续学习解决。不过无论是010 editor还是HexEdit,e_lfanew值都是正确指向了NT头的位置。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126154540657.png" alt="image-20211126154540657"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126154234660.png" alt="image-20211126154234660"></p></li></ul><h5 id="DOS存根"><a href="#DOS存根" class="headerlink" title="DOS存根"></a>DOS存根</h5><ul><li><p>DOS存根在DOS头下方,是个可选项,且大小不固定(即使没有DOS存根,文件也能正常运行)</p></li><li><p>DOS存根由代码与数据混合而成,下图展示了DOS存根所在位置,其中在40~4D区域为16位汇编指令,Windows中不会执行该命令,(因为被识别为PE文件所以完全忽视该代码),在DOS环境中则可以运行那部分代码(不认识PE文件格式,被识别为DOS EXE文件)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126160216935.png" alt="image-20211126160216935"></p></li><li><p>接下来测试下这部分dos命令,因为这里用的是win10,所以cmd命令行已经没有debug命令了,要像书中那样测试,就需要用到<a href="https://www.dosbox.com/">DOSBox</a>和<a href="https://github.com/Microsoft/MS-DOS">MS-DOS</a>,来测试。</p></li><li><p>下载安装好DOSBox后,打开DOSBox,可以看到以下界面,DOSBox默认没有挂载我们的硬盘,需要我们自己挂载硬盘路径</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126162223300.png" alt="image-20211126162223300"></p></li><li><p>运行命令<code>mount c c:\</code>,挂载C盘,出现以下信息说明挂载成功</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126162404348.png" alt="image-20211126162404348"></p></li><li><p>接下来我们进入MS-DOS的解压路径</p> <figure class="highlight powershell"><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">c: <span class="comment"># 切换c盘</span></span><br><span class="line"><span class="built_in">cd</span> USERS\CC\DESKTOP\MS<span class="literal">-DOS</span>\V2.<span class="number">0</span>\BIN <span class="comment"># MS-DOS解压后的v2.0 bin所在路径</span></span><br></pre></td></tr></table></figure><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126162617566.png" alt="image-20211126162617566"></p></li><li><p>执行<code>DEBUG.COM C:\WINDOWS\SYSTEM32\NOTEPAD.EXE</code>,如下图出现了16位汇编指令</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126163011504.png" alt="image-20211126163011504"></p></li><li><p>我们还可以用DOSBox直接运行notepad.exe,如下图,可以看到DOS下运行notepad.exe会出现"This program cannot be run in DOS mode."</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211126163316314.png" alt="image-20211126163316314"></p></li><li><p>利用以上特性就可以编写出可以同时在DOS和Windows运行的程序</p></li></ul><h5 id="NT头"><a href="#NT头" class="headerlink" title="NT头"></a>NT头</h5><ul><li><p>NT头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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_NT_HEADERS</span> {</span><br><span class="line"> DWORD Signature; <span class="comment">// PE Signature : 50450000("PE"00)</span></span><br><span class="line"> IMAGE_FILE_HEADER FileHeader;</span><br><span class="line"> IMAGE_OPTIONAL_HEADER32 OptionalHeader;</span><br><span class="line">} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;</span><br></pre></td></tr></table></figure></li><li><p>NT头包含文件头和可选头,下面介绍文件头和可选头结构体</p></li><li><p>文件头(_IMAGE_FILE_HEADER)是表现文件大致属性的IMAGE_FILE_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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_FILE_HEADER</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><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>IMAGE_FILE_HEADER结构体有以下4种重要成员</p><ul><li>Machine: 每个CPU都有唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C,兼容64位AMD x64芯片的Machine码为8664,分别用010 editor和Hex Edit打开C:\Windows\System32\notepad.exe,010 editor识别的机器码是8664,Hex Edit则14C,说明一个识别为64位,另一个则识别为32位,这里推测原因是:010 editor是64位的程序,Hex Edit为32位,notepad.exe文件在64位虚拟内存和32位虚拟内存中是有差异的。这种差异应该也是造成上面e_lfanew不一样的原因。</li><li>NumberOfSections: 用来指出PE文件中存在的节区数量,该值一定要大于0,且当定义的节区数与实际节区不同时,将运行出错</li><li>SizeOfOptionalHeader: IMAGE_NT_HEADERS结构体最后一个成员为IMAGE_OPTIONAL_HEADER32结构体,SizeOfOptionalHeader用来指出IMAGE_OPTIONAL_HEADER32结构体长度供PE装载器识别。PE32+文件中使用的是IMAGE_OPTIONAL_HEADER64,而不是IMAGE_OPTIONAL_HEADER32,两者大小不同,因此需要SizeOfOptionalHeader中指出结构体的大小。</li><li>Characteristics: 该字段用于标识文件的属性(文件是否可执行、是否为DLL等信息),以bit OR形式组合起来</li></ul></li><li><p>可选头(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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_DATA_DIRECTORY</span> {</span><br><span class="line"> DWORD VirtualAddress;</span><br><span class="line"> DWORD Size;</span><br><span class="line">} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;</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><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="keyword">struct</span> <span class="title class_">_IMAGE_OPTIONAL_HEADER</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></li><li><p>在IMAGE_OPTIONAL_HEADER32结构体中比较重要的成员,有以下几个:</p><ul><li><p>Magic:为IMAGE_OPTIONAL_HEADER32时为10B,为IMAGE_OPTIONAL_HEADER64时为20B</p></li><li><p>AddressOfEntryPoint:EP的RVA值,这个值指出程序最先执行的代码起始地址,非常重要</p></li><li><p>ImageBase:32位系统中进程虚拟地址为0~FFFFFFFF,PE文件被加载到如此大的内存中时,ImageBase指出文件的优先装入地址。EXE、DLL被装载在0~7FFFFFFF中,SYS文件被载入内核内存的80000000~FFFFFFFF中。一般使用开发工具(VB/VC++/Delphi)创建好EXE文件后,其ImageBase值为00400000,DLL文件的ImageBase值为10000000(当然也可以为其他),执行PE文件时,PE装载器先创建进程,再把文件载入内存,然后把EIP寄存器的值设为ImageBase+AddressOfEntryPoint</p></li><li><p>SectionAlignment,FileAlignment:PE文件的Body部分划分为若干个节区,这些节区储存着不同类别的数据。FileAlignment指定了节区在磁盘文件中的最小单位,而SectionAlignment则指定了节区在内存中的最小单位(一个文件中FileAlignment的值与SectionAlignment的值可能相同也可能不相同)。磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment的整数倍。</p></li><li><p>SizeOfImage:该值指定了PE Image在虚拟内存中所占空间的大小</p></li><li><p>SizeOfHeaders:该值用来指出整个PE头的大小,值必须为FileAlignment的整数倍。第一节区所在位置与SizeOfHeaders距文件开始偏移的量相同。</p></li><li><p>Subsystem:该值用来区分系统驱动文件(*.sys)与普通的可执行文件(*.exe,*.dll)。Subsystem成员可拥有的值如下所示</p><table><thead><tr><th align="center">值</th><th align="center">含义</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">Driver文件</td><td align="center">系统驱动(如:ntfs.sys)</td></tr><tr><td align="center">2</td><td align="center">GUI文件</td><td align="center">窗口应用程序(如:notepad.exe)</td></tr><tr><td align="center">3</td><td align="center">CUI文件</td><td align="center">控制台应用程序(如:cmd.exe)</td></tr></tbody></table></li><li><p>NumberOfRvaAndSizes:用来指定DataDirectory(IMAGE_OPTIONAL_HEADER32结构体最后一个成员)数组的个数,供PE装载器识别</p></li><li><p>DataDirectory:由IMAGE_DATA_DIRECTORY结构体组成的数组,数组的每项都有被定义的值,DataDirectory的值如下,比较重要的是前两个</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></pre></td><td class="code"><pre><span class="line">DataDirectory[<span class="number">0</span>] = EXPORT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">1</span>] = IMPORT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">2</span>] = RESOURCE Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">3</span>] = EXCEPTION Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">4</span>] = SECURITY Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">5</span>] = BASERELOC Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">6</span>] = DEBUG Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">7</span>] = COPYRIGHT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">8</span>] = GLOBALPTR Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[<span class="number">9</span>] = TLS Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[A] = LOAD_CONFIG Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[B] = BOUND_IMPORT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[C] = IAT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[D] = DELAY_IMPORT Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[E] = COM_DESCRIPTOR Directory</span><br><span class="line"></span><br><span class="line">DataDirectory[F] = Reserved Directory</span><br></pre></td></tr></table></figure></li></ul></li><li><p>notepad.exe中IMAGE_OPTIONAL_HEADER32的结构体如下所示:</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211127170006761.png" alt="image-20211127170006761"></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><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></pre></td><td class="code"><pre><span class="line">[ IMAGE_OPTIONAL_HEADER ]</span><br><span class="line">offsetvaluedescription</span><br><span class="line">=======================================================================================</span><br><span class="line">00000108010Bmagic</span><br><span class="line">0000011A0Emajor linker version</span><br><span class="line">0000011B14minor linker version</span><br><span class="line">0000011C00022400size of code</span><br><span class="line">0000012000007600size of initialized data</span><br><span class="line">0000012400000000size of uninitialized data</span><br><span class="line">0000012800021860address of entry point</span><br><span class="line">0000012C00001000base of code</span><br><span class="line">0000013000024000base of data</span><br><span class="line">0000013400400000image base</span><br><span class="line">0000013800001000section alignment</span><br><span class="line">0000013C00000200file alignment</span><br><span class="line">00000140000Amajor OS version</span><br><span class="line">000001420000minor OS version</span><br><span class="line">00000144000Amajor image version</span><br><span class="line">000001460000minor image version</span><br><span class="line">00000148000Amajor subsystem version</span><br><span class="line">0000014A0000minor subsustem version</span><br><span class="line">0000014C00000000win32 version value</span><br><span class="line">000001500002E000size of image</span><br><span class="line">0000015400000400size of headers</span><br><span class="line">000001580002C834Checksum</span><br><span class="line">0000015C0002subsystem</span><br><span class="line">0000015EC140DLL characteristic</span><br><span class="line">0000016000040000size of stack reserve</span><br><span class="line">0000016400011000size of stack commit</span><br><span class="line">0000016800100000size of heap reserve</span><br><span class="line">0000016C00001000size of heap commit</span><br><span class="line">0000017000000000loader flags</span><br><span class="line">0000017400000010number of directories</span><br><span class="line">0000017800000000RVA of export directory</span><br><span class="line">0000017C00000000size of export directory</span><br><span class="line">000001800002647CRVA of import directory</span><br><span class="line">000001840000021Csize of import directory</span><br><span class="line">000001880002A000RVA of resource directory</span><br><span class="line">0000018C00000BD8size of resource directory</span><br><span class="line">0000019000000000RVA of exception directory</span><br><span class="line">0000019400000000size of exception directory</span><br><span class="line">0000019800000000RVA of security directory</span><br><span class="line">0000019C00000000size of security directory</span><br><span class="line">000001A00002B000RVA of basereloc directory</span><br><span class="line">000001A400002428size of basereloc directory</span><br><span class="line">000001A800004FA0RVA of debug directory</span><br><span class="line">000001AC00000054size of debug directory</span><br><span class="line">000001B000000000RVA of copyright directory</span><br><span class="line">000001B400000000size of copyright directory</span><br><span class="line">000001B800000000RVA of globallptr directory</span><br><span class="line">000001BC00000000size of globallptr directory</span><br><span class="line">000001C00000140CRVA of TLS directory</span><br><span class="line">000001C400000018size of TLS directory</span><br><span class="line">000001C800001360RVA of LOAD_CONFIG directory</span><br><span class="line">000001CC000000ACsize of LOAD_CONFIG directory</span><br><span class="line">000001F000000000RVA of BOUND_IMPORT directory</span><br><span class="line">000001F400000000size of BOUND_IMPORT directory</span><br><span class="line">000001F800026000RVA of IAT directory</span><br><span class="line">000001FC00000478size of IAT directory</span><br><span class="line">000001E000022E48RVA of DELAY_IMPORT directory</span><br><span class="line">000001E4000000E0size of DELAY_IMPORT directory</span><br><span class="line">000001E800000000RVA of COM_DESCRIPTOR directory</span><br><span class="line">000001EC00000000size of COM_DESCRIPTOR directory</span><br><span class="line">000001F000000000RVA of reserved directory</span><br><span class="line">000001F400000000size of reserved directory</span><br></pre></td></tr></table></figure></li><li><p>节区头:节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区,节区头定义了各节区属性,不同内存属性的访问权限如下</p><table><thead><tr><th align="center">类别</th><th align="center">访问权限</th></tr></thead><tbody><tr><td align="center">code</td><td align="center">执行,读取权限</td></tr><tr><td align="center">data</td><td align="center">非执行,读写权限</td></tr><tr><td align="center">resource</td><td align="center">非执行,读取权限</td></tr></tbody></table></li><li><p>IMAGE_SECTION_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></pre></td><td class="code"><pre><span class="line"><span class="meta"># <span class="keyword">define</span> IMAGE_SIZEOF_SHORT_NAME8</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_SECTION_HEADER</span> {</span><br><span class="line"> BYTE Name[IMAGE_SIZEOF_SHORT_NAME];</span><br><span class="line"> <span class="keyword">union</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></li><li><p>IMAGE_SECTION_HEADER重要成员</p><table><thead><tr><th align="center">项目</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center">VirtualSize</td><td align="center">内存中节区所占大小</td></tr><tr><td align="center">VirtualAddress</td><td align="center">内存中节区起始地址(RVA)</td></tr><tr><td align="center">SizeOfRawData</td><td align="center">磁盘文件中节区所占大小</td></tr><tr><td align="center">PointerToRawData</td><td align="center">磁盘文件中节区起始位置</td></tr><tr><td align="center">Characteristics</td><td align="center">节区属性(bit OR)</td></tr></tbody></table></li><li><p>VirtualAddress和PointerToRawData不带有任何值,分别由(定义在IMAGE_OPTIONAL_HEADER32中的)SectionAlignment,FileAlignment确定。</p></li><li><p>VirtualSize与SizeOfRawData一般有不同的值,即磁盘文件中节区的大小与加载到内存中的节区大小是不同的</p></li><li><p>Characteristics的值由多个值组合(bit OR)而成</p></li><li><p>Name成员:Name成员不像C语言中的字符串一样以NULL结束,并且没有"必须使用ASCII值"的限制。PE规范未明确规定节区的Name,所以可以向其中放入任何值,甚至可以填充NULL值,所以节区Name仅供参考,不能保证其百分之百地被用作某种信息。</p></li><li><p>notepad.exe的节区头数据,如下图所示</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211129104554159.png" alt="image-20211129104554159"></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><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></pre></td><td class="code"><pre><span class="line">[ IMAGE_SECTION_HEADER ]</span><br><span class="line">offsetvaluedescriptrion</span><br><span class="line">=======================================================================================</span><br><span class="line">000001F82E746578Name(.text)</span><br><span class="line">000001FC74000000</span><br><span class="line">00000200000223B8virtual size</span><br><span class="line">0000020400001000RVA</span><br><span class="line">0000020800022400size of raw data</span><br><span class="line">0000020C00000400offset to raw data</span><br><span class="line">0000021000000000offset to relocations</span><br><span class="line">0000021400000000offset to line numbers</span><br><span class="line">000002180000number of relocations</span><br><span class="line">0000021A0000number of line numbers</span><br><span class="line">0000021C60000020characteristics</span><br><span class="line"></span><br><span class="line">000002202E646174Name(.data)</span><br><span class="line">0000022461000000</span><br><span class="line">0000022800001F74virtual size</span><br><span class="line">0000022C00024000RVA</span><br><span class="line">0000023000000A00size of raw data</span><br><span class="line">0000023400022800offset to raw data</span><br><span class="line">0000023800000000offset to relocations</span><br><span class="line">0000023C00000000offset to line numbers</span><br><span class="line">000002400000number of relocations</span><br><span class="line">000002420000number of line numbers</span><br><span class="line">00000244C0000040characteristics</span><br><span class="line"></span><br><span class="line">000002482E696461Name(.idata)</span><br><span class="line">0000024C74610000</span><br><span class="line">000002500000214Evirtual size</span><br><span class="line">0000025400026000RVA</span><br><span class="line">0000025800002200size of raw data</span><br><span class="line">0000025C00023200offset to raw data</span><br><span class="line">0000026000000000offset to relocations</span><br><span class="line">0000026400000000offset to line numbers</span><br><span class="line">000002680000number of relocations</span><br><span class="line">0000026A0000number of line numbers</span><br><span class="line">0000026C40000040characteristics</span><br><span class="line"></span><br><span class="line">000002702E646964Name(.didat)</span><br><span class="line">0000027461740000</span><br><span class="line">00000278000000BCvirtual size</span><br><span class="line">0000027C00029000RVA</span><br><span class="line">0000028000000200size of raw data</span><br><span class="line">0000028400025400offset to raw data</span><br><span class="line">0000028800000000offset to relocations</span><br><span class="line">0000028C00000000offset to line numbers</span><br><span class="line">000002900000number of relocations</span><br><span class="line">000002920000number of line numbers</span><br><span class="line">00000294C0000040characteristics</span><br><span class="line"></span><br><span class="line">000002982E727372Name(.rsrc)</span><br><span class="line">0000029C63000000</span><br><span class="line">000002A000000BD8virtual size</span><br><span class="line">000002A40002A000RVA</span><br><span class="line">000002A800000C00size of raw data</span><br><span class="line">000002AC00025600offset to raw data</span><br><span class="line">000002B000000000offset to relocations</span><br><span class="line">000002B400000000offset to line numbers</span><br><span class="line">000002B80000number of relocations</span><br><span class="line">000002BA0000number of line numbers</span><br><span class="line">000002BC40000040characteristics</span><br><span class="line"></span><br><span class="line">000002C02E72656CName(.reloc)</span><br><span class="line">000002C46F630000</span><br><span class="line">000002C800002428virtual size</span><br><span class="line">000002CC0002B000RVA</span><br><span class="line">000002D000002600size of raw data</span><br><span class="line">000002D400026200offset to raw data</span><br><span class="line">000002D800000000offset to relocations</span><br><span class="line">000002DC00000000offset to line numbers</span><br><span class="line">000002E00000number of relocations</span><br><span class="line">000002E20000number of line numbers</span><br><span class="line">000002E442000040characteristics</span><br></pre></td></tr></table></figure></li></ul><h5 id="RVA-to-RAW"><a href="#RVA-to-RAW" class="headerlink" title="RVA to RAW"></a>RVA to RAW</h5><ul><li><p>PE文件加载到内存时,每个节区都要能准确完成内存地址与文件偏移的映射,这种映射一般称为RVA to RAW,方法如下:</p><ul><li><p>查找RVA所在节区</p></li><li><p>使用简单公式计算文件偏移(RAW)</p></li><li><p>根据IMAGE_SECTION_HEADER结构体,换算公式如下:</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">RAW - PointerToRawData = RVA - VirtualAdress</span><br><span class="line"></span><br><span class="line">RAW = RVA - VirtualAdress + PointerToRawData</span><br></pre></td></tr></table></figure></li></ul></li><li><p>下图为win10 notepad.exe的文件与内存间的映射关系</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211129162653285.png" alt="image-20211129162653285"></p></li><li><p>测试练习(注:这里notepad.exe文件与内存映射关系跟《逆向工程核心原理》书中不一样是因为notepad.exe的版本不一样)</p><ul><li><p>Q1: RVA=5000时,File Offset=?</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">A1:首先查找RVA值所在节区。</span><br><span class="line">=> RVA 5000位于第一个节区(.text)(假设ImageBase为00400000)</span><br><span class="line">使用公式换算如下:</span><br><span class="line">=> RAW = 5000(RVA)-1000(VirtualAddress)+400(PointerToRawData)=4400</span><br></pre></td></tr></table></figure></li><li><p>Q2:RVA=13314时,File Offset=?</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">A2:查找RVA值所在节区。</span><br><span class="line">=> RVA 13314位于第一个节区(.text)</span><br><span class="line">使用公式换算如下:</span><br><span class="line">=> RAW = 13314(RVA)-1000(VirtualAddress)+400(PointerToRawData)=12714</span><br></pre></td></tr></table></figure></li><li><p>Q3:RVA=时25999,File Offset=?</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">A3:查找RVA值所在节区。</span><br><span class="line">=> RVA 25999位于第二节区(.data)</span><br><span class="line">使用公式换算如下:</span><br><span class="line">=> RAW = 25999(RVA)-24000(VA)+22800(PointerToRawData)=24799(X)</span><br><span class="line">=> 计算结果为RAW=24799,但是该偏移在第三个节区(.rsrc)。RVA在第二个节区,而RAW在第三个节区,这显然是错误的。这种情况表明"无法定义与RVA(25999)相对应的RAW值"。出现这种情况的原因在于,第二个节区的VirtualSize值要比SizeOfRawData值大。</span><br></pre></td></tr></table></figure></li></ul></li></ul><h5 id="IAT"><a href="#IAT" class="headerlink" title="IAT"></a>IAT</h5><ul><li><p>IAT(Import Address Table,导入地址表):IAT保存的内容与Windows操作系统的核心进程、内存、DLL结构等有关。</p></li><li><p>学习IAT前先学习一下DLL内容(Dynamic Link Library,动态链接库)的知识,16位DOS时代不存在DLL这一概念,只有"库"(Library)一说,编译器编译时会程序调用的外部函数二进制代码加入到程序中,这就造成了磁盘和内存含大量重复的问题,而DLL就是用来解决这一问题的。DLL概念如下:</p><ul><li>不要把库包含到程序中,单独组成DLL文件,需要时调用即可</li><li>内存映射技术使加载后的DLL代码、资源在多个进程中实现共享</li><li>更新库时只要替换相关DLL文件即可,简便易行</li></ul></li><li><p>加载DLL的方式有两种:一种是"显示链接"(Explicit Linking),程序使用DLL时加载,使用完后释放内存。另一种是"隐示链接"(Implicit Linking),程序运行时即一同加载DLL,程序终止时再释放占用内存</p></li><li><p>下图是调用CreateFileW()函数的代码,调用该函数并非直接调用,而是通过读取"763B0FD0"处的值("75F4FFC0")来间接调用而不是直接<code>call 75F4FFC0</code>。地址"763B0FD0"是notepad.exe的内存区域(更确切地说是IAT内存区域)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211129215650794.png" alt="image-20211129215650794"></p></li><li><p>IMAGE_IMPORT_DESCRIPTOR,IMAGE_IMPORT_DESCRIPTOR结构体中记录着PE文件要导入哪些库文件,IMAGE_IMPORT_DESCRIPTOR结构体如下所示</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_IMPORT_DESCRIPTOR</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">union</span> {</span><br><span class="line"> DWORD Characteristics; </span><br><span class="line"></span><br><span class="line"> DWORD OriginalFirstThunk; <span class="comment">// INT(Import Name Table) address (RVA)</span></span><br><span class="line"></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> DWORD TimeDateStamp; </span><br><span class="line"></span><br><span class="line"> DWORD ForwarderChain;</span><br><span class="line"></span><br><span class="line"> DWORD Name; <span class="comment">// library name string address (RVA) </span></span><br><span class="line"></span><br><span class="line"> DWORD FirstThunk; <span class="comment">// IAT(Import Address Table) address (RVA)</span></span><br><span class="line"></span><br><span class="line">} IMAGE_IMPORT_DESCRIPTOR;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> sturct _IMAGE_IMPORT_BY_NAME {</span><br><span class="line">WORDHint;<span class="comment">// ordinal</span></span><br><span class="line">BYTEName[<span class="number">1</span>];<span class="comment">// function name string</span></span><br><span class="line">} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;</span><br></pre></td></tr></table></figure></li><li><p>执行一个普通程序时,往往需要导入多个库,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体,这些结构体形成了数组,且结构体数组最后以NULL结构体结束。IMAGE_IMPORT_DESCRIPTOR中的重要成员如下表所示</p><ul><li>INT与IAT是长整型(4个字节数据类型)数组,以NULL结束</li><li>INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值)</li><li>INT与IAT大小应相同</li></ul><table><thead><tr><th align="center">项 目</th><th align="center">含 义</th></tr></thead><tbody><tr><td align="center">OriginalFirstThunk</td><td align="center">INT的地址(RVA)</td></tr><tr><td align="center">Name</td><td align="center">库名称字符串的地址</td></tr><tr><td align="center">FirstThunk</td><td align="center">IAT的地址(RVA)</td></tr></tbody></table></li><li><p>下图描述了notepad.exe的kernel32.dll的IMAGE_IMPORT_DESCRIPTOR结构,图中INT和IAT指向了相同地址,但也有很多情况下它们是不一致的。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130092303445.png" alt="image-20211130092303445"></p></li><li><p>接下来了解下PE装载器把导入函数输入至IAT的顺序</p><ol><li><p>读取IID的Name成员,获取库名称字符串"kernel32.dll"</p></li><li><p>装载相应库=>LoadLibrary("kernel32.dll")</p></li><li><p>读取IID的OriginalFirstThunk成员,获取INT地址</p></li><li><p>逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)</p></li><li><p>使用IMAGE_IMPORT_BY_NAME的Hint(ordinal)或Name项,获取相应函数的起始地址=>GetProcAddress("GetCurrentThreadId")</p></li><li><p>读取IID的FirstThunk(IAT)成员,获得IAT地址</p></li><li><p>将上面获得的函数地址输入相应的IAT数组值</p></li><li><p>重复步骤4~7,直到INT结束(遇到NULL)</p></li></ol></li><li><p>IMAGE_IMPORT_DESCRIPTOR结构体不位于PE头中,它位于PE体中,但查找其位置的信息在PE头中。IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress的值即是IMAGE_IMPORT_DESCRIPTOR结构体数组的起始地址(RVA值)。IMAGE_IMPORT_DESCRIPTOR结构体数组也被称为IMPORT Directory Table</p></li><li><p>从上面展示IMAGE_OPTIONAL_HEADER32结构体的数据里面可以得知IMAGE_IMPORT_DESCRIPTOR数组的起始地址为"0002647C"(RVA)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130094432647.png" alt="image-20211130094432647"></p></li><li><p>因为RVA为"2647C"所以文件偏移为"2367C"(使用RVA To RAW公式计算)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130101448622.png" alt="image-20211130101448622"></p></li><li><p>下面查看IMAGE_IMPORT_DESCRIPTOR结构体数组的第一个元素的各个成员</p><table><thead><tr><th align="center">文件偏移</th><th align="center">成员</th><th align="center">RVA</th><th align="center">RAW</th></tr></thead><tbody><tr><td align="center">2367C</td><td align="center">OriginalFirstThunk(INT)</td><td align="center">00026700</td><td align="center">00023900</td></tr><tr><td align="center">23680</td><td align="center">TimeDateStamp</td><td align="center">-</td><td align="center">-</td></tr><tr><td align="center">23684</td><td align="center">ForwarderChain</td><td align="center">-</td><td align="center">-</td></tr><tr><td align="center">23688</td><td align="center">Name</td><td align="center">00027110</td><td align="center">00024310</td></tr><tr><td align="center">2368C</td><td align="center">FirstThunk(IAT)</td><td align="center">00026068</td><td align="center">00023268</td></tr></tbody></table></li><li><p>库名称(Name),根据RVA"27110"转换得到RAW"24310",查看notepad.exe文件偏移"24310"处,可以看到字符串"KERNEL32.dll"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130102614648.png" alt="image-20211130102614648"></p></li><li><p>OriginalFirstThunk - INT,INT是一个包含导入函数信息(Ordinal,Name)的结构体指针数组,只有获得了这些信息才能在加载到进程内存的库中准确求得相应的函数的起始地址。跟踪OriginalFirstThunk成员RVA:"26700"=>RAW:"23900",INT以地址数组组成(以NULL结束)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130134400940.png" alt="image-20211130134400940"></p></li><li><p>IMAGE_IMPORT_BY_NAME,跟踪数组的第一个值"00026D3C"(RVA)=>"00023F3C"(RAW),其中文件偏移"00023F3C"处最初两个字节"02B0"为Ordinal,是库中函数的固有编号。Ordinal的后面可以看到导入的API函数名称字符串"GetProcAddress"(同C语言一样以'\0'结束)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130134720401.png" alt="image-20211130134720401"></p></li><li><p>FirstThunk - IAT(Import Address Table),IAT的RVA为"26068"则RAW为"23268",于INT类似IAT数组以"NULL"结尾,IAT第一个元素值为"00026D3C",与INT的值一样,但有很多情况两者不一样,该值无实际意义,notepad.exe加载到内存时,准确的地址会取代该值</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130135523901.png" alt="image-20211130135523901"></p></li><li><p>下面使用Ollydbg查看notepad.exe的IAT,如下图,可以看到EP的地址为"00771860",说明这里实际运行时,ImageBase并不是文件中的默认值"00400000",这里可以反推处实际ImageBase为"00750000"(VA:"771860"- RVA:"21860"=ImageBase:"750000"),因此IAT在内存的地址为"00776068",从下图左下角的数据窗口中可以得到验证,里面"7634F550"即为kernel32.GetProcAddress函数的实际地址</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130140535517.png" alt="image-20211130140535517"></p></li></ul><h5 id="EAT"><a href="#EAT" class="headerlink" title="EAT"></a>EAT</h5><ul><li><p>Windows中"库"是为了方便其他程序调用而集中包含相关函数的文件(DLL/SYS)。WIN32 API是最具代表性的库,其中的kernel32.dll文件被称为最核心的库文件。</p></li><li><p>EAT是一种核心机制,它使不同的应用程序可以调用库文件中提供的函数。也就是说,只有通过EAT才能准确求得从相应库中导出函数的起始地址。与前面讲解的IAT一样,PE文件内的特定结构体(IMAGE_EXPORT_DIRECTORY)保存着导出信息,且PE文件中仅有一个结构体用来说明EAT的IMAGE_EXPORT_DIRECTORY结构体</p></li><li><p>下图为kernel32.dll文件的IMAGE_OPTIONAL_HEADER32.DataDirectory[0](第一个4字节为RVA,第二个4字节为Size成员变量),由于RVA值为"00092D30"故RAW为"00078D30"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130142823529.png" alt="image-20211130142823529"></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">RVARAW</span><br><span class="line">.text 10000 1000</span><br><span class="line">.rdata8000066000</span><br><span class="line">.data B000091000</span><br><span class="line">.didatC000092000</span><br><span class="line">.rsrcD000093000</span><br><span class="line">.relocE000094000</span><br></pre></td></tr></table></figure></li><li><p>IMAGE_EXPORT_DIRECTORY:IMAGE_EXPORT_DIRECTORY结构体定义代码如下</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">_IMAGE_EXPORT_DIRECTORY</span> {</span><br><span class="line"> DWORD Characteristics;<span class="comment">// creation time date stamp</span></span><br><span class="line"> DWORD TimeDateStamp;</span><br><span class="line"> WORD MajorVersion;</span><br><span class="line"> WORD MinorVersion;</span><br><span class="line"> DWORD Name;<span class="comment">// address of library file name</span></span><br><span class="line"> DWORD Base;<span class="comment">// ordinal base</span></span><br><span class="line"> DWORD NumberOfFunctions;<span class="comment">// number of funcitons</span></span><br><span class="line"> DWORD NumberOfNames;<span class="comment">// number of names</span></span><br><span class="line"> DWORD AddressOfFunctions; <span class="comment">// address of function start address array</span></span><br><span class="line"> DWORD AddressOfNames; <span class="comment">// address of function name string array</span></span><br><span class="line"> DWORD AddressOfNameOrdinals; <span class="comment">// address of ordinal array</span></span><br><span class="line"> }IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;</span><br></pre></td></tr></table></figure></li><li><p>IMAGE_EXPORT_DIRECTORY结构体重要成员如下表:</p><table><thead><tr><th align="center">项 目</th><th align="center">含 义</th></tr></thead><tbody><tr><td align="center">NumberOfFunctions</td><td align="center">实际Export函数的个数</td></tr><tr><td align="center">NumberOfNames</td><td align="center">Export函数中具名的函数个数</td></tr><tr><td align="center">AddressOfFunctions</td><td align="center">Export函数地址数组(数组元素个数=NumberOfFunctions)</td></tr><tr><td align="center">AddressOfNames</td><td align="center">函数名称地址数组(数组元素个数=NumberOfNames)</td></tr><tr><td align="center">AddressOfNameOrdinals</td><td align="center">Ordinal地址数组(数组元素个数=NumberOfNames)</td></tr></tbody></table></li><li><p>下图描述的是kernel32.dll文件的IMAGE_EXPORT_DIRECTORY结构体与整个EAT结构</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130172628462.png" alt="image-20211130172628462"></p></li><li><p>用HexEditor进入kernel32.dll的"00078D30"偏移处,如下图所示</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130164239802.png" alt="image-20211130164239802"></p></li><li><p>图中高亮部分为IMAGE_EXPORT_DIRECTORY结构体区域,该IMAGE_EXPORT_DIRECTORY的成员如下表所示</p><table><thead><tr><th align="center">文件偏移</th><th align="center">成 员</th><th align="center">值</th><th align="center">RAW</th></tr></thead><tbody><tr><td align="center">78D30</td><td align="center">Characteristics</td><td align="center">00000000</td><td align="center">-</td></tr><tr><td align="center">78D34</td><td align="center">TimeDateStamp</td><td align="center">56B90D14</td><td align="center">-</td></tr><tr><td align="center">78D36</td><td align="center">MajorVersion</td><td align="center">0000</td><td align="center">-</td></tr><tr><td align="center">78D38</td><td align="center">MinorVersion</td><td align="center">0000</td><td align="center">-</td></tr><tr><td align="center">78D3C</td><td align="center">Name</td><td align="center">00096C1E</td><td align="center">7CC1E</td></tr><tr><td align="center">78D40</td><td align="center">Base</td><td align="center">00000001</td><td align="center">-</td></tr><tr><td align="center">78D44</td><td align="center">NumberOfFunctions</td><td align="center">00000647</td><td align="center">-</td></tr><tr><td align="center">78D48</td><td align="center">NumberOfNames</td><td align="center">00000647</td><td align="center">-</td></tr><tr><td align="center">78D4C</td><td align="center">AddressOfFunctions</td><td align="center">00092D58</td><td align="center">78D58</td></tr><tr><td align="center">78D50</td><td align="center">AddressOfNames</td><td align="center">00094674</td><td align="center">7A674</td></tr><tr><td align="center">78D54</td><td align="center">AddressOfNameOrdinals</td><td align="center">00095F90</td><td align="center">7BF90</td></tr></tbody></table></li><li><p>函数名称数组:AddressOfNames成员的值为RVA:00094674,即RAW:7A674。用HexEditor查看该地址,如下图所示。此处为4字节RVA组成的数组,数组元素个数为NameberOfNames(0x647),逐一跟随所有RVA值即可发现函数名称的字符串</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130184544649.png" alt="image-20211130184544649"></p></li><li><p>由第一个RVA"96C8A"=>"7CC8A",查看该处地址可以看到函数名称为"AcquireSRWLockExclusive"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130185039095.png" alt="image-20211130185039095"></p></li><li><p>接下来查找"AcquireSRWLockExclusive"函数的Ordinal值,AddressOfNameOrdinals成员的值为RVA:00095F90=>RAW:7BF90,如下图可以看到高亮部分为多个2字节的ordinal组成的数组(ordinal数组中的各元素大小为2个字节),从图可以知道对应的ordinal=3</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130185745351.png" alt="image-20211130185745351"></p></li><li><p>函数地址数组 - EAT,最后查找"AcquireSRWLockExclusive"的实际地址,AddressOfFunctions成员的值为RVA:92D58=>RAW:78D58,将上面得到的ordinal=3用作下图数组的索引,得到RVA:96CA2</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B07/image-20211130191850713.png" alt="image-20211130191850713"></p></li><li><p>kernel32.dll ImageBase为"6B800000",所以AcquireSRWLockExclusive地址为"6B896CA2",这里因为暂时没找到ollydbg调试kernel32.dll的方法,因此无法用ollydbg验证</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记6</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第十章"><a href="#逆向工程核心原理第十章" class="headerlink" title="逆向工程核心原理第十章"></a>逆向工程核心原理第十章</h3><h5 id="函数调用约定"><a href="#函数调用约定" class="headerlink" title="函数调用约定"></a>函数调用约定</h5><ul><li><p>函数调用约定是对函数调用时如何传递参数的一种约定</p></li><li><p>函数调用前会把各个参数逆序压入栈(因为栈是先进后出),栈是定义在进程中的一段内存空间,向低地址方向扩展,且其大小被记录在PE头中,进程运行时就确定了栈内存大小</p></li><li><p>函数执行完后,栈中参数如何处理?</p><blockquote><p>不用管,因为只是临时存储在栈中,即使不在使用,清除相应的值也会浪费CPU资源,下一次向栈中存入其他值时会覆盖原来的值,因此函数执行完后不用管栈中参数</p></blockquote></li><li><p>函数执行完后,ESP如何变化?</p><blockquote><p>栈内存是固定的,ESP用来指示栈当前的位置,如果ESP指向栈底,则无法继续使用该栈。函数调用后如何处理ESP,这就是函数调用约定要解决的问题。</p></blockquote></li></ul><span id="more"></span><ul><li><p>主要的函数调用约定有以下3种</p><ul><li>cdecl</li><li>stdcall</li><li>fastcall</li></ul></li><li><p>cdecl</p><ul><li><p>cdecl是C语言函数默认使用的方式,调用者复则处理栈,调用者的代码有以下特征,在调用完函数后,调用者会调整esp指向的位置(比如下面的<code>ADD ESP, 8</code>),这种由调用者直接清理其压入栈的参数的方式就是cdecl</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">...</span><br><span class="line">push 2</span><br><span class="line">push 1</span><br><span class="line">call 00401000</span><br><span class="line">add esp, 8</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li></ul></li><li><p>stdcall</p><ul><li><p>stdcall常用于Win32API,该方式是由被调用者来清理栈,C语言中如果要用该方式,需要在定义时,在函数名前加上"__stdcall"关键字,stdcall被调用这有以下特征,<code>retn 8</code>含义retn + pop 8字节,即返回后使ESP增加到指定大小,这就是由被调用者来完成栈的清理工作</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">pop ebp</span><br><span class="line">retn 8</span><br></pre></td></tr></table></figure></li></ul></li><li><p>fastcall</p><ul><li>fastcall与stdcall基本相似,但fastcall通常用寄存器传递部分函数参数(ECX和EDX传递前2个参数)</li></ul></li></ul><hr><h3 id="逆向工程核心原理第十一章"><a href="#逆向工程核心原理第十一章" class="headerlink" title="逆向工程核心原理第十一章"></a>逆向工程核心原理第十一章</h3><ul><li><p>Part10Tus.ReverseMe下载链接<a href="https://forum.tuts4you.com/files/file/1307-lenas-reversing-for-newbies/">1307-lenas-reversing-for-newbies</a>,如果提示缺少MSVBVM50.dll,需要下载VB5依赖,VB5依赖下载链接:<a href="http://download.microsoft.com/download/vb50pro/utility/1/win98/EN-US/Msvbvm50.exe">http://download.microsoft.com/download/vb50pro/utility/1/win98/EN-US/Msvbvm50.exe</a></p></li><li><p>运行查看</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125110112003.png" alt="image-20211125110112003"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125110139464.png" alt="image-20211125110139464"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125110211414.png" alt="image-20211125110211414"></p></li><li><p>软件第一个弹窗提示要去除该弹窗并找到正确的序列号</p></li><li><p>用OD打开程序exe文件,可以看到是EP具有典型的VB特征</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125110650406.png" alt="image-20211125110650406"></p></li><li><p>接下来我们还是采用检索字符串的方式定位相关代码,因为第一个目标是去掉第一个弹窗,因此先定位到字符串"Get rid of all ..."所在位置"00402C85"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125110921291.png" alt="image-20211125110921291"></p></li><li><p>往下继续调试,可以发现,第一个弹窗是在"00402CFE"调用弹出的,接下来就是去掉这个弹窗了</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125111841629.png" alt="image-20211125111841629"></p></li><li><p>尝试直接nop掉这句call,运行发现无事发生,没有弹出输入序列号的窗口。原因是输入序列号的窗口是需要我们点击第一个窗口的确定才能弹出来的,我们除了nop掉这句代码,还需要模拟运行这句call命令并且用户点击了确定的状态,这样才能正确去掉弹窗</p></li><li><p>我们重新运行到"00402CFE"位置,然后F8让程序弹出窗口,并且我们点击"确定",观察寄存器的变化</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125113147271.png" alt="image-20211125113147271"></p></li><li><p>可以看到变化了寄存器有EAX、ECX、EDX、ESP、EIP、EFLAGS这些寄存器,为了更好判断哪些值至关重要,我们再次重新调试并运行到"00402CFE"位置,F8让程序弹出窗口,这次我们点击"取消",观察寄存器的变化</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125113224180.png" alt="image-20211125113224180"></p></li><li><p>对比上面两张图,我们可以推断出EAX寄存器的值是弹窗函数的返回值,并且"1"代表用户点击了确定,"2"代表点击了取消</p></li><li><p>那么去弹窗就可以按这样来实现,修改EAX值为1,修复ESP指向位置(以保证代码不会乱),所以可以这样修改</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, 0x1</span><br><span class="line">add esp, 0x14</span><br></pre></td></tr></table></figure></li><li><p><code>add esp, 0x14</code>是怎么得出来的呢?这个思路可以跟上面一样,记录下执行"ç"处的call命令前的ESP值"0019F9E4",在记录下执行完后ESP的值"0019F9F8",两者一减就得出了ESP需要偏移的大小了"0x14"</p></li><li><p>但这里有个问题是"00402CFE"处的call 命令只有5个字节大小,<code>mov eax, 0x1</code>(机器码:B801000000,共5个字节)和<code>add esp, 0x14</code>(机器码:83C414)两句总共需要8个字节的大小,单改"00402CFE"处的代码是不够的,所以这里决定往上一点从"00402CF9"(压入函数参数)处开始修改,这样命令大小就足够了。因为修改位置变了,所以add命令整理栈的也相应地需要改变下,因为程序运行到"00402CF9"处时ESP值为"0019F9EC",因此ESP偏移的大小为0x0019F9F8-0x0019F9EC=0xC,</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, 0x1</span><br><span class="line">add esp, 0xc</span><br></pre></td></tr></table></figure></li><li><p>按上述修改后,调试运行,可以发现成功弹出了主窗口,第一步去掉多余弹窗完成</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125122435657.png" alt="image-20211125122435657"></p></li><li><p>《逆向工程核心原理》书中提到了另一种跟简单粗暴的方式,就是从"00402CFE"位置处往上找,直到函数初始位置,然后直接修改<code>push ebp</code>和<code>mov ebp, esp</code>为<code>retn 4</code>,也就是让函数直接返回并整理栈,这样相当于调用rtcMessageBox的上层函数直接返回了也就没有调用rtcMessageBox了就不会弹窗了</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125123236337.png" alt="image-20211125123236337"></p></li><li><p>接下来,找出序列号,继续检索字符串,我们双击成功提示信息的字符串"Yep! You succeeded registering! "定位到相应的代码位置</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125145857445.png" alt="image-20211125145857445"></p></li><li><p>定位到相应代码位置,再往上查看代码,可以看到有"004028C2"地方调用了__vbaStrCmp函数,从名字上来看就是字符串比较函数,而且其中一个参数为"I'mlena151",显然这就是我们要找的注册码</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/3.00.26.png" alt="3.00.26"></p></li><li><p>回到软件中输入序列号"I'mlena151"验证下,可以看到成功弹出正确提示,至此分析结束</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B06/image-20211125150610505.png" alt="image-20211125150610505"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记5</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第八章"><a href="#逆向工程核心原理第八章" class="headerlink" title="逆向工程核心原理第八章"></a>逆向工程核心原理第八章</h3><h5 id="abex-crackme2"><a href="#abex-crackme2" class="headerlink" title="abex' crackme2"></a>abex' crackme2</h5><ul><li><p>首先运行软件,了解一些软件基本信息</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124142604406.png" alt="image-20211124142604406"></p></li></ul><span id="more"></span><ul><li><p>可以看到软件需要注册的序列号,其中名字长度还要大于4,根据这些信息,我们接下来的调试就简单多了</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124142718701.png" alt="image-20211124142718701"></p></li></ul><p><img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124142832266.png" alt="image-20211124142832266"></p><h5 id="VisualBasic程序特征"><a href="#VisualBasic程序特征" class="headerlink" title="VisualBasic程序特征"></a>VisualBasic程序特征</h5><ul><li><p>abex's crackme2文件由VB编写的,这里了解下VB程序的文件特征</p></li><li><p>VB使用名为MSVBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0)的VB专用引擎,又称(The Thunder Runtime Engine)</p></li><li><p>VB使用例子:显示消息框时,VB代码中要调用MsgBox函数,其实,VB编辑器真正调用的是MSVBVM60.dll里的rtcMsgBox()函数,在该函数内部通过调用user32.dll里的MessageBoxW()函数(Win32 API)来工作,此外也可以在VB代码中直接调用user32.dll里的MessageBoxW()。用图片来描述的话就像下图一样,相当于在开发者代码和底层代码user32.dll之间加了一层MSVBVM60.dll,而且也保留了开发者代码直接调用底层代码的方式。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124144122133.png" alt="image-20211124144122133"></p></li><li><p>根据便于选项不同,可以将VB编译为本地代码(N Code)和伪代码(P Code)。</p></li><li><p>VB程序采用Windows操作系统的事件驱动方式工作,因此main()或WinMain()中并不存在用户代码,用户代码存在于各个事件处理程序(event handler)之中。</p></li><li><p>接下来开始调试,用OD打开abex's crackme2.exe查看反汇编文件,可以看到EP地址为"00401238",该命令将把VB的RT_MainStruct结构体地址"00401E14"压入栈,然后"0040123D"地址处命令<code>call 00401232</code>调用"00401232"处的<code>jmp dword ptr ds:[0x4010A0]</code>指令,该指令会跳转至VB引擎主函数ThunRTMain()(前面压入栈的值作为函数参数),这3行代码就是VB的全部启动代码。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124145709950.png" alt="image-20211124145709950"></p></li></ul><h6 id="间接调用"><a href="#间接调用" class="headerlink" title="间接调用"></a>间接调用</h6><ul><li>"0040123D"地址处的CALL命令用于调用ThunRTMain()函数,但它不是直接调用,而是通过中间"00401232"地址处的JMP命令跳转,这是VC++、VB编译器中常用的间接调用法(Indirect Call)</li></ul><h6 id="RT-MainStruct结构体"><a href="#RT-MainStruct结构体" class="headerlink" title="RT_MainStruct结构体"></a>RT_MainStruct结构体</h6><ul><li><p>ThunRTMain()函数的参数为RT_MainStruct结构体,RT_MainStruct结构体存在于地址"00401E14"处,微软未公开这个结构体的细节,但有逆向高手已经完成了对这个结构体的分析。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124151422774.png" alt="image-20211124151422774"></p></li><li><p>《逆向工程核心原理》书中没有直接提及RT_MainStruct结构体的信息,谷歌直接搜索"RT_MainStruct",发现了一篇VB的逆向文章,可能就是书中所提到的分析,<a href="http://www.reteam.org/papers/e46.pdf">VISUAL BASIC REVERSED - A decompiling approach</a>,这里先记下链接,关于RT_MainStruct的细节,有待后面学习补充。</p></li></ul><h6 id="分析crackme"><a href="#分析crackme" class="headerlink" title="分析crackme"></a>分析crackme</h6><ul><li><p>接下来我们跳过ThunRTMain()函数,来分析crackme的代码</p></li><li><p>要定位目标代码,有多种方式,这里采用检索字符串方式,因为前面运行软件时,我们已经知道了软件包含了哪些字符串,右键代码窗口选择 中文搜索引擎=>智能搜索,可以看到软件的所有字符串信息如下</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124152406392.png" alt="image-20211124152406392"></p></li><li><p>我们直接双击Wrong serial!查看引用该字符串的代码位置,可以看到,标题("Wrong serial!"),内容("Nope, this serial is wrong!"),以及实际调用消息框函数代码(004034A6)都显示出来了</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124153235226.png" alt="image-20211124153235226"></p></li><li><p>从编程角度来看,这种校验序列号软件,一般根据某种算法生成序列号,然后将用户输入的序列号进行比对,如果相同则跳转正确,否则跳转错误。根据这个思路,我们向上查找相应代码。</p></li><li><p>按照上面思路查找,果然发现了条件语句分支的地方,而且不止一处,一共有两次判断两个字符串是否相等的地方,分别位于"00403332"(调用__vbaVarTstEq()函数)和"00403424"(调用__vbaVarTstNe()函数),从参数来看两个比较的是同两个参数,但是两个函数逻辑相反,前者判断的是否相等,后者是否不相等。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124154042910.png" alt="image-20211124154042910"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124154139598.png" alt="image-20211124154139598"></p></li><li><p>test指令说明:逻辑比较,与bit-wise logical 'AND'一样(仅改变EFLAGS寄存器而不改变操作数的值,若两个操作数中一个为0则AND运算结果被置为0=>ZF=1),je指令:jump if equal,若ZF=1,则跳转</p></li><li><p>这里顺便学习下EFLAGS寄存器(标志寄存器)的内容,EFLAGS有32个位元,全部记住比较困难,其中最重要的有ZF(Zero Flag, 零标志)、OF(Overflow Flag,溢出标志)、CF(Carry Flag,进位标志),这三个指令之所以重要是因为在某些汇编指令,特别是Jcc(条件跳转)指令中要检查这3个标志的值,并根据它们的值决定是否执行某个操作</p><ul><li>ZF:若运算结果为0,则其为1(True),否则其为0(False)</li><li>OF:有符号整数(signed integer)溢出时,OF值被置为1。此外,MSB(Most Significant Bit,最高有效位)改变时,其值也被设为1</li><li>CF:无符号整数(unsigned integer)溢出时,CF被设为1</li></ul></li><li><p>分析到这其实就可以先直接爆破crackme,爆破这个crackme很简单,只需要nop掉第一个跳转语句然后将第二个跳转语句"je"改为"jmp",这样输入任意序列号都会提示成功。(如果只修改第一跳转,那么程序虽然会提示成功,但点确定后又会再弹一次失败的窗。)</p></li><li><p>还有一种方式能达到破解效果就是,在条件语句前下断点,调试运行,输入任意序列号,点确定,然后就可以在右下角栈窗口查看得到正确的序列号。下图为输入Name:"CCCC",Serial:"11111",后的结果,可以看到,比较的两个字符串为"A7A7A7A7"和"11111",因为Name全是单一字符"C",序列号全是"A7",所以基本可以确定,这个软件的序列号生成算法中"C"映射为"A7"。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124185309910.png" alt="image-20211124185309910"></p></li><li><p>这里决定继续分析,找出软件的序列号的生成算法的位置</p></li><li><p>要找出序列号生成算法位置,当然得从函数的起始位置开始找起,根据之前学习的栈帧概念,我们继续往上找,直到找到<code>push ebp</code>和<code>mov esp, ebp</code>这两行代码为止,这样就可以找到函数的起始位置"00402ED0"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124190104892.png" alt="image-20211124190104892"></p></li><li><p>虽然找到了函数的起始地址,但是这么多行代码,而且还有好几处调用其他函数的代码,该怎么找到生成序列号的算法呢?这里可以输入的Name为思路去入手,因为序列号是随Name变化而变化的,说明生成序列号的算法用到了Name中的字符串,我们只要调试时时刻注意栈内存的变化,什么时候栈内存出现了(获取了)Name的字符串,那么从该处开始,往下的代码就离我们要找的生成序列号算法不远了</p></li><li><p>按照上面的思路,我们逐步单步测试,并注意dump窗口或者栈窗口的数据变化。由于VB中,字符串是使用字符串对象(与C语言使用char数组不同),直接在dump窗口查看内存,很难认出实际的字符串,需要右键选择 长型=>ASCII数据地址。栈窗口则不用这么设置,可以直接认出字符串。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124192820218.png" alt="image-20211124192820218"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124192505933.png" alt="image-20211124192505933"></p></li><li><p>最终我们在运行完"00402F98"的call命令后,内存中出现了我们输入的Name:"CCCC",找到这个基准点后,离我们要找的序列号生成算法代码就不远了</p></li><li><p>继续往下调试,在"0040300F处"我们可以看到,调用了__vbaLenVar函数,这部分代码代码逻辑就是判断用户序列号长度是否小于4的地方,离序列号算法更近了一步</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05/image-20211124194613294.png" alt="image-20211124194613294"></p></li><li><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">0403102 . BB 04000000 mov ebx,0x4; EBX = 4 (循环次数)</span><br><span class="line">...</span><br><span class="line">0040318B . FF15 30104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForInit>>; \__vbaVarForInit(For循环初始化)</span><br><span class="line">00403191 . 8B1D 4C104000 mov ebx,dword ptr ds:[<&MSVBVM60.#632>] ; msvbvm60.rtcMidCharVar</span><br><span class="line">00403197 > 85C0 test eax,eax; 循环开始</span><br><span class="line">00403199 . 0F84 06010000 je abexcrac.004032A5; 符合条件则跳出循环</span><br><span class="line">...</span><br><span class="line">0040329A . FF15 C0104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForNext>>; \__vbaVarForNext(下一个循环)</span><br><span class="line">004032A0 .^\E9 F2FEFFFF jmp abexcrac.00403197; 循环结束</span><br><span class="line">004032A5 > 8B45 08 mov eax,dword ptr ss:[ebp+0x8]</span><br></pre></td></tr></table></figure></li><li><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">取用户输入的Name前4位字符</span><br><span class="line">将每位字符先转为ASCII码值</span><br><span class="line">再加上偏移量0x64得到新的16进制值</span><br><span class="line">将16进制转为字符串</span><br><span class="line">拼接所有生成的字符串得到序列号</span><br></pre></td></tr></table></figure></li><li><p>值得一题的是这篇文章很有参考意义<a href="http://www.reteam.org/papers/e46.pdf">VISUAL BASIC REVERSED - A decompiling approach</a>,这篇文章末尾提到,VB中api函数的参数和返回值很多都是对象(Object)而不是具体的值(Value),因此当你分析VB的函数参数和返回值时,你可以考虑把它当作对象来分析</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记4</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第七章"><a href="#逆向工程核心原理第七章" class="headerlink" title="逆向工程核心原理第七章"></a>逆向工程核心原理第七章</h3><h5 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h5><ul><li><p>软件:Ollydbg,<a href="https://visualstudio.microsoft.com/zh-hans/downloads/">visual studio community</a></p></li><li><p>使用visual studio创建一个名为StackFrame的c++控制台项目,粘入以下代码,然后运行生成x86(Win32)的StackFrame.exe文件</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="keyword">include</span> <span class="string">"stdio.h"</span></span></span><br><span class="line"><span class="function"><span class="type">long</span> <span class="title">add</span><span class="params">(<span class="type">long</span> a, <span class="type">long</span> b)</span> </span>{</span><br><span class="line"><span class="type">long</span> x = a, y = b;</span><br><span class="line"><span class="keyword">return</span> (x + y);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>* argv[])</span> </span>{</span><br><span class="line"><span class="type">long</span> a = <span class="number">1</span>, b = <span class="number">2</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%d\n"</span>, <span class="built_in">add</span>(a, b));</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></li></ul><span id="more"></span><h5 id="栈帧"><a href="#栈帧" class="headerlink" title="栈帧"></a>栈帧</h5><ul><li><p>简单来说,栈帧是利用<strong>EBP</strong>(栈帧指针,注意不是<strong>ESP</strong>)寄存器访问栈内局部变量、参数、函数返回地址等的手段</p></li><li><p>跟据前面所学知识,我们知道<strong>ESP</strong>为<strong>栈顶</strong>指针,它会随着数据出入栈,而不断改变,如果以它为基准来访问函数的局部变量、参数,那么编写程序将十分困难,这时候就要用到<strong>EBP</strong>栈帧指针来解决这一问题。调用函数时,把函数起始地址的<strong>ESP</strong>值记录到<strong>EBP</strong>当中,这样函数运行时无论<strong>ESP</strong>怎么变化,用<strong>EBP</strong>作为基准值能够安全访问函数的局部变量、参数、返回地址,这就是<strong>EBP</strong>这个寄存器作为栈帧指针的作用</p></li><li><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></pre></td><td class="code"><pre><span class="line">PUSH EBP; 函数开始(使用EBP前先把已有值保存到栈中)</span><br><span class="line">MOV EBP, ESP; 保存当前的ESP(函数起始地址)到EBP中</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">MOV ESP, EBP; 将函数的起始地址返回到ESP中</span><br><span class="line">POP EBP; 函数返回前,将最开始保存在栈中的EBP值恢复至EBP</span><br><span class="line">RETN; 函数终止</span><br></pre></td></tr></table></figure></li><li><p>下面用图片来描述上面汇编代码发生变化时,<strong>EBP</strong>和<strong>ESP</strong>的变化,测试使用的exe为上述编译的StackFrame.exe</p><ul><li><p>函数开始时,ESP指向"0019FE78"地址,该地址储存着"00B320B3"地址,"00B320B3"地址是当前函数执行完后的返回地址,EBP则指向"0019FE94"地址,这是调用者所在函数(也就是上一个函数)的基础地址</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123153654338.png" alt="image-20211123153654338"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.18.09.png" alt="截屏2021-11-23 下午3.18.09"></p></li><li><p>接下来,我们执行<code>push ebp</code>,可以看到,由于执行了PUSH命令,ESP栈顶指针往栈顶方向移动,ESP指向的地址变为"0019FE74","0019FE74"所储存的则为我们EBP指向的地址"0019FE94"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123154418096.png" alt="image-20211123154418096"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.19.40.png" alt="截屏2021-11-23 下午3.19.40"></p></li><li><p>接着运行<code>mov ebp, esp</code>,可以看到,EBP指向了ESP所指向的"0019FE74"地址,这样当前函数的栈帧就生成了(设置好了EBP)</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123154907891.png" alt="image-20211123154907891"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.20.02.png" alt="截屏2021-11-23 下午3.20.02"></p></li><li><p>接下来运行<code>sub esp, 0xD8</code>,模拟执行函数体,改变ESP的值,查看EBP和ESP的编号情况</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123155609652.png" alt="image-20211123155609652"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.22.16.png" alt="截屏2021-11-23 下午3.22.16"></p></li><li><p>可以看到执行函数体时,ESP的值会发生改变,但EBP的值则不变,因此可以用EBP为基准点来访问局部变量、函数参数。因为局部变量和函数参数储存的位置就在EBP和ESP所指的两块地址之间</p></li><li><p>接着我们nop掉中间其他语句,直接测试函数返回时,EBP和ESP的变化情况</p></li><li><p>运行<code>mov esp, ebp</code>,可以看到,此时ESP重新指向了"0019FE74"地址</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123160345570.png" alt="image-20211123160345570"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.26.29.png" alt="截屏2021-11-23 下午3.26.29"></p></li><li><p>执行<code>pop ebp</code>,可以看到,EBP和ESP又回到了函数运行前的状态</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123160827249.png" alt="image-20211123160827249"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.28.34.png" alt="截屏2021-11-23 下午3.28.34"></p></li><li><p>最后我们执行<code>RETN</code>,可以看到,函数返回到了"00B320B3"所对应的代码语句处</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/3.30.32.png" alt="截屏2021-11-23 下午3.30.32"></p></li><li><p>此时EBP和ESP的值则如下图所示,由此可知,函数运行<code>RETN</code>时,所对应的操作是从栈中弹出函数返回地址,让CPU从该处开始执行,所以ESP才会往POP方向移动</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B04/image-20211123161317268.png" alt="image-20211123161317268"></p></li></ul></li><li><p>小结:学习了有关栈帧的内容,了解了函数开始运行和结束运行时ESP和EBP两个寄存器的运作机制,又多学会了两个寄存器。</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记3</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第五章"><a href="#逆向工程核心原理第五章" class="headerlink" title="逆向工程核心原理第五章"></a>逆向工程核心原理第五章</h3><h5 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h5><p><img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122201000078.png" alt="image-20211122201000078"></p><span id="more"></span><h5 id="栈的作用"><a href="#栈的作用" class="headerlink" title="栈的作用"></a>栈的作用</h5><ul><li>暂时保存函数内的局部变量</li><li>调用函数时传递参数</li><li>保存函数返回后的地址</li></ul><h5 id="栈的特征"><a href="#栈的特征" class="headerlink" title="栈的特征"></a>栈的特征</h5><ul><li><p>栈的内存结果如上图所示,栈底==>栈顶 是从高地址到低地址扩展。一个进程中,栈顶指针(ESP)初始状态指向栈底端。当执行PUSH命令将数据压入栈时,栈顶指针就会上移到栈顶端,反之当执行POP命令从栈中弹出数据,若栈为空,则栈顶指针重新移动到栈的底端。</p></li><li><p>下面测试栈实际是怎样运作的,用Ollydbg打开任意一个exe,这里还是用之前的LittleEndian.exe为例,在main函数起始位置下断点,运行后如下图,可以看到初始时ESP值为"006FFCF8"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122202044385.png" alt="image-20211122202044385"></p></li><li><p>按空格修改汇编指令为<code>push 100</code>,F7运行,然后查看ESP的变化,从下图的寄存器窗口可以看到ESP的值变为了"006FFCF4",比原来少了4个字节,并且从右下角的栈窗口可以看到006FFCF4存放的值为100,也就是我们压入栈的数据</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/8.24.35.png" alt="截屏2021-11-22 下午8.24.35"></p></li><li><p>按空格修改汇编指令为<code>pop eax</code>,F7运行,如下图,可以看到ESP的值变回了"006FFCF8",而且EAX的值变成了100,这说明<code>pop eax</code>指令含义是从栈弹出数据并赋值给EAX。继续观察右下角的栈窗口,可以看到光标停在了"006FFCF8",代表栈顶指针指向该位置,而且可以看到"006FFCF4"的值仍为4,说明<code>pop</code>指令并不会去修改数据的值,只是移动栈顶指针。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/8.29.47.png" alt="截屏2021-11-22 下午8.29.47"></p></li></ul><hr><h3 id="逆向工程核心原理第6章"><a href="#逆向工程核心原理第6章" class="headerlink" title="逆向工程核心原理第6章"></a>逆向工程核心原理第6章</h3><h5 id="分析abex-crackme1"><a href="#分析abex-crackme1" class="headerlink" title="分析abex' crackme1"></a>分析abex' crackme1</h5><ul><li><p>首先运行crackme,大致了解软件,运行后如下图,只有一个弹窗</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122214315722.png" alt="image-20211122214315722"></p></li><li><p>点击确定后弹出另外一个窗</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122214510630.png" alt="image-20211122214510630"></p></li><li><p>从弹窗提示的内容可以知道,需要做的是让软件将我们的硬盘识别为CD-ROM</p></li><li><p>接下来,用Ollydbg打开crackme</p></li><li><p>从截图可以看到,跟我们之前自己编译得到的HelloWrold.exe不同,软件的EP代码非常短,这是因为这个crackme是使用汇编语言编写的</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122214725326.png" alt="image-20211122214725326"></p></li><li><p>从图中代码,基本可以知道,软件运行时会获取我们的C盘的盘类型:CD-ROM or Other,我们需要做的是让软件识别为 CD-ROM</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122215044068.png" alt="image-20211122215044068"></p></li><li><p>修改方式有多种,例如:可以修改00401026地址处代码,将<code>je short 0040103D</code>改为<code>jmp short 0040103D</code>,这里"je"代表"jump if equal","je"改为"jmp"即无条件跳转至地址"0040103D"处,修改后运行,点确定后可以看到弹出跟最初不一样的窗口,破解成功</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B03/image-20211122215508393.png" alt="image-20211122215508393"></p></li><li><p>还可以将<code>cmp eax, esi</code>的前几行汇编代码修改为<code>mov eax, esi</code>,这样在程序比较eax和esi值之前,我们让它们值变为一样,这样也可以达到跟上面一样的效果</p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记2</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第三章"><a href="#逆向工程核心原理第三章" class="headerlink" title="逆向工程核心原理第三章"></a>逆向工程核心原理第三章</h3><h5 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h5><ul><li><p>软件:Ollydbg,<a href="https://visualstudio.microsoft.com/zh-hans/downloads/">visual studio community</a></p></li><li><p>使用visual studio创建一个名为LittleEndian的c++控制台项目,粘入以下代码,然后运行生成x86(Win32)的LittleEndian.exe文件</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="meta">#<span class="keyword">include</span> <span class="string">"windows.h"</span></span></span><br><span class="line"></span><br><span class="line">BYTE b = <span class="number">0x12</span>;</span><br><span class="line">WORD w = <span class="number">0x1234</span>;</span><br><span class="line">DWORD dw = <span class="number">0x12345678</span>;</span><br><span class="line"><span class="type">char</span> str[] = <span class="string">"abcde"</span>;</span><br><span class="line"><span class="function"><span class="type">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"> byte lb = b;</span><br><span class="line"> WORD lw = w;</span><br><span class="line"> DWORD ldw = dw;</span><br><span class="line"> <span class="type">char</span>* lstr = str;</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></li></ul><span id="more"></span><h5 id="字节序"><a href="#字节序" class="headerlink" title="字节序"></a>字节序</h5><ul><li><p>字节序指<strong>多字节</strong>数据在计算机内存中存放的字节顺序,字节序主要分为大端序和小端序。大端序:数据的高位储存在低位内存地址(高位在前,低位在后这种符合我们人类的书写习惯,例如书写时:个位在后,十位在前);小端序:数据的低位储存在高位内存地址(低位在前,高位在后,这种更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的,而运算也是从低位开始的)</p></li><li><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></pre></td><td class="code"><pre><span class="line">BYTE b = <span class="number">0x12</span>;</span><br><span class="line">WORD w = <span class="number">0x1234</span>;</span><br><span class="line">DWORD dw = <span class="number">0x12345678</span>;</span><br><span class="line"><span class="type">char</span> str[] = <span class="string">"abcde"</span>;</span><br></pre></td></tr></table></figure><table><thead><tr><th align="center">Type</th><th align="center">Name</th><th align="center">Size</th><th align="center">大端序类型</th><th align="center">小端序类型</th></tr></thead><tbody><tr><td align="center">BYTE</td><td align="center">b</td><td align="center">1</td><td align="center">[12]</td><td align="center">[12]</td></tr><tr><td align="center">WORD</td><td align="center">w</td><td align="center">2</td><td align="center">[12][34]</td><td align="center">[34][12]</td></tr><tr><td align="center">DWORD</td><td align="center">dw</td><td align="center">4</td><td align="center">[12][34][56][78]</td><td align="center">[78][56][34][12]</td></tr><tr><td align="center">char[]</td><td align="center">str</td><td align="center">6</td><td align="center">[61][62][63][64][65][00]</td><td align="center">[61][62][63][64][65][00]</td></tr></tbody></table></li></ul><h5 id="用OD查看小端序"><a href="#用OD查看小端序" class="headerlink" title="用OD查看小端序"></a>用OD查看小端序</h5><ul><li><p>Ollydbg打开我们的LittleEndian.exe文件,如图先用搜索字符串的方式定位到我们的main函数所在位置,在函数开始位置"00641750"设下断点</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/image-20211122092145991.png" alt="image-20211122092145991"></p></li><li><p>运行调试,然后F8逐步运行至"00641775"位置,可以看到左下方位于代码窗口和数据窗口之间出现了"ds:[0064A000]=12",结合我们的代码和"00641775"的汇编指令可知,此时程序正在获取全局变量"b=0x12"的值,点击左下角数据窗口,Ctrl + G 输入"0064A000"地址跳转查看"0064A000"的数据,可以看到数据正是我们的0x12</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/image-20211122092505200.png" alt="image-20211122092505200"></p></li><li><p>找到全局变量b所在位置后,其余全局变量也很容易找到,这里直接可以看出其余变量紧跟在全局变量b之后,从图可以看到WORD和DWORD类型采用的是小端序的方式储存</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/image-20211122093227707.png" alt="image-20211122093227707"></p></li></ul><hr><h3 id="逆向工程核心原理第四章"><a href="#逆向工程核心原理第四章" class="headerlink" title="逆向工程核心原理第四章"></a>逆向工程核心原理第四章</h3><h5 id="IA-32寄存器"><a href="#IA-32寄存器" class="headerlink" title="IA-32寄存器"></a>IA-32寄存器</h5><ul><li>通用寄存器(32位,8个):EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP</li><li>段寄存器(16位,6个):CS、DS、SS、ES、FS、GS</li><li>程序状态与控制寄存器(32位,1个):EFLAGS</li><li>指令指针寄存器(32位,1个):EIP</li></ul><blockquote><p>E(Extended)开头的寄存器代表,该寄存器在16位CPU(IA-16)时就已经存在</p></blockquote><h5 id="通用寄存器"><a href="#通用寄存器" class="headerlink" title="通用寄存器"></a>通用寄存器</h5><ul><li>EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP,这8个寄存器均有对应的16位寄存器,其中AX~DX还可细分位高(H)、低(L)两种独立的寄存器。以EAX为例,如果需要用全部4个字节则使用EAX,如果只使用2个字节则AX(EAX低16位),如果使用一个字节可以使用AH(AX高8位)或者AL(AX低8位)</li></ul><table><thead><tr><th align="center">31==>16</th><th align="center">15==>8</th><th align="center">7==>0</th><th align="center">16-bit</th><th align="center">32-bit</th></tr></thead><tbody><tr><td align="center"></td><td align="center">AH</td><td align="center">AL</td><td align="center">AX</td><td align="center">EAX</td></tr><tr><td align="center"></td><td align="center">BH</td><td align="center">BL</td><td align="center">BX</td><td align="center">EBX</td></tr><tr><td align="center"></td><td align="center">CH</td><td align="center">CL</td><td align="center">CX</td><td align="center">ECX</td></tr><tr><td align="center"></td><td align="center">DH</td><td align="center">DL</td><td align="center">DX</td><td align="center">EDX</td></tr><tr><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">BP</td><td align="center">EBP</td></tr><tr><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">SI</td><td align="center">ESI</td></tr><tr><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">DI</td><td align="center">EDI</td></tr><tr><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">SP</td><td align="center">ESP</td></tr></tbody></table><ul><li><p>下面用Ollydbg演示EAX、AX、AH、AL使用时的效果,首先可以用Ollydbg任意打开一个exe可执行文件,这里还是用上面的LittleEndian.exe为例</p></li><li><p>按空格快捷键,修改程序下一句运行的汇编指令,这里修改为<code>mov eax, 0x12345678</code>,可以看到修改前,左边的寄存器EAX值为0x00FF5DC0,保存后按F7运行查看结果</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/2.52.41.png" alt="截屏2021-11-22 下午2.52.41"></p></li><li><p>运行后可以看到寄存器EAX结果变为了"0x12345678",修改EAX值成功</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/2.57.13.png" alt="截屏2021-11-22 下午2.57.13"></p></li><li><p>下面测试AX,同样按空格,修改汇编指令,输入<code>mov ax, 0x8765</code>,保存后按F7运行查看结果</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/3.02.20.png" alt="截屏2021-11-22 下午3.02.20"></p></li><li><p>运行可以看到寄存器EAX由原来的"0x12345678"变成了"0x12348765",说明AX寄存器为EAX寄存器的低16位</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/3.03.24.png" alt="截屏2021-11-22 下午3.03.24"></p></li><li><p>同样地接着来测试AH,空格修改下一句汇编指令为<code>mov ah, 0xFF</code>,保存后按F7运行,可以看到寄存器EAX的值由原来的"0x12348765"变为了"0x1234FF65",说明AH寄存器为AX寄存器的高8位</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/3.08.03.png" alt="image-20211122150744281"></p></li><li><p>同样地测试AL,修改汇编指令为<code>mov al, 0x00</code>,保存后按F7运行,可以看到寄存器EAX最后两位由原来的"65"变为了"00",所以AL寄存器位于AX和EAX的低8位</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B02/3.10.55.png" alt="截屏2021-11-22 下午3.10.55"></p></li></ul><h5 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h5><ul><li>学习和测试了通用寄存器的使用,了解了低位和高位寄存器使用时的具体效果,其他寄存器的使用有待后面继续学习补充</li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>《逆向工程核心原理》学习笔记1</title>
<link href="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/"/>
<url>/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/</url>
<content type="html"><![CDATA[<h3 id="逆向工程核心原理第二章"><a href="#逆向工程核心原理第二章" class="headerlink" title="逆向工程核心原理第二章"></a>逆向工程核心原理第二章</h3><h5 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h5><ul><li><p>软件:Ollydbg,<a href="https://visualstudio.microsoft.com/zh-hans/downloads/">visual studio community</a></p></li><li><p>使用visual studio创建一个c++控制台项目,粘入以下代码,然后运行生成**x86(Win32)**的HelloWorld.exe文件</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="meta">#<span class="keyword">include</span> <span class="string"><windows.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><tchar.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> _tmain(<span class="type">int</span> argc, TCHAR* argv[]) {</span><br><span class="line"><span class="built_in">MessageBox</span>(<span class="literal">NULL</span>, <span class="string">L"HelloWorld"</span>, <span class="string">L"blog.iz4.cc"</span>, MB_OK);</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></li><li><p>《逆向工程核心原理》作者提供的资源下载github仓库:<a href="https://github.com/reversecore/book.git">https://github.com/reversecore/book.git</a>;书中用到的代码和可执行文件都能在里面获取</p></li></ul><span id="more"></span><h5 id="调试定位main函数"><a href="#调试定位main函数" class="headerlink" title="调试定位main函数"></a>调试定位main函数</h5><ul><li><p>简单使用 F7(step into)和F8(step over)查找main函数位置</p></li><li><p>使用ollydbg打开HelloWorld.exe后,可以看到入口点(EntryPoint)跟《逆向工程核心原理》书中不太一样,应该是vs版本不一致导致的,但不影响后续的分析,这里直接F7跳至下一语句</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121152315527.png" alt="image-20211121152315527"></p></li><li><p>跟踪跳转后可以看到002B1F63的地方调用了一个HelloWor*函数,我们继续F7跟踪跳转</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121152838001.png" alt="image-20211121152838001"></p></li><li><p>F7跟踪进入后,发现函数内部又调用了两个函数,这里为了可以改用F8来筛选出这两个哪个函数是包含我们main函数的代码,因为我们已知程序是会弹出一个HelloWorld的窗,所以F8执行时,只要看执行这两个函数时,哪个会弹窗,那么那个函数就是需要我们进一步分析的</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121153107618.png" alt="image-20211121153107618"></p></li><li><p>按照上面的思路,最终定位到第二个HelloWor.__scrt_comon_main<em>这个函数是我们要找的,找到后,我们重新调试Ctrl + F2,然后F7步入HelloWor.__scrt_comon_main</em>这个函数进行进一步分析</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121154103746.png" alt="image-20211121154103746"></p></li><li><p>F7步入后可以发现,这个函数内部相比前面的函数要复杂的多,调用其他函数的地方也比较多,这里也一样用上面一样的思路来使用F8单步跳过来查找main函数,每执行完一个函数,查看下程序是否出现弹窗,如果出现了那么说明那个函数就是包含我们main函数代码的函数</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121154705583.png" alt="image-20211121154705583"></p></li><li><p>最终先定位到HelloWor.invoke_main_slow_path*这个函数,其实从函数名字也大致能看出来(invoke main => 执行main),这个函数就是调用我们main函数的地方,我们F7步入跟踪查看</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121155019244.png" alt="image-20211121155019244"></p></li><li><p>可以看到,该函数内部也调用了4个函数,这里还是上面的老办法,继续排查,最终终于找到我们的main函数HelloWor.002B114F,F7步入查看</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121155242994.png" alt="image-20211121155242994"></p></li><li><p>可以看到我们代码中定义的字符串"blog.iz4.cc"和"HelloWorld",以及调用MessageBoxW,可以确定这就是我们的main函数,至此终于找到了main函数</p></li><li><p>小结:我们想定位程序某段代码所在位置时,可以利用F7和F8来完成,根据程序执行到某个位置时,程序状态是否发生改变来定位相应的位置,但这个方法只适用于程序比较简单的情况下,如果程序比较复杂,那么这个方法则非常的麻烦。</p></li></ul><h5 id="使用搜索字符串来定位main函数"><a href="#使用搜索字符串来定位main函数" class="headerlink" title="使用搜索字符串来定位main函数"></a>使用搜索字符串来定位main函数</h5><ul><li><p>重新开始调试,右键选择 中文搜索引擎 => 智能搜索<img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121160743342.png" alt="image-20211121160743342"></p></li><li><p>这里可以看到排在最前面的两个字符串就是我们要找的字符串,如果不能一眼看出所找字符串在哪,那么可以Ctrl + F进行搜索查找,这里我们双击字符串"HelloWrold"就能进入我们的main函数当中,比上面的运行调试的代码更为快捷。</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121160910496.png" alt="image-20211121160910496"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121161101566.png" alt="image-20211121161101566"></p></li></ul><h5 id="根据API调用查找main函数位置"><a href="#根据API调用查找main函数位置" class="headerlink" title="根据API调用查找main函数位置"></a>根据API调用查找main函数位置</h5><ul><li><p>原理:根据程序运行时的行为来推断其可能用到了哪些API,然后查看程序所有调用该API的地方,再判断是否为目标函数</p></li><li><p>重新开始调试,右键选择 查找 => 所有模块间的调用</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121181902720.png" alt="image-20211121181902720"></p></li><li><p>点击窗口内容,输入MessageBoxW,则自动定位到相应API调用的地方,双击user32.MessageBoxW,定位到程序调用该API的位置,也就找到了我们的main函数所在的位置</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121182118531.png" alt="image-20211121182118531"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121183241282.png" alt="image-20211121183241282"></p></li></ul><h5 id="在API代码中打断点查找main函数"><a href="#在API代码中打断点查找main函数" class="headerlink" title="在API代码中打断点查找main函数"></a>在API代码中打断点查找main函数</h5><ul><li><p>原理:根据程序运行时的行为来推断其可能用到了哪些API,在相应API的代码中设下断点,然后让程序运行至断点处,再执行"运行到返回"来找到程序相应的调用该API的代码位置</p></li><li><p>重新开始调试,右键选择 查找 => 所有模块名称,然后点击数据窗口,输入MessageBoxW进行筛选,然后双击筛选结果MessageBoxW进入MessageBoxW函数所在位置</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121201358362.png" alt="image-20211121201358362"></p></li><li><p>按下F2(设置/取消)断点,管理所有断点可以点击图中BreakPoint<strong>下方的b按钮</strong>打开断点管理窗口,设置断点后,可以按F9使之运行到断点处(需要只有一个断点,多个断点则停在靠前的断点处),或着按F4运行运行至光标处</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121201551539.png" alt="image-20211121201551539"></p></li><li><p>然后Ctrl + F9运行到返回,这里实际上并不会直接返回程序调用MessageBoxW的地方,需要我们先点击程序的"确定",点击完后再按Ctrl + F9,则能返回到main函数</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121202252468.png" alt="image-20211121202252468"></p></li><li><p>如下图,可以看到最终返回的位置是程序调用MessageBoxW之后的下一句语句,这样也找到了main函数所在位置</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121202454724.png" alt="image-20211121202454724"></p></li></ul><h5 id="修改字符串-HelloWorld-为-Hi-World"><a href="#修改字符串-HelloWorld-为-Hi-World" class="headerlink" title="修改字符串"HelloWorld"为"Hi World!""></a>修改字符串"HelloWorld"为"Hi World!"</h5><ul><li><p>找到main函数后,我们可以知道"HelloWorld"这个字符串所在的地址,如下图地址为"002B7B4C"</p></li><li><p><img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121203058760.png" alt="image-20211121203058760"></p></li><li><p>接下来,我们点击左下角的数据窗口,Ctrl + G输入"002B7B4C",跳转至地址"002B7B4C",可以看到该数据内容就是字符串"HelloWorld"</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121203421142.png" alt="image-20211121203421142"></p></li><li><p>光标选择HelloWorld字符串,然后Ctrl + E进行编辑,这里一般来说修改字符串后的字符串长度应该小于等于原字符串长度,否则可能引起程序出错,这里选择修改Unicode的"HelloWorld"为"Hi World!",然后选择HEX将末尾的64 00 改为 00 00,即用NULL填充,从c++的角度就是用'\0'标记该处为字符串终点,此外这里是两个字节的原因是编码为Unicode,所以才两个字节一个字符</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121203541937.png" alt="image-20211121203541937"></p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121203916635.png" alt="image-20211121203916635"></p></li><li><p>保存后,运行程序,可以看到程序的弹窗内容已被修改为"Hi World!",但是这样的修改不是永久的</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121204417052.png" alt="image-20211121204417052"></p></li><li><p>如果需要永久保存我们的修改,需要右键选择左下角的数据窗口,然后选择复制到可执行文件</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121204635349.png" alt="image-20211121204635349"></p></li><li><p>然后进入如下的数据窗口,右键选择保存文件即可</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121204811555.png" alt="image-20211121204811555"></p></li></ul><h5 id="修改字符串-HelloWorld-为长字符串-HelloReversingWorld"><a href="#修改字符串-HelloWorld-为长字符串-HelloReversingWorld" class="headerlink" title="修改字符串"HelloWorld"为长字符串"HelloReversingWorld!!!""></a>修改字符串"HelloWorld"为长字符串"HelloReversingWorld!!!"</h5><ul><li><p>前面提到的方法虽然也能修改字符串内容,但当我们需要修改的字符串比原字符串长的多时,该方法就不适用了,下面介绍的方法原理很简单,在程序中非常多"00"的地方也就是比较"空闲"的地方里面放我们需要加的字符串,然后将程序在引用"HelloWorld"的地方将相应的地址替换为我们的字符串地址即可,但有点需要注意的是,不是所有非常多"00"的地方都适合,我们需要在程序存放字符串常量的区域去找大片的空白区</p></li><li><p>我们先跟之前方法一样跳转至"HelloWrold"字符串所在位置,然后向下拖动,找到一大片"00"数据区域,然后选择一个位置开始写入我们的字符串即可</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121210133985.png" alt="image-20211121210133985"></p></li><li><p>如下图,选择编辑002B861C处的数据,取消勾选保持大小,这样可以输入我们想要的字符串长度,输入HelloReversingWorld!!!</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121210636401.png" alt="image-20211121210636401"></p></li><li><p>保存完后,我们回到程序引用"HelloWorld"字符串的地方,修改该处的汇编指令,将指向"HelloWorld"的地址替换为"HelloReversingWorld!!!"的地址</p></li><li><p>如下图,按"空格"修改光标所在的汇编代码,将地址改为"HelloReversingWorld!!!"的地址即"0x002B861C",保存然后运行查看结果</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121211137711.png" alt="image-20211121211137711"></p></li><li><p>最终效果如下,可以看到同样达到修改字符串的目的</p><p> <img src="/note/2021/11/%E3%80%8A%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E3%80%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B01/image-20211121211644720.png" alt="image-20211121211644720"></p></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 逆向 </tag>
</tags>
</entry>
<entry>
<title>Android使用gdisk调整system分区大小</title>
<link href="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/"/>
<url>/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li>最近尝试给谷歌nexus 5编译HavocOS (android 11分支),折腾一大堆后终于生成了zip刷机包,结果死活刷不进去,卡在"E1001: Failed to update system image",原因推测是:system分区太小,因为编译时报错提示system镜像过小,所以在编译配置文件中BoardConfig.mk调整过system分区的大小,修改后才编译打包成功。</li><li>显然只改BoardConfig.mk,只是能正常编译打包,但是手机(物理)上的system分区还是那么大,太大刷不进去</li><li>为了验证是否是system分区太小缘故,因为编译还生成了system.img的中间文件,这个img是可以用fastboot刷入的,手机usb连接到电脑,进入bootloader模式,运行<code>fastboot system system.img</code>,不出意外失败了,而且报错信息为:"remote: 'size too large'",这就验证了是手机/system分区不够大的原因。所以问题就变为了怎么扩增system分区?</li><li>经过网上查找相关教程,最终按这个问题里的答案:<a href="https://android.stackexchange.com/questions/216123/android-how-to-increase-system-partition-and-decrease-data-partition">https://android.stackexchange.com/questions/216123/android-how-to-increase-system-partition-and-decrease-data-partition</a>尝试成功</li></ul><h2 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h2><ul><li>需要手机解锁bootloader并刷入twrp recovery</li><li>下载gdisk等工具<a href="parted_gdisk_fdisk_mkfs.ext4-ARM.zip">parted_gdisk_fdisk_mkfs.ext4-ARM.zip</a>,<a href="parted_gdisk_fdisk_mkfs.ext4-AARCH64.zip">parted_gdisk_fdisk_mkfs.ext4-AARCH64.zip</a>(根据手机CPU架构选择,一般多为arm)</li><li>PC需要先安装好adb</li><li><strong>如果手机上有重要数据请先备份</strong></li><li><strong>操作有风险,请谨慎操作,请自行承担风险</strong></li></ul><span id="more"></span><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li><p>手机进入recovery模式</p></li><li><p>下载解压gdisk,电脑usb连接手机,adb push将gdisk传至手机</p> <figure class="highlight shell"><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">adb push gdisk /sbin/</span><br><span class="line">adb push mkfs.ext4 /sbin/</span><br></pre></td></tr></table></figure></li><li><p>运行</p> <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">adb shell</span><br></pre></td></tr></table></figure> <figure class="highlight shell"><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"><span class="meta prompt_"># </span><span class="language-bash">给 gdisk mkfs.ext4赋予可执行权限</span></span><br><span class="line">chmod +x /sbin/*</span><br></pre></td></tr></table></figure></li><li><p>twrp中选择挂载/system分区,终端运行<code>df -h</code></p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804164621667.png" alt="image-20210804164621667"></p></li><li><p>可以看到system分区对应的区块为"/dev/block/platform/msm_sdcc.1/by-name/system",userdata分区对应的区块为"/dev/block/platform/msm_sdcc.1/by-name/userdata",但这个by-name说明是软连接,我们需要获得它真实的区块位置,运行<code>ls -lah /dev/block/platform/msm_sdcc.1/by-name/system</code>和<code>ls -lah /dev/block/platform/msm_sdcc.1/by-name/userdata</code>即可获得</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804165043465.png" alt="image-20210804165043465"></p></li><li><p>可以看到/system分区对应的为"/dev/block/mmcblk0p25",/data分区对应的为"/dev/block/mmcblk0p28",记下这两个值,后面需要使用,不同手机这两个可能跟本文不一样,要以实际情况为准</p></li><li><p>运行<code>gdisk /dev/block/mmcblk0</code>,注意这里没有"p25","p28","p*"</p></li><li><p>输入help可以查看帮助<img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804165518769.png" alt="image-20210804165518769"></p></li><li><p>这里因为要扩增system分区,所以最好的办法是从减小userdata分区的大小来实现</p></li><li><p>直接调整已有分区大小(增加或者减小),需要下载工具里<strong>parted</strong>命令(使用方法最开始的链接有,这里没有使用parted,因为使用parted比较繁琐),这里不打算直接调整userdata分区大小,而是将userdata分区和system分区都删除掉,再重新分区</p></li><li><p>gdisk删除分区</p></li><li><p>先输入<code>d</code>,回车后输入需要删除的分区这里需要删除的为system(25)和userdata(28),这两个数值即为mmcblk0p25和mmcblk0p28里的数字</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804170455330.png" alt="image-20210804170455330"></p></li><li><p>删除后新建分区,输入<code>n</code>,然后输入原system分区数字,接着会提示选择分区起始扇区位置,回车默认即可,接着会提示选择分区终止扇区位置,这里选择**"+2G"**,表示分配2GB空间,之后回车默认即可</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804170626103.png" alt="image-20210804170626103"></p></li><li><p>再输入<code>n</code>,输入原userdata分区数字,接着会提示选择分区起始扇区位置,回车默认即可,接着会提示选择分区终止扇区位置,<strong>这里可以直接回车使用默认即可</strong>(这里不使用+*G的原因是,让剩余未分配空间都尽可能利用),然后一路回车即可</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804170958139.png" alt="image-20210804170958139"></p></li><li><p>接下来输入<code>c</code>,来重命名新增加的两个分区,选择原system分区的数字,输入名字system,同样的将另一个分区命名为userdata</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804171254615.png" alt="image-20210804171254615"></p></li><li><p>输入<code>p</code>,查看所有分区信息,检查分区大小和分区名称是否正确,如下图可以看到25对应分区大小为2GB名称为system,28对应的分区大小为25.2GB名称为userdata,说明修改没有问题</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804171517220.png" alt="image-20210804171517220"></p></li><li><p>接着保存分区的改动,输入:<strong>w</strong>,按提示选择y即可</p><p> <img src="/tutorial/2021/08/Android%E4%BD%BF%E7%94%A8gdisk%E8%B0%83%E6%95%B4system%E5%88%86%E5%8C%BA%E5%A4%A7%E5%B0%8F/image-20210804171742985.png" alt="image-20210804171742985"></p></li><li><p>最后格式化system分区和userdata分区为ext4</p> <figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">将mmc0blk0p25修改为实际system分区所在分块</span></span><br><span class="line">mkfs.ext4 /dev/block/mmc0blk0p25</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">将mmc0blk0p25修改为实际userdata分区所在分块</span></span><br><span class="line">mkfs.ext4 /dev/block/mmc0blk0p28</span><br></pre></td></tr></table></figure></li><li><p>至此/system分区大小调整完毕,可以用twrp正常刷入编译的rom包了</p></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><p><a href="https://android.stackexchange.com/questions/216123/android-how-to-increase-system-partition-and-decrease-data-partition">https://android.stackexchange.com/questions/216123/android-how-to-increase-system-partition-and-decrease-data-partition</a></p></li><li><p><a href="https://forum.xda-developers.com/t/how-to-boot-from-sd-card-successfully-on-qmobile-z8-with-bricked-dead-emmc.3712171/">[HOW TO] BOOT FROM SD CARD [SUCCESSFULLY] on QMobile Z8 with BRICKED/DEAD eMMC</a></p></li><li><p><a href="https://www.cnblogs.com/Sunzz/p/6908329.html">gdisk用法</a></p></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> android </tag>
<tag> 教程 </tag>
</tags>
</entry>
<entry>
<title>Docker容器编译LineageOS笔记</title>
<link href="/note/2021/07/Docker%E5%AE%B9%E5%99%A8%E7%BC%96%E8%AF%91LineageOS%E7%AC%94%E8%AE%B0/"/>
<url>/note/2021/07/Docker%E5%AE%B9%E5%99%A8%E7%BC%96%E8%AF%91LineageOS%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h4 id="编译环境"><a href="#编译环境" class="headerlink" title="编译环境"></a>编译环境</h4><ul><li><strong>运行内存至少8G</strong>,运行内存太小会导致编译过程出现奇怪错误</li><li>JDK(本文编译的为cm-14.1,即Android 7.1,因此需要JDK8,Android6及之前的需要更早的JDK版本)</li><li><strong>区分大小写的文件系统</strong>,MacOS和Windows默认的文件系统不区分大小写,使用不区分大小写文件系统编译Android会出错。docker容器中,建议直接docker里面的目录下编译,而不是挂载宿主机的目录下编译</li><li>建议使用非root用户编译</li></ul><span id="more"></span><h4 id="准备开始"><a href="#准备开始" class="headerlink" title="准备开始"></a>准备开始</h4><ul><li><p>Docker Destop中设置容器内存至少8G,拉取ubuntu镜像</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">docker pull ubuntu:20.04</span><br></pre></td></tr></table></figure></li><li><p>创建并连接到容器</p></li><li><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">sudo apt install -y bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick lib32ncurses5-dev lib32readline-dev lib32z1-dev liblz4-tool libncurses5-dev libsdl1.2-dev libssl-dev libwxgtk3.0-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev bsdmainutils</span><br></pre></td></tr></table></figure><blockquote><p>如果编译出现缺少libncurses.so.5错误,可在bash下运行<code>sudo apt install libncurses*</code></p></blockquote></li><li><p>安装OpenJDK-8</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">sudo apt install openjdk-8-jdk</span><br></pre></td></tr></table></figure></li><li><p>如果有其他版本JDK,可以使用update-alternatives切换JDK</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">sudo update-alternatives --config java</span><br></pre></td></tr></table></figure></li><li><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">sudo apt install -y wget unzip net-tools inetutils-ping lsof screen adb</span><br></pre></td></tr></table></figure></li><li><p>安装repo</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> <span class="variable">$HOME</span>/bin</span><br><span class="line">sudo curl https://storage.googleapis.com/git-repo-downloads/repo > <span class="variable">$HOME</span>/bin/repo</span><br><span class="line"><span class="built_in">chmod</span> a+x <span class="variable">$HOME</span>/bin/repo</span><br></pre></td></tr></table></figure></li><li><p>在.bashrc或者.zshrc中加入<code>export PATH=$HOME/bin:$PATH</code></p></li><li><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"><span class="built_in">mkdir</span> -p /media/android</span><br></pre></td></tr></table></figure></li><li><p>初始化</p> <figure class="highlight bash"><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"><span class="built_in">cd</span> /media/android</span><br><span class="line">repo init -u git://github.com/LineageOS/android.git -b cm-14.1</span><br></pre></td></tr></table></figure></li><li><p>修改AOSP源为清华或者其他次级镜像</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">vi .repo/manifests/default.xml</span><br></pre></td></tr></table></figure></li><li><p>将</p> <figure class="highlight xml"><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="tag"><<span class="name">remote</span> <span class="attr">name</span>=<span class="string">"aosp"</span></span></span><br><span class="line"><span class="tag"><span class="attr">fetch</span>=<span class="string">"https://android.googlesource.com"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">revision</span>=<span class="string">"refs/tags/android-7.1.2_r36"</span> /></span></span><br></pre></td></tr></table></figure></li><li><p>修改为</p> <figure class="highlight xml"><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="tag"><<span class="name">remote</span> <span class="attr">name</span>=<span class="string">"aosp"</span></span></span><br><span class="line"><span class="tag"><span class="attr">fetch</span>=<span class="string">"https://aosp.tuna.tsinghua.edu.cn"</span></span></span><br><span class="line"><span class="tag"><span class="attr">revision</span>=<span class="string">"refs/tags/android-7.1.2_r36"</span> /></span></span><br></pre></td></tr></table></figure><p> 或者</p> <figure class="highlight xml"><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="tag"><<span class="name">remote</span> <span class="attr">name</span>=<span class="string">"aosp"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">fetch</span>=<span class="string">"git://ip.to.mirror"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">revision</span>=<span class="string">"refs/tags/android-7.1.2_r36"</span> /></span></span><br></pre></td></tr></table></figure><blockquote><p>这里可以改为用的自己搭建的aosp次级镜像</p></blockquote></li><li><p>LineageOS源也可改为清华的,但这里维持默认github的即可</p></li><li><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">repo <span class="built_in">sync</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="开始编译"><a href="#开始编译" class="headerlink" title="开始编译"></a>开始编译</h4><ul><li><p>正式开始前还需要获取手机的proprietary文件,而且还需要手机先刷入网上的cm-14.1 rom包。。</p></li><li><p>下载并刷入官方的cm-14.1的包,启动手机进入开发者模式,打开USB调试,并启用网络ADB调试,手机连接到跟docker容器宿主机同一局域网下</p><blockquote><p>获取proprietary的脚本原理是利用adb拉取手机上的文件,而adb可以usb或者网络连接,因此不一定需要usb连接到电脑,所以就不用考虑docker容器中怎么usb连接手机的问题</p></blockquote></li><li><p><strong>手机开发者选项中启用root</strong>(否则部分文件无法获取)</p></li><li><p>docker中adb连接手机,运行以下命令使用通过网络方式连接到手机</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">adb connect 192.168.2.148:5555 <span class="comment"># 192.168.2.148:5555改为你的手机adb server地址和端口</span></span><br></pre></td></tr></table></figure></li><li><p>运行<code>device/厂商/手机代号/extract-files.sh</code>,例如这里编译的为nexus 5</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">bash device/lge/hammerhead/extract-files.sh</span><br></pre></td></tr></table></figure></li><li><p>如果没有相应的device文件,需要先运行<code>brunch 手机代号</code>,然后会自动从github上拉取相应的机型的device文件,前提是LineageOS官方github里面有收录这个机型的device文件,否则就得从其他地方自行获取了</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">brunch hammerhead</span><br></pre></td></tr></table></figure></li><li><p>实际运行extract-files.sh过程中,发现可能还是会缺一些.so文件,这里测试的就缺少了一个<strong>vendor/lib/libfrsdk.so</strong>文件,但手机上刷的cm14 ROM后确实没有这个文件,这时候可能就得根据手机代号/型号从github或者谷歌找别人弄好的proprietary文件,从那里获取缺失的.so文件</p></li><li><p>例如这里github上刚好找到nexus一个cm14.1的<a href="https://github.com/shinobizero/proprietary_vendor_lge_hammerhead.git">proprietary_vendor_lge_hammerhead.git</a>,因为版本号一致,所以可以直接用这里面的hammerhead文件夹放到vendor/lge/下</p></li><li><p>获取完proprietary文件后,运行<code>brunch hammerhead</code>,即可开始编译</p></li></ul><h4 id="编译中遇到的错误及解决"><a href="#编译中遇到的错误及解决" class="headerlink" title="编译中遇到的错误及解决"></a>编译中遇到的错误及解决</h4><hr><ul><li><p>make: [out/build-aosp_arm.ninja] Killed</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">frameworks/av/camera/cameraserver/Android.mk:18: Target has integrated cameraserver into mediaserver. This is weakening security measures introduced in 7.0</span><br><span class="line">find: '/media/android/out/target/common/obj/SHARED_LIBRARIES/libwifi-hal-mock_intermediates': No such file or directory</span><br><span class="line">build/core/ninja.mk:167: recipe for target 'out/build-aosp_arm.ninja' failed</span><br><span class="line">make: *** [out/build-aosp_arm.ninja] Killed</span><br><span class="line"></span><br><span class="line">#### make failed to build some targets (24) ####</span><br></pre></td></tr></table></figure></li><li><p>一个很莫名其妙的错误,一眼看上去不知道错哪,没有明确的报错信息。排了很久,一开始还以为是上面的"find ... No such file or directory",缺某个文件导致的,最后发现跟这个无关,不管怎样都会报这个。最终发现原因是<strong>运行内存太小</strong>了。一开始编译用的docker容器只分配了2G内存,后来看了这个提问后<a href="https://stackoverflow.com/questions/40925991/error-on-compiling-aosp-make-out-build-aosp-arm-ninja-killed">Error on compiling AOSP: make: out/build-aosp_arm.ninja Killed</a>,最后把容器分配的内存调至8G,问题得到解决</p></li></ul><hr><ul><li><p>JACK SSL错误,参考<a href="https://segmentfault.com/a/1190000039970343">aosp 编译过程中Jack server SSL error 错误解决方法</a></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">[ 10% 538/4980] Ensuring Jack server is installed and started</span><br><span class="line">FAILED: setup-jack-server </span><br><span class="line">/bin/bash -c "(prebuilts/sdk/tools/jack-admin install-server prebuilts/sdk/tools/jack-launcher.jar prebuilts/sdk/tools/jack-server-4.11.ALPHA.jar 2>&1 || (exit 0) ) && (JACK_SERVER_VM_ARGUMENTS=\"-Dfile.encoding=UTF-8 -XX:+TieredCompilation\" prebuilts/sdk/tools/jack-admin start-server 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update server prebuilts/sdk/tools/jack-server-4.11.ALPHA.jar 4.11.ALPHA 2>&1 || exit 0 ) && (prebuilts/sdk/tools/jack-admin update jack prebuilts/sdk/tools/jacks/jack-4.32.CANDIDATE.jar 4.32.CANDIDATE || exit 47 )"</span><br><span class="line">Jack server already installed in "/home/user/.jack-server"</span><br><span class="line">Communication error with Jack server (35), try 'jack-diagnose' or see Jack server log</span><br><span class="line">SSL error when connecting to the Jack server. Try 'jack-diagnose'</span><br><span class="line">SSL error when connecting to the Jack server. Try 'jack-diagnose'</span><br><span class="line">[ 10% 541/4980] build out/target/product/rk3399_mid/obj/ETC/precompiled_sepolicy_intermediates/precompiled_sepolicy</span><br><span class="line">ninja: build stopped: subcommand failed.</span><br><span class="line">22:32:18 ninja failed with: exit status 1</span><br><span class="line"></span><br><span class="line">#### failed to build some targets (01:11 (mm:ss)) ####</span><br><span class="line"></span><br><span class="line">Build android failed!</span><br></pre></td></tr></table></figure></li><li><p>网上有说这个错是端口后"~/.jack-server/config.properties"和"~/.jack-settings"里面端口号不一致的原因,但我检查后发现端口号一致</p></li><li><p>最后按<a href="https://segmentfault.com/a/1190000039970343">aosp 编译过程中Jack server SSL error 错误解决方法</a>这篇文章修改/etc/java-8-openjdk/security/java.security,取消禁用TLSv1, TLSv1.1,问题解决</p></li><li><p>原因: 编译时用的是open-jdk 8,默认禁用了TLSv1, TLSv1.1,</p></li><li><p>解决办法:修改/etc/java-8-openjdk/security/java.security,找到"jdk.tls.disabledAlgorithms",并改为以下内容</p> <figure class="highlight ini"><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="attr">jdk.tls.disabledAlgorithms</span>=SSLv3, RC4, DES, MD5withRSA, \</span><br><span class="line"> DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \</span><br><span class="line"> include jdk.disabled.namedCurves</span><br></pre></td></tr></table></figure></li></ul><hr><ul><li><p>OOM错误(Out of memory error)参考<a href="https://www.cnblogs.com/zhouliquan/p/7491976.html">编译报错:Out of memory error</a></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">[ 1% 250/22713] Building with Jack: /med...work_intermediates/with-local/classes.dex</span><br><span class="line">FAILED: /bin/bash /media/android/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/with-local/classes.dex.rsp</span><br><span class="line">Out of memory error (version 1.2-rc4 'Carnac' (298900 f95d7bdecfceb327f9d201a1348397ed8a843843 by [email protected])).</span><br><span class="line">GC overhead limit exceeded.</span><br><span class="line">Try increasing heap size with java option '-Xmx<size>'.</span><br><span class="line">Warning: This may have produced partial or corrupted output.</span><br><span class="line">ninja: build stopped: subcommand failed.</span><br><span class="line">make: *** [build/core/ninja.mk:152: ninja_wrapper] Error 1</span><br></pre></td></tr></table></figure></li><li><p>修改 prebuilts/sdk/tools/jack-admin 文件,增加 '-Xmx<size>'</p> <figure class="highlight ini"><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">- <span class="attr">JACK_SERVER_COMMAND</span>=<span class="string">"java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME"</span></span><br><span class="line">+ <span class="attr">JACK_SERVER_COMMAND</span>=<span class="string">"java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -Xmx4G -cp $LAUNCHER_JAR $LAUNCHER_NAME"</span></span><br></pre></td></tr></table></figure></li><li><p>修改完后关闭已有的jack server</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">./prebuilts/sdk/tools/jack-admin kill-server</span><br></pre></td></tr></table></figure></li></ul><hr><ul><li><p>Checking 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><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">You have tried to change the API from what has been previously approved.</span><br><span class="line"></span><br><span class="line">To make these errors go away, you have two choices:</span><br><span class="line"> 1) You can add "@hide" javadoc comments to the methods, etc. listed in the</span><br><span class="line"> errors above.</span><br><span class="line"></span><br><span class="line"> 2) You can update current.txt by executing the following command:</span><br><span class="line"> make update-api</span><br></pre></td></tr></table></figure></li><li><p>原因在修改完系统Api或部分公共Api后(常见于修改Intent.java、KeyEvent.java等等),可以按提示运行<code>make update-api</code>,然后重新编译</p></li></ul><hr><h4 id="编译AUPK"><a href="#编译AUPK" class="headerlink" title="编译AUPK"></a>编译AUPK</h4><ul><li><p>AUPK:官方github仓库<a href="https://github.com/FeJQ/AUPK.git">https://github.com/FeJQ/AUPK.git</a></p></li><li><p>git 拉取AUPK代码</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">git clone https://github.com/FeJQ/AUPK.git</span><br></pre></td></tr></table></figure></li><li><p>因为AUPK是基于android7.1的,所以这里可以用cp命令拷贝art和frameworks文件夹到android源码文件夹下</p></li><li><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">make update-api</span><br></pre></td></tr></table></figure></li><li><p>重新编译rom</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">brunch hammerhead</span><br></pre></td></tr></table></figure></li></ul><h4 id="刷入ROM"><a href="#刷入ROM" class="headerlink" title="刷入ROM"></a>刷入ROM</h4><ul><li><p>将out/target/product/手机代号/目录下的lineage-14.1-*-UNOFFICIAL-hammerhead.zip压缩包传至手机,然后用twrp等第三方recovery双清后刷入即可</p></li><li><p>adb重启至recovery模式</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">adb reboot recovery</span><br></pre></td></tr></table></figure></li></ul><h4 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h4><ul><li><a href="https://stackoverflow.com/questions/40925991/error-on-compiling-aosp-make-out-build-aosp-arm-ninja-killed">Error on compiling AOSP: make: out/build-aosp_arm.ninja Killed</a></li><li><a href="https://segmentfault.com/a/1190000039970343">aosp 编译过程中Jack server SSL error 错误解决方法</a></li><li><a href="https://segmentfault.com/a/1190000039970343">aosp 编译过程中Jack server SSL error 错误解决方法</a></li><li><a href="https://www.cnblogs.com/zhouliquan/p/7491976.html">编译报错:Out of memory error</a></li><li><a href="https://github.com/FeJQ/AUPK.git">https://github.com/FeJQ/AUPK.git</a></li><li><a href="https://github.com/shinobizero/proprietary_vendor_lge_hammerhead.git">https://github.com/shinobizero/proprietary_vendor_lge_hammerhead.git</a></li></ul>]]></content>
<categories>
<category> note </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> android </tag>
</tags>
</entry>
<entry>
<title>Discuz简单验证码识别</title>
<link href="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/"/>
<url>/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li>我之前的文章<a href="https://blog.iz4.cc/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/">解决selenium使用location定位与截图中的坐标偏差问题</a>中提到了,如何利用selenium截取验证码,但是并没有讲该怎么去识别验证码,原因是之前一直没有找到好的方法,一开始尝试过pillow + pytesseract,但是效果很差,显然这种验证码不能用这方法。也尝试过网上的一些在线识别,效果不错,但缺点就是要钱。</li><li>再后来在github上发现了这个项目<a href="https://github.com/nickliqian/cnn_captcha">https://github.com/nickliqian/cnn_captcha</a>,然后又在这篇文章中<a href="https://cuijiahua.com/blog/2018/01/dl_5.html">Tensorflow实战(二):Discuz验证码识别</a>找到了生成Discuz验证码的php代码,这篇文章也给出了他的深度学习的识别验证码的代码,但我测试时没跑成功,又不懂这方面的知识,排不了错,所以最后选择的是用cnn_captcha这个项目来训练模型,用他给的php代码生成训练用的数据集。最终发现训练的模型效果还不错,一开始用2万张图片训练,虽然训练集准确率达到了100%,但测试集图片准确率一直为0,增加图片数量得到改善,最后调整到用20万张图片训练,测试集字符识别率和图片准确率均达到了99%,实际测试从目标网站爬取下来的图片识别准确率也有80%,这个准确率自用完全OK。</li></ul><span id="more"></span><h2 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h2><ul><li><p>最好拥有一台有高性能GPU的电脑,不然训练只靠CPU真跑不动,或者可以使用像谷歌的colab这种云平台来训练模型,训练好后再下载下来使用</p></li><li><p>tensorflow 1.x 环境,试过tensorflow 2.x以兼容1.x方式运行,但是运行还是出了问题,可以用anaconda创建一个tensorflow 1的虚环境,tensorflow1.5下载地址:<a href="https://pypi.org/project/tensorflow/1.15.0/#files">https://pypi.org/project/tensorflow/1.15.0/#files</a></p></li><li><p>php运行环境(生成验证码数据集需要)</p></li><li><p>如果不想生成验证码或者重复训练模型,这里提供训练用的20万张验证码数据集,以及训练好的模型,模型下载后可以直接使用</p><blockquote><p>链接: <a href="https://pan.baidu.com/s/1neXmwUjlqGzxBk3QoqRWCg?pwd=2zx4">https://pan.baidu.com/s/1neXmwUjlqGzxBk3QoqRWCg?pwd=2zx4</a> 密码: 2zx4</p><p>注意事项:使用这个模型进行识别需要图片满足width 160,hight 60的png格式,而且要有alpha通道,因为用于训练的图片就是这种。如果你的图片宽高不符合请等比缩放调整至一直,否则会运行出错;如果png图没有alpha通道,请转换下即可,否则将导致识别结果不准确(之前试了同一网站两种不同方式获取验证码图片,识别结果天壤之别,排了很久才发现是识别差的那组是因为图片没有alpha通道,才导致识别结果不准。。)</p></blockquote></li><li><p>这个模型比较适合这种类型的Discuz验证码,动态和静态一样,因为最终用于识别的都只有那一张图</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/misc.gif" alt="misc.gif"></p></li></ul><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><h4 id="生成验证码数据集"><a href="#生成验证码数据集" class="headerlink" title="生成验证码数据集"></a>生成验证码数据集</h4><ul><li><p>首先生成训练用的数据集,一开始是想的从目标网站爬取验证码,爬下来1000张,自己人工标了几百张,大脑就罢工了。。后来找到这个php代码<a href="discuz.tar.gz">discuz.tar.gz</a>,可以批量生成验证码,还不用自己标注,省去很大麻烦,所以要识别某类验证码,最好看看网上能不能找到相应的验证码库批量生成,实在不行再来考虑爬取标注。</p></li><li><p>下面讲解如何在不会php的情况下,简单跑起这个生成验证码项目,以MacOS+PHPStorm为例</p></li><li><p>首先安装php,打开终端用homebrew安装即可:<code>brew install php</code></p></li><li><p>下载安装PHPStorm,启动后打开discuz.tar.gz解压后的目录</p></li><li><p>选择Preferences > PHP > CLI Interpreter > PHP executable,选择php安装目录,一般homebrew安装的在/usr/local/Cellar目录下可以找到,最后选择OK保存即可</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/img1.png" alt="img1.png"></p></li><li><p>选择右上角Add Configuration</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/img2.png" alt="img2.png"></p></li><li><p>点击+添加一个PHP Built-in Web Server</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/img3.png" alt="img3.png"></p></li><li><p>修改Name,Host,Port,其中Name可以随便起,Host可以设为127.0.0.1或者0.0.0.0等,Port选择你电脑上没有被占用的端口即可,其他保持默认即可,然后保存设置</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/img4.png" alt="img4.png"></p></li><li><p>保存后,可以看到右上角有了绿色三角的运行按钮,点击运行,即可启动这个php项目</p></li><li><p>运行后,就可以用python批量生产验证码了,懂PHP的话其实应该可以修改代码直接PHP生成这些图即可,但奈何不会PHP,下面python代码同样也是改自这篇文章提供的<a href="https://cuijiahua.com/blog/2018/01/dl_5.html">Tensorflow实战(二):Discuz验证码识别</a>,这里因为目标网站就是用的Discuz默认的"BCEFGHJKMPQRTVWXY2346789"词库,所以批量生成验证码时也只用这些来生成,减少干扰项,为了方便cnn_captcha识别,文件命名为"验证码_序号"的格式</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#-*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> urllib.request <span class="keyword">import</span> urlretrieve</span><br><span class="line"><span class="keyword">import</span> time, random, os</span><br><span class="line"> </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Discuz</span>():</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line"><span class="comment"># Discuz验证码生成图片地址</span></span><br><span class="line">self.url = <span class="string">'http://127.0.0.1:8080/index.php?label={}&width=160&height=60&background=1&adulterate=1&ttf=1&angle=1&scatter=1&color=1&size=1&shadow=1'</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">random_captcha_text</span>(<span class="params">self, captcha_size = <span class="number">4</span></span>):</span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">验证码一般都无视大小写;验证码长度4个字符</span></span><br><span class="line"><span class="string">Parameters:</span></span><br><span class="line"><span class="string">captcha_size:验证码长度</span></span><br><span class="line"><span class="string">Returns:</span></span><br><span class="line"><span class="string">captcha_text:验证码字符串</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="comment"># number = ['0','1','2','3','4','5','6','7','8','9']</span></span><br><span class="line"><span class="comment"># alphabet = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']</span></span><br><span class="line"><span class="comment"># char_set = number + alphabet</span></span><br><span class="line">char_set=<span class="string">"BCEFGHJKMPQRTVWXY2346789"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="string">''</span>.join(random.sample(char_set, <span class="number">4</span>))</span><br><span class="line"> </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">download_discuz</span>(<span class="params">self, start=<span class="number">0</span>, end=<span class="number">10000</span></span>):</span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">下载验证码图片</span></span><br><span class="line"><span class="string">Parameters:</span></span><br><span class="line"><span class="string">nums:下载的验证码图片数量</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line">dirname = <span class="string">'./Discuz'</span></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(dirname):</span><br><span class="line">os.mkdir(dirname)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(start, end):</span><br><span class="line">label = self.random_captcha_text()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">'第%d张图片:%s下载'</span> % (i + <span class="number">1</span>,label))</span><br><span class="line">urlretrieve(url = self.url.<span class="built_in">format</span>(label), filename = os.path.join(dirname, <span class="string">f'<span class="subst">{label}</span>_<span class="subst">{i}</span>.png'</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">'恭喜图片下载完成!'</span>)</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">dz = Discuz()</span><br><span class="line">dz.download_discuz(<span class="number">0</span>, <span class="number">50000</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>至此已经生成了用于训练的图片数据集</p></li></ul><h4 id="训练模型"><a href="#训练模型" class="headerlink" title="训练模型"></a>训练模型</h4><ul><li><p>这里以用谷歌的colab为例来训练模型,没办法自己电脑上的显卡太弱,约等于无</p></li><li><p>首先上传打包后的验证码到谷歌云盘上,方便colab上访问</p></li><li><p>新建或者打开一个笔记本,选择左上角的修改 > 笔记本设置,可以选择使用GPU加速</p><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/image-20210728112118379.png" alt="image-20210728112118379"></p></li><li><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> google.colab <span class="keyword">import</span> drive</span><br><span class="line">drive.mount(<span class="string">'/content/drive'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看是否挂载成功</span></span><br><span class="line">os.listdir(<span class="string">'.'</span>)</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/image-20210728112328924.png" alt="image-20210728112328924"></p></li><li><p>选择tensorflow_version 1.x运行环境</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%tensorflow_version <span class="number">1.</span>x</span><br></pre></td></tr></table></figure></li><li><p>从github上clone cnn_captcha</p> <figure class="highlight python"><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">! git clone https://github.com/nickliqian/cnn_captcha.git</span><br><span class="line">! mkdir cnn_captcha/sample</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">! tar -zxvf <span class="string">'/content/drive/MyDrive/Discuz-50000.tar.gz'</span> > /dev/null</span><br><span class="line">! tar -zxvf <span class="string">'/content/drive/MyDrive/Discuz-50000-100000.tar.gz'</span> > /dev/null</span><br><span class="line">! tar -zxvf <span class="string">'/content/drive/MyDrive/Discuz-100000-200000.tar.gz'</span> > /dev/null</span><br><span class="line">! mv Discuz cnn_captcha/sample/origin</span><br></pre></td></tr></table></figure></li><li><p>修改配置文件,如果不是在colab上运行,则可以直接文本编辑器修改json配置文件</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></pre></td><td class="code"><pre><span class="line">! cd cnn_captcha/tools && python3 collect_labels.py</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">'cnn_captcha/conf/sample_config.json'</span>, <span class="string">'r'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> data=json.load(fp)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改需要的参数,其余参数可保持默认</span></span><br><span class="line"><span class="comment"># 最大训练次数</span></span><br><span class="line">data[<span class="string">'cycle_stop'</span>] = <span class="number">60000</span></span><br><span class="line"><span class="comment"># 数据集图片格式</span></span><br><span class="line">data[<span class="string">'image_suffix'</span>]=<span class="string">'png'</span></span><br><span class="line"><span class="comment"># 图片宽高</span></span><br><span class="line">data[<span class="string">'image_width'</span>] = <span class="number">160</span></span><br><span class="line">data[<span class="string">'image_hight'</span>] = <span class="number">60</span></span><br><span class="line"><span class="comment"># 生成验证码的所有字符集合,Discuz默认只有这24个</span></span><br><span class="line">data[<span class="string">'char_set'</span>]=<span class="string">'BCEFGHJKMPQRTVWXY2346789'</span></span><br><span class="line"><span class="comment"># 每次测试集使用的图片数量,内存小的机器可减小这个</span></span><br><span class="line">data[<span class="string">'test_batch_size'</span>]=<span class="number">100</span></span><br><span class="line"><span class="comment"># 每次训练集使用的图片数量,内存小的机器可减小这个</span></span><br><span class="line">data[<span class="string">'train_batch_size'</span>]=<span class="number">128</span></span><br><span class="line"><span class="comment"># 保存配置</span></span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">'cnn_captcha/conf/sample_config.json'</span>, <span class="string">'w'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> json.dump(data, fp)</span><br><span class="line"></span><br></pre></td></tr></table></figure></li></ul><p><img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/image-20210728113839158.png" alt="image-20210728113839158"></p><ul><li><p>设置工作目录为cnn_captcha,方便后面运行训练主函数</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">os.chdir(<span class="string">'cnn_captcha'</span>)</span><br></pre></td></tr></table></figure></li><li><p>拆分图片为训练集和测试集</p> <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">! python3 verify_and_split_data.py</span><br></pre></td></tr></table></figure></li><li><p>拷贝train_model.py文件内容至colab笔记本输入框,然后运行main函数,这里不直接命令运行是因为,colab中使用命令运行,默认用的还是tensorflow 2,前面设置tensorflow 1就白设置了</p></li><li><p>训练完毕后打包model并保存到谷歌云盘</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></pre></td><td class="code"><pre><span class="line">os.chdir(<span class="string">'/content/'</span>)</span><br><span class="line"></span><br><span class="line">! tar -zcvf cnn_captcha.tar.gz cnn_captcha > /dev/null <span class="comment"># 可以不打包整个cnn_captcha文件夹,看需求调整</span></span><br><span class="line">! tar -zcvf model.tar.gz cnn_captcha/model > /dev/null <span class="comment"># 这个即为训练好的模型</span></span><br><span class="line">! cp model.tar.gz drive/MyDrive/ <span class="comment"># 拷贝至谷歌云盘</span></span><br><span class="line">! cp cnn_captcha.tar.gz drive/MyDrive/ <span class="comment"># 拷贝至谷歌云盘</span></span><br></pre></td></tr></table></figure></li><li><p>完整colab的ipynb</p><p> <a href="Discuz.ipynb">Discuz.ipynb</a></p></li><li><p>实际用colab训练发现速度还是可以的:20万张图,按上面参数训练5万多次后,训练集字符和图片准确率100%,测试集的字符和图片准确率达到了99%,总耗时2小时。</p></li></ul><h4 id="启动识别API-Server"><a href="#启动识别API-Server" class="headerlink" title="启动识别API Server"></a>启动识别API Server</h4><ul><li><p>本地git clone cnn_captcha</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/nickliqian/cnn_captcha.git</span><br></pre></td></tr></table></figure></li><li><p>下载训练好的模型,解压至cnn_captcha目录下</p></li><li><p>修改配置文件conf/sample_config.json里面的图片格式和大小为模型使用的大小,其他可以保持默认</p></li><li><p>创建API存放图片文件夹,这个文件夹可以在sample_config.json中修改,启动识别API server前需要有这个错误,否则识别时,报找不到文件错误</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">mkdir -p sample/api</span><br></pre></td></tr></table></figure></li><li><p>启动server,这里运行环境也是需要tensorflow1.x,但是可以不需要GPU</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">python3 webserver_recognize_api.py</span><br></pre></td></tr></table></figure></li></ul><h4 id="测试识别"><a href="#测试识别" class="headerlink" title="测试识别"></a>测试识别</h4><ul><li><p>使用jupyter notebook进行测试,方便展示验证码,比较识别结果</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"></span><br><span class="line"><span class="comment"># 这里参数说明('code.png', open('code.png', 'rb'), 'application'),前面的code.png随意,可以与后面的open中的名称不一致;open('code.png', 'rb')实际上是参数需要一个bytes型的数据,直接传一个bytes型的数据也是可以的;"application"保持默认即可</span></span><br><span class="line">files = {<span class="string">'image_file'</span>: (<span class="string">'code.png'</span>, <span class="built_in">open</span>(<span class="string">'code.png'</span>, <span class="string">'rb'</span>), <span class="string">'application'</span>)}</span><br><span class="line"><span class="comment"># http://127.0.0.1:6000/b为默认的API URL,可以在webserver_recognize_api.py中修改</span></span><br><span class="line">r = requests.post(url=<span class="string">'http://127.0.0.1:6000/b'</span>, files=files)</span><br><span class="line">data = r.json()</span><br><span class="line">data</span><br></pre></td></tr></table></figure><p> <img src="/tutorial/2021/07/Discuz%E7%AE%80%E5%8D%95%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/image-20210728120402589.png" alt="image-20210728120402589"></p></li><li><p>可以看到正确识别了CBXB,实际用某论坛的进行测试,图片准确率可达80%</p></li><li><p>至此可以用这个模型来进行一些使用了之类验证码的Discuz类论坛进行自动登录</p></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://github.com/nickliqian/cnn_captcha">https://github.com/nickliqian/cnn_captcha</a></li><li><a href="https://cuijiahua.com/blog/2018/01/dl_5.html">Tensorflow实战(二):Discuz验证码识别</a></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 教程 </tag>
<tag> 验证码 </tag>
<tag> selenium </tag>
<tag> python </tag>
</tags>
</entry>
<entry>
<title>AOSP次级镜像搭建</title>
<link href="/tutorial/2021/07/AOSP%E6%AC%A1%E7%BA%A7%E9%95%9C%E5%83%8F%E6%90%AD%E5%BB%BA/"/>
<url>/tutorial/2021/07/AOSP%E6%AC%A1%E7%BA%A7%E9%95%9C%E5%83%8F%E6%90%AD%E5%BB%BA/</url>
<content type="html"><![CDATA[<h2 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h2><ul><li><p>以清华AOSP镜像为例搭建本地AOSP次级镜像</p></li><li><p><a href="https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/">清华AOSP官网</a></p></li><li><p>安装repo</p> <figure class="highlight shell"><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">mkdir ~/bin</span><br><span class="line">PATH=~/bin:$PATH</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo</span></span><br><span class="line">curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo > ~/bin/repo</span><br><span class="line">chmod a+x ~/bin/repo</span><br></pre></td></tr></table></figure></li><li><p>修改repo默认REPO_URL(默认为谷歌的)</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">vi ~/bin/repo</span><br></pre></td></tr></table></figure></li><li><p>找到<strong>REPO_URL</strong>,将值替换为:<code>https://mirrors.tuna.tsinghua.edu.cn/git/git-repo</code></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">REPO_URL = <span class="string">'https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'</span></span><br></pre></td></tr></table></figure></li></ul><span id="more"></span><ul><li><p>建立工作目录</p> <figure class="highlight bash"><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"><span class="built_in">mkdir</span> aosp</span><br><span class="line"><span class="built_in">cd</span> aosp</span><br></pre></td></tr></table></figure></li><li><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">repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/mirror/manifest --mirror</span><br></pre></td></tr></table></figure></li><li><p>同步源码</p> <figure class="highlight bash"><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"><span class="comment"># repo sync -j4 # -j指定线程数</span></span><br><span class="line">repo <span class="built_in">sync</span></span><br></pre></td></tr></table></figure></li><li><p>同步完源码后运行</p> <figure class="highlight bash"><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"><span class="comment"># AOSP_PATH替换为工作目录路径</span></span><br><span class="line">git daemon --verbose --export-all --base-path=AOSP_PATH</span><br></pre></td></tr></table></figure></li><li><p>现在可以使用 <code>git://ip.to.mirror/platform/manifest</code> 作为镜像源地址,将"ip.to.mirror"替换为本地镜像的ip地址或者host即可</p></li><li><p>从本地次级镜像同步AOSP源码示例</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">repo init -u git://ip.to.mirror/platform/manifest -b android-4.0.1_r1 <span class="comment"># 选择android分支</span></span><br></pre></td></tr></table></figure></li><li><p>建立次级镜像后,同步LineageOS源码或者其他Android源码,也可以替换其中的AOSP源,这里以同步LineageOS cm-14为例,替换默认的aosp源为我们的本地源,修改default.xml文件<code>vi .repo/manifests/default.xml</code>,将remote name="aosp"的<code>https://android.googlesource.com</code>替换为<code>git://ip.to.mirror</code></p> <figure class="highlight xml"><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="tag"><<span class="name">remote</span> <span class="attr">name</span>=<span class="string">"aosp"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">fetch</span>=<span class="string">"git://ip.to.mirror"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">revision</span>=<span class="string">"refs/tags/android-7.1.2_r36"</span> /></span></span><br></pre></td></tr></table></figure></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> android </tag>
<tag> aosp </tag>
</tags>
</entry>
<entry>
<title>解决selenium使用location定位与截图中的坐标偏差问题</title>
<link href="/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/"/>
<url>/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><ul><li>一开始想用selenium实现某网站的自动登录,这就不可避免的遇到验证码识别问题,要识别验证码那肯定得先获得验证,网上给的获取验证码图片的方法是,先利用WebDriver的save_screenshot函数对网页进行截图,然后再获得验证的图片大小并用location来定位获得验证码的坐标,最后从截图中截取出验证码来</li><li>实际试的过程发现,还是有坑在里面,就是location定位的坐标跟截图里的对应不上,网上找了下相关文章,发现这个:<code>之所以会出现这个坐标偏差是因为windows系统下电脑设置的显示缩放比例造成的,location获取的坐标是按显示100%时得到的坐标,而截图所使用的坐标却是需要根据显示缩放比例缩放后对应的图片所确定的,因此就出现了偏差。 解决这个问题有三种方法:1.修改电脑显示设置为100%。这是最简单的方法;2.缩放截取到的页面图片,即将截图的size缩放为宽和高都除以缩放比例后的大小;3.修改Image.crop的参数,将参数元组的四个值都乘以缩放比例。</code>,但网上给的解决方案不是很好,因为需要写死缩放比例并且是windows下的情况,而我测试用的是mac os,测试好后打算放树莓派上跑,总不能改比例改来改去,有没有办法可以自动计算缩放比例?</li></ul><span id="more"></span><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><ul><li>这里想到了一个自动计算缩放比例方法,思路很简单,就是先利用selenium获取根标签"html"的size,再获取截图的size,两者长宽分别相比就出来了</li><li>但是实际过程并没有这么简单,还有个小坑的地方就是,这么做理论上没什么问题,但测试发现还是出现了偏差,最后发现原因是save_screenshot截图截的就是浏览器显示出来的界面,但是有些网页比较长,会出现一部分页面没有被显示出来,但利用"html"标签获得的size却是获取的完整网页,所以这样计算的比例还是有问题</li><li>最终方案:用selenium截长图,并用selenium获取"html"标签的size,将截图size和"html"的size相比得到缩放比例</li></ul><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><ul><li><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.common.exceptions <span class="keyword">import</span> TimeoutException</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support <span class="keyword">import</span> expected_conditions <span class="keyword">as</span> EC</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support.wait <span class="keyword">import</span> WebDriverWait</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.chrome.options <span class="keyword">import</span> Options</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">options = Options()</span><br><span class="line"><span class="comment"># 网上说selenium截长图需要使用无图形界面模式</span></span><br><span class="line">options.add_argument(<span class="string">'headless'</span>)</span><br><span class="line">browser = webdriver.Chrome(options=options)</span><br><span class="line">wait = WebDriverWait(browser, <span class="number">5</span>)</span><br><span class="line">login_url = <span class="string">"https://www.91wii.com/member.php?mod=logging&action=login"</span></span><br><span class="line">browser.get(login_url)</span><br><span class="line"><span class="comment"># 用js获取页面的宽高</span></span><br><span class="line">width = browser.execute_script(<span class="string">"return document.documentElement.scrollWidth"</span>)</span><br><span class="line">height = browser.execute_script(<span class="string">"return document.documentElement.scrollHeight"</span>)</span><br><span class="line"><span class="comment"># 将浏览器的宽高设置成刚刚获取的宽高</span></span><br><span class="line">browser.set_window_size(width, height)</span><br><span class="line"><span class="comment"># 获取验证码所在的img标签</span></span><br><span class="line">img = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, <span class="string">'img[src*="seccode"]'</span>)))</span><br><span class="line"><span class="comment"># 等待网页加载完毕</span></span><br><span class="line">time.sleep(<span class="number">3</span>)</span><br><span class="line"><span class="comment"># 截图并保存</span></span><br><span class="line">browser.save_screenshot(<span class="string">'temp.png'</span>)</span><br><span class="line"><span class="comment"># 获取html标签</span></span><br><span class="line">html = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, <span class="string">'html'</span>)))</span><br><span class="line"><span class="comment"># location定位验证码的坐标</span></span><br><span class="line">location=img.location</span><br><span class="line"><span class="comment"># 获取验证码的长宽</span></span><br><span class="line">size = img.size </span><br><span class="line"><span class="comment"># 打开截图</span></span><br><span class="line">i = Image.<span class="built_in">open</span>(<span class="string">"temp.png"</span>) </span><br><span class="line"><span class="comment"># 计算缩放比例</span></span><br><span class="line">w=i.size[<span class="number">0</span>]/html.size[<span class="string">'width'</span>]</span><br><span class="line">h=i.size[<span class="number">1</span>]/html.size[<span class="string">'height'</span>]</span><br><span class="line"><span class="comment"># 计算验证码在截图中的位置</span></span><br><span class="line">rangle = (<span class="built_in">int</span>(location[<span class="string">'x'</span>])*w, <span class="built_in">int</span>(location[<span class="string">'y'</span>])*h, <span class="built_in">int</span>(location[<span class="string">'x'</span>] + size[<span class="string">'width'</span>])*w,</span><br><span class="line"> <span class="built_in">int</span>(location[<span class="string">'y'</span>] + size[<span class="string">'height'</span>])*h)</span><br><span class="line"><span class="comment"># 使用Image的crop函数,从截图中再次截取我们需要的区域</span></span><br><span class="line">frame = i.crop(rangle)</span><br><span class="line">frame.save(<span class="string">'code.png'</span>)</span><br></pre></td></tr></table></figure></li><li><p>最终效果如下,可以看到已经可以正确获取到图片验证码了</p><p> <img src="/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/image-20210724181823219.png" alt="image-20210724181823219"></p></li></ul><h2 id="gif动态验证码优化"><a href="#gif动态验证码优化" class="headerlink" title="gif动态验证码优化"></a>gif动态验证码优化</h2><ul><li><p>上面的方案比较适合获取静态图片的验证码,但有种验证码是gif动态图的,上面方案也可以凑合用,代码里测试的网址就是gif的,能成功获取到验证码图片是因为这种gif验证码一般会在真实验证码停留较长时间,所以有比较大概率截图到真实验证码,脸黑的情况下则截取到假验证码,那有没有办法100%获取到真实验证码图片呢?</p></li><li><p>这里保存了两张网页的gif验证码进行简单分析,直接用mac os下自带的发现预览app查看发现,gif一共10张图片组成,真实验证码出现位置随机</p><p> <img src="/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/misc.gif" alt="misc"></p><p> <img src="/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/misc2.gif" alt="misc2"></p></li><li><p>这里给出一个获取真实验证码图片可行的方案:正如上面所说这种gif动态验证码会在真实验证码停留时间比较长,非真实的图片会快速跳过,所以我们可以在网页加载完后每隔1秒截一下图,截5张图,这样5张图出现相同的图片一定是真实验证码</p></li><li><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.common.exceptions <span class="keyword">import</span> TimeoutException</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support <span class="keyword">import</span> expected_conditions <span class="keyword">as</span> EC</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.support.wait <span class="keyword">import</span> WebDriverWait</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.chrome.options <span class="keyword">import</span> Options</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">options = Options()</span><br><span class="line"><span class="comment"># 网上说selenium截长图需要使用无图形界面模式</span></span><br><span class="line">options.add_argument(<span class="string">'headless'</span>)</span><br><span class="line">browser = webdriver.Chrome(options=options)</span><br><span class="line">wait = WebDriverWait(browser, <span class="number">5</span>)</span><br><span class="line">login_url = <span class="string">"https://www.91wii.com/member.php?mod=logging&action=login"</span></span><br><span class="line">browser.get(login_url)</span><br><span class="line"><span class="comment"># 用js获取页面的宽高</span></span><br><span class="line">width = browser.execute_script(<span class="string">"return document.documentElement.scrollWidth"</span>)</span><br><span class="line">height = browser.execute_script(<span class="string">"return document.documentElement.scrollHeight"</span>)</span><br><span class="line"><span class="comment"># 将浏览器的宽高设置成刚刚获取的宽高</span></span><br><span class="line">browser.set_window_size(width, height)</span><br><span class="line"><span class="comment"># 获取验证码所在的img标签</span></span><br><span class="line">img = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, <span class="string">'img[src*="seccode"]'</span>)))</span><br><span class="line"><span class="comment"># 等待网页加载完毕</span></span><br><span class="line">time.sleep(<span class="number">3</span>)</span><br><span class="line"><span class="comment"># 每隔5秒截一张图并保存</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line"> browser.save_screenshot(<span class="string">f'temp/<span class="subst">{i}</span>.png'</span>)</span><br><span class="line"> time.sleep(<span class="number">1</span>)</span><br><span class="line"><span class="comment"># 获取html标签</span></span><br><span class="line">html = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, <span class="string">'html'</span>)))</span><br><span class="line"><span class="comment"># location定位验证码的坐标</span></span><br><span class="line">location=img.location</span><br><span class="line"><span class="comment"># 获取验证码的长宽</span></span><br><span class="line">size = img.size </span><br><span class="line"><span class="comment"># 打开一张截图用于计算缩放比例</span></span><br><span class="line">i = Image.<span class="built_in">open</span>(<span class="string">"temp/0.png"</span>) </span><br><span class="line"><span class="comment"># 计算缩放比例</span></span><br><span class="line">w=i.size[<span class="number">0</span>]/html.size[<span class="string">'width'</span>]</span><br><span class="line">h=i.size[<span class="number">1</span>]/html.size[<span class="string">'height'</span>]</span><br><span class="line"><span class="comment"># 计算验证码在截图中的位置</span></span><br><span class="line">rangle = (<span class="built_in">int</span>(location[<span class="string">'x'</span>])*w, <span class="built_in">int</span>(location[<span class="string">'y'</span>])*h, <span class="built_in">int</span>(location[<span class="string">'x'</span>] + size[<span class="string">'width'</span>])*w, <span class="built_in">int</span>(location[<span class="string">'y'</span>] + size[<span class="string">'height'</span>])*h)</span><br><span class="line"><span class="comment"># 使用Image的crop函数,从截图中再次截取我们需要的区域</span></span><br><span class="line">ls=[]</span><br><span class="line">real_img=<span class="literal">None</span></span><br><span class="line"><span class="comment"># 从5张网页截图里面截取验证码图片</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line"> t = Image.<span class="built_in">open</span>(<span class="string">f"temp/<span class="subst">{i}</span>.png"</span>)</span><br><span class="line"> ls.append(t.crop(rangle))</span><br><span class="line"><span class="comment"># 两两比较从5张图片里截取到的验证码图片,一旦出现相等情况则说明找到了真实验证码图片</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>,<span class="number">5</span>):</span><br><span class="line"> <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(i+<span class="number">1</span>, <span class="number">5</span>):</span><br><span class="line"> <span class="keyword">if</span> ls[i] == ls[j]:</span><br><span class="line"> real_img=ls[i]</span><br><span class="line"> real_img.save(<span class="string">"temp/real.png"</span>)</span><br><span class="line"> exit()</span><br></pre></td></tr></table></figure></li><li><p>最后可以100%获取到gif图里面的真实验证码图片</p><p> <img src="/tutorial/2021/07/%E8%A7%A3%E5%86%B3selenium%E4%BD%BF%E7%94%A8location%E5%AE%9A%E4%BD%8D%E4%B8%8E%E6%88%AA%E5%9B%BE%E4%B8%AD%E7%9A%84%E5%9D%90%E6%A0%87%E5%81%8F%E5%B7%AE%E9%97%AE%E9%A2%98/image-20210724232000267.png" alt="image-20210724232000267"></p></li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><p><a href="https://www.cnblogs.com/lvchengda/p/12626949.html">selenium获取图片验证码</a></p></li><li><p><a href="https://blog.csdn.net/llf_cloud/article/details/84310644">selenium使用location定位元素坐标偏差</a></p></li><li><p><a href="https://www.cnblogs.com/ddd98dy/p/13586055.html">selenium使用location定位元素坐标偏差</a></p></li><li><p><a href="https://www.cnblogs.com/hanfe1/p/13131583.html">Python selenium操作浏览器全屏截图</a></p></li></ul>]]></content>
<categories>
<category> tutorial </category>
</categories>
<tags>
<tag> 笔记 </tag>
<tag> 教程 </tag>
<tag> 验证码 </tag>
<tag> selenium </tag>
<tag> python </tag>
</tags>
</entry>