![]() |
||||||||||||
|
|
|||||||||||
Worum geht es?Im letzten Artikel wurden nur
Der Unterschied zwischen Schau Dir als Beispiel diese
private int intTest(){
int a = 1;
return a;
}
Der C# compiler übersetzt sie so:
.method private hidebysig instance int32
intTest() cil managed
{
// Code size 8 (0x8)
.maxstack 1
.locals init ([0] int32 a,
[1] int32 CS$00000003$00000000)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: br.s IL_0006
IL_0006: ldloc.1
IL_0007: ret
} // end of method Form1::intTest
Der Compiler hat eine zweite Variable erzeugt, um den Rückgabewert abzulegen.
Am Ende der Methode wird dieser Wert auf den Stack gelegt, das ist schon alles.
Also wird nichts kaputt gehen, wenn wir ein paar Zeilen zwischen
.method private hidebysig instance int32
intTest() cil managed
{
// Code size 8 (0x8)
.maxstack 2 //Stack-Größe anpassen
.locals init ([0] int32 a,
[1] int32 CS$00000003$00000000)
.locals init (int32 myvalue)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: br.s IL_0006
IL_0006: ldloc.1
.locals init (int32 returnvalue) //Variable hinzufügen
stloc returnvalue //Rückgabewert zwischenspeichern
ldstr "DEBUG - current value is: {0}" //etwas das wie alter Debug-Code aussieht
ldc.i4 111 //Heir ist unser versteckter Wert
box [mscorlib]System.Int32
call void [mscorlib]System.Console::WriteLine(string,
object)
ldloc returnvalue //Rückgabewert dorthin zurücklegen, wo er her kam
IL_0007: ret
} // end of method Form1::intTest
Jetzt kann ILAsm den Code re-compilieren. Wenn man ihn wieder decompiliert, kann man sehen, dass ILAsm die Variablendeklarationen optimiert hat:
.method private hidebysig instance int32
intTest() cil managed
{
// Code size 36 (0x24)
.maxstack 2
.locals init (int32 V_0, //ILAsm hat die lokalen Variablen zusammengefaßt !
int32 V_1,
int32 V_2,
int32 V_3)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: stloc.1
IL_0004: br.s IL_0006
IL_0006: ldloc.1
IL_0007: stloc V_3
IL_000b: ldstr "DEBUG - current value is: {0}"
IL_0010: ldc.i4 0x6f
IL_0015: box [mscorlib]System.Int32
IL_001a: call void [mscorlib]System.Console::WriteLine(string,
object)
IL_001f: ldloc V_3
IL_0023: ret
} // end of method Form1::intTest
ILAsm räumt meine Zeilen auf, ist das nicht nett? Nein, das ist überhaupt nicht nett, denn wir
können uns nicht darauf verlassen, dass unsere eingefügten Zeilen noch vorhanden sind,
nachdem der IL Code compiliert und wieder decompiliert wurde.
Das heisst, was immer wir einfügen, um Teile der geheimen Nachricht zu verstecken, muss einen Sinn ergeben.
Eine zusätzliche Einen Schlüssel-Stream verwendenIm letzten Artikel wurden immer diese zwei Zeilen verwendet, um einen ldc.i4 65; stloc myvalue Wie Du schon oben gesehen hast, können diese Zeilen die gleichen Daten verstecken:
ldstr "DEBUG - current value is: {0}"
ldc.i4 65
box [mscorlib]System.Int32
call void [mscorlib]System.Console::WriteLine(string, object)
Es gibt hunderte solcher Blöcke, welche Variantion sollen wir verwenden? Wir werden alle verwenden, oder - um das Beispiel einfach zu lassen - diese zwei Variationen. Der Benutzer kann eine beliebige Datei angeben, und für jeden Vier-Byte-Block liest die Anwendung ein Byte aus dieser Datei: Wenn es eine gerade Zahl ist, wird die erste Variation verwendet, sonst die Zweite.
private bool ProcessMethodHide(String[] lines, ref int indexLines,
Stream message, Stream key){
//...
//Zeilen fü [bytesPerMethod] Bytes aus dem Nachrichten-Stream einfügen
//jeweils 4 bytes zu einem Int32 kombinieren
int keyValue; //aktueller Wert aus dem Schlüssel-Stream
for(int n=0; n<bytesPerMethod; n+=4){
isMessageComplete = GetNextMessageValue(message, out currentMessageValue);
//nächstes Bytes aus dem Schlüssel lesen
if( (keyValue=key.ReadByte()) < 0){
key.Seek(0, SeekOrigin.Begin);
keyValue=key.ReadByte();
}
if(keyValue % 2 == 0){
//der Schlüssel ist gerade - erste Variation verwenden
writer.WriteLine("ldc.i4 "+currentMessageValue.ToString());
writer.WriteLine("stloc myvalue");
}else{
//der Schlüssel ist ungerade - zweite Variation verwenden
writer.WriteLine("ldstr \"DEBUG - current value is: {0}\"");
writer.WriteLine("ldc.i4 "+currentMessageValue.ToString());
writer.WriteLine("box [mscorlib]System.Int32");
writer.WriteLine("call void [mscorlib]System.Console::WriteLine(string, ");
writer.WriteLine( "object)" ); //ILDAsm fü hier einen Zeilenumbruch ein
}
}
//...
}
Bei der ersten Variation müssen wird die versteckte Konstante in der ersten Zeile suchen, bei der zweiten Variation müssen wir sie aus der zweiten Zeile lesen. Beim Auslesen der versteckten Nachricht müssen wir also die erste Zeile überspringen, wenn das Schlüssel-Byte ungerade ist:
private bool ProcessMethodExtract(String[] lines, ref int indexLines,
Stream message, Stream key){
//[bytesPerMethod] Bytes in den Nachrichten-Stream lesen
//wenn [bytesPerMethod]==0 ist, wurde es noch nicht gelesen
for(int n=0; (n<bytesPerMethod)||(bytesPerMethod==0); n+=4){
if(bytesPerMethod > 0){
//nächstes Bytes aus dem Schlüssel lesen
if( (keyValue=key.ReadByte()) < 0){
key.Seek(0, SeekOrigin.Begin);
keyValue=key.ReadByte();
}
if(keyValue % 2 == 1){
//ldc.i4 steht in der zweiten Zeile des versteckten Blocks
indexLines++;
}
}
//ILDAsm setzt Zeilennummern - Anfange der Anweisung finden
indexValue = lines[indexLines].IndexOf("ldc.i4");
if(indexValue >= 0){
//...
Jetzt können wir Daten verstecken und extrahieren, aber viele re-compilierte
Assemblies stürzen mit einer
.maxstack 1 //eine kleine Methode verwendet nur eine Variable auf einmal
...
ldstr "DEBUG - current value is: {0}"
ldc.i4 0x6f //wir versuchen, einen zweiten Wert auf den Stack zu laden
box [mscorlib]System.Int32
call void [mscorlib]System.Console::WriteLine(string,
object)
Deshalb müssen wir sicherstellen, dass der
CopyBlock(lines, startIndex, endIndex);
//...
private void CopyBlock(String[] lines, int start, int end){
String[] buffer = new String[end-start];
Array.Copy(lines, start, buffer, 0, buffer.Length);
writer.WriteLine(String.Join(writer.NewLine, buffer));
}
Jetzt müssen wir die
private void CopyBlockAdjustStack(String[] lines, int start, int end){
for(int n=start; n<end; n++){
if(lines[n].IndexOf(".maxstack ")>0){
//Stack-Größe lesen
int indexStart = lines[n].IndexOf(".maxstack ");
int maxStack = int.Parse( lines[n].Substring(indexStart+10).Trim() );
//maxstack muss 2 oder größer sein
if(maxStack < 2){
lines[n] = ".maxstack 2";
}
}
writer.WriteLine(lines[n]);
}
}
Rückgabewerte behandelnDer Rückgabetyp einer Methode wird im Header deklariert, das heisst wir müssen ihn lesen und zwischenspeichern, sobald wir eine neue Methde betreten:
private String GetReturnType(String line){
String returnType = null;
if(line.IndexOf(" void ") > 0){ returnType = "void"; }
else if(line.IndexOf(" bool ") > 0){ returnType = "bool"; }
else if(line.IndexOf(" int32 ") > 0){ returnType = "int32"; }
else if(line.IndexOf(" string ") > 0){ returnType = "string"; }
return returnType;
}
private bool ProcessMethodHide(String[] lines, ref int indexLines,
Stream message, Stream key){
//..
//Rückgabewert der aktuellen Methode lesen
String returnType = GetReturnType(lines[indexLines]);
if(returnType != null){
//void/bool/int32/string-Methode gefunden
//...
//Position des letzten ".locals init" und ersten "ret" suchen
positionInitLocals = positionRet = 0;
SeekLastLocalsInit(lines, ref indexLines,
ref positionInitLocals, ref positionRet);
//...
//Rest der Methode bis zur Zeile vor "ret" kopieren
CopyBlockAdjustStack(lines, indexLines, positionRet);
//nächste Zeile ist "ret" - auf dem Stack kann nichts kaputt gehen
indexLines = positionRet;
if(returnType != "void"){
//not a void method - store the return value
writer.Write(writer.NewLine);
writer.WriteLine(".locals init ("+returnType+" returnvalue)");
writer.WriteLine("stloc returnvalue");
}
//Zeile für [bytesPerMethod] Bytes vom Nachrichten-Stream einfügen
//4 Bytes zu einem Int32 kombinieren
int keyValue;
for(int n=0; n<bytesPerMethod; n+=4){
//...
}
//...
if(returnType != "void"){
//keine void-Methode - Rückgabewert zurück auf den Stack laden
writer.WriteLine("ldloc returnvalue");
}
//...
} //else diese Methode auslassen
}
Wir müssen die Zeile
private bool ProcessMethodExtract(String[] lines, ref int indexLines,
Stream message, Stream key){
bool isMessageComplete = false;
int positionRet, //index der "ret"-Zeile
positionStartOfMethodLine; //index der ersten Zeile
String returnType = GetReturnType(lines[indexLines]);
int keyValue = 0;
if(returnType != null){
//void/bool/int32/string-gethode gefunden
//ein Teil der Nachricht ist hier versteckt
//...
//Position des "ret" suchen
positionRet = SeekRet(lines, ref indexLines);
if(bytesPerMethod == 0){
//zwei Zeilen zurück gehen - dort haben wir "ldc.i4 "+bytesPerMethod eingefügt
indexLines = positionRet - 2;
}else{
//[linesPerMethod] Zeilen pro erwartetem Nachrichten-Byte zurück gehen
//dort haben wir "ldc.i4 "+currentByte eingefügt
linesPerMethod = GetLinesPerMethod(key);
indexLines = positionRet - linesPerMethod;
}
if(returnType != "void"){
indexLines--; //die Zeile "ldloc returnvalue" überspringen
}
//...
}
}
Jetzt können wir eine Schlüssel-Datei anwenden, und die meisten Methoden ausnutzen.
Wenn Du mehr Methoden verwenden möchtest, brauchst Du nur die Methode |