29 评论

用图片隐藏信息的技术实现

  上一个帖子,介绍了“用图片传播翻墙工具”。今天,来聊一下其中的技术原理。如果你对IT技术没啥兴趣,可以略过本文。


★图片隐藏信息的用途


  先来说说信息隐藏的目的。俺大致总结了一下,信息隐藏可以用于如下几种场合。

◇规避敏感词过滤


  所谓的“敏感词过滤”,常翻墙的同学,应该都很熟悉了。用图片来隐藏信息,可以规避 GFW 的敏感词过滤。

◇规避肉眼审查


  俺在上一个帖子介绍的方法(把翻墙工具嵌入图片中),主要是为了躲开网站管理人员的人工审查。国内的很多网站,对于上传的图片,都会进行人工审查。如果能通过技术手段把信息隐藏在图片中,而图片本身又看不出什么异样,人工审核就看不出来。

◇传递加密信息


  最后,图片还可以用来隐藏加密的信息。关于加密的用途及重要性,俺在《文件加密的扫盲介绍》中,已经强调过加密性。用图片来隐藏加密信息,除了具有加密的效果,还具有很大的欺骗性——因为外人难以知道一张图片是否包含有加密信息。


★准备工作——先压缩


  下面,俺会介绍几种不同的隐藏方式。在动手之前,先说一下准备工作——把要隐藏得文件先用压缩工具(比如 7zip 或 WinRAR)压缩一下。
  压缩有如下几个好处:

◇优点1


  如果你要隐藏的文件是文本格式或者 Office 格式,它内部的内容是明码的。如果里面包含敏感词,在通过网络传输时,会遭遇 GFW 的敏感词过滤(技术术语叫“深度包检测”)。而压缩后的文件,原有的内容已经变得面目全非,可以规律敏感词过滤。

◇优点2


  压缩之后,体积变小,有利于增加隐蔽性。因此,应尽量使用“最大压缩”的选项。

◇优点3


  对于后面介绍的2种方法(尾部追加法、内容覆盖法),如果你隐藏的文件是压缩格式的,到时候提取信息会很简便——直接用压缩工具来解压,即可。


★尾部追加法


  先介绍最简单的一种方法。

◇技术原理


  顾名思义,“尾部追加法”就是把要隐藏的文件追加到图片尾部。这种方法不会破坏图片原有的任何数据,因此,图片看起来和原来一模一样。

◇隐藏信息的步骤


  隐藏的过程很简单,用 Windows 内置的文件拷贝命令,即可完成。假设你的图片文件叫 A.jpg,需要隐藏的压缩文件叫 B.zip,那你只需要执行如下命令,就可以把两个文件合并成一个新文件。
copy /b A.jpg + B.zip C.jpg
  (注:上述命令中的 /b 选项表示:以【二进制】方式合并)
  该命令执行之后,即可得到一个新的图片文件 C.jpg。这个图片文件的大小是前两者的总和。你可以用各种看图工具来打开 C.jpg,【不会】发现啥异常。

◇提取信息的步骤


  由于你追加的是压缩文件,提取的时候就简单了——只要用压缩工具打开 C.jpg,就可以直接看到压缩包里面的内容了。

◇优点


  1. 制作简单,只用 copy 命令就搞定啦;如果隐藏的是压缩文件,提取的过程也很简单。
  2. 用看图工具看生成的新文件,还是跟原来一样。
  3. 隐藏的文件,大小不受限制。比如,你可以在一张 100KB 的图片尾部,追加 200KB 的隐藏数据。

◇缺点


  1. 由于隐藏的文件附加在尾部。当你把这个新的图片文件上传到某些贴图的网站,(假如这个网站对图片格式的校验比较严格)它有可能会发现图片尾部有多余的数据,并且会把这个多余的数据丢弃掉。
  2. 追加后,图片的文件尺寸变大了。如果你追加的文件太大,容易被发现破绽(比方说,一张 640*480 的 jpeg 图片,大小竟然有好几兆,对于有经验的 IT 技术人员,一下子就会觉得有猫腻)


★内容覆盖法


  说完尾部追加的办法,再来介绍内容覆盖的办法。

