照例每年写游记。原本以为去年的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
,mmap
的包装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分钟。平台会做如下事情: 1
2
3
4
5
6for team T
for challenge C
run poller against CB(round, T, C) # service level agreement
for team T'
run POV(round, T', C) against CB(round, T, C)
# stdin/stdout are recorded and sent to 10.$id.5.2 1999/udp
然后计算每支队伍每一道题的得分。
下面解释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自行实现了平台部分模块。
以下流水帐有很多脑补成分,毕竟时日已远。
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北京机房中转。
1 | (1.1.2.0/24) (1.1.1.0/24) |
各种服务运行在rr上,一个去年pandada8购置的小服务器。
firesun、刘一吨yu4fn、BrieflyX等基于HTTP API开发更好用的team interface。
下午Riatre去参观Cyber Grand Challenge final event,18:30其他人多人也去参观,19:20 blue-lotus的人和slipper去一家自助餐厅吃饭,8人以上需要加收服务费。
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用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包: 1
2iptables -I INPUT -p udp --dport 1999 -j NFLOG --nflog-group 1
dumpcap -i nflog:1 -b filesize:102400 -b duration:300 -w a.pcapng
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: 1
2
3
4
5Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00060 0x00060 R 0x4
LOAD 0x000000 0x08048000 0x08048000 0x55c2f 0x55c2f R E 0x1000
LOAD 0x055bf4 0x0809ebf4 0x0809ebf4 0x1432f 0x15000 RW 0x1000
在这个page放修改过的prologue与epilogue: 1
2
3
4
5
6
7
8
9
10
11
12
13LOAD:0809DC00 push ebp
LOAD:0809DC01 sub esp, 100h
LOAD:0809DC07 mov ebp, esp
LOAD:0809DC09 push ebx
LOAD:0809DC0A push edi
LOAD:0809DC0B jmp loc_80480C5
LOAD:0809DC10 pop esi
LOAD:0809DC11 pop edi
LOAD:0809DC12 pop ebx
LOAD:0809DC13 add esp, 100h
LOAD:0809DC19 pop ebp
LOAD:0809DC1A retn
在这个文件中被共享的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模拟器,似乎是根据gekko-gc-emu修改的。
出现新题LEGIT_00007,是一个用字节码switch-threaded dispatch的计算器。带字符串运算功能。 1
2
3
4
5
6
7
8a:="test"
a.upper
a
<string> TEST
a:="a\tb"
a.expandtab
a
<string> a b
有几处bug:expandtab
会buffer overflow、、、、。TODO
第27轮LEGIT_00008又有SIGSEGV了,没搞懂为什么会SIGSEGV(应该是平台的随机事件)第49轮换上了类似于DEFKOR的新补丁。
15:42 jackyxty搞出了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名。 13:00发现我们修补的LEGIT_00007 overhead很高,研究其他队伍的patch。Riatre说DEFKOR一直在上传新的patch。后来发现PPP的patch在用损坏功能的方式patch,针对它写POV。
LEGIT_00007都能攻击全场,LEGIT_00003能打10多支队伍。
libmaru做了一个Slack bot,检测dashboard网页变化,当有队伍修补CB时进行提示。于是我也开始弄关于分数变化的bot,把分数以外的其他数据也抓取下来。
约14:10发现新题LEGIT_00004,内存中的文件系统,参看https://blog.forallsecure.com/2016/08/10/case-study-legit_00004/,通过buffer overflow修改栈上保存的EBP,进而修改返回地址。
14:51发现新题LEGIT_00006,是个实现C部分功能的编译器。binja 约round 101迅速修补。PPP Round 119上传了CB,一如既往,打乱了函数位置,很难分析。DEFKOR Round 119也上传了一个NOP掉函数的CB,Round 123我们采纳了,后来改为修改栈帧大小。
Round 127终于重新回到第2名。
快结束时出现新题LEGIT_00001,四个进程的网络协议题,关闭了LEGIT_00007和LEGIT_00008。
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。
6:00多小花椒修补了LEGIT_00004,之后我用已有流量测试了一下,似乎没问题。 8:00 kelwin说LEGIT_00006是堆溢出。
10:00比赛开始,照例今天不显示积分榜。Round 139,memeda试了4个POV,发现9447、Shellphish、DEFKOR都打不了,似乎是修补了。 12:00小花椒把LEGIT_00004翻新了。
LEGIT_00001的POV发现只能打三四支队伍,后来又换libmaru的 约10:26出现新题LEGIT_00009,10:43就发现有队伍给强队throw了假的POV,产生SIGSEGV但似乎并无法利用。kelwin在用American fuzzy lop
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名。
明年DEF CON 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
遭遇Delta Air系统故障,被困在Las Vegas……T_T感谢各位高中同学、大学同学、学长,一路蹭吃蹭住混了十几天。