Apple released the brand new APFS on WWDC 2017 with a bunch of new features. With curiousity, Ben tried it out at once. Also, he left some surprise for you
This challenge is not related to the version of your MacOS
look CAREFULLY at the challenge description apfs_snapshot
HFS+: 1 sec APFS: 10^-9 sec
A ^ B = F
L1: 16bytes-aligned L2: hint “apfs_snapshot” L3: hint “HFS+: 1 sec APFS: 10^-9 sec” L4: mtime LSB, A ^ B = zip
文件名:497e2194-d11f-4dfd-9246-b9746f289bc7.dmg
文件哈希:5fcfa02736ea8bf82c01f37a0309369a(MD5)
文件大小:8,543,242 Byte
下载得到一个dmg文件,dmg为macOS中的磁盘镜像格式。所以直接双击打开,发现需要密码
一开始以为是macOS的密码提示那个漏洞,尝试使用了低版本macOS(10.12.6)发现并没有密码提示,然后这时候提示1也出来了,所以明白点不在这。于是拖到010editor中查看,在文件尾发现了密码 N1CTF_APFS
输入密码后成功挂载。
挂在之后发现只有一个名为ctf的文件夹,里面存放了531个0 Byte的txt
然后在这里思路卡壳,队里一个朋友发现这个dmg里还有一层快照
于是使用 tmutil listlocalsnapshots /Volumes/N1CTF_APFS 查看快照信息,发现快照名为ctf
然后尝试恢复快照,这里遇到了一个巨大的坑,在新版的macOS(10.13.3)上没有快照恢复的相关指令。于是又去py出题人,出题人表示没有问题。让我再去看看,网上找找相关介绍,找了一圈发现有个文章上说可以用 apfs_snapshot 来恢复,然而我cd到
/System/Library/Filesystems/apfs.fs/Contents/Resources发现并没有apfs_snapshot
不得已,找朋友借了一个低版本的macOS(10.12.6)进行恢复的
使用./apfs_snapshot -b ctf /Volumes/N1CTF_APFS 来恢复快照
然后在磁盘管理当中卸载然后重新挂载该磁盘即可
后来讨论也可以使用如下指令直接把这层快照挂载出来。
cd Volumes sudo mkdir N1CTF_APFS_snapshot sudo mount_apfs -s ctf ./N1CTF_APFS ./N1CTF_APFS_snapshot
于是得到了镜像前后的磁盘在这里为了获取快照前后的情况,花了好几个小时。。然后去py出题人,出题人表示提没问题,也和 macOS 版本无关,当时真的想吐血。
在我搞定了快照前后的磁盘之后。。。提示(提示2)又恰到好处的放出来了。。
接下来在这里思路卡壳了好久。尝试进行了数据恢复,找出来一大堆没用的东西,也浪费了很多时间。然后去py出题人,出题人表示让我认真读题,好好看看加粗的with a bunch of new features。然后我滚去把 WWDC 2017 中关于 APFS 所有的的新特性都看过去了。看来看去就一个快照,快照已经用过了。这时候提示3出来了。讲道理这提示我一时摸不着头脑,做了如下猜想:
APFS 比 HFS+ 读写快,但是不可能快9个数量级啊,划掉
APFS 比 HFS+ 索引快,但是依然不可能快9个数量级啊,而且 APFS 里实际的数据只存一份,有点像Linux的硬链接,不对不对,划掉划掉
肯定与什么东西是1秒(s)和1纳秒(ns)的差距,找找就行,Bingo
于是Google it,终于在Apple的开发者中心(https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/APFS_Guide/VolumeFormatComparison/VolumeFormatComparison.html)找到了这个详情。
原来 APFS 中记录的时间戳精确到纳秒,而 HFS+ 只精确到秒。看来问题在时间戳了
然后写 python 脚本提取时间戳,这里又遇到一个坑。我的 python 的 os.path.getmtime() 只能精确到小数点后两位时间,我对着这时间发了一小时呆。。。不得已又去py出题人,出题人骂了我一顿,并表示爱莫能助。后来跟出题人进行了深入交流之后,出题人表示要不你换个工具试试。然后我搜索到 coreutils , 于是brew 启动!coreutils 安装!
终于可以看到精确的时间戳了
然后卡壳。这特么能看出个鬼。然后继续py出题人,出题人把我从头鄙视到脚,鄙视进每一个细胞。每一个粒子。在我诚心诚念我是渣之后,出题人给出了有用的信息,去看看修改时间(mtime),好的,然后把快照前后的修改时间全部提取出来。
cd /Volumes/N1CTF_APFS/ctf
gstat -c "%n %y" * > ~/qian.txt
cd /Volumes/N1CTF_APFS_snapshot/ctf
gstat -c "%n %y" * > ~/hou.txt
取出来的数据如下图此时提示4也上了,那A B 就很明确了,A表示快照前,B表示快照后,二者异或就行了
好的,异或,异或完了,我对着这数据又发呆了,这能看出来个鬼啊。不管了,继续py出题人,出题人问我你知道 LSB 么,我说我不知道(说的同时 Google 了一下),大概看了一下,最低有效位,用来做一些标志,比如0为正1为负之类的。
原来如此,那我把最后一位取出来,看下是奇还是偶不就拿到二进制数据了么,还没弄完就想了一下,不对啊,这才531个字符,够干嘛啊。继续问出题人,出题人表示让我仔细观察。那行,我先拿出来再看看
写了个py来提取最低位,最低位拿出来的数据如下:然后仔细观察。发现数据的范围都是0-7,没有8和9,然后联想到八进制,每个数字就是3个bit,所以最终能得到一个531 * 3bit = 1593 bit的数据,然后此时提示4页很明白了,A为快照前,B为快照后,异或就行了,然后写程序处理一下。获得了所有数的二进制
#!/usr/bin/env python # coding: utf-8 def oct2bin(number): return str(bin(number)).replace("0b", "").zfill(3) def main(): qian = "2 4 0 4 5 4 0 3 0 1 0 0 5 0 0 0 0 0 2 0 4 0 0 0 0 0 0 6 7 5 6 3 3 1 4 4 6 1 7 2 4 3 3 4 6 6 1 6 1 4 2 0 0 0 0 0 0 0 0 2 2 4 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 3 1 4 6 6 1 4 1 3 1 6 2 7 1 6 4 3 6 0 7 2 3 7 4 6 6 4 5 6 2 3 2 3 3 2 1 2 6 6 5 3 6 6 5 6 2 2 6 3 0 7 4 4 3 0 4 7 5 6 2 6 1 6 5 6 7 4 7 2 7 1 4 5 6 3 5 3 1 5 7 5 3 5 6 6 7 2 2 5 1 5 5 1 4 5 0 6 3 2 1 4 3 1 3 1 2 1 5 3 7 5 6 2 4 5 1 5 5 0 6 4 5 3 7 4 3 2 4 4 5 1 3 0 5 4 4 6 5 7 3 3 0 6 2 5 6 3 3 0 0 0 6 2 4 0 4 5 4 0 1 0 0 4 3 7 4 0 0 0 2 4 0 0 0 0 1 0 2 0 0 0 0 0 0 3 3 6 7 1 5 4 6 2 3 0 7 5 2 1 5 6 3 3 0 7 0 6 1 0 0 0 0 0 0 0 0 1 1 2 0 0 0 0 0 0 0 0 0 4 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 3 1 5 4 3 0 2 6 3 4 5 6 3 5 0 7 4 1 6 4 0 2 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 4 0 0 0 1 2 3 7 4 7 6 4 1 4 6 1 2 2 6 5 6 4 6 0 0 4 6 2 3 7 3 5 4 1 7 2 0 4 5 3 2 3 2 3 0 0 2 6 7 3 3 6 2 0 2 5 0 3 0 3 5 4 7 5 1 4 0 1 2 4 0 4 5 4 0 5 0 1 4 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 4 0 0 2 6 4 0 0 0 0 0 0 0 0 5 3 4 0 0 0 0 0 0 0 0 0 0 0 0" qian = qian.split(" ") hou = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 3 0 1 0 0 4 0 0 0 0 7 3 6 5 5 3 5 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 5 1 1 0 5 6 0 6 7 2 4 6 0 6 7 1 0 3 2 2 5 2 5 6 2 6 1 2 2 5 1 0 2 3 0 7 2 6 0 3 5 7 0 6 1 3 1 2 3 5 7 3 2 7 3 2 0 2 2 3 4 5 6 6 1 7 4 2 4 5 1 2 5 3 2 0 5 6 5 1 1 2 4 7 4 0 2 1 1 1 6 2 1 6 5 6 7 4 7 3 2 6 7 6 7 3 0 4 5 6 3 7 4 1 6 3 1 2 5 6 4 0 1 1 7 4 4 7 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 6 5 4 0 4 0 2 0 0 0 0 3 5 7 2 6 5 6 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 4 3 1 5 0 3 5 7 3 5 3 4 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" hou = hou.split(" ") r = "" for i in range(0, len(qian)): a = int(qian[i]) b = int(hou[i]) f = a ^ b r += oct2bin(f) print r if __name__ == '__main__': main()
然后得到拼接在一起的二进制数据然后就简单了,每8 bit转一字节,然后写到文件,依然用python处理
#/usr/bin/env python
#coding: utf-8
def bin2hex(string):
return chr(int(string, 2))
def main():
data = "010100000100101100000011000001000000101000000000000000010000100000000000000000000011010110110010011001000100110001111101111101110001101011001010001100010000000000000000000000000010010100000000000000000000000000001000000000000000000000000000011001100110110001100001011001110010111001110100011110000111010000100001111111100000000010101101001111100001100001111101000100100000100111100100101001101110001010000110100101001000000001101000001111010110001110011111000001000111100010111111111001110010011001100011010101111001100101111101101001010100010110000010011110110001011101100111011010111000011000110001000010000101010100100011111101101110110110011000111011100010001101000110101111010100100100111010010100000100101100000001000000100011111100000000000010100000000000000001000010000000000000000000001101011011001001100100010011000111110111110111000110101100101000110001000000000000000000000000001001010000000000000000000000000000100000000000001001000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000110011001101100011000010110011100101110011101000111100001110100000010100000000000100000000000000000000000000000000000000000000000000001000000000001100000000000001101111100101010110111100011101100001110110011110100110000000100110010011111011101100001111010000100101011010011010011000000010110111011011110010000010101000011000011101100111101001100000001010100000100101100000101000001100000000000000000000000000000000000000001000000000000000100000000010110100000000000000000000000000101011100000000000000000000000000000000000000"
tmp = ""
r = ""
for i in data:
if len(tmp) == 8:
r += bin2hex(tmp)
tmp = ""
tmp += i
f = open("result", "w")
f.write(r)
f.close()
if __name__ == '__main__':
main()
得到了一个result文件,file一下发现是个zip包
直接解压,发现需要密码。
一开始以为是伪加密,但是修改了加密位之后发现还是如此,所以猜测是真加密,尝试使用之前的密码N1CTF_APFS,一次成功,得到flag个人感悟
在做题的过程中始终与出题人保持联系,中间也有聊到一些有的没的,出题人表示这个题目是取材于真实的取证工作当中的,完完全全的还原了当时取证的详细过程。
做题过程中不断地在吐槽出题人脑洞,在做完这个题之后返回来看看,出题人并不脑洞,只能说自己当时的心态确实出现了问题。
自己认认真真的思考了一下比赛环境与真实环境的差异,以及什么是取证,怎么才算取证等等。
感觉到自己之前的眼界还是太局限,仅仅停留在比赛是远远不够的,在真实的取证工作中,不会有任何的提示,细心才是唯一的法宝,数据可能藏在任何地方,不漏掉任何蛛丝马迹,不放过一分一毫才是取证的真谛。
同时也感觉到自己的底子实在是太弱了,在了解到出题人的一些经历之后觉得自己还差得太远,除了基础性知识缺失,相关经验也十分匮乏。
在CTF中能够见到这种十分贴近于实战的题目是非常开心的,希望未来能够遇见更多这样的题目,CTF不是为了比赛而比赛,而是信安爱好者交流技术,切磋技能的平台。
Lipstick
lipstick为口红的意思,这次题目是一张图片
首先使用隐写神器Stegsolve,
在中间地带发现了YSL(杨树林,b( ̄▽ ̄)d)这个口红品牌的字样。再继续深入,Analyse→Data Extract
Save Bin保存为一个zip包
这里用winrar打开会报错,得用7z等压缩工具打开才可以。
尝试了下伪加密,无果。于是整个过程就剩下一个密码。一般来说图片隐写的话,要么是二进制里藏了东西,要么就是图形藏了东西。这里二进制里藏了zip包,剩下的密码就只能从图形里入手。图形里是21个颜色格,我分别取色
BC0B28D04179D47A6FC2696FEB8262CF1A77C0083EBC0B28BC0B28D132746A1319BC0B28BC0B28D4121DD75B59DD8885CE0A4AD4121D7E453AD75B59DD8885
这里折腾了好久,发现是要找颜色所对应的YSL口红的色号(lll¬ω¬)
搜到一个网址:
https://www.yslbeautyus.com/on/demandware.store/Sites-ysl-us-Site/en_US/Product-Variation?pid=194YSL
这里颜色值可以对应上色号,于是写脚本收集颜色值对应的色号,并把色号转换为二进制,再组合,再bin2text
# -*- coding:utf8 -*- __author__='[email protected]' import requestsimport re import libnum def foo(): url=r'https://www.yslbeautyus.com/on/demandware.store/Sites-ysl-us-Site/en_US/Product-Variation?pid=194YSL' cont=requests.get(url).content # print cont pattern=r'YSL_color=(.*?)%20[sS]*?background-color: #(.*?)"' rst=re.findall(pattern,cont) dYSL={} for num,color in rst: dYSL[color]=int(num.lstrip('0')) lst=['BC0B28','D04179','D47A6F','C2696F','EB8262', 'CF1A77','C0083E','BC0B28','BC0B28','D13274', '6A1319','BC0B28','BC0B28','D4121D','D75B59', 'DD8885','CE0A4A','D4121D','7E453A','D75B59', 'DD8885'] flag=''.join('{:b}'.format(dYSL[i]) for i in lst) print libnum.b2s(flag) pass if __name__ == '__main__': foo() print 'ok'
打印出来是“白学家”,用7z进行解压缩
解压后打开flag.txt即可。
ps,两个题目附件的下载链接:
https://pan.baidu.com/s/12J01OX2o81Tsq8Y9fMJX7w