Steganografie mit ASM und AutoIt

  • Hallo liebe AutoIt-Community! :)


    Wie die Überschrift schon verrät, bin ich dabei, ein Steganografie-Script nach diesem Vorbild zu schreiben. Mit AutoIt allein hat das ganze für mich auch kein Problem dargestellt. Nun wollte ich jedoch den Hauptteil, nämlich das Verstecken in der Bilddatei, mit Assembler (mit AssembleIt) umsetzen.
    Darin bin ich jedoch noch nicht gerade so erfahren wie in AutoIt, weshalb ich jetzt auch auf einige Problemchen stoße... ^^


    Zu meinem Skript:

    • Es wird per GDIPlus eine Bitmap-Datei geladen und der Pointer auf die Pixeldaten an den ASM-Code übergeben.
    • Der Geheimtext wird in eine DLL-Struct gepackt (ich hoffe, da mache ich soweit alles richtig - ganz sicher bin ich mir nicht...) und der Pointer dazu auch übergeben.
    • Der ASM-Code liest nun für jedes Pixel die ursprüngliche Pixelfarbe aus sowie ein Byte des Geheimtextes.
    • Die Pixelfarbe wird in (A)RGB getrennt und die untersten Bits der Farben durch Bits des Geheimtextes ersetzt (bei Rot und Grün 3 Bit, bei Blau 2 Bit -> insgesamt 8 Bit (1 Byte) pro Pixel).
    • Der neue Farbwert wird zusammengesetzt und am Ende in einer neuen Bitmap-Datei gespeichert.


    Was nicht so funktioniert: :S

    • Ich habe als Bitmap eine 2x2 Pixel reinweiße Bitmap genommen (Farbe: #FFFFFF; Datei siehe Anhang) und als Geheimtext "aaaa". Da in jedem Pixel genau ein Buchstabe versteckt wird, sollten alle Pixel am Ende wieder gleich sein (4 mal ein weißer Pixel mit "a" drin). Dies ist aber nicht der Fall, das erste Pixel ist andersfarbig.
    • Wenn die Länge des Geheimtextes kleiner ist als die Pixelanzahl, stürzt das Programm ab. Ich frage mich, wieso das Programm nicht richtig weiter macht, wenn es als Byte des Geheimtextes 0 ausliest. Denn das ist ja schließlich auch nur eine Zahl, oder nicht?


    Ich bedanke mich schonmal sehr für eure Hilfe, ich habe nun schon viele Stunden an diesem Skript verbracht und komme der Lösung leider nicht näher...


    LG Xenon :)


    PS: Ich habe es mal in diesem Forum gepostet, weil es hauptsächlich um ASM und weniger um AutoIt geht. Ich hoffe das ist okay so, ansonsten bitte verschieben. ;)

  • Hi,
    schöne Idee, die Steganographie in Assembler umzusetzen!


    Nach etwas debugging einige Tips:


    - Wenn du mit Bytes bspw aus einem String arbeitest, kannst du diese per "movsx edx, byte[adresse]" "movzx edx, byte[adresse]" (löscht die oberen 3 bytes = zero extension ! ) direkt ansprechen. Dann musst du auch nicht darauf achten (wie in deinem String) über das Ende der Stringstruct hinaus den Speicher auszulesen, das kann in die Hose gehen, wenn dieser Speicherbereich reserviert ist!


    - Maskiere die Bits bspw. des Buchstabens direkt in ein DWORD. Das heisst, schreibe die Bits von bspw
    "A" = chr(65) = 10000001 so in ein
    DWORD BGRA 00000001|00000000|00000100|11111111,
    dass du per AND einfach das ürsprüngliche Pixel mit dem DWORD verknüpfen kannst!
    Damit sparst du dir die PUSH/POPs.


    - Du bearbeitest, wenn du den String in ein DWORD einliest, das LETZTE Byte! Das heisst, bei dem zweiten, dritten und vierten Byte liest du immer nur NULL ein!
    Somit wird in deinem Beispiel nur ein Byte (das erste) "abgespeichert"!
    Anpassen könnte man das, indem man einfach 3 NullBytes (oder irgendetwas anderes) vor den String schreibt^^

    $sString = chr(0)&chr(0)&chr(0)&chr(0)&chr(0)&chr(0)&chr(255);"ABCD" ; String, der versteckt werden soll
    $String = DllStructCreate ("char[" & ($iWidth * $iHeight) +4 & "]") ; DLL-Struct für den String erstellen (für jeden Pixel ein Byte)
    ;~ $bBinary = StringToBinary ($sString) ; String in binär umwandeln
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $bBinary = ' & $bBinary & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    DllStructSetData ($String, 1, $sString) ; DLL-Struct füllen

    macht dein Script lauffähig!

    ciao
    Andy



    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay


    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    3 Mal editiert, zuletzt von Andy ()

  • Hallo Andy,


    erst einmal vielen Dank für deine Hilfe! :thumbup:


    Ich habe mein Problem jetzt lösen können, mein Programm funktioniert! Getestet habe ich es, indem ich die Entschlüsselung eben auch noch "gebastelt" habe. (Ich baue noch eine schöne GUI und dann veröffentliche ich das ganze im Skripte-Forum...)



    Deinen ersten Tipp mit movzx habe ich umgesetzt, wodurch sich auch das Problem mit den Null-Bytes (dein dritter Tipp) gelöst hat.
    Dein zweiter Vorschlag mit der Maske und der Verknüpfung erscheint auch logisch, aber den habe ich bis jetzt noch nicht umgesetzt. Dazu habe ich nämlich noch eine Frage. ^^ Was ist der genaue Vorteil? Denn mit meiner bisherigen Methode mit Stack klappt es bisher einwandfrei. Gibt es irgendwelche Nachteile bei der Verwendung des Stacks oder weshalb soll ich mir die push und pops sparen? Ich möchte deinen Tipp nicht ablehnen, mich interessiert nur der Unterschied zwischen den beiden Methoden. ;)


    Liebe Grüße,
    Xenon
    :)

  • Hi,

    Zitat

    Ich habe mein Problem jetzt lösen können, mein Programm funktioniert!

    DAS ist genau der Grund, wieso du sehr entspannt über

    Zitat

    Dein zweiter Vorschlag mit der Maske und der Verknüpfung erscheint auch logisch, aber den habe ich bis jetzt noch nicht umgesetzt.

    nachdenken kannst ;)


    Zitat

    Gibt es irgendwelche Nachteile bei der Verwendung des Stacks oder weshalb soll ich mir die push und pops sparen? Ich möchte deinen Tipp nicht ablehnen, mich interessiert nur der Unterschied zwischen den beiden Methoden.

    Naja, der "wirkliche" Grund ist natürlich völlig jenseits von Gut und Böse. Geschwindigkeit und Übersicht. Über die paar Takte, die das Speicherinterface bei PUSH/POP länger braucht, und mit dem Hintergrund, dass Assembler sowieso für die meisten sog. "Programmierer" völlig daneben und nicht lesbar ist, KÖNNTEST du das natürlich so lassen.
    Die Intension, Assembler zu nutzen ist imho aber, möglichst schnellen und kurzen Code zu erstellen.
    Und da ist jedes "unnötige" PUSH/POP ein Dorn im Fleisch :D
    Du hast auch keinerlei sog. Register-Pressure, d.h. du hast noch einige der Prozessorregister übrig, um darin Zwischenergebnisse zu puffern.


    Btw. leider funktioniert bei mir unter Win7/64 und 3.3.10.2 der in Assembleit integrierte Debugger nicht mehr. Das ist natürlich besonders ärgerlich, ich vermute wieder mal einige "Script breaking changes" in den bisher funktionierenden Funktionen...
    //EDIT schon übel, wenn man seine eigenen Programme nicht mehr bedienen kann, hab das

    _("org " & FasmGetBasePtr($Fasm))

    vergessen :rolleyes:

    ciao
    Andy



    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay


    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy ()

  • Okay, vielen Dank für deine tolle Erklärung! ;)
    Ich habe mein Skript jetzt nochmal umgeschrieben und dabei ist es gleich noch kürzer geworden:



    Jetzt läuft es ohne PUSH/POP und wird per AND bzw. OR verknüpft.



    Btw. leider funktioniert bei mir unter Win7/64 und 3.3.10.2 der in Assembleit integrierte Debugger nicht mehr. Das ist natürlich besonders ärgerlich, ich vermute wieder mal einige "Script breaking changes" in den bisher funktionierenden Funktionen...


    Also ich habe auch Win7/64 Bit und die Version 3.3.10.2, aber zumindest folgendes Debug-Beispiel von dir funktioniert bei mir:



    MfG Xenon :)


    PS: Ich hatte leider über Ostern nicht so viel Zeit und konnte erst jetzt antworten...

  • Seeehr schick^^
    Dein Code ist jetzt kürzer, es geht aber noch wesentlich einfacher. Du musst nicht immer das Byte aus dem Speicher laden, mit den Schiebebefehlen kannst du einfach die RRR GGG BB trennen...

    _("Pixel:") ; Beginn der Schleife für jeden Pixel


    _("movzx edx, byte[esi]") ; edx ist das erste Byte des Binärstrings, wird nur 1x aus dem Speicher gelesen
    ;(binär EDX: 00000000 00000000 000000000 RRRGGGBB)
    _("shl edx, 11") ;(binär EDX: 00000000 00000RRR GGGBBB000 00000000) komlett nach links schieben
    _("shr dx, 5") ;(binär EDX: 00000000 00000RRR 00000GGG BB000000) nur DX shiften
    _("shr dl, 6") ;(binär EDX: 00000000 00000RRR 00000GGG 00000BB) nur lowbyte shiften


    _("mov eax, dword [edi]") ; Farbe des Pixels (= Wert an edi-Position) in eax Form: #AARRGGBB
    _("and eax, 0xFFF8F8FC") ; Pixelfarbe mit 11111111 11111000 11111000 11111100 verknüpfen -> die Bits, die überschrieben werden auf 0 setzen
    _("or eax, edx") ; und mit den neuen Daten per OR verknüpfen
    _("mov [edi], eax") ; Farbwert in der Bitmap speichern


    Genauso kannst du natürlich auch beim "Auslesen" die Bits zusammenfassen....
    -------- -----RRR -----GGG ------BB
    low-byte 6 nach links shiften
    -------- -----RRR -----GGG BB-----
    high- und lowbyte zusammen (AX,BX, usw) 5 nach links
    -------- -----RRR GGGBB--- --------
    alles 11 nach rechts
    -------- -------- -------- RRRGGGBB
    voila :D



    Insgesamt könnte man noch einige Zeilen sparen, aber so ist es lesbarer :thumbup:


    Weiterhin läuft dein Programm nun alle Pixel der gesamten Grafik durch, du könntest beim Ende des Strings abbrechen!
    Die Struct, in der dein zu verbergender String steckt, müsste nur ein Byte (= NULL) größer sein als der String.


    Auch beim Aufrufen per Call reicht es, wenn du die Adresse des Bildes und die Adresse des Strings übergibst! Abbruchbedingung ist das Nullbyte am Ende des Strings!
    Die Abfrage, ob der String größer als die Anzahl der Pixel des Bildes ist, würde ich in eine Zeile AutoIt packen^^



    Zitat

    Also ich habe auch Win7/64 Bit und die Version 3.3.10.2, aber zumindest folgendes Debug-Beispiel von dir funktioniert bei mir:

    Natürlich funktioniert bei mir der Debugger auch, man sollte seine eigenen Programme auch bedienen können... :rolleyes:

    ciao
    Andy



    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay


    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    14 Mal editiert, zuletzt von Andy ()

  • Hi,
    es geht natürlich NOCH kürzer/schneller, aber wen juckt das... 8o


    Anbei Kodierer/Dekodierer (der Dekodierer besteht aus 25 BYTES Code :thumbup: )



    AssembleIt braucht man natürlich nicht mehr, wenn man per $_ASSEMBLEIT_FLAG = 0 assembliert und den Code direkt ausführt.
    Dann sieht das so aus...



    Errorhandling sollte man tunlichst hinzufügen, denn wenn der Text länger ist, als das Bild Pixel hat, gibts nen Crash^^
    Aber das kann jeder selbst zusammenpfriemeln ;)


    Es bleibt natürlich offen, die Textbits statt -------- -----RRR -----GGG ------BB bspw. als -------- ------RR -----RGG -----GBB abzuspeichern, oder nur in jedem 5. Pixel. Oder nur 1 Bit in jedem 7. Pixel...
    Sehr einfach in ASM zu realisieren ist das tauschen der Bits, das käme noch einer internen Verschlüsselung gleich, also aus -------- ------RR -----RGG -----GBB würde dann bspw. -------- ------RB -----GRG -----BRG
    Der Phantasie sind keine Grenzen gesetzt!


    Zur Not kann man die Bilder auch als PNG speichern, dann sind sie etwas kleiner, aber immer noch verlustfrei komprimiert ;) Oder man verwendet libjpeg um verlustfrei in jpg zu transformieren.
    Btw. wird mit ca. 500MB/Sekunde kodiert/dekodiert! Was bedeutet, dass 500KB Text in einer Millisekunde in einem Bild verschlüsselt/entschlüsselt werden! Das entspricht ca. 6 Prozessortakten pro verschlüsseltem Buchstaben!


    Wer mag, kann natürlich auch binäre Dateien "verstecken", ich würde dann einfach einen Integer mit der Länge der Daten in die ersten 4 Pixel packen, und so lange kodieren/dekodieren, bis dieser Wert erreicht ist! Oder direkt Base64 verwenden...


    In diesem kleinen Kerl steckt übrigens auch ein Text -> autoit.de/wcf/attachment/24339/


    Beschränkt man sich auf Bitmap-Dateien (*.BMP), kann man sogar komplett auf GDI(+) verzichten und direkt in die "Pixel"-Daten schreiben.
    Das wäre extrem leicht in jeder anderen Computersprache (und jedem anderen Gerät) umzusetzen!