照例每年写游记。原本以为去年的DEFCON 23 CTF是最后一次。
每一张贴纸承载了一段故事,毕业前后的境遇对比。想当年,金戈铁马……
……
感谢组织今年又带上了我,达成了酱油喵的第四次DEFCON CTF之行。本次blue-lotus战队和0ops战队联合成为b1o0p,由两位队长kelwin和slipper分别带队,slipper在长亭科技实习,于是关系错综复杂。两支队伍的赞助是独立的,blue-lotus由原安全宝,现已被百度收购的百度安全XXX的赞助;0ops由腾讯赞助。
好多队员都有实习或工作,投入时间有限,也能看出大家并没有特别准备这个比赛。我大概7月28日才开始了解DEF CON CTF决赛采用的Cyber Grand Challenge平台。给0ops的队友们点赞,他们做了很多准备。
本次DEF CON 24 CTF用了Cyber Grand Challenge(CGC)的比赛平台,因此先介绍一下CGC。这是Defense Advanced Research Projects Agency(DARPA)发起的机器自动网络攻防的计划,可以与之做比较的是围棋界的AlphaGo。CGC让机器来寻找漏洞、修补漏洞、防火墙规则保护漏洞、利用漏洞。分为两轮,2015年7月3日的CGC Qualifying Event(CQE)和2016年8月4日的CGC Finals Event(CFE)。28支队伍参加了CQE,24小时内让机器自动分析约一百个可执行文件里的漏洞。
最终有7支队伍晋级CFEhttps://repo.cybergrandchallenge.com/cqe_results/:
CFE在Paris Las Vegas Hotel举行,队伍介绍和比赛规则参见https://s3.amazonaws.com/cgcdist/cfe/cgc-final_event-cfe-brochure.pdf。运行Cyber Reasoning systems(CRS)的7组机器据说每组造价70万美元,不同队伍的机箱闪烁着不同颜色,引入注目。观赏效果极佳,可视化效果构思巧妙,并非一味注重观赏效果而忽视展示的信息量和清晰度。解说的功力也很强,现场采访参赛选手,还能实时配上采访选手的名字、隶属机构。
ForAllSecure的Mayhem第一,Techxicians第二,Shellphish第三。Mayhem作为机器选手参加接下来三天的DEF CON 24 CTF Finals。
为了便于评分、减少竞争条件、减少CTF attack and defense类比赛中不确定因素(后门、系统管理、难以自动化的琐碎规则等)、增加确定性,CGC修改Linux源码,采用基于x86-32的受限平台。
修改了Unix System V Release 4的ELF实现,把ELF文件头从ELF
改成CGC
创建了一种新的可执行文件格式。不支持dynamic linking,只能static linking。
为CGC可执行文件创建了personality,设置了ADDR_NO_RANDOMIZE
禁用了地址空间随机化,设置了STICKY_TIMEOUTS
防止fdwait
syscall修改时间戳参数。
执行CGC可执行文件时内核检查命令行参数,解析seed=
、skiprng=
、max_transmit=
等参数,影响随机数、调度器和两个I/O系统调用的最大传输字节数。
CGC可执行文件启动时内核自动在0x4347c000分配一页,填充随机值称为magic page,代替了传统attack-and-defense CTF中的flag文件。命令行参数seed=
和skiprng=
用于决定magic page的内容,把flag的生成确定化。
设置了7种系统调用,x86-32的其他系统调用均不可用:
allocate
,mmap
的包装deallocate
,munmap
的包装fdwait
,select
的包装random
,获取随机数receive
,read
的包装terminate
,exit
的包装transmit
,write
的包装Signal的处理,SIGPIPE
默认行为SIG_IGN
,SIGSEGV
、SIGILL
、SIGBUS
为coredump,其他为退出,返回值为signal值。SEGSEGV
时printk通用寄存器的值。
设置CPU的CR4寄存器禁用performance monitoring center。
修改了out-of-memory killer的行为,优先杀死CGC可执行文件格式的进程。
cgc2elf
把CGC executable的magic改成ELF。
strace
可以显示CGC内核的系统调用名。
clang
、ld
编译链接生成CGC object file和executable,修改的部分很少,似乎只能静态链接。
参赛选手不再控制单独的机器。
输入:
输出:
除了UDP端口接收的packet captures,输入均通过HTTP API获取。输出也通过HTTP API提交。
比赛分为若干轮,每一轮5分钟。平台会做如下事情:
然后计算每支队伍每一道题的得分。
下面解释CB、POV、IDS、poller。
题目所用的可执行文件,原始可执行文件会特意放置一些漏洞。
平台设置了检测CB正确性和性能的poller generator,检查CB的各项功能是否满足原CB的预期行为,否则选手可以无节制地删除功能来移除vulnerability。Poller generator采用了类似QuickCheck的方式,构造finite state automaton表示CB执行时所处的阶段,比如未登录、已登录、等待输入X等,使用规则描述每个阶段应产生什么样的I/O,随机产生输入喂给CB,把CB的输出保存在变量中通过字串slice或正则表达式构造新的输入等,判断CB的输出是否满足要求。
这种checker在以往的attack-and-defense CTF中称为service level agreement(SLA)。CGC明确提出finte state automaton是一大进步。CGC要求poller generator至少要能产生1000000种不同的输出,太少容易被选手检测出特征,写出针对poller,刚好能实现poller检测的功能,但本质上不正确的challenge binary。
每个题目,对于不同队伍可以上传不同的POV,并指定攻击次数(最大为10)。
POV是一个CGC executable,使用与CB一样的办法编译链接。平台会在几个file descriptors上创建管道与其他队伍CB进行交互。POV要与平台进行challenge response,宣称自己能产生Type-1或Type-2的vulnerability,平台会给出challenge,之后与CB进行交互,如果CB如challenge崩溃或偷取到magic page即算攻击成功,可以获得Evaluation分数(见评分标准)。
https://github.com/CyberGrandChallenge/cgc-release-documentation/blob/master/walk-throughs/understanding-cfe-povs.md描述了proof of vulnerability。
Type 1 vulnerability:可以控制EIP寄存器的20个bits与另一个general purpose register的20个bits,与平台协商时指出能控制哪20个bits,平台会给出这20个bits的应有取值(challenge),让CB崩溃后两个寄存器的指定20 bits与平台给出的challenge一致即攻击成功。
Type 2 vulnerability:与平台协商时平台指定要读取magic page区间的连续若干字节。与CB交互时偷取这些字节(flag)想平台报告即攻击成功。
除了CB和POV外,选手还可以提交intrusion detection system规则(简称为IDS),使用一种类似snort的domain-specific language描述防火墙规则,过滤CB接收的流量。
每轮某题如果上传CB或IDS,不得分;否则根据以下三个要素加分:
每道题加分为\(200*security*availability*evaluation\),一轮加分为所有题目的加分之和。当前分数为之前所有轮的加分之和。
平台会提供各个CB的packet captures:poller或POV两类clients和自己的CB产生的交互。可以从流量中辅助理解CB的逻辑,揣摩vulnerability所在及POV攻击自己的方式。
所有队伍的CB和IDS公开。可以分析其他CRS的CB和IDS来针对性攻击。修补CB时可以加入自己的后门:自己知道如何攻击的漏洞,其他队伍若抄袭,则自己可以攻破,但其他队伍可能较难发现这一漏洞。
上传CB会丢失当轮分数,一道题可能会更改多次CB,因此很多CRS解出一道题后选择不上传修补后的CB,当有其他CRS解出题后再进行修补。
修补CB可以使用通用的防御手段,也可以发现漏洞后针对性修补。另外还可以使用别人修补的CB,照抄后还可以略作修改。
https://github.com/CyberGrandChallenge带有比赛采用的很多组件的源码。
math.h
功能。其他工具:
主办方Legitimate Business Syndicate自行实现了平台部分模块。
14支人类队伍晋级决赛,CGC CFE中胜出的Mayhem作为第15支队伍。
每支队伍得到8块badge,即只有8人能去现场,想要同时去更多人就得买badge。尽管今年现场嘈杂情况比往年改善了一些,大家还是喜欢待在酒店干活。
以下流水帐有很多脑补成分,毕竟时日已远。
Riatre、firesun、我从上海出发,在Seattle转机去Las Vegas,在Seattle遇到了blue-lotus的其他人。本次终于知道要在国内提前买好当地电话卡和附带的流量,可惜比较晚只买到lycamobile的,信号不好,北京也租了几个提供WiFi热点的移动设备。
第一程长达12个小时,没有意识到飞机上有电源插座,写了约两小时代码,试图重写去年使用的PCAP搜索引擎中由fqj1994实现的部分:把PCAP规整为便于检索的形式,以及为检索到的内容提供多种展示类型为漏洞利用的队友提供便利。
今年DEF CON仍旧在Bally’s Las Vegas Hotel,到达酒店后大家都很疲惫,都去休息了,想到任务在身,继续重写。
天有不测风云,BrieflyX的电脑坏了。祸不单行,Riatre的笔记本电脑屏幕无法显示,只得熟悉使用携带的一台MacBook。
libmaru和Riatre去吃Hash a Go Go。firesun和我去附近的7-11买了水,我顺便去买早饭,很快不适,yuf4n随身带药真厉害。
晚上大家聚在套房讨论比赛分工;小花椒准备了大量二进制文件相关工具,写了一个流量分析、重放工具;azure准备了IDA Pro使用的CGC可执行文件的IDA Pro loader(可能来自http://idabook.com/cgc/),和一些C函数库的F.L.I.R.T signatures;neoni准备了zynamics公司出品的BinDiff,找出各队伍上传的challenge binary与原版的差异等,firesun把它搬到服务器rr上,hen准备了反汇编工具。
我们携带了一款RT-AC68U Dual Band 802.11ac Gigabit Router,放在套房内。比赛现场的网络环境是主办方Legitimate Business Syndicate提供的,和Bally’s酒店WiFi不同,另外还需要考虑连回国内。libmaru是网管,制定了方案。酒店和国内要用CERNET2才能达到几十Mbps的带宽,酒店网络没有IPv6因此找一台带IPv6,两边带宽延迟都不差的VPS中转,几经测试选择了RamNode的Los Angeles机房。国内CERNET2选用的是浙江大学的机器,因为没有公网IP因此和北京长亭科技的连接通过Amazon AWS北京机房中转。
|
|
各种服务运行在rr上,一个去年pandada8购置的小服务器。
firesun、刘一吨yu4fn、BrieflyX等基于HTTP API开发更好用的team interface。
照例,晚上百度安全部邀请一些同行、我们吃饭。之后原来打算给我们表演无人机,后因下雨取消。
PCAP搜索引擎重构工作完成了一部分,但根据UDP/TCP stream重组PCAP不好做,以及怕出现bug,放弃。开始写把UDP端口接收的流量根据CB id、connection id、sequence ID重组为IP/UDP的PCAP、PCAP转XML格式POV等工具。
libmaru在rr上配置OpenVPN dial-in clients、systemd-networkd,我把我需要启动的服务弄成systemd service,塞到defcon.target
里,这样启动就方便了。后来向lilydjwg学到一招,systemctl --user status \*.service
可以查看所有服务状态。
下午Riatre去参观Cyber Grand Challenge final event,18:30其他人多人也去参观,19:20 blue-lotus的人和slipper去一家自助餐厅吃饭,8人以上需要加收服务费。
Riatre用HDMI线把笔记本电脑接到电视上操作,给我们直播逆向。
HTTP API不方便使用,主办方另外提供了网页版,在http://10.3.1.21/u/。为了让参赛队伍熟悉环境,10:00测试赛开始,放出测试题LEGIT_00010,即https://github.com/CyberGrandChallenge/cgc-release-documentation/blob/master/walk-throughs/building-a-cb.md提到的LUNGE_00001。
我们的队伍编号为4,分配到一根网线,内含native VLAN与VLAN id 99。native VLAN为比赛平台内网,每支队伍分配到一个10.5.\(id.0/24子网,平台会用`cb-proxy`把challenge binary的TCP流量通过UDP发送到10.5.\)id.2:1999/udp。VLAN id 99则为外网,可以用DHCP client得到172.19.192.0/24段的IP。
我们的服务器rr连接这根网线,VLAN id 99对外配置为10.5.4.2,连接交换机后给现场队友的电脑作为连接外网的路由器。使用packet-log
把1999/UDP上的包整理成方便查看的IP/UDP pcap。为防程序出错,用iptables的NFLOG记录原始的UDP包:
libmaru配置路由后很长一段时间没有收到UDP流量,后来发现了两个问题:配错了iptables、没考虑reverse path filter。
正式比赛为15:00到20:00,期望5分钟一轮,但主办方的机器比较差,到第三天近10分钟才能结束一轮。最早放出测试题LEGIT_00008,一些保护后大致执行了如下代码:char a[32] = {}, b[256] = {}, c[256] = {}; read(0, a, 31); read(0, b, 255); sprintf(c, "%s %s", b, a);
会buffer overflow覆盖saved EBP和return address。
Round 5 DEFKOR修补了LEGIT_00008。太快了!很像是lokihardt的手笔。考虑到LEGIT_00008只输出一次,把交互函数 epilogue恢复栈帧的指令换成push 1; push 0; jmp 0x08093f0b
,0x08093f0b会设置EAX指定syscall号码后调用int 0x80
,起到强制terminate(1)
的效果。
Round 7 Shellphish修补了LEGIT_00008,大意是把b
读入的最大字节数减小到222。他们修复的方式看上去像是反编译后再重新编译的,比原始CB多出了符号表,另外原来CB末尾附带的PDF被移除了。
Round 9我们换上修补过的LEGIT_00008,把交互函数的栈帧调大320字节(prologue处sub esp, 0x2fc
增大320),再修复几处引用局部变量的指令。
Round 9 PPP修补了LEGIT_00008,至少做了这些事:
retaddr ^= *(int*)0xbaaaaffc;
,使得难以控制EIP,保护Type 1 vulnerability程序依然受到buffer overflow影响,但攻击者难以获取0xbaaaaffc的值控制return address。
Round 9 HITCON修补了LEGIT_00008。.text, .rodata
所在的segment 1在CB中放置在.data .bss
所在的segment 2前面。CB文件中一个page的内容同时映射给segment 1的最后一个page与segment 2的第一个page:
在这个page放修改过的prologue与epilogue:
在这个文件中被共享的page添上代码,会影响segment 2,好在.data
开头的若干字节是库函数用的,程序并没有调用这些库函数,这么做不会影响正确性。HITCON在交互函数入口处jmp 0x0809dc00
,在0x0809dc00(文件中被共享的page)处把栈帧增大,之后再跳转回原函数,buffer overflow时就不会影响saved EBP和return address了。
Mayhem在Round 47修补了LEGIT_00008,和PPP的修补很像(可以理解,Mayhem开发人员与PPP队员有交集),带有0xbaaaaffc的canary,但是做的改动更为激进:
.e_shentsize = 20300, .e_shnum = 65356, .e_shstrndx = 0
,内核可以执行,但binutils无法识别Spam And Hex用的方法则是在文件末尾添加一个新的LOAD segment放置代码,调用sprintf
前检查两个变量a
和b
的长度和。
15:25发现新题LEGIT_00003,是个有JIT和interpreter两种执行模式的IBM PowerPC Gekko模拟器,azure发现是根据gekko-gc-emu修改的。
kelwin每题都会先用American fuzzy lop试试,让我把流量中的stdin提取出来便于replay attack。
出现新题LEGIT_00007,是一个用default,base,next,check
四个数组实现的Trie解析输入,之后再用字节码switch-threaded dispatch的计算器。带字符串运算功能。
至少有两处bug:
expandtab
会buffer overflowa[i] := "A"
的修改操作,0x0804c48b处下标和长度的比较使用有符号比较,指定大于等于0x80000000的下标可以越界写任意字节,可以修改前一个变量的字串指针指向magic page,读取该变量即可第27轮LEGIT_00008又有SIGSEGV了,没搞懂为什么会SIGSEGV(应该是平台的随机事件)第49轮换上了类似于DEFKOR的新补丁。
15:42 jackyxty搞出了expandtab
的Type 1 POV。小花椒弄了a[i] := "A"
的Type 2 POV,kelwin之后也弄了一个很复杂的POV。
18:10 slipper写了一个LEGIT_00003 POV。
Round 9升到了第2名,但后来又跌到第5名,Round 19又上升到第4名一段时间,之后稳定在第5名。下午kelwin老马失蹄,修补时把POV提交成CB了。
20:00第一天比赛结束,Round 59。kelwin的学弟陈羽北给我们准备了晚饭。感谢他几天来给我们的后勤支援。0ops好多队友先躺着睡了一会儿,隔了几个小时后相继爬起来继续分析。
slipper在弄LEGIT_00003的POV。azure用coverity分析了gekko-gc-emu,xky准备了用流量测试CB的工具。6:00之后kelwin、小花椒、jackyxty、memeda弄了LEGIT_00007几个版本的POV。
主办方昨日平台有问题,似乎每一轮都要上传POV,而不会复用上一轮的。今天根据昨天的数据重新演算了各队分数,我们变成了第四名。开赛时主办方没调试好,rollback到Round 58,hotfix,没改好,又rollback数次。照往年惯例,今天只显示排名。
10:00开赛后分析各队上传的CB,针对性地选择合适的POV。很快我们升到了第3名。
Round 65约11:10发现新题LEGIT_00004,内存中的文件系统,参看https://blog.forallsecure.com/2016/08/10/case-study-legit_00004/,通过buffer overflow修改栈上保存的EBP,进而修改返回地址。
12:08 xky发现了有人用LEGIT_00004的明显的Type 2 vulnerability攻击我们:新建文件系统初始化文件时会读取magic page,makefs
命令后接list
就能读取,jackyxty 12:16把流量转成了POV。
13:00发现我们修补的LEGIT_00007 overhead很高,研究其他队伍的patch。Riatre说DEFKOR一直在上传新的patch。后来发现PPP的patch在用损坏功能的方式patch,针对它写POV。
LEGIT_00007都能攻击全场,LEGIT_00003能打10多支队伍。
libmaru做了一个Slack bot,检测dashboard网页变化,当有队伍修补CB时进行提示。于是我也开始弄关于分数变化的bot,把分数以外的其他数据也抓取下来。
TODO 13:52 samurai free条件。 注意到PPP的CB不能用a[0] := "A"
,扣功能分但是不会被攻击。
Round 94(约14:16)关闭了LEGIT_00008。
14:51发现新题LEGIT_00006,是个实现C部分功能的编译器。binja 约round 101迅速修补。PPP Round 119上传了CB,一如既往,打乱了函数位置,很难分析。DEFKOR Round 119也上传了一个NOP掉函数的CB,Round 123我们采纳了,后来改为修改栈帧大小。这是libmaru把LEGIT_00004的文件系统结构题逆向得差不多了。
Round 127终于重新回到第2名。
快结束时出现新题LEGIT_00001,四个进程的网络协议题。Round 134(约19:01)就发现有SIGSEGV。
Round 136(约19:20)后关闭了LEGIT_00007。
jackyxty、memeda在找随机C程序生成工具,riatre在逆向,16:00多就发现有Type 2 POV,之后发现DEFKOR的patch抄了过来,18:00多又发现DEFKOR的patch挡不了新的攻击。pidgenx也加入进来分析。
20:00第二天比赛结束。第二天比赛只有4小时,套房里弥漫着通宵的气氛。
好多人都在做LEGIT_00006。Riatre回房睡了很久,2:00多冲进套房说“睡觉真耽误时间”然后开始逆向LEGIT_00006。伪随机数生成、singly-linked list、仿stringstream、词法分析直接调用的一些函数,逆向难度非常大。 libmaru在写LEGIT_00001的POV。
小花椒睡到两三点起来,因为负责各种CB的修补,责任重大:“椒哥你现在睡了我们就完了。”
我把之前给kelwin提供的单向stdin改了改,把stdin/stdout都提取出来了。另外写了个ptrace检测部分Type 1/2 vulnerability。这个东西应该早点做的,因为多数题目都没用到随机数。
6:00多小花椒修补了LEGIT_00004,之后我用已有流量测试了一下,似乎没问题。
8:00 kelwin说LEGIT_00006是堆溢出。
10:00比赛开始,照例今天不显示积分榜。每个Round的时间显著增长。
Round 139,memeda试了4个POV,发现9447、Shellphish、DEFKOR都打不了,似乎是修补了。比赛最后4小时,大家把更多的注意力放到replay attack上面。这时候各种工具总算齐备了,可惜LEGIT_00006还是不知道怎么做。发现了一种POV可以replay,但是成功率低于10%。
12:00小花椒把LEGIT_00004翻新了。
LEGIT_00001的POV发现只能打三四支队伍,后来又换libmaru的 约10:26出现新题LEGIT_00009,是一个CD和tracks管理程序:
hen指出destory cd之后还能fix the track’s real name,可能有use after free。
0x08052150到0x0805f7ed有个54941字节的巨大函数。seabreeze指出增大sub esp
栈帧大小后可以用Hex-Rays Decompiler反编译,生成12158行C。
10:40 kelwin用American fuzzy lop构造出了可以让程序SIGSEGV的输入,但没找到利用方式,给强队throw了假POV产生SIGSEGV混淆。
12:00关闭了我们具有巨大优势的LEGIT_00003。
12:10出现新题LEGIT_00002,memeda和jackyxty在搞,很快发现POV流量,0x08048f89处似乎有sprintf buffer overflow。Round 157 Shellphish在prologue/epilogue把return address XOR一个值修复buffer overflow。
13:26左右我们试图修LEGIT_00002,增大栈帧。引用变量的地方有两处lea ecx, [ebp-0x45]
,占3个字节,偏移调大超过int8_t
范围后需要5字节,改坏了。
14:00比赛结束,定格在Round 163。我们最后第2名,参见https://blog.legitbs.net/2016/09/2016-def-con-ctf-final-scores.html。
Team | Final Score |
PPP | 113555 |
b1o0p | 98891 |
DEFKOR | 97468 |
HITCON | 93539 |
KaisHack GoN | 91331 |
LC↯BC | 84412 |
Eat Sleep Pwn Repeat | 80859 |
binja | 80812 |
pasten | 78518 |
Shellphish | 78044 |
9447 | 77722 |
Dragon Sector | 75320 |
!SpamAndHex | 73993 |
侍 | 73368 |
Mayhem | 72047 |
明年DEFCON CTF将采用custom architecture和custom operating system了。
机器选手Mayhem有些可惜,比赛前两天他们收到的流量似乎都有问题。后来才发现DEF CON CTF Finals用的平台和CGC CFE不同,第三天收到流量,据说9个CB找出了7个exploit、修补了6个。如果来场公平的较量也许能碾压人类。
可以去Reddit /r/IAmA围观Mayhem:https://www.reddit.com/r/IAmA/comments/4x9yn3/iama_mayhem_the_hacking_machine_that_won_darpas/。
共享CB给比赛带来了新的玩法,像PPP就给自己的CB都塞了后门。从前不知道大家怎么修补executable的,今年是个好机会,学到了很多招数。
Shellphish把他们用的很多东西开源了:https://github.com/shellphish。
我在现场写的一些代码:https://github.com/MaskRay/DEFCON24CTFFinalsAdmin。
遭遇Delta Air系统故障,滞留在Las Vegas一天半,其他队友也滞留0~2天不等,Las Vegas algorithm:最终我们都能离开,但消耗的资源不确定。10日3:00多才离开机场T_T。感谢各位高中同学、大学同学、学长,之后一路蹭吃蹭住在San Francisco Bay Area、New York City、Boston、Cambridge、Jersey City辗转混了十几天。