A Logo

Feel free to include my content in your page via my
RSS feed

Help Irongeek.com pay for
bandwidth and research equipment:

Subscribestar or Patreon

Search Irongeek.com:

Affiliates:
Irongeek Button
Social-engineer-training Button

Help Irongeek.com pay for bandwidth and research equipment:

paypalpixle




Unicode and LSB Steganography program examples

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

Printable version of this article

15 most recent posts on Irongeek.com:


If you would like to republish one of the articles from this site on your webpage or print journal please contact IronGeek.

Copyright 2020, IronGeek
Louisville / Kentuckiana Information Security Enthusiast