Unicode and LSB Steganography program examples
I wrote these Autoit3 code examples to illustrate some of the ways that steganography (hiding data in other data, or as I like to call it "hiding your stuff in other stuff so people can't find your stuff") can be done. These sorts of techniques can be of great use in passing messages without others knowing, in anti-forensics activities, or as covert command and control channels for botnets (as I plan to study for my final project in the malware class I'm enrolled in). There are probably better stego tools out there for these tasks, I know steghide is a better option if you want to encode hidden data into images. Still, these examples are fairly simple and should help students begin to learn the concepts. The download contains two programs and their source code:
unisteg.exe: This tool lets you encode data using different representations Unicode of characters. The hidden message can then be pasted into Twitter or Facebook. On Twitter, I can only get 17 hidden characters encoded into a 140 byte message, but for a command and control channel I'm sure I can split it up across multiple posts. I'm hoping to get Robin Wood to implement an encoding scheme similar to this one into his KreiosC2 project.
lsbsteg.exe: This tool is a simple image editor that takes a message and hides it as bits in a lossless image format (PNGs for example).
I plan to use some of these stego techniques in future projects. Looks at the comments at the top of the source code for more details on the specifics of how each algorithm works. Most of the real technical information is in those comments.
Download Tools and Source Code
Code printouts:
Unicode Steganography
#Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_icon=steg.ico #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #cs Steganography using Unicode homoglyphs example ver 1.0 From: Adrian Crenshaw http://irongeek.com Written in Autoit3 In Unicode there are multiple ways to encode characters that look more or less the same to the human eye, but have different numerical values (homoglyphs). I thought this would be a great way to try to hide data in user generated content. I did some searching around, but did not find any code. Antonio Alcorn had a demo page (link below), which he may put up code for later. I looked at what he did, but went a slightly different direction in how I do my encoding (his results looks better, mine hides more data). It seems the characters: !"$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ are represented in both the 0021-007E (Basic Latin) and the FF01-FF5E (Fullwidth Latin) ranges. This means I can use the lower range to represent 0s and the higher range to represent 1s. I have to do something special with spaces and encode them as Unicode number 8287 decimal. This means changing from one encoding to another is as easy as adding the decimal values 65248 to the lower range versions. With some effort, I should be able to encode more data or make the output look better by using other homoglyphs, but that will make the algorithm more complicated. Also, some sort of XOR based encryption to the payload would be an easy option to add. Since this first release is for learning, I’ve opted for simplicity of concept. Great thing about this code, you can hide your messages in Twitter or Facebook posts. :) Useful Links: http://en.wikipedia.org/wiki/Steganography http://homoglyphs.net/ http://www.cs.trincoll.edu/~aalcorn/steganography/ http://en.wikipedia.org/wiki/IDN_homograph_attack #ce #include <ButtonConstants.au3> #include <EditConstants.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #include <WindowsConstants.au3> #include <String.au3> #include <Math.au3> #Region ### START Koda GUI section ### Form=c:\users\adrian\desktop\stego\unisteg\unisteg.kxf $UniStegFrm = GUICreate("UniSteg - Crappy code from Irongeek", 529, 577, 192, 114, BitOR($WS_MAXIMIZEBOX, $WS_MINIMIZEBOX, $WS_SIZEBOX, $WS_THICKFRAME, $WS_SYSMENU, $WS_CAPTION, $WS_OVERLAPPEDWINDOW, $WS_TILEDWINDOW, $WS_POPUP, $WS_POPUPWINDOW, $WS_GROUP, $WS_TABSTOP, $WS_BORDER, $WS_CLIPSIBLINGS)) $InputMessageEdit = GUICtrlCreateEdit("", 8, 32, 513, 105, BitOR($ES_AUTOVSCROLL, $ES_WANTRETURN, $WS_VSCROLL)) $Label1 = GUICtrlCreateLabel("Input to encode/decode:", 8, 8, 151, 17) $CoverTextEdit = GUICtrlCreateEdit("", 8, 168, 513, 105, BitOR($ES_AUTOVSCROLL, $ES_WANTRETURN, $WS_VSCROLL)) $Label2 = GUICtrlCreateLabel("Cover Text:", 8, 144, 75, 17) $OutputEdit = GUICtrlCreateEdit("", 8, 304, 513, 105, BitOR($ES_AUTOVSCROLL, $ES_WANTRETURN, $WS_VSCROLL)) GUICtrlSetData(-1, "") $Label3 = GUICtrlCreateLabel("Output/Errors:", 8, 280, 85, 17) $EncodeBut = GUICtrlCreateButton("Encode", 64, 432, 113, 25, $WS_GROUP) $DecodeBut = GUICtrlCreateButton("Decode", 192, 432, 113, 25, $WS_GROUP) $Info = GUICtrlCreateButton("Info", 320, 432, 113, 25, $WS_GROUP) $InfoReadoutLbl = GUICtrlCreateLabel("", 8, 504, 510, 50) ;Main code loop below GUISetState(@SW_SHOW) #EndRegion ### END Koda GUI section ### $DetectChangesInInput = "" While 1 $CoverStr = GUICtrlRead($CoverTextEdit) $InputMessageStr = GUICtrlRead($InputMessageEdit) If $CoverStr & $InputMessageStr <> $DetectChangesInInput Then $DetectChangesInInput = $CoverStr & $InputMessageStr GUICtrlSetData($InfoReadoutLbl, ShowInfo($CoverStr, $InputMessageStr)) EndIf $nMsg = GUIGetMsg() Switch $nMsg Case $CoverTextEdit ShowInfo($CoverStr, $InputMessageStr) Case $EncodeBut If CanEncode($CoverStr, $InputMessageStr) Then GUICtrlSetData($OutputEdit, UniEncodeString($CoverStr, $InputMessageStr)) ShowInfo($CoverStr, $InputMessageStr) Else MsgBox(0, "", ShowInfo($CoverStr, $InputMessageStr)) EndIf Case $DecodeBut GUICtrlSetData($OutputEdit, UniDecodeString($InputMessageStr)) Case $Info ;GUICtrlSetData($OutputEdit, "a " & "b" & ChrW(8287) & "c" & chrW(32+65248) & "d") ; Code I use for testing MsgBox(0, "Unicode Values", PrintDecodeString(GUICtrlRead($OutputEdit))) Case $GUI_EVENT_CLOSE Exit EndSwitch WEnd ;Function checks to see if a valid encode is possible Func CanEncode($CoverStr, $InputMessageStr) $TotalCoverChrs = StringLen($CoverStr) ;$CoverStrNoSpaces = StringReplace($CoverStr, " ", "") ;$EncodablePlaces = StringLen($CoverStrNoSpaces) / 8 $EncodablePlaces = StringLen($CoverStr) / 8 $ChrToEncode = StringLen($InputMessageStr) If $EncodablePlaces >= $ChrToEncode Then Return True Else Return False EndIf EndFunc ;==>CanEncode ;This function just lets the use know what they can encode Func ShowInfo($CoverStr, $InputMessageStr) $TotalCoverChrs = StringLen($CoverStr) ;$CoverStrNoSpaces = StringReplace($CoverStr, " ", "") ;$EncodablePlaces = StringLen($CoverStrNoSpaces) / 8 $EncodablePlaces = StringLen($CoverStr) / 8 $ChrToEncode = StringLen($InputMessageStr) $InfoMessage = "You have " & $ChrToEncode & " characters to encode" & @CRLF $InfoMessage = $InfoMessage & "With the current cover text, you can only encode " & $EncodablePlaces & " characters" & @CRLF $InfoMessage = $InfoMessage & "You have " & $TotalCoverChrs & " total cover characters (140 is all Twitter will allow)" Return $InfoMessage EndFunc ;==>ShowInfo ;Encodes the input string into the cover text Func UniEncodeString($CoverStr, $InputMessageStr) $HideMeBin = BinEncodeString($InputMessageStr) $results = "" $BinIndex = 0 For $i = 1 To StringLen($CoverStr) $TheChr = StringMid($CoverStr, $i, 1) If AscW($TheChr) > 31 Then ;do nothing if it's a lower ASCII character $BinIndex = $BinIndex + 1 $TheBit = StringMid($HideMeBin, $BinIndex, 1) Select Case $TheBit = "1" $TheChr = UnicodeReplace($TheChr) Case Else ;Just leave it latin EndSelect EndIf $results = $results & $TheChr Next Return $results EndFunc ;==>UniEncodeString ;Decodes the input string into the hidden text Func UniDecodeString($InputStr) $BinTemp = "" For $i = 1 To StringLen($InputStr) $TheChr = StringMid($InputStr, $i, 1) Select Case AscW($TheChr) < 32 ;do nothing, it's a lower ASCII character, so we skip it Case AscW($TheChr) > 126 $BinTemp = $BinTemp & "1" Case Else $BinTemp = $BinTemp & "0" EndSelect Next Return BinDecodeString($BinTemp) EndFunc ;==>UniDecodeString ;Somethin I put here to help me debug Func PrintDecodeString($arg) $results = "" For $i = 1 To StringLen($arg) $TheChr = StringMid($arg, $i, 1) $results = $results & $TheChr & " " & AscW($TheChr) & @CRLF Next Return $results EndFunc ;==>PrintDecodeString ;Just a simple function to make the code easier to read Func UnicodeReplace($arg) If AscW($arg) = 32 Then Return ChrW(8287) ;encode a space this way, since 32+65248 does not work Else Return ChrW(AscW($arg) + 65248) EndIf EndFunc ;==>UnicodeReplace ;Loops though the sting, turing the Binary to ASCII Func BinDecodeString($arg) $results = "" For $i = 1 To StringLen($arg) Step 8 $results = $results & Byte2Asc(StringMid($arg, $i, 8)) Next Return $results EndFunc ;==>BinDecodeString Func Byte2Asc($arg) $results = 0 If StringMid($arg, 1, 1) = 1 Then $results = $results + 128 If StringMid($arg, 2, 1) = 1 Then $results = $results + 64 If StringMid($arg, 3, 1) = 1 Then $results = $results + 32 If StringMid($arg, 4, 1) = 1 Then $results = $results + 16 If StringMid($arg, 5, 1) = 1 Then $results = $results + 8 If StringMid($arg, 6, 1) = 1 Then $results = $results + 4 If StringMid($arg, 7, 1) = 1 Then $results = $results + 2 If StringMid($arg, 8, 1) = 1 Then $results = $results + 1 Return Chr($results) EndFunc ;==>Byte2Asc ;Loops though the sting, turing the ASCII to a series of 1s and 0s Func BinEncodeString($arg) $results = "" For $i = 1 To StringLen($arg) $results = $results & Asc2Byte(StringMid($arg, $i, 1)) Next Return $results EndFunc ;==>BinEncodeString Func Asc2Byte($arg) $tempasc = Asc($arg) Local $result = "" While ($tempasc >= 1) $tempasc /= 2 If StringIsDigit($tempasc) Then $result &= 0 Else $split = StringSplit($tempasc, ".") $tempasc = $split[1] $result &= 1 EndIf WEnd $result = _StringReverse($result) While StringLen($result) < 8 $result = "0" & $result WEnd Return $result EndFunc ;==>Asc2Byte ;based on a post from Mpro |
Least Signifigant Bit in a PNG Steganography
#Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_Icon=steg.ico #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #cs Steganography using LSB (least significant bit) example ver 1.1 From: Adrian Crenshaw http://irongeek.com Written in Autoit3 This simple sample code shows using “Least Significant Bit” encoding on a lossless image file format. We can modify the LSB to hide data if we wish. The human eye would have a hard time telling the difference between the RGB color value FF9999 (light red) and FF9899 (which encodes the binary value 101 on the least significant bit of Red Green and Blue), but a computer has no problem. Using a lossy algorithm would make this tougher so this educational code only used PNGs(For JPGs, look up “StegHide”). Also, some sort of XOR based encryption to the payload would be an easy option to add. Since this first release is for learning, I’ve opted for simplicity of concept. Useful Links: http://en.wikipedia.org/wiki/Steganography http://en.wikipedia.org/wiki/Least_significant_bit http://steghide.sourceforge.net/ Changes: 02/24/2010 - Made a change so the Encode/Decode functions use null termination so they don't have to iterate through the whole image. 02/24/2010 - First released. #ce #include <GDIPlus.au3> #include <String.au3> #include <Color.au3> #include <Math.au3> ;Main code begins $choice = MsgBox(3, "Irongeek's LSB Stego 1.1: Decode or Encode?", "Yes=Encode" & @CRLF & "No=Decode" & @CRLF & "Cancel=Exit") Select Case $choice = 6 $TextToEncode = FileOpenDialog("What text should I encode?", @ScriptDir, "Text (*.txt)") $InputImage = FileOpenDialog("What image should I use as cover?", @ScriptDir, "PNGs (*.png)") $OutputImage = FileSaveDialog("Where should I save the output image?", @ScriptDir, "PNGs (*.png)") $DataToEncode = FileRead($TextToEncode) ;msgbox(0,"",$DataToEncode) EncodeImage($DataToEncode, $InputImage, $OutputImage) Case $choice = 7 $ImageToDecode = FileOpenDialog("What should I decode?", @ScriptDir, "PNGs (*.png)") $DecodedImageData = BinDecodeString(DecodeImage($ImageToDecode)) $TextFileToSaveTo = FileSaveDialog("Where should I save the output?", @ScriptDir, "Text (*.txt)") FileWrite($TextFileToSaveTo, $DecodedImageData) Case $choice = 2 Exit EndSelect ;Main code ends ;Extracts our bits Func DecodeImage($InputImage) $iPosX = 0 $iPosY = 0 $results = "" _GDIPlus_Startup() $hImage = _GDIPlus_ImageLoadFromFile($InputImage) $iXmax = _GDIPlus_ImageGetWidth($hImage) $iYmax = _GDIPlus_ImageGetHeight($hImage) For $iPosY = 0 To $iYmax - 1 For $iPosX = 0 To $iXmax - 1 $colval = _GDIPlus_BitmapGetPixel($hImage, $iPosX, $iPosY) $aColor = _ColorGetRGB(Dec($colval)) $results = $results & GetLSB($aColor[0]) $results = $results & GetLSB($aColor[1]) $results = $results & GetLSB($aColor[2]) _GDIPlus_BitmapSetPixel($hImage, $iPosX, $iPosY, RGBIt(_ColorSetRGB($aColor))) TrayTip("LSBSteg", "On Pixle " & $iPosX & "x" & $iPosY & " max " & $iXmax & "x" & $iYmax, 0) If StringRight($results, 16) = "0000000000000000" Then ExitLoop 2 ;exit both for loops Next Next Return $results _GDIPlus_ImageDispose($hImage) _GDIPlus_Shutdown() EndFunc ;==>DecodeImage ;Changes the image to encode our bits Func EncodeImage($StringToHide, $InputImage, $OutputImage) $iPosX = 0 $iPosY = 0 $BinStringToHide = BinEncodeString($StringToHide) & "0000000000000000" ;double null terminate _GDIPlus_Startup() $hImage = _GDIPlus_ImageLoadFromFile($InputImage) $iXmax = _GDIPlus_ImageGetWidth($hImage) $iYmax = _GDIPlus_ImageGetHeight($hImage) For $iPosY = 0 To $iYmax - 1 For $iPosX = 0 To $iXmax - 1 $colval = _GDIPlus_BitmapGetPixel($hImage, $iPosX, $iPosY) $aColor = _ColorGetRGB(Dec($colval)) $r = PopBinValue($BinStringToHide) $b = PopBinValue($BinStringToHide) $g = PopBinValue($BinStringToHide) $aColor[0] = SetLSB($aColor[0], $r) $aColor[1] = SetLSB($aColor[1], $b) $aColor[2] = SetLSB($aColor[2], $g) _GDIPlus_BitmapSetPixel($hImage, $iPosX, $iPosY, RGBIt(_ColorSetRGB($aColor))) TrayTip("LSBSteg", "On Pixle " & $iPosX & "x" & $iPosY & " BinLen " & StringLen($BinStringToHide), 0) If StringLen($BinStringToHide) < 1 Then ExitLoop 2 ;exit both for loops Next Next _GDIPlus_ImageSaveToFile($hImage, $OutputImage) _GDIPlus_ImageDispose($hImage) _GDIPlus_Shutdown() EndFunc ;==>EncodeImage ;Setsu the valus of the least significant bit Func SetLSB($arg, $bit) If $bit = "1" Then $results = BitOR($arg, 1) ; 1 as LSB Else $results = BitAND($arg, 254) ; 0 as LSB EndIf Return $results EndFunc ;==>SetLSB ;Gives you the value of the least significant bit Func GetLSB($arg) ;msgbox(0,"",$arg) $results = _MathCheckDiv($arg, 2) If $results = 2 Or $results = -1 Then $results = 0 ;msgbox(0,"",$results) Return $results EndFunc ;==>GetLSB ;Had to use this to implement a stack, it made the code logic easier Func PopBinValue(ByRef $arg) $results = StringLeft($arg, 1) $arg = StringRight($arg, StringLen($arg) - 1) Return $results EndFunc ;==>PopBinValue ; Turns decimal color code into a hex encoded string Func RGBIt($deccolor) Return Hex($deccolor, 6) EndFunc ;==>RGBIt ;Just get the color of a pixel Func _GDIPlus_BitmapGetPixel($hBitmap, $iX, $iY) Local $tArgb, $pArgb, $aRet $tArgb = DllStructCreate("dword Argb") $pArgb = DllStructGetPtr($tArgb) $aRet = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, "ptr", $pArgb) Return Hex(DllStructGetData($tArgb, "Argb"), 6) EndFunc ;==>_GDIPlus_BitmapGetPixel ; Vossen ;Just set the color of a pixel Func _GDIPlus_BitmapSetPixel($hBitmap, $iX, $iY, $iArgb) Local $aRet $iArgb = "0xFF" & $iArgb $aRet = DllCall($ghGDIPDll, "int", "GdipBitmapSetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, "dword", $iArgb) Return EndFunc ;==>_GDIPlus_BitmapSetPixel ;based on a post from Malkey ;Loops though the sting, turing the Binary to ASCII Func BinDecodeString($arg) $results = "" For $i = 1 To StringLen($arg) Step 8 $results = $results & Byte2Asc(StringMid($arg, $i, 8)) Next Return $results EndFunc ;==>BinDecodeString Func Byte2Asc($arg) $results = 0 If StringMid($arg, 1, 1) = 1 Then $results = $results + 128 If StringMid($arg, 2, 1) = 1 Then $results = $results + 64 If StringMid($arg, 3, 1) = 1 Then $results = $results + 32 If StringMid($arg, 4, 1) = 1 Then $results = $results + 16 If StringMid($arg, 5, 1) = 1 Then $results = $results + 8 If StringMid($arg, 6, 1) = 1 Then $results = $results + 4 If StringMid($arg, 7, 1) = 1 Then $results = $results + 2 If StringMid($arg, 8, 1) = 1 Then $results = $results + 1 Return Chr($results) EndFunc ;==>Byte2Asc ;Loops though the sting, turing the ASCII to a series of 1s and 0s Func BinEncodeString($arg) $results = "" For $i = 1 To StringLen($arg) $results = $results & Asc2Byte(StringMid($arg, $i, 1)) Next Return $results EndFunc ;==>BinEncodeString Func Asc2Byte($arg) $tempasc = Asc($arg) Local $result = "" While ($tempasc >= 1) $tempasc /= 2 If StringIsDigit($tempasc) Then $result &= 0 Else $split = StringSplit($tempasc, ".") $tempasc = $split[1] $result &= 1 EndIf WEnd $result = _StringReverse($result) While StringLen($result) < 8 $result = "0" & $result WEnd Return $result EndFunc ;==>Asc2Byte ;based on a post from Mpro |