隱寫術-LSB算法實現


隱寫術


隱寫術是一門關於信息隱藏的技巧與科學,所謂信息隱藏指的是不讓除預期的接收者之外的任何人知曉信息的傳遞事件或者信息的內容。隱寫術的英文叫做Steganography,來源於特里特米烏斯的一本講述密碼學與隱寫術的著作Steganographia,該書書名源於希臘語,意為“隱秘書寫”。比如電視中出現頻率較高的天書,經過水泡、火烤才能看見其中內容,這就是一種隱寫術。


RGB色彩模式


RGB色彩就是常說的光學三原色,R代表Red(紅色),G代表Green(綠色),B代表Blue(藍色)。自然界中肉眼所能看到的任何色彩都可以由這三種色彩混合疊加而成,因此也稱為加色模式。

RGB模式又稱RGB色空間。它是一種色光表色模式,它廣泛用於我們的生活中,如電視機、計算機顯示屏、幻燈片等都是利用光來呈色。印刷出版中常需掃描圖像,掃描儀在掃描時首先提取的就是原稿圖像上的RGB色光信息。RGB模式是一種加色法模式,通過R、G、B的輻射量,可描述出任一顏色。計算機定義顏色時R、G、
B三種成分的取值范圍是0-255,0表示沒有刺激量,255表示刺激量達最大值。R、G、B均為255時就合成了白光,R、G、B均為0時就形成了黑色。在顯示屏上顯示顏色定義時,往往采用這種模式。圖像如用於電視、幻燈片、網絡、多媒體,一般使用RGB模式。


LSB算法


全稱為Least Significant Bit,在二進制數中意為最低有效位,一般來說,MSB(最高有效位)位於二進制數的最左側,LSB位於二進制數的最右側。比如一種顏色,用8位的二進制表示為:00100011,那么最左側的0所在的位置,就是最高有效位MSB,最右側的1所在的位置,就是最低有效位LSB。

