Tatort Internet: Matrjoschka in Flash

Um Ersatz für das Flash-Filmchen zu finden, das sich gestern als Trojaner entpuppte, gebe ich „new iphone video“ bei Google ein. Schon der zweite Link verspricht ein „exklusives Preview“; er führt auf eine Webseite mit einem Video – doch was ist das? Das funktioniert schon wieder nicht!

In Pocket speichern vorlesen Druckansicht
Lesezeit: 22 Min.
Von
  • Sergei Shevchenko
Inhaltsverzeichnis

Anders als gestern sind diesmal auch keine verdächtigen Strings in der SWF-Datei, die ich schnell mal heruntergeladen habe. Aber nach der Analyse mit swfdump wundert es mich nicht, dass da kein Film erscheint:

[HEADER]        Frame count: 1
[HEADER] Movie width: 1.00
[HEADER] Movie height: 1.00

Nur ein einziger Frame mit einer Größe von 1 x 1 Pixel – das sieht nicht danach aus, als wollte der Autor mir ein Video vorführen. Dafür enthält der Dump seitenweise sogenannten P-Code, der ziemlich schwer zu lesen ist. Das ist noch nicht so ungewöhnlich, kann man doch in ActionScript unter anderem auch Spiele und interaktive Filme programmieren.

Der P-Code ist dabei so etwas wie Assembler für CPU-Maschinenbefehle. Ähnlich wie Java oder .NET wird ActionScript nämlich in Bytecode übersetzt. Den wandelt der Just-in-Time-Compiler einer virtuellen Maschine in nativen Code um, den die CPU ausführen kann. Und wenn man diese binären Bytecode-Befehle zurückübersetzt, kommt eben dieser P-Code heraus.

Was gäbe ich jetzt für eine lauffähige Installation des Action Script Viewer von Buraks oder des Sothink SWF Decompiler, die daraus wieder so was ähnliches wie lesbares ActionScript produzieren. Aber auf diesem Rechner hier muss ich mich mit dem kostenlosen abcdump begnügen, das Adobe im Rahmen des Tamarin-Projekts veröffentlicht hat. Damit lassen sich die P-Code-Listings immerhin schon besser weiterverarbeiten als die Ausgabe von swfdump. Und bearbeiten muss man die Listings, wenn man überhaupt etwas verstehen will.

Bereits die Statistik im Kopf der Datei bestätigt meinen aufkeimenden Verdacht, dass es hier nicht mit rechten Dingen zugeht: Über 25.000 pushshort- und pushbyte-Befehle, die rund 97 Prozent des Codes ausmachen, verheißen nichts Gutes. Und die API-Analyse von abcdump bestätigt diesen Verdacht noch weiter:

class EySpSUmzVvhfxjxHBjknyJec extends Object
class EySpSUmzVvhfxjxHBjknyJec
function EySpSUmzVvhfxjxHBjknyJec():*
class EySpSUmzVvhfxjxHBjknyJec
static var DAeBnlwHPuJPkQrZogFcTVoLn:String =
"fx46RIu1kelToyIVefnbEF"

Meine Erfahrung sagt mir: Wer solche Bezeichner verwendet, hat etwas zu verbergen. Aber keine voreiligen Schlüsse! Immerhin hab ich Ähnliches auch schon in legitimem Code gesehen, etwa als ein Flash-Künstler versuchte, sein „geistiges Eigentum“ mit einem Scrambler zu sichern.

Um meine P-Code-Kenntnisse aufzufrischen, lade ich mir schnell noch die von Adobe ebenfalls veröffentlichte Beschreibung der ActionScript Virtual Machine 2 herunter. P-Code arbeitet sehr viel mit dem Stack – ähnlich wie einige steinzeitliche Taschenrechner, bei denen man zur Addition zweier Werte diese zunächst auf den Stack schieben muss. Das Folgende etwa setzt das Äquivalent einer Variablen – einen Slot – auf den Wert 0:

pushbyte 0  // push 0 on stack
convert_d // pop, convert to double, push it back
setslot 1 // pop value from stack into slot 1

