Ich, der Assembler

Was tut man mit einem Stück Closed-Source Software, was den einen Vorteil hat genau das zu tun was man will (und nicht mehr), aber leider den Nachteil regelmäßig abzustürzen?
Und was tut man, wenn dieses Stück der KernelMode-Treiber einer Paketfilter-Firewall ist und „abstürzen“ bedeutet dass man einen Bluescreen bekommt?

Richtig, man holt WinDbg und IDA raus und fängt an den Fehler zu suchen 😉 Continue reading „Ich, der Assembler“

Trust in Me!

Nehmen wir mal an, ein (Delphi-)object, welches eine Liste gleicher Objekte verwalten soll. Man könnte das einen Baum nennen.

  TValueList = array of TValue;
  TValue = object
    FList: TValueList;
    SomeOtherStuff: integer;
  end;

(Für später geborene: das ist die alte Syntax für „record mit Methoden“.)

Leider nimmt uns der Compiler das so nicht ab. Warum? Weil TValue natürlich vorher noch nicht bekannt ist. Und im Gegensatz zu Klassen kann man Records nicht vorwärtsdeklarieren. Wie also könnten wir dieses Problem lösen? Man könnte Pointer verwenden, und bei Verwendung entsprechend erzeugen. Aber freigeben? Es gibt ja keinen Destructor, also keine Option. Man könnte das gesamte Objekt in eine Klassen-Instanz verwandeln, die von IUnknown die Referenzzählung erbt. Das ist aber wesentlich mehr Aufwand als das bisherige „Deklarieren und Verwenden“.

Die Lösung hier zeigt sich in einem fiesen, aber einfachen Hack:

TValueListAlias = array of boolean;
TValue = object
  FList: TValueListAlias;
  SomeOtherStuff: integer;
end;
TValueList = array of TValue;

Dabei spielt es keine Rolle, welchen Basistypen man für das Alias verwendet. Es muss sich nur um ein dynamisches Array handeln. Jetzt kann man in jedem Zugriff auf das eigentliche TValueList casten und alles funktioniert. Dabei kann man die Hässlichkeiten wunderbar in Zugriffsmethoden verbergen.

SetLength(TValueList(FList), 42);

Das funktioniert auch, aber wenn TValue nun Felder die finialisiert werden müssen enthält, wird sich FastMM beschweren, dass eben diese nie freigegeben werden. Es stellt sich heraus, dass SetLength zwar den BaseType richtig speichert und auch in @DynArrayClear exakt diesen wieder beräumt, aber Felder im Gegensatz zu Variablen nicht damit, sondern mit FinalizeArray freigegeben werden. Und dieses richtet sich nach der Deklaration, nicht nach dem was wirklich passiert.

Hier kommt dann der eigentliche Grund für diesen Beitrag: wir sagen der RTL einfach: „Vertrau mir, das ist ein anderer Datentyp!“. Mit anderen Worten: wir patchen die Feld-Tabelle der betroffenen Klasse so, dass FinalizeArray dort ein TValueList (BaseType also TValue, nicht unser Dummy von weiter oben) sieht und dieses korrekt finalisiert.

Das ist zunächst einfacher als gedacht:

TI:= TypeInfo(TValue);
FT := Pointer(Integer(TI) + Byte(TI.Name[0]));
for I := FT.Count-1 downto 0 do begin
  if FT.Fields[I].TypeInfo^ = TypeInfo(TValueListAlias) then
    ppti:= FT.Fields[I].TypeInfo^:= TypeInfo(TValueList);
end;

Nur – das funktioniert so nicht 😉 Runtime Error 216 ist die Folge einer Exception beim Schreiben der neuen TypeInfo. Da hat nämlich mal jemand mitgedacht und die dazugehörige Seite als PAGE_EXECUTE_READ markiert. Was man zum Glück in seinem eigenen Prozess beliebig ändern kann, und so ergibt sich (mit allen Deklarationen) folgendes Meisterwerk:

procedure FixFieldTable(TheRecord, Find, Replace: PTypeInfo);
type
  TFieldInfo = packed record
    TypeInfo: PPTypeInfo;
    Offset: Cardinal;
  end;
 
  PFieldTable = ^TFieldTable;
  TFieldTable = packed record
    X: Word;
    Size: Cardinal;
    Count: Cardinal;
    Fields: array [0..0] of TFieldInfo;
  end;
var
  FT: PFieldTable;
  ppti: PPTypeInfo;
  I, old, dummy: cardinal;
begin
  FT := Pointer(Integer(TheRecord) + Byte(TheRecord.Name[0]));
  for I := FT.Count-1 downto 0 do begin
    if FT.Fields[I].TypeInfo^ = Find then begin
      ppti:= FT.Fields[I].TypeInfo;
      VirtualProtect(ppti,SizeOf(ppti), PAGE_READWRITE, old);
      try
        ppti^:= Replace;
      finally
        VirtualProtect(ppti, sizeof(ppti), old, dummy);
      end;
    end;
  end;