由於圖像的每一個像素點都是由RGB(紅、綠、藍)三原色組成,可以暫時稱其為顏色通道,如果每個顏色通道的值占8位,那么RGB色彩模式的圖片中,每個像素點的顏色就可以用6位長度的十六進制數來(如#FFFFFF),LSB隱寫即是修改每個顏色通道的最低一位,將其替換為我們想要嵌入的信息中的內容,以此來實現數據隱藏。因為是最低有效位,所以對實際的顏色影響不大,肉眼幾乎分辨不出區別。
一個像素點包含三種顏色,每個顏色修改最后1位,這樣一個像素點就可以攜帶3位信息
應用LSB算法的圖像格式需為位圖形式,即圖像不能經過壓縮,如LSB算法多應用於png、bmp等格式,而jpg格式較少。

詳細參考:https://wenku.baidu.com/view/ff590e9d5f0e7cd1842536d7.html


Python Imaging Library


Python Imaging Library(簡稱PIL)為Python解釋器提供了圖像處理的功能,PIL提供了廣泛的文件格式支持、高效的內部表示以及相當強大的圖像處理功能。PIL圖像處理庫的核心被設計成為能夠快速訪問以幾種基本像素類型表示的圖像數據,它為通用圖像處理工具提供了一個堅實基礎。結合PIL可以方便的編寫Python腳本處理圖片隱寫問題。


StegSolve


StegSolve是一款基於Java開發的流行圖片隱寫分析軟件,其支持常見的圖片文件格式,可以對不同的文件進行結合(包括XOR、ADD、SUB等操作),可以對圖片文件格式進行分析,可以提取GIF文件中的幀等,覆蓋了基本的圖片隱寫分析需求。


LSB隱寫題目


以前的時候,CTF中遇到類似的題目,還會有“最低”,“最下面”等程度不一的提示,但隨着CTF的難度越來越大,像最低位隱寫這樣的隱寫術,也幾乎沒有什么難度了。所以對應的題目幾乎也沒有什么提示,解題者能做的就是嘗試了。

假設我們現在得到了一張圖片,我們先使用Stegsolve來對他進行分析。先打開需要分析的圖片:
image
image

勾選如下的選項。
image

然后勾選RGB三個顏色通道的最低位(我們所說的RGB即是Red,Green,Blue三個顏色,而下圖中的7,6,…1,0即是顏色用二進制表示時的高位到低位,我們是LSB最低有效位,所以自然選擇0),並且旁邊的“Bit Order”選擇“LSB First”(其實這個選不選,影響不大)。然后點擊“Preview”就可以看到最低位的隱藏信息了。
image


python實現LSB隱寫


python安裝PIL、Pillow模塊的教程:https://www.cnblogs.com/pcat/p/6790058.html

我們可以整理一下進行最低位隱寫的邏輯,然后用python寫出一個腳本,來實現將指定內容寫入指定圖片的最低位。要想把數據寫入圖片的最低位,我們就需要考慮整體的流程:

1. 獲取要寫入的內容,並將其轉換為二進制字符串
2. 依次讀取轉換后的二進制字符串,並按照順序替換掉圖片某個像素的三個顏色通道的最低位數值
3. 保存替換后的圖片

我們跟着這個順序來,首先,獲取要寫入的內容,並將其轉換為二進制字符串:

def getHideString(hide_string):
  #獲取要隱藏的文件內容
  tmp = hide_string
  f = file(tmp,"rb")
  str_bin = ""
  s = f.read()
  for i in range(len(s)):
  	#逐個字節將要隱藏的文件內容轉換為二進制,並拼接起來
  	#1.先用ord()函數將s的內容逐個轉換為ascii碼
  	#2.使用bin()函數將十進制的ascii碼轉換為二進制
  	#3.由於bin()函數轉換二進制后,二進制字符串的前面會有"0b"來表示這個字符串是二進制形式,所以用replace()替換為空
  	#4.又由於ascii碼轉換二進制后是七位,而正常情況下每個字符由8位二進制組成,所以使用zfill() 方法返回指定長度的字符串,原字符串右對齊,前面填充0
    str_bin = str_bin + str(bin(ord(s[i])).replace('b','')).zfill(8)
    #print str
  f.closed
  return str_bin

然后是將內容寫入到圖片中,先獲取圖片的寬高,初始化計數器count,

#original_file為載體圖片路徑,hide_string為隱寫文件,new_file為加密圖片保存的路徑
def encode(original_file,hide_string,new_file):  
  im = Image.open(original_file)
  #獲取圖片的寬和高
  width = im.size[0]
  print "width:"+str(width)+"\n"
  height = im.size[1]
  print "height:"+str(height)+"\n"
  count = 0

調用前面的getHiderString函數來獲取需要隱藏的信息,並計算信息的長度

 #獲取需要隱藏的信息
  key = getHideString(hide_string)
  keylen = len(key)

進行循環,橫向挨個讀取像素點的三個顏色通道的值,

   for h in range(0,height):
       for w in range(0,width):
           pixel = im.getpixel((w,h))
           R = pixel[0]
           G = pixel[1]
           B = pixel[2]

然后進行數據的寫入,每做一步都需要判斷一下信息是否寫完:

      if count == keylen:
        break
      #分別將每個像素點的RGB值余2,這樣可以獲得最低位的值,然后用原來的值減去最低位的值
      #再從需要隱藏的信息中取出一位,轉換為整型
      #兩值相加,需要隱藏的信息就將原來的最低位信息替換掉了
      R= R-mod(R,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break
      G =G-mod(G,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break
      B= B-mod(B,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break

循環完一次(一個像素點)后,還需要判斷一個像素點的是否都替換了,如果替換了,就真正的寫進圖片中

      if count % 3 == 0:
        im.putpixel((w,h),(R,G,B))

最后保存圖片

  im.save(new_file)

完整代碼如下:

# -*- coding: UTF-8 -*-
from PIL import Image

def getHideString(hide_string):
  #獲取要隱藏的文件內容
  tmp = hide_string
  f = file(tmp,"rb")
  str_bin = ""
  s = f.read()
  for i in range(len(s)):
  	#逐個字節將要隱藏的文件內容轉換為二進制,並拼接起來
  	#1.先用ord()函數將s的內容逐個轉換為ascii碼
  	#2.使用bin()函數將十進制的ascii碼轉換為二進制
  	#3.由於bin()函數轉換二進制后,二進制字符串的前面會有"0b"來表示這個字符串是二進制形式,所以用replace()替換為空
  	#4.又由於ascii碼轉換二進制后是七位,而正常情況下每個字符由8位二進制組成,所以使用zfill() 方法返回指定長度的字符串,原字符串右對齊,前面填充0
    str_bin = str_bin + str(bin(ord(s[i])).replace('b','')).zfill(8)
    #print str
  f.closed
  return str_bin

def mod(x,y):
  return x%y;
#original_file為載體圖片路徑,hide_string為隱寫文件,new_file為加密圖片保存的路徑
def encode(original_file,hide_string,new_file):  
  im = Image.open(original_file)
  #獲取圖片的寬和高
  width = im.size[0]
  print "width:"+str(width)+"\n"
  height = im.size[1]
  print "height:"+str(height)+"\n"
  count = 0
  #獲取需要隱藏的信息
  key = getHideString(hide_string)
  keylen = len(key)
  for h in range(0,height):
    for w in range(0,width):
      pixel = im.getpixel((w,h))
      R = pixel[0]
      G = pixel[1]
      B = pixel[2]
      if count == keylen:
        break
      #分別將每個像素點的RGB值余2,這樣可以獲得最低位的值,然后用原來的值減去最低位的值
      #再從需要隱藏的信息中取出一位,轉換為整型
      #兩值相加,需要隱藏的信息就將原來的最低位信息替換掉了
      R= R-mod(R,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break
      G =G-mod(G,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break
      B= B-mod(B,2)+int(key[count])
      count+=1
      if count == keylen:
        im.putpixel((w,h),(R,G,B))
        break
      if count % 3 == 0:
        im.putpixel((w,h),(R,G,B))
  im.save(new_file)

#原圖
original_file = "flag.png"
#處理后輸出的圖片路徑
new_file = "out.png"
#需要隱藏的信息
hide_string = "flag.txt"
encode(original_file,hide_string,new_file)

接下來測試腳本是否真的可以隱藏信息,先創建文件flag.txt,並將字符串寫入其中。
image

創建好后運行腳本:
image

查看文件夾下,確實有新的圖片生成。
image

用Stegsolve對生成的圖片進行驗證,確認字符串是否寫入成功:
image


Python提取LSB隱寫信息


實現腳本代碼如下:

# -*- coding:UTF-8 -*-
from PIL import Image
 
def mod(x,y):
    return x%y;
 
def toasc(strr):
    return int(strr, 2)
#le為所要提取的信息的長度,str1為加密載體圖片的路徑,str2為提取文件的保存路徑
def func(le,str1,str2):
    a=""
    b=""
    im = Image.open(str1)
    lenth = le*8
    width = im.size[0]
    height = im.size[1]
    count = 0
    for h in range(0, height):
        for w in range(0, width):
        #獲得(w,h)點像素的值
            pixel = im.getpixel((w, h))
            #此處余3,依次從R、G、B三個顏色通道獲得最低位的隱藏信息
            if count%3==0:
                count+=1
                b=b+str((mod(int(pixel[0]),2)))
                if count ==lenth:
                    break
            if count%3==1:
                count+=1
                b=b+str((mod(int(pixel[1]),2)))
                if count ==lenth:
                    break
            if count%3==2:
                count+=1
                b=b+str((mod(int(pixel[2]),2)))
                if count ==lenth:
                    break
        if count == lenth:
            break
    with open(str2,"wb") as f:
        for i in range(0,len(b),8):
        #以每8位為一組二進制,轉換為十進制
            stra = toasc(b[i:i+8])
            #將轉換后的十進制數視為ascii碼,再轉換為字符串寫入到文件中
            f.write(chr(stra))
            stra =""
    f.closed
#文件長度
le = 30
#含有隱藏信息的圖片
new = "step2_out.png"
#信息提取出后所存放的文件
tiqu = "get_flag.txt"
func(le,new,tiqu)

運行腳本,提取到指定長度的最低位信息,保存在get_flag.txt文件夾內。
image


有關LSB隱寫的思考


  1. 其實LSB現在很少以單獨的考點出現在CTF比賽中了,一般都是很與其他的信息隱藏技術一起出現的。
  2. 文中只是介紹了文本的寫入方式,如何將一個文件寫入到其中?其實也很簡單,直接將文件的內容當作文本來處理,寫入到目標圖片的最低位。但是如何將寫入的文件提取出來,這就需要平時的積累,能夠在看到文件的十六進制的頭部時快速分辨其文件類型。當然,有這種功能的工具也早就有了,比如binwalk。
  3. 了解了最低位隱寫的原理之后,我們就很容易知道,他還可以引申出其他的一些利用方式,比如在更高的位數中隱藏數據、同時用兩個位數來隱藏數據等(當然這樣的隱藏數據,可能會對原圖片的影響比較大,甚至用肉眼即可分辨出與原圖片的差別);又或者不使用RGB的順序來隱藏數據,而是使用GBR、BRG等順序來隱藏數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM