Ein Treeview im Speicher

Der folgende Artikel bezieht sich auf die Betriebsysteme Windows2000/XP. Die hier aufgeführten Tests wurden unter Windows2000 unter einem Administratoraccount durchgeführt.
Für das Nachvollziehen der hier gemachten Aussagen sind folgende Zusatzprogramme erforderlich:
- Tasks and Token
- Eine Version von XProfan.
- Profan2Cpp

Als Ausgangspunkt für unsere Betrachtungen soll einmal folgender Quelltext herhalten:

Declare Treeview3&,Tv_insert#,Tv_text$,Parent&,Child&,Neu&

Windowstyle 31

Windowtitle "Treeviewtest"

Window 0,0-640,440

Dim Tv_insert#,48

Treeview3&=@Control("SysTreeView32","",$50A10832,10,100,600,300,%Hwnd,2603,%Hinstance)

Clear Tv_insert#

Let Tv_text$="Root" 'Der Text im Label.

Long Tv_insert#,0=0 'Der Eintrag ist der erste Eintrag im Treeview.

Long Tv_insert#,4=$Ffff0002'Der Eintrag kommt an den Schluß.

Long Tv_insert#,8=$0001'Nur Text, keine weiteren Angaben zum Eintrag.

Long Tv_insert#,24=@Addr(Tv_text$) 'Adresse des Textes des Eintrags.

Long Tv_insert#,28=$Ffff'Maximale Länge des Eintrags.

@Sendmessage(Treeview3&,$1100,0,Tv_insert#)'Message zum Einfügen eines Eintrages.

Print "Handle des angezeigten Treeviews: "+@Str$(Treeview3&)

Treeview3&=@Val(@Input$("Handle eines Treeviews eingeben:","Handle",@Str$(Treeview3&)))

Let Parent&=@Sendmessage(Treeview3&,$110A,0,0)

Print "Handle des Rootitems des eingegebenen Treeviews: "+@Str$(Parent&)

Child&=@Sendmessage(Treeview3&,$110A,$4,Parent&)

Print "Handle des ersten Kinditems des eingegebenen Treeviews: "+@Str$(Child&)

Print "Zeit="+@Time$(0)'Daten werden zur Kontrolle ausgegeben.

Clear Tv_insert#

Tv_text$=@Time$(0)+" Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! "

Tv_text$=Tv_text$+"Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! "

Tv_text$=Tv_text$+"Dies ist ein Treeview-Test! Dies ist ein Treeview-Test! "

Tv_text$=Tv_text$+"Dies ist ein Treeview-Test!"

Long Tv_insert#,0=Parent& 'Der zweite Eintrag wird unter dem Rooteintrag erstellt.

Long Tv_insert#,4=$Ffff0002 'Der Eintrag kommt an den Schluß.

Long Tv_insert#,8=$0001'Nur Text, keine weiteren Angaben zum Eintrag.

Long Tv_insert#,24=@Addr(Tv_text$) 'Adresse des Textes

Long Tv_insert#,28=$Ffff'Maximale Länge des Eintrags.

Neu&=@Sendmessage(Treeview3&,$1100,0,Tv_insert#) 'Message zum Einfügen eines Eintrages.

While 0=0

Waitinput

Wend

Dispose Tv_insert# 'Müll wegräumen...


Bitte einfach immer OK drücken und vorerst im Eingabefeld nichts verändern. Bei mir sieht die Geschichte nach einem Doppelklick auf den Eintrag ROOT dann so aus:

Uns soll vorerst einmal nicht der Rooteintrag, sondern der Eintrag mit der Uhrzeit darunter interessieren. Das von der Message TVM_INSERTITEM zurückgegebene Handle dieses Eintrags lautet bei mir 36687824.
Nun kommt wieder Tasks and Token ins Spiel. Nachdem wir im Treeview von Tasks and Token den gerade erzeugten Prozess angeklickt haben, machen wir mal wieder das altbekannte Spiel und tun ganz frech so, als wäre das Handle eine Adresse. Dazu lassen wir uns die Heaps des Prozesses listen - wenn man das Programm nicht mit Profan2Cpp compiliert, kann das Listing etwas nachdem die Heapblockadresse die dem Handle entspricht (36687824) erreicht wurde, mit der Taste 'PAUSE' abgebrochen werden. Nun suchen wir mal die Heapblockstartadresse, die dem Handle des Treevieweintrags entspricht und lassen uns den Inhalt des Blocks als dezimale Doublewords anzeigen:

Folgendes wird bei mir angezeigt:

X1=36685936

X2=0

X3=0

X4=36688672

X5=0

X6=0

X7=0

X8=131071

X9=-1412628479

X10=47382540

X11=393242

X12=524544


X1 ist hier scheinbar das Handle des übergeordneten Eintrags, das ist leicht ersichtlich. Nun schauen wir uns mal X4 an. Wir tun wieder so, als wäre das Doubleword X4 eine Adresse (hoffentlich wirds nicht langweilig) und schauen nach, ob wir einen Heapblock finden, dessen Startadresse dieser Adresse (bei mir 3668872) entspricht und lassen uns dann den Inhalt des Heapblocks als String darstellen. So sieht's bei mir aus:

Wie man sieht, hier steht der Text des Treevieweintrages. Das Handle, das wir durch das Senden der Message TVM_INSERTITEM und das damit verbundene Erstellen eines neuen Treevieweintrags erhalten, ist also also das Handle des Heapblocks, in dem der Eintrag steht - und das Handle entspricht genau der Startadresse des Blockes. Eingentlich sieht das ja ganz unspektakulär aus, ist es aber nicht - denn was wichtig ist, ist die Tatsache daß ich hier durch einfaches senden von Messages eine Adresse eines Speicherbereiches - vielleicht auch innerhalb eines fremden Prozesses - erhalte. Eine weitere, sehr interessante Geschichte ist, daß sich der Heapblock mit dem Text des Items nur wenig oberhalb dieser Adresse befindet.
Jetzt wird etwas phantasiegeladen: Wenn es nun möglich wäre, einen Treevieweintrag in einem fremden Prozess zu erzeugen, würde mir mit der Erzeugung gleich eine Adresse mitgeliefert werden. Statt Text könnte ich beim Erzeugen eine DLL mit Quelltext übertragen und die beim Senden der Message TVM_INSERTITEM erhaltene Adresse könnte ich als Ausgangspunkt dafür nehmen, den in der DLL enthaltenen Quelltext (mittels WM_TIMER, eine Sache die ich schon erfolgreich mit einem Multiedit durchgeführt habe) anzusprechen - und das ohne irgendwelche Schreib- oder Leserechte auf den Prozess zu haben!
Aber geht das wirklich? Mal schauen...
Dazu müssen wir einfach den schon bekannten Quelltext zweimal starten. Beim ersten Start klicken wir beim Eingabefeld auf OK, beim zweiten Start geben wir das Handle des Treeviews des ersten Prozesses (nicht des Eintrages) ein und bestätigen dann mit OK: Zwischen den Starts der beiden Prozesse müssen unbedingt einige Minuten liegen, das ist sehr wichtig. Das hier zeigt bei mir dann der zuerst gestartete Prozess an:

Mmh - wenn man nicht ganz genau hinsieht, war unser vorhaben erfolgreich. Was bei näherer Betrachtung aber auffällt - die Uhrzeiten der beiden Einträge müßten ja unterschiedlich sein, sind sie aber nicht! Was ist genau geschehen? Von mir gibt's hier folgende Erklärung: Was beim Senden einer Message übertragen wird sind immer nur vier Doublewords - in unserem Fall wurde hier als vierter Parameter ein Pointer, also die Startadresse der Struktur TV_INSERT#, übertragen. Der Empfänger der Message (Prozess Nummer eins) bezieht die mit der Message übertragene Adresse komplett auf seinen Adressbereicht. Da es sich bei den beiden Prozessen um das gleiche Programm handelt, steht an dieser Adresse ebenfalls noch die Struktur TV_INSERT# - was in TV_TEXT$ steht, unterscheidet sich aber.

Fazit:
- a) Messages übertragen keine Strukturen sondern nur Adressen, die sich auf den jeweils aktuellen Prozess beziehen.
- b) Das Handle eines Treeview Eintrages entspricht dessen virtueller Adresse im Speicherbereich des jeweiligen Prozesses. Einträge in Treeviews können mittels dieser Adresse direkt im Speicher geändert werden. Das Senden von Messages kann mittels solcher direkter Schreibzugriffe recht einfach umgangen werden.

Wie aus Abschnitt b) ersichtlich ist, stellt die in Treeviews verwendete Rückgabe eines Handles bei TVM_INSERTITEM und TVM_GETNEXTITEM ein meiner Meinung nach nicht zu unterschätzendes Sicherheitsproblem da, das beim Schreiben von sicherheitsrelevanten Anwendungen unbedingt berücksichtigt werden sollte!


Impressum