Um nicht ganz verrückt zu werden, lasse ich als erstes einige Suchen&Ersetzen-Sequenzen durchlaufen und ersetze Variablennamen wie GaAnighKAUXBTVKnoMpTnQgKB, EySpSUmzVvhfxjxHBjknyJec und DAeBnlwHPuJPkQrZogFcTVoLn systematisch durch die Kurzformen Gaa, Eys und Dae. Weitere Tipps für sinnvolle Ersetzungen liefern mir praktischerweise die Decompiler-Kommentare zum Funktionskopf von Main().

var ii:Number                        /* slot_id 5 */
var i:Number /* slot_id 3 */
var j:Number /* slot_id 2 */
var bytes:flash.utils::ByteArray /* slot_id 1 */

Die Zuweisung von slot_id 3 zur Variablen i wird schon ihren tieferen Sinn haben, also folge ich ihr erst einmal. Dabei benutze ich einen Trick, der sich die systematische Formatierung des Listings zu Nutze macht. Indem ich etwa slot 3 durch i ersetze, werden alle get- und set-Befehle für dieses Feld in einem Rutsch passend geändert und aus

67    getslot         3

wird ein einfaches get i. Okay – Zeit, sich den Einstieg in den Code bei Main() mal näher anzusehen.

Nach einer erstaunlich kurzen Gewöhnungsphase kann ich den Code schon recht flüssig lesen und versehe ihn mit Notizen. Am Ende baue ich mir aus den Kommentaren eine leichter verständliche Pseudo-Code-Darstellung. Die übersetzt zwar sicher kein Compiler dieser Welt mehr in lauffähigen Code, aber dafür erkennt mein darauf trainiertes Gehirn auf einen Blick, was da passiert.

 1 function Main()
2 {
3 var ii as Number;
4 var i as Number;
5 var j as Number;
6 var bytes as ByteArray;
7 var Gaa as Loader;
8
9 Gaa = NULL;
10 bytes = new ByteArray;
11
12 j = 0;
13 for (i=0; i < Eys.data.length; i++)
14 j = j + 1;
15 bytes[i] = (Eys.data[i] XOR Eys.Dae.charCodeAt(j));
16 if (j > Eys.Dae.length) {
17 j = 0; // zuruecksetzen
18 }
19 }

20
21 Gaa = new Loader;
22
23 ii = 0;
24 while (ii < 2) {
25 ii = ii + 1; // ergibt keinen Sinn, aber so ist es!!!
26 }
27
28 addChild(Gaa);
29 Gaa.loaderInfo.addEventListener(Event.COMPLETE, function(e:Event));
30 Gaa.loadBytes(bytes);
31}

Der grün markierte Code dieser Funktion verwendet die Werte in Eys.Dae als Maske, die in einem sich wiederholenden Muster über den großen Datenpuffer Eys.data gelegt wird, um die Werte dann mit XOR zu verknüpfen. i und j sind dabei Offsets in die jeweiligen Puffer. Während i linear vom ersten bis zum letzten Byte des Datenpuffers läuft, wird der Zähler j immer wieder auf 0 zurück gesetzt, wenn er das Ende der Maske erreicht.

Und jetzt wird's spannend: Nachdem die Funktion den Puffer dekodiert hat, lädt sie ihn via loader.loadBytes() und führt ihn damit als Flash-Datei aus. Also muss der dekodierte Puffer eine SWF-Datei im Arbeitsspeicher darstellen. Die will ich sehen!

Doch um da ranzukommen, brauche ich Eys.Dae und Eys.data. Etwas mehr Geschmöker im P-Code fördert die Struktur der Klasse Eys mit ihren zwei Properties Dae und data zu Tage. Und schon habe ich das "Sesam Öffne Dich"

7 findproperty Dae
9 pushstring "fx46RIu1kelToyIVefnbEF"
11 setproperty Dae

in Form des Strings Dae mit dem XOR-Codewort. data entpuppt sich als ein Feld mit 10.343 Bytes, die eins nach dem anderen über pushbyte befüllt werden:

   20    pushbyte          37
22 pushbyte 47
...
25909 pushbyte 21
25911 newarray [10343]

// fill data array with 10,343 values poped from stack
25914 setproperty data

Somit muss ich mir nur die „gepushten“ Werte besorgen, um sie dann selber zu dekodieren. Ich spiele kurz mit dem Gedanken, mir die aus dem formatierten Dump von abcdump zu holen, entscheide mich dann aber doch für die elegantere Variante, sie direkt aus der SWF-Datei herauszuoperieren. Die Opcodes der P-Code-Befehle hat Adobe ja fein säuberlich in der VM-Beschreibung dokumentiert.