end;
{...}
 
  FixFieldTable(TypeInfo(TValue), TypeInfo(TValueListAlias), TypeInfo(TValueList));

Wer sich die Datenstruktur ansieht, wird feststellen dass TFieldTable ein Feld namens „Size“ enthält. Sollten wir das nicht anpassen? Die einfache Antwort ist: nein! In TValue selbst wird vom dynamischen Array nur ein Pointer auf den Anfang gespeichert, und der ist unabhängig vom genauen Aussehen des Arrays immer gleich groß: nämlich 32bit oder 4 Byte. Es ändert sich also nichts.
Außerdem praktisch: da SetLength den Array-Typ anlegt, den man ihm übergibt (und wir ja auf TValueList casten), werden auch untergeordnete Arrays als das freigegeben was in ihnen steckt.

Das funktioniert so bis mindestens BDS2006, ich kann mir aber vorstellen dass die neuen RTTI-Strukturen in XE hier einige Änderungen notwendig machen. Das müssen dann aber andere testen 😉

Oh, übrigens, wer es nicht erkannt hat: der Titel ist natürlich eine Referenz auf Disney’s Dschungelbuch.

Kleine Hacks erhalten die Freundschaft

Heute mal wieder was aus der Bastelkiste:

Unser Institut für Mechanik hat für Belege und Praktika ein schickes kleines Delphi-Programm, welches die Aufgabenblätter generieren kann. Das ganze hat den Zweck, dass die Aufgaben sich mit der Matrikelnummer personalisieren lassen (RandSeed) und man seine berechneten Werte mittels dieses Programms direkt auf Korrektheit prüfen lassen kann.

Nun sind die Aufgaben nicht immer so ganz ohne, und manchmal hilft es ungemein, nicht nur Eingabefelder zu haben, die einem sagen ob man richtig oder falsch lag, sondern auch mal spicken zu können, wo denn der Fehler liegt.

Aber erstmal eine kleine Anekdote.
Man sitzt also in einer Vorlesung, und macht nebenbei mit einem Kommilitonen ein solches Übungsbelegblatt. Die Gleichungen sehen auch erstmal alle ganz schön aus. Nun hat man ja einen Laptop dabei, kann also die Ergebnisse kontrollieren. Und siehe da: nix passt.
Was tut man also? Man versucht herauszubekommen, was das Programm denn als Zahlenwert erwartet. Irgendwie dachte ich mir (warum, weiß ich auch nicht; vielleicht wars ja Schicksal 😛 ) dass mir ein Hex-Editor weiterhelfen könnte. Konnte er natürlich nicht, hat aber soweit geholfen, als dass ich festgestllt habe, dass es zu jedem Aufgabenblatt 2 Definitionen gibt. Eine generiert die Aufgabenstellung, und eine generiert… Moment. Was ist das? Ein Lösungsweg!
Es gibt also nicht nur 2 Programmversionen (wie schon das _student im Dateinamen vermuten lässt), sondern zu allem Überfluss enthält die selbe Echse offenbar beide Teile. Irgendwo muss also umgeschaltet werden, was gemacht werden soll.

Nachdem man seinen IDA Pro wiedergefunden und gestartet hat, wird also mal das Programm hineingeworfen. Etwas warten, und die Programmstelle wieder finden. Das macht sich recht einfach, immerhin ist der Titel des Belegs ein sehr eindeutiges Merkmal (im Code-Segment jedenfalls). Nun muss man nur noch die XRefs zurückverfolgen, um rauszubekommen wo der wichtige Aufruf erfolgt. Der ist nämlich da, wo basierend auf einem Vergleich der eine oder andere Zweig (=Ausgabeanweisungen) aufgerufen wird. It always boils down to a simple comparision.
Der letzte Schritt (wo allerdings leider die Vorlesung zuende war; der Teil mit dem „IDA wiederfinden“ hatte etwas lange gedauert) ist es nun nur noch, die Zuweisung an die überprüfte Variable zu finden. Auch hier hilft wieder die XRefs-Funktion. Es gibt nämlich nur eine, und die schreibt eine $00, also den Ordinalwert für false. Man könnte also sagen, die betreffende Variable ist sowas wie IsTutorenVersion.
Kurzer Patch in der Exe der (wir wissen ja: es ist Delphi) eine $01 (für true) stattdessen schreibt, und fertig.

Wer das liest und ein berechtigtes Interesse hat, der hat auch Wege mich nach dem Teil zu fragen. Das ist nun grade ein Hack, bei dem ich garantiert kein Responsible Disclosure machen werde. Das wäre nämlich sehr verantwortungslos. Mir selbst gegenüber 😀

Das wars erstmal von meiner Seite,
Martok