
把代码藏进图片这事我第一次见到是在CTF比赛的隐写题里后来才发现真实工作中这个需求远不止“炫技”。运维想传一份服务器配置文件又不想让聊天记录里的关键词被识别开发想给教程配一张图、顺便把可复现的代码连同图片一起发给同事甚至有人把一段脚本伪装成产品截图放进项目管理平台——本质上都是同一件事让图片变成载体把代码悄悄捎过去。这篇文章想聊的是几种真正把代码写进图片文件、能随时提取的“隐藏代码到图片”的实用技巧。从最直观的Notepad手工拼接到Windows命令行里的copy /b再到Linux下的一条cat命令最后还有带密码保护的steghide和完全可定制的Python脚本。前两种适合零基础快速上手后三种适合追求隐蔽性、可玩性和自动化的人。五种方法我都实际跑过会尽量把原理、命令、坑位都讲透你照着做就行。1. 先摸清5种隐藏方案的底层逻辑很多人一听“把代码藏进图片”就觉得高深其实拆开来看底层思路只有三类字符追加、文件拼接、像素替换。后面具体方法再多都跳不出这三板斧。1.1 字符追加最直白的“藏尾”玩法字符追加的思路非常简单图片文件对阅读器来说并不是每个字节都会被解析。JPEG文件以FF D8开头以FF D9标记图像数据结束解码器读到FF D9就认为图片已经读完了后面再跟什么内容都会被忽略。PNG文件则是靠“块”结构组织解码器处理完IEND块后同样不再关心后续字节。这就给“藏尾”留出了空间把代码文本直接追加到图片文件末尾图片查看器依然能正常打开但用文本编辑器打开文件时能看到图片二进制内容的尾部跟着一段可读代码。日常开发里这种方法最常见动手门槛最低唯一的要求是图片格式选JPEG最稳——PNG的块结构带CRC校验部分严格解码器发现尾部多出数据会报警甚至拒绝打开。1.2 文件拼接在二进制层把两个文件粘在一起字符追加是给人看的玩法文件拼接则是把这个过程交给命令行把图片文件的二进制数据流和代码文件的二进制数据流按顺序串联输出成一个新文件。因为图片解码器会在自己的结束标记处停止读取所以多出来的文件数据不会干扰图片展示。这个方式的最大好处是“可脚本化”——一条命令就能处理大量文件而且拼接后的文件不管用什么工具打开数据都是完整的。它的最大弱点也很明显文件体积是两者之和代码稍微大一点图片文件大小就会露出马脚。隐蔽性靠的是“看起来正常”不是真正的抗检测。1.3 专业隐写把数据写进图片的“像素缝隙”第三种思路就有点技术含量了不是把代码追加到文件尾部而是把代码的二进制比特流拆散嵌入到图片像素的颜色通道里。最常用的叫LSBLeast Significant Bit最低有效位替换——每个像素的颜色值最后一位改动人眼根本分辨不出颜色变化但这一位却可以代表一个比特的数据。这类方案的代表工具就是steghide也可以用Python自己实现。它的隐蔽性远高于前两种图片能正常打开文件大小基本不变化而且没有明显的“尾部多余数据”。代价是嵌入容量有限并且部分专业工具可以通过统计分析方法发现LSB隐写的痕迹。简单说它适合“藏给特定接收方看”不适合对抗专业取证。1.4 5种方法怎么选先问自己三个问题动手之前先对照自己的真实需求选方法能省不少时间。我的建议是看三件事第一你的接收方用什么平台如果对方是Windows 微信那Notepad或copy /b的方法最方便如果两边都是Linux服务器直接上cat或steghide。第二代码有多长几十行的配置文件用字符追加完全没问题几百KB的代码包建议先压缩再藏数据量太大时就该认真考虑LSB容量问题了。第三是否需要防别人提取如果只是同事之间传文件前四种方法都够用如果希望别人即使拿到图片也提取不出代码必须用带密码的steghide或者自己在Python脚本里加加密逻辑。把这几个问题想清楚再往下看具体操作就不会觉得方法多而无从下手。2. 方法一Notepad 快速拼接把代码“粘”进图片末尾如果你现在用的是Windows桌面上已经装了Notepad那这是最快能跑通的一种方式全程不需要敲命令。整个过程一句话总结用Notepad打开图片把图片的二进制内容当作普通文本把代码粘贴到文件最末尾保存。2.1 为什么选Notepad而不是系统记事本我知道很多人第一反应是用Windows自带的记事本打开图片。试一下就知道了记事本打开大一点的图片会直接卡到无响应因为它在加载时会尝试对整个文件做编码检测和文本渲染。Notepad对大文件的支持好很多而且默认以UTF-8编码处理文本粘贴中文代码注释不容易出现乱码。另外Notepad有一个很关键的能力它不会像IDE那样对文件类型做严格限制打开二进制文件虽然显示出来是一堆乱码但文件内容会被完整加载末尾追加文本保存后原文件字节不会被破坏。如果用某些“二进制安全”较差的编辑器保存时可能会帮你做换行符转换甚至截断意外字节那图片就废了。想省心就用Notepad。2.2 操作步骤30秒实现“代码藏进图片”整个流程可以分成四步实际操作下来不超过一分钟第一步准备一张JPEG格式的图片文件尺寸不用太大几百KB的风景图、截图都行。第二步用Notepad直接打开这张图片你会看到满屏的乱码这是正常的不要慌按Ctrl End跳到文件最末尾。第三步在末尾先输入一行容易识别的标记比如BEGIN_CODE然后换行粘贴你的代码。注意代码前后都留一行分隔标记提取的时候就知道该从哪里开始、到哪里结束。第四步按Ctrl S保存文件的扩展名保持.jpg不变。保存后再用图片查看器打开图片显示完全正常看不出任何改动。但你用Notepad再次打开这个文件、跳到末尾就能看到那行标记和代码仍然躺在图片二进制内容的尾部。2.3 提取代码与反查验证提取代码有两种方式。轻度使用者直接用Notepad重新打开这个图片文件Ctrl End跳到末尾从BEGIN_CODE的下一行开始复制内容到新文件把标记行删掉保存为.txt或原代码扩展名即可。重度使用者如果图片文件很大、代码很长用编辑器拖动定位太累更好用的是命令行检索。Windows下打开CMD进入图片所在目录执行一行命令就能把尾部文本导出来findstr /C:BEGIN_CODE output.jpg extracted.txt这条命令会把包含BEGIN_CODE的行及其后所有内容写入extracted.txt虽然二进制部分也会被读进去但你从BEGIN_CODE之后开始引用就好了。需要精细处理的话推荐用HxD这类十六进制编辑器打开图片定位到末尾文本位置直接选中复制比在Notepad里拖滚动条舒服得多。2.4 这个方法的短板你心里要有数优缺点都得说清楚。它最大的优点是真零门槛普通人也能三分钟学会缺点是隐蔽性很差文件大小会明显增加任何人都能用文本编辑器打开图片看到末尾的明文代码。如果你的使用场景只是“把代码和图放一起方便传”那没问题但如果希望代码不被人一眼看出来那就要考虑后面的方法了。另外强烈建议图片格式用JPEGPNG在一些严格解码器下可能会因为CRC报错打不开。3. 方法二Windows 命令行 copy /b一条命令完成二进制拼接Notepad方法是给人看的命令行方法则是给脚本和批处理用的。Windows下最经典的文件拼接命令是copy /b/? 参数少但效果好。3.1 为什么copy /b在Windows下最稳copy命令默认工作在文本模式会把文件当作文本流处理遇到特殊字节可能做换行符转换这会导致二进制数据被破坏。加上/b参数后命令强制进入二进制模式原样复制文件里的每一个字节不做任何转换。这就是为什么网上所有图片藏文件的教程都会强调这个/b漏了它拼接出来的图片大概率打不开。拼接之后的文件结构是图片的完整二进制数据 代码文本的完整二进制数据。图片解码器读到JPEG的FF D9结束标记就不再向后读取所以尾部代码不会影响图片展示。这也是为什么我把方法二和方法一归为同一类底层原理但方法二更适合批量操作和脚本化。3.2 实际操作从拼接命令到大小校验先在CMD里准备两个文件一张cover.jpg和一份code.txt然后执行copy /b cover.jpg code.txt output.jpg执行完成后系统会提示“已复制 1 个文件”。这行命令的意思是把cover.jpg和code.txt按字节顺序合并输出到output.jpg。顺序不能反一定先图片后文本文本放前面的话很多图片解码器会在文件头部一开始就找不到JPEG的SOI标记FF D8直接报格式错误。拼接完后做两步验证。第一步看文件大小用dir命令查看dir output.jpg正常情况下output.jpg的大小应该等于cover.jpg和code.txt两者大小之和大小不对基本就是文本模式搞的鬼。第二步直接用图片查看器打开能正常显示就没问题。如果文件名或路径里带空格记得用双引号包起来这是命令行最容易被忽略的细节。3.3 读取数据时的两个小坑坑一很多人在PowerShell里执行copy /b发现不生效。这是因为PowerShell把copy解释成自身的Copy-Item别名参数语法对不上。解决方式是在前面加cmd /c强制走CMD解释器cmd /c copy /b cover.jpg code.txt output.jpg坑二代码文本本身如果比较大建议在代码末尾多留两个空行避免后面如果再拼接其他数据时两段内容边界不清。读取时找代码起始位置可以用findstr /N也可以直接用CFF Explorer这类十六进制工具定位到文件尾部从文本可读的地方开始提取。4. 方法三Linux cat 命令一劳永逸的隐写组合拳如果你日常工作在Linux服务器上那方法三才是真正的主力方案。Linux下没有花哨的工具也能完成同样的拼接而且配合管道和重定向可以玩出比Windows命令行更舒服的流程。4.1 cat拼接的基础版方法很简单就是利用cat把多个文件按顺序输出再用重定向写进新文件cat cover.jpg code.txt output.jpg这里有个细节要强调重定向会把cat输出的所有内容写到output.jpg顺序完全取决于命令行里文件的排列顺序。确保图片在前、文本在后和Windows的copy /b是一样的原理。拼接完同样用两个命令验证。一个是file用来确认图片格式是否仍然正常file output.jpg正常输出类似JPEG image data, baseline, precision 8, 600x400如果显示data或ASCII text说明拼接顺序出了问题。另一个是binwalk它可以扫描文件里嵌入的其他文件结构能直观看到图片尾部是否藏了文本或归档文件binwalk output.jpg4.2 玩法升级代码先压缩再藏直接在图片尾巴上贴明文代码代码不长还好代码一长就特别引人注目而且关键词检索能直接扫出来。我的习惯是先把代码打包压缩再往图片里拼。比如准备一个project目录里面有多个代码文件tar czf code.tgz project/ cat cover.jpg code.tgz output.jpg接收方拿到图片后用binwalk就能看到尾部多了一个gzip压缩包提取时把文件从尾部切出来再解压就行。如果想更准确定位可以在压缩包前面加一个文本标记比如(echo BEGIN_CODE; tar czf - project/) payload.tmp cat cover.jpg payload.tmp output.jpg这条命令先把标记和压缩包合成一个临时文件再拼到图片后面。提取的时候直接搜索BEGIN_CODE从标记位置开始就是完整的压缩数据流。4.3 验证与“反侦察”很多人在Linux下用cat拼接图片和代码后会忽略文件校检。如果图片本身被人用看图软件重新保存过一次比如从JPEG转存成PNG再拼接上去的代码就可能因为格式变化而无法稳定提取。所以我在实际操作中会额外做一次哈希比对# 提取尾部数据并保存为 extracted.tgz dd ifoutput.jpg ofextracted.tgz bs1 skipBEGIN_CODE的字节偏移 # 对比哈希 md5sum code.tgz extracted.tgz两条命令的哈希一致就说明拼接和提取的链路没有丢数据。至于“反侦察”我只能说所有尾部拼接类方法都无法对抗文件大小分析和熵值检测——如果对方用工具扫描图片尾部数据是藏不住的。所以它更适合“便捷传递”而不是“对抗提取”。4.4 哪些场景最推荐用cat方案我个人最常用的场景有三个一是给远程服务器传配置片段直接把一个.env或nginx.conf拼到产品图上传到对象存储后需要时再拉回来切出全程不经过聊天工具的关键词扫描二是做自动化脚本把多个项目文件压缩成包拼到一张公司封面图上一起丢进团队的共享目录三是CTF赛前练习快速把payload藏进图片验证完再换steghide方案。如果你只是偶尔用一次cat方案完全够了。5. 方法四steghide 专业隐写让代码“溶”进像素里前面三种方法本质都是“拼接”只要有人拿十六进制工具打开图片多加出来的数据就一览无余。如果希望代码真正“隐形”得用专业的隐写工具steghide。5.1 steghide和前面几种有什么本质区别steghide不再把代码附加到文件尾部而是把代码的二进制数据分散嵌入到图片的像素颜色值里具体说是修改每个颜色通道的最低位。一张JPEG图片的像素数量是几十万到上百万级别每个像素有RGB三个通道把每个通道的最后1比特换成数据比特肉眼看起来颜色没有任何变化文件大小也基本不变。更关键的是这让文件尾部干干净净用十六进制编辑器看也看不出明显的“多余内容”。它和前面几种方案的差距就像是把纸条贴在书本最后一页和把文字用隐形墨水写在纸页行间——前者翻到最后一页就能看见后者需要特定的方法才能显现。5.2 安装、嵌入、提取的全流程安装很简单Debian/Ubuntu系统直接执行sudo apt install steghide -y嵌入代码文件到图片使用embed子命令steghide embed -cf cover.jpg -ef secret.txt -p your_password参数解释一下-cf指定载体文件cover file就是那张你要隐藏数据的图片-ef指定要嵌入的文件就是代码文件-p指定密码。执行后会输出类似embedding secret.txt in cover.jpg... done的提示。如果图片尺寸太小、塞不进代码会报空间不足的错误。提取时是把过程反过来steghide extract -sf output.jpg -p your_password-sf指定隐写后的图片文件执行后会在当前目录生成secret.txt。这里要注意提取时密码必须正确而且图片文件不能被二次压缩或转码否则嵌入的数据可能损坏。5.3 一句话讲清LSB原理用一句话解释LSB把每个像素颜色值的最低一位换成你的数据比特改完之后像素颜色从250变成251肉眼根本察觉不到。单个像素能藏3个比特RGB各1位一张1000x1000的图片就能藏约37.5万个字节足够放一个中等规模的脚本或配置文件了。steghide在LSB之上还做了一层散布处理——数据会按密码生成的伪随机序列打散到图片各处不是简单地从第一个像素开始顺序写。这就是为什么即使有人猜到用了steghide没有密码也很难恢复原始数据顺序。5.4 steghide的边界与注意事项先说支持格式steghide只支持JPEG、BMP、WAV和AU这几种格式不支持PNG这是个比较常见的坑。很多人习惯用PNG做素材结果报错说格式不支持换一张JPEG就顺利了。再说容量JPEG图片嵌入的数据量不能太大设计上要留足余量否则会嵌入失败或导致图片质量明显下降。我建议代码文件压缩后控制在图片原始大小的10%以内比如500KB的图片最多塞50KB左右的数据稳定性和画质都比较好。最后是操作顺序永远保留原始图片和源码备份因为你嵌入后的图片已经带了数据如果多次嵌入同一张图旧数据不保证能被正确提取。我自己就吃过这个亏把两张图反复嵌入最后提取时一片乱码。6. 方法五Python 自研 LSB 隐写脚本彻底掌控隐藏过程steghide虽然好用但有两个问题没法绕过去格式只支持JPEG和BMP且嵌入规则是写死的。如果你需要把代码藏进PNG图片或者希望自己控制嵌入位置、数据格式、加密逻辑那就得自己写脚本了。这里给出一份我实际用过的Python实现基于Pillow库操作像素代码量不大但足够跑通整个流程。6.1 为什么要自己写脚本自己写脚本最大的意义是“可控”。steghide对嵌入位置有内部算法你的数据变成什么样、散布在哪里对使用者来说是个黑盒。而自己实现LSB可以自由决定哪些像素通道存数据、数据头用什么格式、是否加密甚至能做成批量处理的工具。另外Python生态里的Pillow可以处理PNG这正好补上steghide缺失的一环。6.2 核心原理LSB隐写到底在改什么先把图像拆成像素矩阵每个像素有RGB三个值取值范围都是0-255。在计算机里255的二进制是11111111253是11111101两者最低位不同。把某个像素的某个颜色通道最低位改成0或1这个像素的颜色变化程度只有“1/255”人眼几乎分辨不出来。存储数据时我们把代码文本转成一串二进制比特流然后按照“每1个比特占1个像素的R通道最低位”的方式写进去。以一张1920x1080的图片为例有约207万个像素每个像素的R通道可以存1个比特总共能存约25.9万字节按UTF-8编码一个汉字3字节计算能存约8.6万个汉字放普通代码文件绰绰有余。如果R、G、B三通道全用上容量再翻三倍。6.3 完整代码与逐段解读下面是一份可运行的LSB隐写脚本包含嵌入和提取两个功能。from PIL import Image import sys END_MARKER 00110101 # 自定义结束标记用于提取时判断数据边界 def text_to_bits(text: str) - str: 把文本转成二进制比特串末尾附加结束标记。 data text.encode(utf-8) bits .join(f{byte:08b} for byte in data) return bits END_MARKER def embed_text(cover_path: str, output_path: str, secret: str) - None: img Image.open(cover_path).convert(RGB) pixels img.load() width, height img.size bits text_to_bits(secret) idx 0 for y in range(height): for x in range(width): if idx len(bits): break r, g, b pixels[x, y] # 只改R通道的最低位 new_r (r 0xFE) | int(bits[idx]) pixels[x, y] (new_r, g, b) idx 1 if idx len(bits): break img.save(output_path) print(f[] 已写入 {len(bits)} 比特到 {output_path}) def extract_text(stego_path: str) - None: img Image.open(stego_path).convert(RGB) pixels img.load() width, height img.size bits [] for y in range(height): for x in range(width): r, _, _ pixels[x, y] bits.append(str(r 1)) raw .join(bits) end_pos raw.find(END_MARKER) if end_pos -1: print([-] 未找到结束标记可能图片没有被隐写过) return data_bits raw[:end_pos] # 每8个比特还原成一个字节 byte_data bytes(int(data_bits[i:i8], 2) for i in range(0, len(data_bits), 8)) print(byte_data.decode(utf-8, errorsreplace)) if __name__ __main__: if sys.argv[1] embed: secret open(sys.argv[3], r, encodingutf-8).read() embed_text(sys.argv[2], sys.argv[4], secret) elif sys.argv[1] extract: extract_text(sys.argv[2])使用方式# 嵌入 python lsb_tool.py embed cover.png code.txt output.png # 提取 python lsb_tool.py extract output.png几个关键点拆开说。r 0xFE是把R通道的最低位清零不管原来最后一位是0还是1都会变成0然后再用| int(bits[idx])把我们的数据位填进去这样既不影响高7位又能准确写入1个比特。提取时用r 1取最低位一路读下来就还原出比特流。结束标记END_MARKER的作用是告诉提取方“数据到这里结束”。如果没有它提取方会把整张图所有像素的最低位都读出来后面的内容全是被隐藏数据覆盖不到的区域全是随机0和1没法判断真实长度。用固定结束标记的缺点是如果被隐藏数据本身碰巧包含连续相同的8位序列可能会提前截断实战中更稳妥的做法是在数据头部用4个字节记录数据长度我这里是简化版本方便理解。6.4 进阶中文、加密、长文本怎么处理脚本里已经默认用UTF-8编码处理文本所以中文注释和路径都没问题。但要注意如果你要藏的是二进制文件而不是文本可以直接用open(file, rb).read()拿到字节流再把每个字节转成8位二进制提取时再按字节还原写到文件里这样就不受文本编码限制了。加密方面推荐在LSB嵌入前先对数据做一层加密比如用AES-CTR模式加密后再调用嵌入函数。这样即使别人发现了LSB数据没有密钥也还原不出原始内容。Python里可以用cryptography库几行代码就能实现。我给个伪代码思路from cryptography.fernet import Fernet key Fernet.generate_key() cipher Fernet(key) ciphertext cipher.encrypt(secret_bytes) # 再把 ciphertext 交给 embed_text 写入图片长文本处理要分两步看一是容量够不够前面算过一张1080P的图三通道全用能存约70多万字节普通代码根本用不完二是嵌入速度纯Python逐像素操作大图可能会跑十几秒甚至更久。想提速的话可以用numpy把图片一次性读成数组批量操作也可以把循环改成每隔N个像素写入一位牺牲一点容量换速度。7. 常见问题与排查技巧实录到这儿五种方法都撸了一遍。说实话实际跑起来后遇到的问题往往比原理更磨人我把这些年踩过的坑集中整理成一份速查表方便你直接对照。7.1 问题速查表现象可能原因解决方案拼接后图片打不开copy /b或cat的文件顺序反了确保图片在前、文本在后JPEG文件头必须是FF D8图片能打开但提取不到代码图片被看图软件二次保存过尾部数据被清除保留原始拼接文件不要随意用其他工具重新保存提取出的代码开头有一堆乱码拼接位置没对齐提取时从图片二进制中间开始切了搜索BEGIN_CODE标记从标记之后开始提取Notepad打开图片特别卡图片文件太大编辑器加载困难换成HxD或010 Editor按二进制方式浏览PowerShell执行copy /b失败copy被解释成PowerShell的Copy-Item改用cmd /c copy /b强制走CMDsteghide嵌入时报格式不支持载体是PNG而steghide不支持PNG把PNG转成JPEG或BMP再执行embedsteghide能嵌入但提取时报could not extract any data密码记错或图片经过压缩/转码先确认密码再确认图片未被任何非原方案保存LSB提取结果全是乱码结束标记误判或图片从有损格式保存过改用长度头方案PNG图片不要用JPEG压缩格式保存代码文件本身太大拼接后文件体积异常没有压缩就拼进去了先用tar/zip压缩代码包再拼到图片里7.2 踩坑实录我在实操中遇到的3件事第一件事我曾经用copy /b批量处理了上百张图片结果发现有些图片在手机里能打开在电脑的某些看图软件里却提示文件已损坏。排查后发现原因是部分软件会严格按照JPEG末尾的FF D9标记检查文件长度尾部多出数据就认为文件不完整。解决方案倒简单拼接前先把图片统一转成JPEG格式并确认原图本身没有损坏如果必须要用PNG就改用LSB方案。第二件事用steghide嵌入代码后我习惯性把图片压缩了一下再发给同事结果对方提取时直接报错。这就是有损压缩的破坏力——JPEG压缩会重新编码像素把嵌入到最低位的比特全部打乱。后来我的原则是涉及隐写的图片绝不二次压缩传送过程也不允许任何转码操作。第三件事写Python LSB脚本时第一次用结束标记刚好被隐藏的文本里有一大段连续的二进制序列与结束标记撞车导致提取时数据被截断后面一大半内容丢失。修复方法就是前面说的不要把结束标记写死成固定8位串而是用一个4字节长度头先读长度再按长度取数据。虽然代码多了几行但稳定性提升非常明显。7.3 安全边界与合规使用最后必须说一句这类“隐藏代码到图片”的技巧本质上是一把工具刀。它可以用来做CTF练习、保护个人配置文件、方便代码分享也可以被滥用。我在这篇文章里演示的所有方法都只建议用于学习、开发和正当的数据管理场景不要用来藏恶意代码、绕过他人的系统保护机制更不要用于任何违法违规的事。技术本身是中性的怎么用取决于人这一点希望你心里有数。我个人在实际操作中的体会是日常工作里80%的需求用cat拼接或copy /b就能解决真正需要steghide的场景其实很少Python脚本更像是“给自己造工具”的乐趣。如果你第一次尝试建议从Notepad或copy /b开始跑通一次之后再玩steghide和LSB这样对整个体系的把握会扎实得多。