Also such ich mir den Beginn der Lade-Routine, der sich in Zeile 20 des P-Code-Listings findet. Doch ich brauche die zugehörige Stelle in der SWF-Datei. Der direkt davor befindliche debug-Befehl hat den Opcode 0xEF gefolgt von vier Parametern; das folgende findproperty wird zu 0x5E und erwartet wie pushbyte mit 0x24 nur ein Argument:

13    debug          1 19 1 4    //  ef ?? ?? ?? ??
18 findproperty data // 5e ??
20 pushbyte 37 // 24 ??
22 pushbyte 47 // 24 ??

Das reicht, um die Stelle im Hex-Editor zu lokalisieren. Ich kopiere die komplette Routine mit dem Hex-Editor in die Datei „push_stub.bin“. Als nächstes hack ich mir schnell ein einfaches Dekodierprogramm in C++ zusammen. Etwas anspruchsvoller wird die Aufgabe durch die ebenfalls vorkommenden pushshort-Befehle (Opcode 0x25), die einen „unsigned integer“ mit 30 Bit schreiben. Gut, dass meine Programmierkenntnisse noch nicht allzu sehr eingerostet sind.

Fast hätte ich dabei die gelegentlich eingestreuten dup-Anweisungen übersehen, die den letzten auf den Stack geschriebenen Wert wiederholen. Letztlich bedeutet jedoch auch dies nur eine weitere if-Abfrage und drei Zeilen zusätzlichen Codes in meinem kleinen C++-Programm, das auch gleich das XOR für mich erledigt und die dekodierte Flash-Datei nach „decoded.bin“ schreibt.

Also „Zurück auf Los und ziehe keine DMˇ4000 ein!“ – aber ich krieg dich schon noch. Doch zuerst brauch ich einen Kaffee. Als ich meiner Frau bei der Gelegenheit erkläre, dass das mit dem iPhone-Video wohl wieder nichts wird und ich den Rechner wohl noch eine Zeit lang blockieren werde, ernte ich überraschenderweise nur ein „Ja, ja, mach nur – ich seh dich dann morgen früh …“ Na ja, mir soll's recht sein.

Wie ich beim obligatorischen ersten Blick im Hex-Editor am CWS am Anfang erkenne, handelt es sich um eine komprimierte Flash-Datei; abcdump schluckt sie natürlich trotzdem anstandslos. Gleich beim ersten Durchblättern der abcdump-Ausgabe springen mir mehrere pushstring-Befehle ins Auge.

   16    pushstring        "4657530825060000300A00A000
0C03034411080000004302..." // truncated very long string
18 coerce String
20 setlocal1
21 pushstring "4657530825060000300A00..."
23 coerce String
25 setlocal2
...
79 pushstring "4657530825060000300A00..."
81 coerce String
83 setlocal 12

Ganze zwölf Mal wird da also eine ziemlich lange Zeichenkette auf den Stack geschoben. Erst beim zweiten Hinsehen stelle ich fest, dass sich die Strings in den Details weiter hinten doch zumindest geringfügig unterscheiden. Als Nächstes ermittelt der P-Code die Version des aktuellen Flash-Players:

93    getlex            flash.system::Capabilities
95 getproperty version

und unterscheidet dann sogar zwischen einem Browser-Plug-in, wie es Mozilla & Co verwenden

103   pushstring        "PlugIn"
105 ifne L2

und einem ActiveX-Control

192   pushstring        "ActiveX"
194 ifne L8

das auf den Internet Explorer hinweist. Ich glaub, ich weiß schon, wohin das führt. Und richtig – da werden Versionsnummern verglichen. Genau sechs Strings sind ihm genehm:

  1. "WIN 9,0,115,0"
  2. "WIN 9,0,16,0"
  3. "WIN 9,0,28,0"
  4. "WIN 9,0,45,0"
  5. "WIN 9,0,47,0"
  6. "WIN 9,0,64,0"

Sechs Flash-Versionen auf zwei Plattformen macht zwölf!

