简介:面向CTF竞赛初学者及安全爱好者的PDF资料,系统讲解杂项基础解题技能,重点覆盖文件类型识别、分离与合并三大模块。文档从常考题型切入,逐步演示file命令与010Editor判别文件类型,再结合Binwalk、foremost、dd、fcrackzip等工具完成文件分离与隐藏内容提取,并给出Linux下cat命令、Windows下copy命令以及Python脚本三种文件合并方法。每部分均配有具体操作步骤与实战练习,全文按模块整理为速查笔记,方便读者针对压缩包、隐写、流量等题目场景快速对照工具用法,也适合以此为基础梳理个人解题框架,并能在实战中形成自身的工具使用习惯。资源共1个PDF文件,约2.2MB,结构清晰、目录完整,可反复翻阅巩固,适合入门阶段对照练习。已有299人学习下载,是CTF入门者解决附件文件处理问题的实用参考。
1. 文件类型识别是关键:CTF MISC的起点
CTF竞赛MISC(杂项)题目里,超过一半的题眼不在加密算法,而在一个最基础的环节:你根本不知道手上这个文件是什么。扩展名被改成.png,file命令却显示data;拿到一个.bin,直接打开是乱码;甚至一张正常图片里藏了另一个压缩包,需要你先识别出它不是纯图片。文件类型识别、分离与合并,是ctf入门必过的第一道关卡,也是buuctf misc和各大平台杂项题反复考的手法。这篇笔记把我平时解题的完整流程、命令参数和踩过的坑写出来,新手能照着敲,熟手也能看看边界。
2. 文件类型识别:从 magic number 到 file、binwalk 的实战用法
2.1 为什么 file 会“认错”:magic 与扩展名
做ctf杂项题目,第一反应是把文件丢给file命令。file并不是看扩展名,而是读文件开头的几个字节,也就是magic number。比如PNG图片文件头固定是89 50 4E 47 0D 0A 1A 0A,JPEG是FF D8 FF,ZIP压缩包是50 4B 03 04。file命令内部维护了一套magic数据库,通过匹配这些特征来判断真实类型。
但这里有个典型的“翻车”场景:题目把压缩包改名为图片,或者把文件头抹掉,file就会显示data,意思是“没认出来”。更麻烦的是有些文件本身是多段数据拼起来的,比如一个PNG图片后面追加了一个ZIP。file只读头部,会告诉你这是PNG,但真正的flag躲在后面那段ZIP里。所以file只是第一层判断,不能只信它。
那么什么时候需要手动看magic?当你发现file返回data,或者比赛题目提示“文件头被破坏了”,就需要用十六进制工具直接看文件头。常见做法是用xxd或hexdump查看前16个字节,然后对照常见文件特征表。这里有我常用的一个最小命令:
xxd -l 16 flag.png逻辑说明:xxd把二进制文件按十六进制逐字节显示,-l 16只取前16个字节,避免整个文件刷屏。输出类似:
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR参数说明:-l是limit,限制读取的字节数,一般看文件头用16或32足够;如果怀疑文件中间藏东西,可以去掉-l配合grep搜索特征字符串。另一个工具是hexdump -C,格式类似但带ASCII对照,适合人眼扫。实际解题时我会先用xxd,因为它输出更紧凑。
2.2 用 file + xxd 识别伪装文件:手把手命令
ctf入门最常见的一道misc题是:拿到一个misc.png,死活打不开,提示文件损坏。很多新手会直接去修图片宽度,但我的习惯是先做类型识别。把文件扔给file看看,再查十六进制头部。
file misc.png如果输出是PNG image data, 800 x 600,那扩展名没问题,问题在图片内容本身。如果输出是data,那大概率是伪装。再看头部:
xxd -l 4 misc.png输出如果是50 4B 03 04,这其实是一个ZIP压缩包被改名成了png。这种情况直接改回.zip然后解压即可。如果是FD377A585A00,这是xz压缩格式;1F 8B是gzip。我做过一份常见magic对照表,贴在下面,照着比对就够用了。
| 真实类型 | magic(十六进制) | file常见输出 |
|---|---|---|
| PNG图片 | 89 50 4E 47 0D 0A 1A 0A | PNG image data |
| JPEG图片 | FF D8 FF E0 或 FF D8 FF E1 | JPEG image data |
| GIF图片 | 47 49 46 38 | GIF image data |
| ZIP压缩包 | 50 4B 03 04 | Zip archive data |
| RAR压缩包 | 52 61 72 21 | RAR archive data |
| ELF可执行文件 | 7F 45 4C 46 | ELF 64-bit ... |
| PDF文档 | 25 50 44 46 | PDF document |
| 7z压缩包 | 37 7A BC AF 27 1C | 7-zip archive data |
这里有个关键点:file命令识别的是“头部”特征,但如果一个文件是“PNG + 尾部附加数据”,file仍然只会报PNG。所以我会配合binwalk来做深度扫描,binwalk会列出文件中所有已知文件结构的偏移位置。它的价值在于,即使文件头被伪装,只要内部存在完整的数据块,binwalk也能扫出来。
binwalk misc.png逻辑说明:binwalk不是只读头部,而是从文件起始位置开始滑动扫描,匹配已知文件签名。输出会显示每个文件类型的偏移地址,比如15934 0x3E3E Zip archive data,意思是从这个偏移开始有一块ZIP数据。这通常就是题目藏的线索。
参数说明:binwalk默认扫描所有常见格式,如果题目涉及压缩包、固件、磁盘镜像,直接跑就好。加上-y zip可以只匹配zip签名,减少干扰;-A是扫描所有已知签名。我遇到多段数据时,会先binwalk看偏移,再用dd提取,比直接交给foremost更可控。
2.3 遇到压缩包和磁盘镜像:binwalk 的指纹库
CTF misc题型里,有一种是直接给一个无扩展名文件,大小几百KB,打开全是乱码。这时候先做类型识别,再决定后续手法。如果一个文件是磁盘镜像或者固件,binwalk的价值尤其明显。比如有些题会把flag放在一个FAT文件系统的镜像里,扩展名被删了,你用file可能识别出filesystem data,但不清楚内部结构。这时跑一下binwalk:
binwalk -A firmware.bin-A是只显示已知文件签名的扫描结果,不执行提取。输出可能包含:
DECIMAL HEXADECIMAL DESCRIPTION 0 0x0 Linux kernel ARM boot executable zImage 10240 0x2800 gzip compressed data看到gzip数据,说明固件里有压缩层。这时再针对这个偏移提取,而不是直接对整个文件盲目处理。参数说明:-A和默认模式的区别是,默认模式还会做熵分析,-A更纯粹。对于ctf入门选手,用-A看签名就够了。
还有一种情况是“文件头缺失”。题目把一个ZIP的头几个字节删掉,你看到xxd输出变成了?? ?? ?? ?? 50 4B 03 04或者干脆只剩后半部分。我的做法是:先判断缺失的字节数,再补上对应magic。比如ZIP头是4字节,如果文件显示从50 4B开始的偏移只有2字节,那就在前面补2个零字节(或正确的前导字节)。这个手法会在第4章详细展开,识别这一步要做到的就是判断“缺了什么”。
3. 文件分离:把藏起来的文件“掏”出来
3.1 foremost 自动分离:按文件头扫描
识别出文件里有多个文件块之后,下一步就是分离。ctf杂项题目最爱干的事是把一个ZIP或者另一个图片藏在一个正常图片后面。foremost就是干这个的经典工具。它基于文件头特征扫描整个文件,把匹配到的每一段独立提取成文件。默认配置覆盖常见图片、压缩包、文档格式。
foremost -T -o output_dir misc.png逻辑说明:-T让foremost把时间戳加到输出文件名前面,避免两个题目的结果混淆;-o output_dir指定输出目录。foremost会在output_dir下建立jpg、zip、png等子目录,把分离出的文件放进去。
参数说明:如果题目是某个冷门格式,默认配置可能扫不出来,可以用-c指定配置文件。比如foremost -c /etc/foremost.conf -T -o out file.bin。默认配置路径可以通过foremost -h查看。我一般先跑默认,扫不到再改配置文件加自定义签名。
foremost的缺点也很明显:它只看文件头,不看文件尾。如果被分离的文件内部有损坏,或者两个同类型文件相邻,foremost可能只提取出第一个,或者提取出不完整的碎片。另外,foremost对“追加在图片后面的文件”很有效,但如果隐藏数据是错位存放,或者被加密过,foremost就无能为力了。
3.2 binwalk -e 提取:从固件到附加数据
binwalk不仅能识别,还能直接提取。最常见的用法是binwalk -e,它会根据扫描到的文件签名自动把每个块提取出来。在ctf misc题里,一个文件里藏了另一个压缩包,一条命令就能解开,比自己记录偏移再用dd切方便得多。
binwalk -e --run-as=root mystery.bin逻辑说明:-e是extract,--run-as=root是因为binwalk提取某些压缩格式时需要调用系统解压工具,普通用户权限不够会失败。输出会在当前目录生成_mystery.bin.extracted文件夹,里面是所有分离出的文件。
参数说明:有些场景需要加-M(recursive extraction),也就是“递归提取”――分离出来的文件里如果又藏了文件,继续解压。这在wifi固件、bootrom题里非常常见。比如binwalk -Me mystery.bin。但注意递归会爆炸,解压好几层后文件会很多,需要靠时间戳和大小判断哪个是真正的flag文件。
binwalk -e并不是万能的。它对“格式嵌套”处理得很好,但如果两个文件块之间有空字节,或者文件头被部分篡改,它可能识别不出来或提取错。另一个坑是:binwalk提取ZIP时必须依赖系统unzip,如果你的环境里没有安装,会静默跳过。这也是我遇到的“提取目录里没有zip”的原因。解决办法是先安装依赖:Debian/Ubuntu执行apt install unzip p7zip-full。
3.3 dd 手动切割:精确到偏移量的分离
当foremost和binwalk都不靠谱时,就要回到最基础的工具:dd。foremost按签名自动分离,但有些题目故意把文件头改了一个字节,导致自动工具识别不出。这时你已经通过binwalk或者hexdump定位到了真实数据的偏移,直接用dd抠出来最踏实。
比如我在一张图片的偏移0x1234处发现了一个ZIP的文件头50 4B 03 04,想从这个位置开始提取1024字节作为zip文件:
dd if=misc.png of=hidden.zip bs=1 skip=$((0x1234)) count=1024逻辑说明:if是输入文件,of是输出文件,bs=1表示每次读写1字节(这样偏移和计数都以字节为单位),skip跳过多少字节,count读取多少字节。执行后hidden.zip就是从原图0x1234开始的1024字节内容。
参数说明:如果文件很大,bs=1会很慢,可以先把偏移换算成十进制并加大bs。比如跳过4660字节,读取1024字节,可以写成:
dd if=misc.png of=hidden.zip bs=4660 skip=1 count=1这种写法更快,但需要你确定数据正好在4660字节边界,一般不建议。更稳妥的是保持bs=1,牺牲一点时间换准确。追求效率时,也可以先skip=0x1234配合bs=1,注意dd的skip参数在GNU版本中支持0x前缀。在macOS上需要先写成十进制。
出了偏移后,我建议验证一下提取出来的文件类型:
file hidden.zip xxd -l 4 hidden.zip两次确认,防止前一步的偏移位置记错,导致分离出的文件头不符。用dd最怕的是看走眼把偏移多算了1字节,那整个文件内容就全错位了。所以我习惯提取后马上看前几字节,确认和你预期的一致再往下做。
4. 文件合并:构造合法文件与还原原文件
4.1 合并的三种常见需求:拼接、追加、修复
别把“合并”想成简单地把文件A拼到文件B后面。在ctf misc题里,合并通常有三种需求,对应不同做法。
第一种是“尾附隐藏”:将flag文件内容直接追加到一张正常图片末尾。这种合并不需要什么技巧,就是cat命令拼接,解题时用binwalk或foremost再分离回去。
第二种是“文件头缺失修复”:题目把一个文件的部分头部删掉,你需要把缺失的magic补回去,让这个文件能被正常打开。这本质上是“在文件开头插入指定字节”,也就是把正确头文件和残缺主体合并。
第三种是“分段文件重组”:题目给两个或多个文件,每个都是原文件的一部分,比如part1、part2,需要按顺序拼接才能还原出完整的zip或图片。这种就是纯粹的字节流合并,顺序不能错。
理解了这三种需求,你就知道什么时候该用cat,什么时候该用dd,什么时候该用Python脚本。先看最直接的方式。
4.2 cat 与十六进制合并:保存与还原
“尾附隐藏”最常见的场景是:题目给你一个hint.png,你用binwalk看到尾部藏了一个文本文件,里面是flag。反过来,如果你想构造一个攻击样本,把flag文件藏到图片尾部,用cat一行就行:
cat flag.txt >> cover.png但这里有个关键点:如果flag.txt是文本文件,直接追加到图片末尾,图片依然能正常打开。因为PNG解码器读到IEND标记后就不再读取后面的数据了。所以追加任何数据都不影响图片显示。同理,把ZIP文件追加到图片后面,图片还是能显示,zip数据变成“附加数据”而已。
如果要还原出原始zip文件,du一个反操作:
dd if=cover.png of=extracted.zip bs=1 skip=512假设原本cover.png大小512字节,那么从512开始到结尾就是追加的数据。当然用binwalk更轻松。这一节重点不是说这个,而是说“合并”还有一个容易翻车的点:文件尾部的长度不是固定的,如果图片本身有注释块或调色板,追加的位置千万不能覆盖到原有数据。所以我的原则是:先备份原文件,再合并。
当你需要把两个二进制文件按指定偏移合并时,直接cat就不够了。比如要把一个PNG头和一个残缺的图片主体拼起来,cat header.bin body.bin > full.png是可以的,前提是header.bin以PNG head开头的完整字节流。更精细的操作可以用dd的conv=notrunc在一个已有文件上覆盖写,但ctf解题里很少需要这么精细,除非做隐写题。
4.3 处理文件头缺失:补 magic 的实战
文件头缺失是ctf misc的高频考点。题目形式一般是:一个文件被hexdump后开头几个字节变成了00,或者压缩包解压时提示“central directory not found”。这时候你要靠“合并”把缺失的magic装回去。
具体场景:我有一个broken文件,xxd -l 8显示50 4B 03 04后面就断掉了。但正常ZIP文件头后面还有版本、标志位、压缩方法等字段。如果题目的破坏只是把前4字节改成00 00 00 00,那你只需要把前4字节覆盖回50 4B 03 04。
用dd配合printf可以精确地在指定位置写入数据:
printf '\x50\x4b\x03\x04' | dd of=broken.zip bs=1 seek=0 conv=notrunc逻辑说明:printf输出的是ZIP的magic字节。dd of=broken.zip bs=1 seek=0 conv=notrunc表示从文件的0偏移开始写入4字节,conv=notrunc防止写入后文件被截断,只覆盖原有字节,保留文件其余部分。
参数说明:seek=0代表起始偏移是文件开头;如果破坏点在偏移4,改成seek=4。注意printf在bash内置命令和/bin/sh下输出可能有差异,建议在bash下执行。如果写入的magic是动态计算的,可以用echo -ne '\x50\x4b...',效果类似。
更复杂的情况是“文件头缺失但文件尾完整”。比如ZIP文件被砍掉前8字节,你直接改导致central directory位置错乱。常见做法是下载一个同版本的空zip模板,把它的头部字段按原文件剩余数据补上。但这个题基本不会在入门misc出现,更常见的是图片文件头缺失。比如PNG标志89 50 4E 47 0D 0A 1A 0A前4字节被改成00 00 00 00,图片无法显示。用同样的printf覆盖即可。但要注意:PNG头缺失后,高度宽度值也可能被破坏,那就要手动修IHDR长度了,这是另一个话题。
5. CTF MISC 文件操作避坑与排查:5个常见翻车现场
5.1 现象:file 显示 data 但其实是 zip
ctf入门时我把一个flag文件直接扔给file,它显示data,我就以为这个文件被加密了。实际上,像ZIP、RAR这类压缩包,只要magic被擦掉,file就会返回data。我试过很多次,原因往往不是加密,而是文件头被清空了。
排查方法:用xxd -l 16看前几字节。如果看到PK(0x50 0x4B)出现在偏移4而不是偏移0,说明前面有4字节被改掉或填充了。再从出现PK的位置开始提取,得到的就是完整的zip。解决命令:
dd if=flag bs=1 skip=4 of=out.zip file out.zip如果out.zip能解压,说明就是头部冗余字节的问题。如果是头部彻底被删,则需要在原始文件前面补一个标准magic,也就是第4章讲的覆盖写入。
5.2 现象:binwalk 提取出一堆碎片,flag 找不到
用binwalk -e解出一个压缩包,里面全是零散的小文件,但找不到flag。这是最高频的坑。原因:binwalk的递归提取会把压缩包里的文件层层解出,同时也会把压缩包内嵌的“冗余数据”当成独立文件提取出来,生成大量以十六进制为名称的碎片文件。真实flag往往藏在某个不显眼的文件里,比如一个叫flag.txt或者hint的文件,但它混在几十个无意义文件里。
解决:不要直接翻目录,先在提取目录里搜索字符串“flag”或“ctf”:
grep -ra "flag" . --include='*' -l-a把二进制文件也按文本处理,-r递归目录,-l只列文件名。如果flag是十六进制形式,比如666c6167,直接用字符串搜不到,这时用grep -ra "666c6167"。另一个办法是按文件大小排序,flag文件通常不会太小,排除0字节文件。推荐先排序再搜。
5.3 现象:合并后文件打不开,报格式错误
我把两个文件用cat合并后,目标文件提示“格式无效”,常见于合并ZIP或PNG这种格式敏感的文件。原因有两个:一是合并顺序错了,把part2拼在了part1前面;二是合并时没有去掉文件尾的空字节,导致目标文件尾部多出一大堆00。
排查方法:先确认每个分片的magic。如果part1开头不是PK但part2开头是PK,那顺序就是反的。另外,用xxd -l 8检查分片之间的重叠或缺失。如果分片之间有重叠,合并后会重复数据;有缺失则格式不对。解决:用truncate -s裁掉多余字节,或者用Python按字节精确组装。
5.4 现象:分离出来文件大小不够
用foremost分离时,发现分离出的文件比原始文件小很多,或者打开后内容截断。原因:foremost默认按文件头标记开始,在下一个同类型文件头处停止,但如果原文件只有一个文件,它会读到文件尾。如果分离出的小文件内容不完整,往往是隐藏文件的数据被“中间的空字节”切断了,或者foremost的配置文件限制了该类型的最大长度。
排查:用binwalk看分离出的那个文件偏移附近是否还有同一类型的签名。如果存在两个连续的JPEG头,foremost会把第一个头到第二个头之前的内容提取为第一个文件,但这只是第一个JPEG的残缺部分。解决方法是改用dd直接从识别出的完整数据区提取,或者调整foremost配置文件里的max_size限制。
5.5 现象:图片里藏了文本却直接打开看不见
做misc题时,我有时会遇到strings命令能看到一段话,但打开图片就是看不到。很多人以为是隐写需要调色板,其实只是文本被放在了图片的尾部。cat cover.png时,数据在原图片尾部追加,而图片显示时忽略了这部分,所以你看不见。这类题用strings+grep就能解决,不需要分离:
strings -n 8 cover.png | grep -i "flag"-n 8只显示长度大于等于8的连续可打印字符,避免大量噪声。但如果文本被压缩过,strings也看不到,需要先分离再解压。我的坚持是:遇到图片藏东西,先跑binwalk,再跑strings,顺序不要反。binwalk能告诉你藏在哪里,strings告诉你藏了什么。
6. 进阶:用 Python 脚本批量处理文件识别、分离与合并
当你CTF入门期做过几十道misc题后,你会发现手动敲命令的流程完全可以脚本化。比赛时间紧张,与其每题都file+xxd+binwalk三步走,不如写一个通用脚本,把识别、分离、合并封装成函数。这里给一个最小实现,直接复制就能用。
#!/usr/bin/env python3 import os, sys, struct MAGIC_DB = { b'\x89PNG\r\n\x1a\n': '.png', b'\xFF\xD8\xFF': '.jpg', b'PK\x03\x04': '.zip', b'\x7fELF': '.elf', b'%PDF': '.pdf', } def detect_type(data: bytes): for magic, ext in MAGIC_DB.items(): if data.startswith(magic): return ext, magic return None, None def extract_embedded(src: str, out_dir: str): data = open(src, 'rb').read() # 在文件中查找所有已知magic出现的位置 for magic, ext in MAGIC_DB.items(): pos = 0 while True: idx = data.find(magic, pos) if idx == -1: break # 从该位置向后找下一个同signature或文件尾,作为结束 next_idx = data.find(magic, idx + len(magic)) end = next_idx if next_idx != -1 else len(data) name = f"extracted_{idx}_{ext.strip('.')}" with open(os.path.join(out_dir, name), 'wb') as f: f.write(data[idx:end]) pos = idx + 1 print(f"done, files written to {out_dir}") if __name__ == '__main__': src = sys.argv[1] out = sys.argv[2] if len(sys.argv) > 2 else 'out' os.makedirs(out, exist_ok=True) extract_embedded(src, out)逻辑说明:detect_type根据文件开头字节判断真实类型,extract_embedded在文件里扫描所有已知magic,找到一处就提取到下一个相同magic之前。这个脚本比binwalk简陋,但它只做你想做的事,且不会碰系统依赖。
参数说明:MAGIC_DB可以自己扩充,比如加b'Rar!\x1a\x07\x00'对应.rar。脚本的切割逻辑用了data.find,如果藏的文件内部也有相同magic,可能误拆分。更精确的做法是解析每个格式的文件头长度,但那是另一个话题。
我的实战习惯是:先用这个脚本做批量筛查,发现可疑索引位置后再用dd精确提取。因为脚本会把同一类型的所有块都抽出来,数量多但不会漏。合并功能也很简单,按顺序读取分片:
def merge_files(parts: list, out: str): with open(out, 'wb') as f: for p in parts: f.write(open(p, 'rb').read())使用它时,先检查每个分片头部,确认顺序正确再合并。我吃过一次亏:把两个分片顺序弄反了,解压时报“crc错误”,后来换成脚本先打印各分片magic,一眼看出问题。最后,我想把这条“血泪经验”送给你:做任何分离、合并、覆盖操作之前,先对原始文件做一次cp备份。dd的conv=notrunc一旦写错,是没法用命令还原的。我自己的习惯是每个题目建一个工作目录,原始附件放origin/,所有操作产物放work/,这样不管怎么折腾都能撤销。希望这篇CTF MISC文件操作笔记能帮你在比赛里少走几步弯路。
本文还有配套的精品资源,点击获取