◇技术原理


  通常来说,图片文件都有包含2部分:文件头和数据区。而“内容覆盖法”,就是把要隐藏的文件,直接【覆盖】到图片文件的【数据区】的【尾部】。比方说,某图片有 100KB,其中文件头占 1KB,那么,数据区就是 99KB。也就是说,最多只能隐藏 99KB 的文件。
  切记:覆盖的时候,千万不可破坏【文件头】。文件头一旦破坏,这个图片文件就【不再是】一个合法的图片文件了。
  使用这种方法,对图片文件的格式,是有讲究的——最好用【24位色的 BMP 格式】。为啥捏?
一来,BMP 格式本身比较简单,数据区随便覆盖,问题不大;
二来,24位色的 BMP 相对其它的格式 BMP,文件尺寸更大,可以隐藏更多内容。

◇隐藏信息的步骤


  用这个招数来隐藏信息,稍微有点麻烦,需要借助一些小工具。对于这种简单的活计,俺通常用 Python 来搞定。以下是俺写的一个简单 Python 脚本。你的电脑中如果有 Python 环境,可以直接拿这个脚本去用。
  事先声明:如下代码没有严格计算 BMP 的文件头尺寸,俺只是大致预留了 1024 字节,感觉应该够了。
import sys

def embed(container_file, data_file, output_file) :
    container = open(container_file, "rb").read()
    data = open(data_file, "rb").read()

    if len(data)+1024 >= len(container) :
        print("Not enough space to save " + data_file)
    else :
        f = open(output_file, "wb")
        f.write(container[ : len(container)-len(data)])
        f.write(data)
        f.close()

if "__main__" == __name__ :
    try :
        if len(sys.argv) == 4 :
            embed(sys.argv[1], sys.argv[2], sys.argv[3])
        else :
            print("Usage:\n%s container data output" % sys.argv[0])
    except Exception as err :
        print(err)

  上述 Python 代码很好懂。略有编程基础的同学,10分钟之内就可以用自己熟悉的语言重写一个类似的。
  没学过 Python 的同学,如果有兴趣,可以看看俺之前写的《为什么俺推荐 Python?》系列博文。
  对于某些熟悉 Python 的同学,可能会质疑上述代码——对文件操作,为啥不用 with 语句块捏?
  俺来解释一下:
  上述代码这么写是为了让【不懂】Python 的程序员更容易理解——因为其它编程语言大都采用“打开/读写/关闭”三部曲来操作文件内容。

◇提取信息的步骤


  和前一种方法类似。如果你覆盖的是压缩文件,提取的时候,可以用压缩工具打开图片,就可以直接看到压缩包里面的内容了。

◇优点


  1. 图片的文件尺寸没变。
  2. 虽然隐藏文件覆盖到数据区,破环了原图像的内容。但是从格式上来讲,该图片文件的格式还是合法的(因此,你可以把这种图片上传到各种贴图的网站,技术上不会出问题)
  3. 如果隐藏的是压缩文件,提取的过程很简单。

◇缺点


  1. 由于隐藏的文件覆盖了数据区,因此,图片在显示的时候,会有一块区域变成灰蒙蒙的(如果遭遇“肉眼审查”,可能会引起怀疑)
  2. 隐藏文件的大小,有一定的限制——不能大于图片数据区的尺寸。
  3. 对图片格式有一定要求。此处再啰嗦一下,建议用 24位色的 BMP 格式。


★隐写法


  最后,来介绍一种最复杂,但是也最高级(最隐蔽)的方法——隐写术。

◇技术原理


  此方法会涉及较深奥的技术领域,俺也就知道个大概。通俗地说:如果把图片的某个像素的颜色,进行微小的调整,肉眼是看不出来的;因此,专门的软件,利用某些高深的算法,就可以在变化的像素中隐藏信息。
  有兴趣的同学,可以看“这里”的介绍;懂洋文的,还可以看更详细的介绍,在“这里”。

◇隐藏/提取信息的步骤


  使用这种方法,你需要用专门的工具来进行信息的隐藏和提取。在进行隐藏时,你除了指定图片文件和被隐藏的文件,还需要设置一个密码。隐写工具会把你的隐藏文件先加密,然后再进行隐写;提取的时候,需要用同一款隐写工具进行提取,并输入同样的密码,才能提取出来。
  假如图片文件落入攻击者手中,他必须【同时知道】这2个信息(你用哪款隐写工具,你隐写时设置的密码),才【有可能】破解出隐含的信息(仅仅是“有可能”)。因此,安全性相当高。