Findet dieser Master-Exploit dann eine dieser zwölf Kombinationen vor, entpackt er den String, indem er ihn als hexadezimal-kodierte Byte-Folge interpretiert und lädt das dann erneut via loader.loadBytes() als Flash-Datei. Ich fasse es nicht: Eine dynamisch dekodierte und dann geladene Flash-Datei lädt erneut eine dynamisch erstellte Flash-Datei aus einem Repertoire von zwölf Strings. Aber es ist wahr. Als ich den ersten String in meinen Hex-Editor kopiere, erkenne ich deutlich die Struktur einer diesmal unkomprimierten Flash-Datei.

Das nenn’ ich professionelle Arbeit! Die verschiedenen Versionen der Flash-Umgebung unterscheiden sich so sehr, dass Exploits, die sich auf bestimmte Adressen verlassen, den Player einfach nur zum Absturz bringen. Damit ist dem Angreifer nicht gedient. Das heißt, er muss entweder sehr generischen Shellcode schreiben – was ziemlich schwierig ist. Oder er legt sich eben ein Arsenal von sehr speziellen Exploits zu und wählt jeweils den zum aktuellen Player passenden. Unser kleiner Bastard hier hat ganz offensichtlich den einfacheren Weg gewählt und trägt ein ganzes Waffenarsensal mit sich herum.

Die Uhr ist schon nach eins und ich sollte langsam ins Bett – aber wenigstens einen der Exploits schau ich mir noch an. Also starte ich IDA Pro und werfe den Disassembler an.

Gleich am Anfang findet sich da ein kleiner Decryptor, der den Rest des Codes entpacken soll. Der Befehl call $+5 schiebt die Rücksprungadresse 0xF0 auf den Stack und springt genau auf den nächsten Befehl. Der holt sie sich mit pop EBP ins Base-Pointer-Register und schiebt den Zeiger danach noch 20 Bytes weiter. Damit zeigt er genau auf den Anfang eines verschlüsselten Datenblocks. Das Register ECX erhält mit 395 die Zahl der auszupackenden Bytes und AL den Wert mit 0x3D für die XOR-Maske.

Das ist für die eingebauten Dekodierfunktionen von IDA Pro ein Kinderspiel, und wenige Momente später hab ich den Code ausgepackt, abgespeichert, erneut geladen und disassembliert.

Und siehe da – der Shellcode sieht letztlich fast genau so aus, wie der von gestern Nacht. Letztlich lädt er eine Datei vom Web-Server des Angreifers nach und startet diese, um das System unter seine Kontrolle zu bringen.

Eigentlich hätte ich mir ja denken können, dass es keine gute Idee ist, mich mal eben durch die Suchmaschinen-Ergebnisse eines so heißen Themas zu klicken. Denn längst haben die kriminellen Banden ihre eigenen Suchmaschinen-Experten, die kontinuierlich Google-Trends beobachten. Kocht ein neues Thema hoch, benutzen sie ihre Legionen aus kompromittierten Systemen, um die eigenen Seiten mit den Web-Exploits in den Suchergebnissen nach oben zu pushen. Dass sie damit beim Kapern aktueller Themen derart erfolgreich sind, überrascht mich doch.

Und dass die Malware-Autoren jetzt die von herkömmlichen Win32-Schädlingen bekannten Verschleierungsstrategien in virtuellen Umgebungen wie der von Flash umsetzen, ist eine schlechte Nachricht für die Antiviren-Hersteller. Denn letztlich bedeutet es, dass sie zukünftig auch noch die Laufzeitumgebungen von JavaScript, dem .NET-Framework und Adobe Action Script emulieren müssen, um Schädlinge zu erkennen. Ach, die Welt ist schlecht – höchste Zeit, ins Bett zu gehen und mir die Bettdecke über den Kopf zu ziehen. (ju)

Die Serie "Tatort Internet" wurde ursprünglich im c't magazin ab Heft 13/2010 veröffentlicht. In den Artikeln können Sie Experten über die Schulter schauen, wie sie verdächtige Dateien analysieren und Schädlingen auf die Schliche kommen. Alle in der Serie vorgestellten Malware-Samples stammen aus echten Angriffen und wurden unter anderem mit den hier vorgestellten Methoden entlarvt. Die Geschichten "drumherum" wurden durch reale Vorkommnisse inspiriert ;-)

