一、credchecker
1.1 writeup
第一题是一个表单提交验证的签到题目:
提交随机内容后点击测试:
通过F12查看源码,找到表单提交的校验函数,发现用户名和密码的校验逻辑如下:
输入符合条件的用户名和密码,即可得到flag输出:
二、Known
2.1 writeup
需留意此处执行的是【循环左移】操作:
算出的解密私钥为:No1Trust,继而可还原出明文flag。
三、antioch
3.1 writeup
sudo docker -I antioch.tar
sudo docker run -ti antioch
导入docker镜像并进行简单的测试:
尝试将容器中的内容dump出来:
发现只有一个名为AntiochOS的ELF文件。
用IDA 分析之,进而发现程序仅包含2个有用的命令:approach和consult:
里面的线索指向《Monty_Python_and_the_Holy_Grail》这部影视作品,通过多道关卡如询问姓名、喜欢的颜色,最终输出flag信息。
经过分析,有30组答案,每组答案由【crc32(姓名\n), crc32(颜色\n), 序号】组成。
如下:
consult命令输出4096字节,以列宽15B的格式进行格式化打印,初看是AsciiArt的格式。
分析AntiochOS的consult命令:
读文件并进行异或操作,最后输出,但尚不清楚目标文件是什么。尝试将AntiochOS拷贝出来在本地以strace方式运行:
提示读的是a-z.dat文件,且根据前面dump出来的结果来看,container里面当前是不包含这些文件的。
继续分析docker文件,发现两篇文章
https://github.com/moby/moby/blob/master/image/spec/v1.2.md
https://segmentfault.com/a/1190000009309347
其中有提到:image镜像本质就是tar包,其中记录了基础layer,以及后续衍生出来的各个上层layer的meta、文件、修改等数据信息。
奇怪的是,antioch的manifest.json和a13ffcf46cf41480e7f15c7f3c6b862b799bbe61e7d5909150d8a43bd3b6c039.json中记录了layers/diff_ids均只有一层内容:
但tar包中却显示存在若干其他的layer:
且这些layer.tar在解包后,里面正好携带有随机不等个数的a~z.dat文件,故怀疑是出题者有意将这些layer的信息给抹除掉了。
随机挑选一个layer.tar解包出来的东西,放在AntiochOS目录下运行,或拷贝至container中运行,发现consult命令的输出内容有新的变化,flag是AsciiArt格式的可能性进一步增加。
因此接下来的关键就是要找到正确的layer排列顺序。
继续翻找文件,发现各layer下的json描述文件中均存在一个不寻常的author字段,都是影视剧中的角色名,如:
共三十组,一开始以为是需要根据这部剧的角色间的某种关系来确定对应layer的次序,比如出场或者片尾字幕顺序。后来经提示,想到approach中有要求输入姓名的环节,再次分析approach的逻辑流程后,发现恰好有一个30组的判别条件能对得上了。
结合之前分析得到的【crc32(姓名\n), crc32(颜色\n), 序号】的结构,推测最后一个字段表示的含义就是对应layer的次序。gdb动态调试,不用去分析颜色要输入什么,只需要确定姓名经过计算后所在的结构体的序号即可,得到:
将对应layer.tar按次序解压到同一目录下,然后在当前目录下运行AntiochOS的consult命令,得到AsciiArt的flag值:
四、myaquaticlife
4.1 writeup
首先upx -d直接脱壳,测试运行,发现有visitor counter的递增操作:
且鼠标在主界面移动过程中,文字和图片均提示为链接形式(手)。但只有点击文字才有反应,点击动物没有任何反应提示。在点击文字后,会切换到you chose.. poorly页,此时只剩下退出关闭窗口的选项。
Visitor Counter的自增,猜测可能是通过文件或者注册表的方式实现的。通过Process Monitor发现该值是记录在registry中的,位置如下:
同时还发现,程序有往C:\Users\XXX\AppData\Local\Temp\MMBPlayer目录下投放文件的行为:
打开index.html和index2.html,发现正好对应2个主界面的内容,只不过href处的内容比较奇怪:
分析myaquaticlife.upx.exe的过程中,发现有一些常量:
一开始看到里面有一些Level、next、forward的字样内容,以为又是一道关卡类的游戏。
线索指向Multimedia Builder这家公司,进而在web.archive.org上找到了一份MMB的手册《Multimedia Builder Help》。结合调试中得到的一些线索,粗略阅读了5%~10%左右的内容,大概理解了这套程序框架的运作机制:
- 程序允许内嵌各种类别的文件,在运行过程中,会释放到临时目录下,正好对应前面用ProcessMonitor观察到的现象;
- 页面上的script标记,会将其绑定到对应的内嵌脚本代码,或者独立的脚本文件,当点击该链接时会触发执行相应的代码执行。其对应的内置语法命令有RunScriptCode、RunScript等,在这两个函数的代码实现中有引用到一个错误提示类的字符串:“Recursion in Script reached 50 levels.”
- 允许以dll插件的形式进一步扩展框架的功能特性,dll插件同样支持在编译时内嵌在程序文件当中,在运行阶段进行释放。前面temp目录下释放的fathom.dll就此类功能插件。
根据“RunScriptCode”,在IDA中找到2个有关的地方:
调试跟踪,发现2处:
- 注册表写操作,恰好就在sub_438CD3中
在调试过程中发现在进入此函数时,从调试器的若干子窗口中正好可以看到一些疑似脚本的语句,如:
除了count自增的操作外,其他一些语句暂时不清楚有什么作用。点击不同的动物对象,对应的partN$=xxx赋值语句也有所变化。
通过010Editor查看文件字符串信息,可以将这17组object对应的脚本整理出来:
每种动物所关联的script可以在index.html中查看到/映射出来。
考虑到说,dll文件可以作为插件提供扩展功能,尝试进行动态调试。在点击动物/文字前,在dll区间设置访问断点,查找对应的入口点。发现:
- 点击文字时,会进入PluginFunc19函数
- 点击动物时,则进入SetFile函数
且每种对象所关联的part$n=xxx正好被传递进来。分析两个函数,发现SetFile的作用是对不同【类别】的动物,将其所关联的value值写到对应类别的内存地址上。当前共有4种类别:
- derelict
- lagan
- flotsam
- jetsam
PluginFunc19入口有2处检测,若在勾选动物的过程中,flotsam或者jetsam其中一种没有被勾选(即相应类别的字段上没有存放对应动物的value值),则函数直接返回。
而返回的内容恰好就是“you chose.. poorly”。
因此推断,只有选中了特定的动物或动物组合,程序才能继续往下走。另外,同一类别的动物如果选择了多只,那么它们所对应的值也会按所选择的顺序进行字符拼接,故而动物的类别、种类和同类别动物的选择次序都对程序的逻辑走向有影响。在这里也可以发现,程序只关注flotsam和jetsam两种类别的动物,即挑选动物时,只需挑选这2类的动物即可。
PluginFunc19继续往下走:
接下来的处理流程是拿前一步拼接得到的flotsam类和jetsam类动物的value值,与一个31B的初始值进行异或操作,并对异或后的内容进行md5计算,计算结果与6c5215…进行比较。
到此,数据流走向和程序运作机制基本搞清楚了:
- 从flotsam和jetsam这两类动物中进行选择,并根据选择的动物,将动物所关联的字符串拼接起来,分别【拼接】存放起来,记为flotsamValue和jetsamValue
- 拿flotsamValue和jetsamValue与64 FE 7C 8B 65 6A CD 29 2E 4D F5 96 11 2B 2F 52 AE 20 38 6A 6F 56 60 ED EC 39 37 DB 0D 3A 54 00进行异或操作,结果记为xorValue
- 对xorValue进行md5,结果记为md5sum
- 将md5sum与6c5215b12a10e936f8de1e42083ba184进行比较
至于比较通过之后的逻辑,可暂时不管。编程工具尝试进行动物的排列组合选择:
算出
即:
flotsam类动物,按顺序选择:PXopvM(4) DFWEyEW(3) BGgsuhn(13)
Jetsam类动物,按顺序选择:SLdkv(11) newaui(7) HwdwAZ(10)
即可满足条件,点击文字即可得到flag。
五、FLARE Linux VM
5.1 writeup
虚拟机导入后查看一番,发现以下线索:
- crontab -l中存在一个* * * * * * /usr/lib/zyppe的定时任务
- bash_profile中存在三个环境变量
3. bashrc中还包含一个关于密码第13字节内容的提示信息
4./root/Documents目录下存放有若干.broken后缀的文件
5.Journalctl翻看日志发现有以下输出,均指向了/usr/lib/zyppe
接下来,分析zyppe文件:
encrypt函数经分析是rc4,它会对Documents目录下的原文件进行rc4加密生成.broken后缀的加密文件。
为了还原这些文件,最简单的办法是将文件后缀更改为非.broken结尾的,然后再执行一次/usr/lib/zyppe即可。此处需要注意的是,需要把crontab给关了,否则位于Documents目录下的文件会自动被加密回去。
rc4解密后的Documents目录中,一部分文件已经可以看到明文信息,还存在另外一部分文件仍然是密文的形式。若要解密余下的文件,需在已还原出来的明文信息提示中继续找线索。
这一系列文件可以按文件名中的首个英文字母进行分类,分成b、d、i、n、o、r、s、t、u共9组,每组文件采用的是同一类别的加密方式,故而分析出解密算法,即可解密同一组下的所有文件。
5.5.1 u组
还原方式:rc4(/usr/lib/zyppe)
还原后的内容:
根据提示信息,尝试使用ROL/ROR对余下文件进行测试,发现s组文件可以按该方式进行还原。
5.1.2 s组
还原方式:Rotate Left 1
示例:CyberChef
还原后的内容:
新产生3个线索信息:
- 密码第2字节
- 下一组文件采用的是base64编码
- /usr/bin/dot文件
其中/usr/bin/dot文件经IDA反编译出来的结果如下:
对用户输入的密码进行sha256加密,需匹配中b3c20caa9a1a82add9503e0eac43f741793d2031eb1c6e830274ed5ea36238bf方可继续往下走。故而需要分析出密码的所有完整字节内容出来,才可以获取flag。
5.1.3 r组
还原方式:Base64
还原后的内容:
从这一组推下一组的线索是xor循环异或解码,key是Reese’s
5.1.4 b组
还原方式:xor循环异或(key: Reese’s)
还原后的内容:
推下一组的线索是需要根据公式进行字节解码,其中NUMBER1/2/3在开头的地方已经得到,经过测试,发现i组文件适用于此解码方式。
5.1.5 i组
还原方式:ENCODED_BYTE + 27 + NUMBER1 * NUMBER2 – NUMBER3
还原后的内容:
这一组的线索需要猜测,其中用于rc4的key根据提示是来自于SREFBE,直接拿该值进行测试发现余下文件无一还原成功,又根据文中线索“If you have no idea what that means, you should give up and bake some muffins.”,猜测是否是对应配方表中每个配方的首字母,取其ID号拼接的结果“493513”,再次测试发现可以解出n组文件。
另外,本组提示信息中给出的第5字节为0xMS,暂时存疑,留待后续处理。
5.1.6 n组
还原方式:rc4
还原后的内容:
本轮提示信息,可以将后2个文件放到Google上搜,均来自与Wikipedia。其中KEYWORD对应eggs,而Felix Delastelle algorithms对应bifid cipher算法。故推测,下一组文件的解密方式是利用bifid cipher算法进行还原。
了解bifid cipher算法后发现该算法有两个特点:1. 处理过程中输入字符均为大写形式(全小写也无所谓,但考虑到一些工具实现可能按照全大写进行编解码,故而在解密的时候可能需要将密文转成全大写,具体要看所使用的工具的限制);2. 该方法仅能处理a-z/A-Z字符,而对其他字符无法进行操作。根据现有的线索,每轮解密的内容,其中必定有一组文件是包含类似于“The xth byte of the password is: 0xXX”的内容,根据这个线索,下一组文件应该是包含有“The 7th”内容,其中7无法被bifid cipher进行替换,必定是可以直接发现的。
据此,发现d组文件对应此解密方法。
另外,根据尾部的0m66,也可以推测出密码的第7字节为0x66。
在进行解密的时候需要注意2点:
- 需要剔除非26个字母之外的其他字符,将余下内容进行拼接后再进行解码
- 转成全大写(需要视所使用的工具的要求而定,非必须)
如.daiquiris.txt.broken文件中的
需要替换为:
然后再参与解密。
5.1.7 d组
还原方式:bifid cipher(KEYWORD:EGGS)
示例:CyberChef
还原后的内容:
本轮线索根据GIOVAN BATTISTA BELLASO检索到下一组文件需要使用Vigenère Decode维吉尼亚密码进行解密,同样的,该算法仅对a-z/A-Z字符有效,根据规律,可以确定该解密方式适用于o组文件:
另外,秘钥key猜测是提示中给出的MICROWAVES。解密时需对密文进行处理,处理方式可参考bifid cipher,即剔除非26个英文字母之外的字符后进行拼接。
5.1.8 o组
还原方式:Vigenère Decode(KEYWORD:MICROWAVES)
示例:CyberChef
还原后的内容:
根据线索,翻看这几个人的tweets,在@anamma_06的tweet中找到一条提示信息:
根据提示可推测,余下文件的解密方法是AES+CBC,其中秘钥为:Sheep should sleep in a shed加Flare Linux VM版本。
Flare Linux VM的版本可通过cat /etc/os-release获取。
5.1.9 t组
还原方式:AES+CBC(KEY:Sheep should sleep in a shed15.2)
示例:CyberChef
还原后的内容:
本轮给出余下线索,汇总一下,当前有关密码的各字节的线索/答案:
- 01: E 0x45
- 02: 4 0x34
- 03: Q 0x51
- 04: 5 0x35
- 05: 0xMS
- 06: 6 0x36
- 07: f 0x66
- 08: ` 0x60
- 09: s 0x73
- 10: 4 0x34
- 11: l 0x6c
- 12:
- 13: 5 0x35
- 14: I 0x49
若部分字段当前无法确定也无关紧要,可以通过暴力枚举推出。根据/usr/bin/dot的逻辑,对password进行sha256后的值需要等于一固定值,因此,可以写脚本进行枚举测试:
得到:
即,password为E4Q5d6f`s4lD5I
运行/usr/bin/dot,输入password即可得到flag。
六、PetTheKitty
6.1 writeup
这道题最重要的一个线索,是流量包的两个会话中出现频率很高的PA30字样。
如果不知道相关知识点,可能想破脑袋都没法往后推进。PA30是Windows Package中用于补丁、更新所采用的Delta Compression格式文件的魔数特征,这些补丁文件的文件格式为:
关于此文件的技术细节可参考2020.08.23的一篇文章《Extracting and Diffing Windows Patches in 2020》,作者在RITSEC-CTF-2019上出过一个涉及该技术点的题目patch-tuesday。
简单来说,Windows在做补丁更新时,会使用ApplyDeltaB等接口将patch文件应用于目标文件即可实现文件的更新(回退、升级、新增等),这些patch文件中记录的只是文件差异因此被称为patch delta。
在session#0中,可以观察到一个png文件头:
尝试使用010Editor等工具将文件提取出来。在文件末尾,观察到有一段AsciiArt形式的内容:
使用010Editor打开之后,套用png模板进行解析会提示文件末尾异常:在IEND块后面有多余的内容:
经过文件隐写方面的测试后发现,这个png图片除了末尾多了一些字节之外再无其他异常。结合题目信息中提到的两个文件均为PE类文件以及PA30线索所对应的patch机制,推测是需要将patch应用于png文件以“还原”出PE文件。可使用《Extracting and Diffing Windows Patches in 2020》中提到的delta_patch.py文件进行png的patch。经测试,发现只有当保留png末尾的AsciiArt部分时才能patch成功:
patch数据即是session#0中144回传的第二部分内容:
此处有一个需要注意的细节,如果是自己编程实现调用ApplyDeltaB接口来应用补丁,需要将PA30前的4字节CRC32给移除掉,再传入ApplyDeltaB。因为delta_patch.py中对这部分内容作了判断,故而带不带0xb1290000均可以直接调用该脚本进行处理。
patch过后的文件是一个32-bit的DLL文件,在导出函数的列表中发现存在一个名为Le_Meow的函数。经分析后发现,该DLL实现了一个reverse shell的效果,将cmd的输入和输出句柄绑定到socket上,以此实现反弹。另外,在命令下发和命令执行结果回传的过程中,程序同样采用了delta patch技术,将明文内容进行了压缩+xor(KEY: meoow)异或从而实现混淆编码。
Session#1记录的是双方的命令通道的交互结果,接下来根据上面分析得到的编码逻辑逆向还原出双方的原始通信内容。
Session#1包含了37组交互,对应37次命令下发和响应回传。根据前面提到的patch文件格式,将这74组packet内容,分别从PA30处开始提取,提取出74组patch文件,记为patch1~74,如下为packet#1提取出来的patch内容:
接下来准备一个稍大一些的00文件作为src文件(小文件会提示补丁失败,暂没有深挖是否跟待打的补丁解压后所作用的文件大小有关),如下:
需要做的操作就是将这74个patch文件,分别作用在该文件上:
如上,使用ApplyDeltaB之后还需要进行xor异或,最终得到74个result文件,如下为前5组的结果:
可以观察到是对应下发的命令和命令执行结果。依次往下翻阅,在第50和第51个文件中,找到flag内容:
七、spel
7.1 writeup
题目直接给了一段提示信息:
尝试通过IDA打开文件发现确实很慢,有一段时间,IDA下方的解析进度似乎是以字节的速度往前推进:
而当IDA最终解析完毕的时候,idb文件并不是很大只有十几M左右。
如果有留意到这一点,并在IDA解析完毕之后跳转到相应的区间段细究一下,则会为本题的前期分析阶段节省一大笔的时间。若未细究上述现象而直接进行程序分析,则会是下面的流程。
首先程序打开运行之后,直接提示error需要关闭重启:
通过分析,该程序由MFC编写,一番动态调试后发现MFC程序初始化和运行阶段并不存在异常的地方。直到关闭MFC程序的时候,主流程将从CDialog::DoModal函数退出返回,此时观察到该处的逻辑实现中包含有一些奇怪的指令操作:
如果接触样本比较多的话,首先会想到程序在这个地方进行了动态的shellcode执行。结合这一长串的mov操作末尾处的指令逻辑:
可以判定程序是执行了一段shellcode代码,根据size大小,可以将该段shellcode从内存中dump出来。
7.1.1 第一段dump(shellcode)
经过分析发现该段dump是使用sRDI组装成的一段payload,关于sRDI的细节可以结合sRDI的实现进行了解。简单来说,该段dump是由以下几部分组成:
其中,位于第二段的rdiShellcode用来实现PE Loader,包括重定位表修复,节区表/段分配空间和对应属性修改等操作,负责将位于内存中的dll文件按运行时映像结构进行解析和加载。在完成DLL的load之后,将执行权转交到DLL的DllMain函数,从而实现DLL的函数调用:
关于这部分Loader的等价C代码实现,可参考ShellcodeRDI.c进行理解和学习。
sRDI区别于原版反射注入RDI的最重要的一点是:sRDI的bootstrap/Loader代码是放在DLL外部实现的,环境准备好之后将执行权直接交给DLL中的函数;而原始RDI反射注入中的bootstrap/Loader代码则是内嵌在DLL中的,通常是封装在DLL的一个导出函数中,Injector需要首先通过file offset的方式定位到位于DLL中的Loader函数,再将执行权交给Loader,由DLL中的Loader实现环境准备工作,一切就绪之后再过渡到DllMain函数。即这些操作均在DLL内部中完成。因此根据该dump文件的行为分析,可以确定此处使用的是sRDI技术。
理解了RDI类注入技术的原理和流程之后,可以直接对DLL文件进行分析。提取DLL文件的方法:
动态调试过程中,根据ECX指向MZ头,以及后面的add r8, 2ED23,大致可以猜测该DLL的区间范围:
或者结合ShellcodeRDI.py中的源码,也可以确定DLL区间范围:
7.1.2 第二段dump(DLL文件)
在提取出来的DLL文件的DllMain函数的开始位置,发现其嵌套了另一段PE文件:
如上图unk_1800168F0指向的是MZ头,后面的0x17A00可以试猜是该PE文件的大小:
分析第二段dump文件的主流程,发现同样存在一些Loader类的处理操作,推测其主要功能仍旧是加载内嵌的PE文件。
根据DllMain中红框标注的函数逻辑,推测在加载完PE之后,会调用Start相关字样的函数。这部分可以通过dump出这个内嵌的PE文件后进行验证。
7.1.3 第三段dump(DLL文件)
在第三个dump出的PE文件的导出表中确实观察到存在一个名为“Start”的函数,进一步印证了之前的猜测。
着重分析该Start函数的执行流程,发现其实现的是一些后门场景中常见的功能函数:即,根据程序运行环境,尝试连接inactive.flare-on.com或inactive.flare-on.com的888端口,并将服务器端的回传内容当做指令进行执行:
倘若服务器端返回的内容为“flare-on.com”则程序将进入另外的逻辑分支。
另外还发现程序会对资源段的一个PNG资源进行AES的解密,并将解密后的内容做进一步的编解码操作并写入注册表中。
整体浏览分析下来尚未发现明显涉及flag操作的逻辑,绝大部分是后门类的功能代码。为了继续挖掘线索,尝试让程序尽可能多地执行以观察动态运行时的细节部分。通过分析,若要让程序顺利执行下去,需满足以下几个条件:
- 域名可解析,服务器可达
- 服务器返回“flare-on.com”
- 进程名不能是默认的“spel.exe”,而是“Spell.EXE”
- 程序中存在一些SleepEx 5~6 分钟的操作,可以尝试patch掉
根据上述运行条件,搭建伪服务器并指定下发“flare-on.com”内容,动态跟踪程序后续执行流程。发现程序在执行完PNG资源解密后,会将解密后的内容放在与“flare-on.com”相邻的位置:
对应如下部分的反编译代码:
可观察到这是一个疑似flag的字符串,只是缺少了@符号,且中间存在若干个不可显字符,另外字符串前缀部分(PNG资源解密后的内容)似乎也是乱序的,隐约可以看出 spellcheck的组合形式。
继续往下分析发现,此后程序并不再对该字符串进行写操作,但存在一处读操作:
其中的case语句恰好有22个分支,正好对应得上“l3rlcps_7r_vb33eehskc3”的长度。
尝试根据case的顺序对该字符串进行重排序后提交至平台,验证通过。
本题最后flag字符串需要进行重排序的提示并不是特别明显,偏脑洞些。
另外本题隐藏在PNG资源中的加密flag所使用的是AES+CBC加密方式,具体的解密参数可移步至此处进行查看。
八、beelogin
8.1 writeup
通过简单的分析,beelogin.html文件中的js代码有经过混淆和膨胀处理,其中Add函数中定义的所有函数均没有被调用。接下来尝试进行去混淆处理,首先通过VS Code中的快捷键Ctrl+Shift+P打开Command Palette,输入Format Document,进行代码美化。接下来,再利用Ctrl+K,Ctrl-0进行代码折叠,位于Add中的非函数定义语句行即是实际会执行到的代码内容。
在Add函数中的第一条语句上打上断点,使用VS Code内置的调试器进行单步跟踪调试。经分析,js代码的内容可简化为:
对应转换后的Python代码为:
即:
将用户表单中第四个输入框提交的内容,与内置的两段代码进行加减运算,最后通过eval进行输出或执行。
很显然,为了知道最后eval的内容,需输入正确的key,接下来需要尝试推算出key的原始内容。根据代码中的线索,推测得知:
- 用以进行解码操作的key的长度为64字节;
- 最终解码出来传入eval函数的内容,或者是字符串或者是代码语句,即,传入的内容需满足可打印字符的条件;
根据这些约束条件,尝试用z3进行求解:
注:可通过限制z3待处理的内容长度(ciphertext)来缩短运行时间,此处取前3000字节为例。
8.1.1 第一层
通过计算,观察到key的取值在当前较为宽松(可打印字符、\t\r\n)的约束条件下有若干组解:
另外还观察到,一部分位置上的内容为固定值,另外一部分位置的内容则存在多组可选值。通过挑选其中任一一组进行正向解码操作,输出:
观察到头部内容为一些英文语句,后半段则为+[]()!等符号组成的类似于AsciiArt的代码。
因此只有确保key中每个位置上的值正确之后,才能解码出余下的提示信息。
接下来有2种方式确定每个位置上的key值:
- 根据z3得到的模板进行微调,观察对应输出内容的有效性;
- 根据输出内容为可打印字符的前提假设,进行解码操作的反向爆破,爆破出每个位置上的候选词,再根据1的方法,从有限集合上进行替换查验;
给出爆破的代码:
输出64个位置上的候选词:
对每组值进行解码测试:
解得key为:ChVCVYzI1dU9cVg1ukBqO2u4UGr9aVCNWHpMUuYDLmDO22cdhXq3oqp8jmKBHUWI
解出的前半段内容为bee movie的台词:
后半段内容则为jsfuck代码:
可通过在线解码平台如http://codertab.com/jsunfuck进行解码,得到另外一段同样经过混淆处理的js代码。在使用该网站的时候,记得把jsfuck代码末尾的最后2个字符”()”(函数调用)给删除后再进行解码:
8.1.2 第二层
利用同样的套路对第二层的代码进行解析,得到第二段key的内容:UQ8yjqwAkoVGm7VDdhLoDk0Q75eKKhTfXXke36UFdtKAi0etRZ3DoHPz7NxJPgHl,解码后的内容同样是由bee movie台词和另一段jsfuck代码组成:
对这部分jsfuck代码进行还原,可得到flag。
九、evil
9.1 writeup
本题是一个通过VEH实现API地址解析和调用的Self modifying code(SMC)程序,程序运行过程中存在多种反调试手段,以独立线程(检测线程+守护线程)方式进行周期性检测。
程序中涉及API函数调用的地方采用了间接的hash动态解析地址的方式,将module名称及API名称这两者的hash值压入栈中,再通过主动触发异常(除零、异常地址访问)进入到VEH handler中实现自修改,如下图所示:
在VEH handler中主要完成以下操作:
- API地址解析
- 代码自修改
- EIP修改
如下图所示:
修改后eax中存放的是当前待执行函数的地址,修改后的结果如下图所示:
程序中此类异常指令序列共有以下几种:
- xor eax, eax; mov eax, [eax]
0x8BC033
- xo reax, eax; div eax
0xF0F7C033
- xor edi, edi; div edi
0xF7F7FF33
- xor esi, esi; div esi
0xF6F7F633
这种手段影响IDA反编译的识别效果,可通过修复/patch这些call eax的逻辑来进一步优化IDA的反编译结果。
@wmsuper提供了一种通过idapython结合lief实现代码patch的方法:
- 通过idapython找到所有异常指令序列的位置,及待解析的2组hash(DLL和API)
- 解析出hash对所对应的DLL模块及API函数名
- 利用lief将上一步得到的DLL和函数名添加至IAT表项中
- 利用lief在第一步找到的指令序列前后进行patch,patch成call IAT的形式
第1步的代码如下:
第3/4步的代码如下:
此处有一个细节需要注意,在IDA中通过手动patch掉div等触发异常指令为nop时,可观察到如下效果(以0x40674c为例):
patch脚本中,我们利用的是上述红框区域的空间,即从hash入栈/寄存器开始,到下一个正常指令范围中间的区域,有7字节可利用的空间被我们替换成了call(0xFF) iat_address,如下图所示:
修改后的PE文件,反编译结果的可读性大大提高,对比如下:
接下来就可以结合优化后的反编译结果进行代码走读和流程梳理。
需要注意的是,本程序使用到了若干反调试技术以干扰动态调试。如main入口点附近的register_detector_sub_4023D0函数中注册了若干反调试子函数用以后续的检测,如下图所示:
调试器可以通过一些如ScyllaHide、SharpOD类的反-反调试插件进行绕过。
随后程序会通过2个独立线程实现反调试检测,如下图所示:
sub_402D50负责拉起检测线程sub_402E70,而sub_402E70则周期性从前面提到的若干组反调试技术中随机抽取一个进行检测,故而也可通过屏蔽这两个线程创建逻辑来绕过此处的反调试检测。
在这里,介绍一种纯静态分析的解法,书接前面lief patch的部分。
在IDA中通过代码走读,在sub_404680中发现存在4组逻辑相同的解码操作,如下:
操作的对象分别指向4组buffer地址:
- dword_6D17A8
- dword_6D1794
- dword_6D17BC
- dword_6D178C
通过模拟执行或者动态调试方法,可以观察到这4组地址经过解码后存放的内容:
- dword_6D17A8:L0ve
- dword_6D1794:s3cret
- dword_6D17BC:5Ex
- dword_6D178C:g0d
其后,这4组字符串分别经crc32_sub_4069F0处理生成4组dword值。
- dword_6D17A8:L0ve:0xe3fc31f4
- dword_6D1794:s3cret:0xd8e9b078
- dword_6D17BC:5Ex:0x77066b5a
- dword_6D178C:g0d:0xa24f5b95
需注意,此处crc32的输入串要包含上字符串末尾的\x00。
每轮计算的结果会依次填充至dword_6D1680~ dword_6D168F处:
交叉引用0x6D1680,发现其在0x4048B1处被函数sub_4067A0所调用,如下图所示:
根据IDA的反编译结果,发现sub_4067A0涉及一段可疑的解密操作,如下图所示:
根据进一步分析,发现之前计算的crc32值作为key参与了解密。至于密文,则根据参数传递,可在调用sub_4067A0前的地方获取到,如下图所示:
byte_6CFB68对应的密文内容为:
根据sub_4067A0中的解密逻辑进行执行,第一步CryptImportKey函数直接返回0提示执行失败,通过GetLastError()得知错误返回码为:0x80090008,对应MSG为NTE_BAD_ALGID,MSDN的解释为“The simple key BLOB to be imported is not encrypted with the expected key exchange algorithm.”,即导入的key BLOB存在异常。分析BLOB的结构发现,有问题的字段有可能来自ALG_ID字段。当前key BLOB结构对应的内容如下:
其中ALG_ID为0x6802,MSDN文档显示对应算法为CALG_SEAL。在注释一栏提示“SEAL encryption algorithm. This algorithm is not supported.”
此字段的嫌疑大大增加,在IDA中通过查找该立即数的交叉引用,发现有另外一处引用的地方,如下图所示:
位于sub_4060E0处进行了ALG_ID的替换如下图所示:
而0x6801对应的算法则为CALG_RC4。
尝试替换解密算法,即可得到flag。
十、wizardcult
10.1 writeup
从pcap包中可以导出一个64-bit的ELF文件,同时从HTTP交互中也可以得知本题模拟的是一个任意命令执行的漏洞攻击场景:下载并执行induct可执行文件。
将induct放入IDA中进行反编译,大致可以得知这道题是通过IRC通信来实现消息传递,flag信息或许就隐藏在交互的信息流中。这一点也可以从pcap报文中发现端倪,如下图所示:
数据流量中存在大量的单词词组以及形如“I cast xxx on the xxx for nnndnnn damage!”格式的语句。因此,想要找到flag就需要分析这些数据内容的编码和交互逻辑。
从main_main函数的IDA反编译结果可以得知IRC服务器为wizardcult.flare-on.com:6667,根据所引用的第三方库lrstanley/girc的文档结合pcap包中的文本内容,可以定位出PRIVMSG命令所关联的解析函数为main_main_func2。
下一步若想通过动态调试的方式分析数据编码逻辑,则需要搭建一个IRC服务器以此实现动态联调测试,此处选用miniircd作为服务器端。
通过动态调试发现交互的文本处理入口位于函数wizardcult_comms_ProcessDMMessage中,服务器将客户端发送的单词组,通过内部的一个词典转换成对应的字节(单词位于词典中的偏移),并将该字节序列利用wizardcult_vm_LoadProgram函数实现加载。从函数名称不难看出,这里有使用到虚拟机技术实现字节码的解析,如下图所示:
在该函数入口点下断点,可以dump出对应的opcode内容。
共有2组vm,一组是以“Potion of Acid Resistance”标记的vm1,如下图所示:
另一组则是以“Potion of Water Breathing”为标记的vm2,如下图所示:
根据后文的分析,induct即是根据这些关键字来实现vm的选择。
结合wizardcult_vm_LoadProgram函数开头的一组解码操作得知vm的opcode经过了gob格式编码,如下图所示:
利用degob工具可以得到反序列化后的处理结果,如下图所示:
进一步美化得到:
结合IDA函数列表中得到的一些线索,如tgt、tlt、teg类型的opcode handler,如下图所示,检索到一款名为SHENZHEN/IO的编程游戏。
根据该游戏的说明手册,可以找到teg、tgt、tlt类型的指令说明,如下图所示:
根据手册中的说明,了解到这些特殊的指令是进行test比较,比较的结果将影响以+/-开头的指令的执行。如上图当p0为100时,teq p0 100为真,则+mov 1 x1指令会被执行,而-mov 0 x1指令则会被跳过。
根据gob解出来的内容中的Opcode字段,统计得到vm中共使用到了7种opcode:
- 1:mov
- 5:teq
- 6:tgt
- 10:sub
- 13:xor 0xffffff
- 16:and
- 18:xor
而Bm字段则对应操作数的类别
- 0:I后面一个立即数
- 1:R I后面第一个是寄存器,第二个是立即数
- 2:I R后面第一个是立即数,第二个是寄存器
- 3:R R后跟两个寄存器
也有例外的情况:Opcode 18 xor后面紧跟一个操作数。
Cond字段用以表示前面提到的指令前的+/-,即是否执行此语句:
- 0:无条件执行
- 1:前述test比较指令结果为true时,则执行
- -1:前述test比较指令结果为false时,则执行
根据上述理解,可以将vm1的opcode翻译成如下的伪汇编指令:
vm2的opcode翻译如下:
结合动态调试的结果,vm1的等价伪代码为:
即,将输入的内容进行单字节异或编码。
而vm2的等价伪代码则为:
即,将输入的内容按字节依次进行xor以及查表操作,根据字节值的大小以及当前所在输入负载中的offset,分别作为查表的索引在内置的4组码表中进行查表替换操作,最后得到替换后的等长字节序列。
输入的字节序列在经过vm的编码处理之后,还会做一次编码转换,执行该步操作的函数为wizardcult_comms_CastSpells。
经分析,该函数的作用是将输入的字节流(经前一步vm编码后的内容)按3字节一组进行处理:
第1个字节:根据wizardcult_tables_Spells词典进行“字节->单词”的转换,如wizardcult_tables_Spells[0] = Eldritch Blast,则0x00会被替换为“Eldritch Blast”
第2个和第3个字节:直接以“%dd%d”的形式进行格式化,如“100d134”
根据分组长度(末尾可能存在不够3字节一组的情况),分别拼装成以下格式的消息文本:
3字节:I cast %s on the %s for %dd%d damage!
2字节:I cast %s on the %s for %d raw damage!
1字节:I cast %s on the %s!
最后,induct把上述编码好的内容发送到IRC频道中,在pcap包中看到的也就是这种格式的数据,如下图所示:
pcap包表明通信双方induct和dung3onm4st3r13共发生过2次对话,其中数据包#441~#482可视为第1次对话,#484~#3768(最后)可视为第二次对话。
伪装成dung3onm4st3r13用户,手动发送dung3onm4st3r13的通信内容模拟与induct进行交互,可观察到双方实际的通信内容。
- 在IRC客户端中以为nickname加入到#dungeon频道,并按顺序依次发送相关通信文本;
- 在induct中的0x652D76 call rcx处下断点
交互1:
发现有3处线索:
- ls /mages_tower
- rcx指向wizardcult_potion_CommandPotion函数
- 命令返回结果的编码流程走的是vm1
倘若跟踪该逻辑执行流程可发现程序内部会出现“ls /mages_tower”命令的执行结果,即第1组交互逻辑是dung3onm4st3r13下发ls命令查看induct所在主机上的/mages_tower目录内容。
交互2:
同样有3处线索:
- /mages_tower/cool_wizard_meme.png
- rcx指向wizardcult_potion_ReadFilePotion函数
- 文件内容的编码方式走的是vm2
猜测可知,第2组交互逻辑为dung3onm4st3r13下发获取/mages_tower/cool_wizard_meme.png文件内容的命令给induct,从而实现文件下载的目的。
根据前面分析得到的vm2的编码逻辑,尝试逐字节进行爆破,爆破的每个字节均需要对得上pcap中的内容,pcap中提取到的编码后的png内容可通过此链接获取。
爆破脚本如下:
最后可还原出png图片内容如下:
十一、结语
本届挑战赛共计10道题目,涵盖Windows、Linux、JavaScript、Golang、Docker、VM等不同领域的技术点,当所有的挑战通过后会提示如下:
每年Flare-On挑战赛的题目大都来源于厂商遇到的真实事件以及研究员们的最近研究成果,这些题目对于专注于漏洞研究、样本分析、应急响应、CTF等领域的专业人员或爱好者来说都是非常有价值的参考资料。通过不断的参与,不断发现自身需要提高的技能,并保持对所关注领域最新趋势热点及资讯的敏锐度和参与度,相信可以让自己的眼界和能力更上一层楼。
期待下一年!
版权声明
本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。
亚甲基蓝