◇相关工具


  下面介绍几款工具,大伙儿可以根据自己喜好,挑选一个试试看。

名称官网界面类型
Silent Eye这里图形界面开源软件
Steg Hide这里命令行界面开源软件
Ultima Steganography这里图形界面商业软件


◇优点


  1. 隐蔽性非常好。图片用肉眼看上去几乎没变(其实是有极其轻微的变化,但是肉眼看不出)
  2. 图片文件的大小没变化。
  3. 即使是专业人士,也很难判断一张图片是否包含了隐写术的数据。

◇缺点


  1. 隐藏信息和提取信息比较麻烦,需要使用专门的工具。
  2. 只能隐藏较少的信息(此方法能隐藏的信息量,和图片面积有关,和图片格式无关。比如一张 1600*1200 尺寸的,无论哪种格式,大约只能隐藏【几 KB】的数据)


★结尾


  今天介绍的几个招数,除了可以用于图片文件,还可以用在其它的多媒体文件中(比如:音频文件、视频文件)。有兴趣的网友,可以自个儿研究一下。


俺博客上,和本文相关的帖子(需翻墙)
用图片传播翻墙软件
文件加密的扫盲介绍
如何用“磁盘加密”对抗警方的【取证软件】和【刑讯逼供】,兼谈数据删除技巧
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想和本文原始地址:
https://program-think.blogspot.com/2011/06/use-image-hide-information.html