Der Autor dieser Folge, Sergei Shevchenko, kann mehr als 10 Jahre praktische Erfahrung in der Analyse von Schädlingen vorweisen. Er ist einer der Autoren des automatisierten Bedrohungsanalysesystems ThreatExpert , von dem unter anderem auch die Verhaltenserkennung Threatfire abstammt. Sergei arbeitet als „Leading Malware Analyst“ bei PC Tools in Sydney, Australien. Mit dieser Folge ist das vorläufige Ende der Serie erreicht. Wenn es ausreichend Nachfrage geben sollte, lassen wir uns jedoch vielleicht zu einer zweiten Staffel überreden ;-)

Übersicht aller Folgen:

  1. Alarm beim Pizzadienst
  2. Zeig mir das Bild vom Tod
  3. PDF mit Zeitbombe
  4. Angriff der Killervideos
  5. Matrjoschka in Flash
Achtung: diese Code-Fragmente könnten zu einem Alarm Ihrer Antiviren-Software führen. Dabei handelt es sich dann um einen Fehlalarm.
class Main extends flash.display::Sprite
{
function Main():* /* disp_id -1*/
{
activation {
var ii:Number /* slot_id 5 */
var i:Number /* slot_id 3 */
var j:Number /* slot_id 2 */
var bytes:flash.utils::ByteArray /* slot_id 1 */
var Gaa:flash.display::Loader /* slot_id 4 */
}
// local_count=3 max_scope=5 max_stack=7 code_len=283
0 debugfile "C:\Documents and Settings\dev\Desktop\exp;;Main.as"
2 debugline 12
4 getlocal0
5 pushscope
6 newactivation
7 dup
8 setlocal1
9 pushscope
10 debug 1 23 0 12

// set Gaa = NULL

15 getscopeobject 1
17 pushnull
18 coerce flash.display::Loader
20 set Gaa

22 debugline 13
24 getlocal0
25 constructsuper (0)
27 debugline 14

// initialize bytes as a new ByteArray

29 getscopeobject 1
31 findpropstrict flash.utils::ByteArray
33 constructprop flash.utils::ByteArray (0)
36 coerce flash.utils::ByteArray
38 set bytes

40 debugline 16
42 getscopeobject 1

// set j = 0

44 pushbyte 0
46 convert_d
47 set j

49 debugline 17
51 getscopeobject 1

// set i = 0

53 pushbyte 0
55 convert_d
56 set i

58 jump L1

L2:
62 label
63 debugline 19

// push bytes[i] on stack

65 getscopeobject 1
67 get bytes
69 getscopeobject 1
71 get i

// push Eys.data[i] on stack

73 getlex Eys
75 getproperty data
77 getscopeobject 1
79 get i
81 getproperty null

// push Eys.Dae on stack

83 getlex Eys
85 getproperty Dae

// push (j = j + 1) on stack

87 getscopeobject 1
89 get j
91 convert_d
92 dup
93 increment
94 convert_d
95 getscopeobject 1
97 swap
98 set j

// pop j from stack and take Eys.Dae.charCodeAt[j]

100 callproperty http://adobe.com/AS3/2006/builtin::charCodeAt (1)

// pop from Eys.data[i] and Eys.Dae.charCodeAt(j) from stack, XOR them, and store the result in bytes[i]:
// bytes[i] = (Eys.data[i] XOR Eys.Dae.charCodeAt(j));

103 bitxor

104 setproperty null
106 debugline 20

// push j on stack

108 getscopeobject 1
110 get j

// push Eys.Dae.lehgth on stack

112 getlex Eys
114 getproperty Dae
116 getproperty length

// pop j and Eys.Dae.length from stack, compare them, and jump to L3 if (i < Eys.Dae.length)

118 ifnge L3

// set j = 0
// that is, if j is still less than Eys.Dae.length, then skip this block and jump to L3, otherwise, set j = 0

122 getscopeobject 1
124 pushbyte 0
126 convert_d
127 set j

L3:
129 debugline 17

// i = i + 1

131 getscopeobject 1
133 get i
135 increment
136 convert_d
137 getscopeobject 1
139 swap
140 set i

L1:
// push i into stack

142 getscopeobject 1
144 get i

// push Eys.data.length into stack

146 getlex Eys
148 getproperty data
150 getproperty length

// pop i and Eys.data.length from stack and jump to L2 if (i < Eys.data.length)

152 iflt L2

// initialize Gaa as new Loader

156 debugline 23
158 getscopeobject 1
160 findpropstrict flash.display::Loader
162 constructprop flash.display::Loader (0)
165 coerce flash.display::Loader
167 set Gaa
169 debugline 24

// set ii = 0

171 getscopeobject 1
173 pushbyte 0
175 convert_d
176 set ii
178 jump L4

L5:

// ii = ii + 1

182 label
183 getscopeobject 1
185 get ii
187 increment
188 convert_d
189 getscopeobject 1
191 swap
192 set ii

L4:
// push ii on stack

194 getscopeobject 1
196 get ii

// push 2 on stack

198 pushbyte 2

// pop 2 and ii from stack, compare, jump to L5 if (i < 2)

200 iflt L5

204 debugline 25
206 findpropstrict addChild
208 getscopeobject 1

// call addChild(Gaa)

210 get Gaa
212 callpropvoid addChild (1)

// push Gaa.loaderInfo on stack

215 debugline 28
217 getscopeobject 1
219 get Gaa
221 getproperty loaderInfo

// push Event.COMPLETE on stack

223 getlex flash.events::Event
225 getproperty COMPLETE

// create new function with a parameter of Event type, e.g. e

227 newfunction var undefined(flash.events::Event):void /* disp_id 0*/

// pop function(e:Event), Event.COMPLETE and Gaa.loaderInfo from stack, then call addEventListener():
// Gaa.loaderInfo.addEventListener(Event.COMPLETE, function(e:Event))

229 callpropvoid addEventListener (2)

232 debugfile "C:\Documents and Settings\dev\Desktop\exp;;Main.as"

// push Gaa on stack

234 debugline 29
236 getscopeobject 1
238 get Gaa
240 getscopeobject 1

// push bytes on stack

242 get bytes

// pop bytes, Gaa from stack and call loadBytes():
// Gaa.loadBytes(bytes)

244 callpropvoid loadBytes (1)
247 debugline 31
249 jump L6

253 getlocal0
254 pushscope
255 getlocal1
256 pushscope
257 newcatch 0
259 dup
260 setlocal2
261 dup
262 pushscope
263 swap
264 set bytes
266 debugline 33
268 findpropstrict trace
270 getscopeobject 2
272 get bytes
274 callpropvoid trace (1)
277 popscope
278 kill 2
L6:
280 debugline 35
282 returnvoid
}
}
void Decode() 
{
HANDLE hInputFile = NULL;
HANDLE hOutputFile = NULL;
HANDLE hMap = NULL;
LPBYTE lpbyBase = NULL;
DWORD dwSize;
DWORD dwBytesWritten = 0;

char szMask[] = "fx46RIu1kelToyIVefnbEF";
int i, j;
BYTE b, bLast;

if ((hOutputFile = CreateFile("decoded.bin",
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
{
if ((hInputFile = CreateFile("push_stub.bin",
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
{

if (((dwSize = GetFileSize(hInputFile, NULL)) != INVALID_FILE_SIZE) &&
((hMap = CreateFileMapping(hInputFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL))
{
if ((lpbyBase = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)) != NULL)
{
j = 0;
bLast = 0;

for (i = 0; i < (int)dwSize; i++)
{
if (((lpbyBase[i] == 0x24) || (lpbyBase[i] == 0x25)) && (i < (int)dwSize - 1))
{
bLast = lpbyBase[i + 1]; // remember byte pushed last for the "dup" operator
b = bLast ^ szMask[j]; // xor it with the mask table
if (lpbyBase[i] == 0x25) // pushshort uses u30 operand that takes extra byte
{
i++;
}
i++;
WriteFile(hOutputFile, &b, 1, &dwBytesWritten, NULL);
}
else if (lpbyBase[i] == 0x2A) // "dup" operator - invoke the byte pushed last
{
b = bLast ^ szMask[j];
WriteFile(hOutputFile, &b, 1, &dwBytesWritten, NULL);
}
else
{
MessageBox("Not recognized opcode!", NULL, MB_OK);
break;
}

j++;
if (j >= (int)strlen(szMask))
{
j = 0;
}
}
UnmapViewOfFile(lpbyBase);
}
CloseHandle(hMap);
}
CloseHandle(hInputFile);
}
CloseHandle(hOutputFile);
}
return;
}
(ju)