29 条评论

  1. 我用copy 的方式追加文件 成 C.jpg后,用Winrar 不能打开,是怎么回事呢?

    回复删除
  2. 你瞧他们把我们逼得……

    回复删除
  3. to aker:
    用 copy 命令是否遗漏了 /b 选项?
    你追加的文件是哪种压缩格式?
    用的 WinRAR 是什么版本?
    如果你手头有 7zip,可以对比一下,看 7zip 是否可以。

    回复删除
  4. 请教楼主,我实验方法2,出现了错误提示:

    -------------------------------------------
    File "F:\Test\meth02\hidder.py", line 22
    except Exception,err :
    IndentationError: unindent does not match any outer indentation level
    -------------------------------------------

    (hidden是我给脚本起的名字)
    这个应该怎么解决呢?

    PS:使用方法1我也有和1楼Aker一样的问题
    追加zip的压缩包可以打开,但追加rar的就不行

    回复删除
  5. 楼上的同学:
    如果RAR不行,那有可能是RAR格式不适合这种方法。
    俺试验过zip格式和7z格式,RAR格式没试验过。

    另外,从你给出的Python出错信息来看,应该是保存的Python代码出现缩进不一致的问题。
    Python对缩进有严格要求的。

    回复删除
  6. 我对隐写术研究有一段时间了,但从来没想过有这样有功能。。

    回复删除
  7. Great! You're the MAN! I always read you.

    回复删除
  8. 翻墙来看 不容易啊 本来是要看看steganography 结果发现第一种方法就够用了

    回复删除
  9. 隐藏的过程很简单,用 Windows 内置的文件拷贝命令,即可完成。假设你的图片文件叫 A.JPG,需要隐藏的压缩文件叫 B.ZIP,那你只需要执行如下命令,就可以把两个文件合并成一个新文件。
    copy /b A.JPG + B.zip C.JPG


    还是不怎么会呢

    这个命令在哪里用,运行 cmd ?

    回复删除
    回复
    1. 需要先运行 cmd 命令,出来一个黑色的命令行窗口。
      然后再执行文中提到的 copy 命令

      删除
    2. 内容覆盖法顺便附上一个我弄的C++代码吧(写得比较随便,没有考虑太多情况),有兴趣的可以拿去编译一下,免费的集成开发工具有Code Block,最新版12.11,官网:http://www.codeblocks.org/

      #include
      #include
      #include
      #include

      using namespace std;

      void filename_check(char filename[]);

      int main()
      {
      fstream fp1;
      ifstream fp2;
      char bmp[200];
      char hide[200];
      unsigned char block;

      cout<<"打开24位bmp格式图片(把文件拉进来按回车即可):"<<endl;
      cin.getline(bmp,200,'\n');
      filename_check(bmp);
      fp1.open(bmp,ios::binary|ios::in|ios::out);
      if(fp1.fail())
      {
      cout<<"What The Fuck ?? 不能打开文件 :( "<<endl;
      system("pause");
      exit(0);
      }

      cout<<"打开要隐藏的文件(压缩文件格式~~)(把文件拉进来按回车即可):"<<endl;
      cout<<"注意,要隐藏的文件大小必须小于bmp文件的大小 :)"<<endl;
      cin.getline(hide,200,'\n');
      filename_check(hide);
      fp2.open(hide,ios::binary|ios::in);
      if(fp2.fail())
      {
      cout<<"What The Fuck ?? 不能打开文件 :( "<<endl;
      system("pause");
      exit(0);
      }

      fp1.seekp(1078,ios::beg);

      fp2.read((char*)&block,1);
      while(!fp2.eof())
      {
      fp1.write((char*)&block,1);
      fp2.read((char*)&block,1);
      }

      cout<<"成功隐藏文件 :)"<<endl;
      cout<<"下次要使用文件,将bmp图的格式改成压缩文件的格式,就可以正常解压了。。"<<endl;

      fp1.close();
      fp2.close();

      system("pause");
      return 0;
      }


      void filename_check(char filename[])
      {
      int i;
      int length;

      length=strlen(filename);

      if(filename[0]=='"' && filename[length-1]=='"')
      {
      for(i=0; i<length-2; i++)
      {
      filename[i]=filename[i+1];
      }
      filename[i]='\0';
      }
      }

      删除
    3. 貌似头文件那段代码没有显示出来,补上。
      "#include "
      "#include "
      "#include "
      "#include "
      (引号不用复制)

      删除
    4. 还是不能显示,坑爹货 =_=||
      那几个头文件分别是:
      iostream
      fstream
      string.h
      stdlib.h

      删除
    5. TO Curtis Wilbur
      多谢分享 C++ 代码。

      因为 #include 语句后面的尖括号被理解为 HTML 的 tag,所以没显示出来。
      尖括号得用转义方式,才能够显示。

      删除
  10. 用命令行来藏东西的话:
    用jpg图片作为容器的话,貌似添加了7z,rar,zip压缩文件后,得到的东西可以打开。
    但貌似用bmp作为容器的话,好像无法成功的样子。

    不知是否操作失误,还是确实是bmp图片不适合作为隐藏容器。

    回复删除
    回复
    1. 请问,你用的是“尾部追加”还是“内容覆盖”?
      BMP 适合用来搞“内容覆盖”,JPG 和 PNG 适合用来搞“尾部追加”

      删除
  11. 没学过编程,表示第二种方法不会用

    回复删除
    回复
    1. TO 11楼网友
      用“内容覆盖法”确实要稍微懂点技术。
      不过你不需要懂编程,只要把俺提供的 Python 脚本直接拿去用,就可以啦

      删除
    2. TO 2单元的网友
      关于“内容覆盖法”,本文已经有原理说明。
      至于实现的手段,请直接参考本文给出的 Python 脚本

      删除
  12. 沒有時間去隱藏文件

    回复删除
  13. 第二个用python脚本的不太懂。我复制保存成.py后缀的文件了,也安装了python,可是运行脚本的时候只见窗口打开一下就不见了···怎么搞?

    回复删除
  14. 用第二种方法一直都是xxx.py container data output
    怎么回事···我在 print("Usage:\n%s container data output" % sys.argv[0])后面加了一行print (len(sys.argv)),输出结果是1而不是4,是哪里搞错了?

    回复删除
  15. YouTube上有一个诡异的频道,叫不详半圆。
    上传了超过几万部全是电视迅号的视频。
    据说使用了隐写术。
    据说将视频中的每一帧拼接起来会得到一个图片。
    介绍。
    https://www.unfavorablesemicircle.com/wiki/LOCK

    回复删除
  16. 想问一下 Linux 也有类似 copy 的命令吗?

    回复删除
    回复
    1. 有的,比如用cat命令将file1和file2合并到新文件file3:
      cat file1 file2 > file3

      删除
  17. 若翻墙软件可以用图片传播,岂非所有反共信息都可以这么干?

    回复删除
    回复
    1. 前篇博文搬运工2021年3月1日 06:18:00

      当然可以啊,微博上这类“隐写术”的图片不要太多哦!只是你不知道而已。

      删除