Zugriff auf Stringresource finden

Hi,

ich habe ein Programm welches ich bisher problemlos selbst gecrackt bekommen habe. Hier ist der Testzeitraum sowie die Anzahl der zu erstellenden Dokumente eingeschränkt. Nun hat sich bei der aktuellen Version aber etwas geändert womit ich nicht mehr zurecht komme.

Bisher habe ich mir im Ollydbg die Stringreferenzen anzeigen lassen und darin nach Strings wie "Die maximale Anzahl von Dokumenten in der Demoversion wurde erreicht" gesucht, und dann die entsprechende Sprungbedingungen darüber geändert. In der aktuellen Version werden mir aber diese Stringreferenzen nicht mehr in Ollydbg angezeigt, da wurde also irgendetwas geändert.

Ich habe die Strings aber im Resource Hacker in den String-Tables gefunden ! Wie kann ich denn jetzt aber im Ollydbg wieder die Stellen finden an denen auf einen bestimmten String in den String-Tables zugegriffen wird ?? Ein String liegt beispielsweise im Stringtable 3618 und hat dort die Nummer 57883. Wie finde ich jetzt im Olly eine Referenz darauf ??

Ich wäre echt für jeden Tip dankbar.

Gruss Holly
 
Eine Reihe an Funktionen sind unter Windows dafür verantwortlich Ressourcen zu laden.
Siehe z.B.:
Finding and Loading Resources (Windows)

Hast du jetzt eine Idee wie du dem Laden des gesuchten Strings auf die Spur kommen könntest?

PS: Nicht vergessen eine gültige Lizenz für die betroffene Software zu erwerben bevor man die Sicherheit von Kopierschutzmechanismen untersucht.
 
Hi,

danke schonmal für den Schubs in die richtige Richtung. Jedoch hab ich immer noch kein Schimmer wie ich alle Aufrufe in Ollydbg finde, wo auf den String zugegriffen wird.

Zuständig ist wohl die Funktion LoadString und diese erwartet als zweiten Parameter die ID des Strings aus dem Stringtable. In meinem Beispiel also 57883d bzw. E21Bh. Wäre die ID nur ein Byte gross würde ich mit Olly nach push FF für ID 255 suchen, aber wie mach ich das wenn die ID zwei Byte gross ist ? Ein push E21B gibt es ja nicht.

Setze ich einen Breakpoint auf LoadStringW sehe ich auch sowas in Olly
Code:
0018F2E8   00400000  |hInst = 00400000
0018F2EC   0000EF42  |RsrcID = STRING "Wahr"
0018F2F0   0C9807AC  |Buffer = 0C9807AC
0018F2F4   00000400  \Count = 400 (1024.)
Hier wurde der String "Wahr" mit der ID EF42 aus dem StringTable geholt, ich erkenne aber nicht wo die ID im Code an die Funktion übergeben wurde !! X(
 
Wäre die ID nur ein Byte gross würde ich mit Olly nach push FF für ID 255 suchen, aber wie mach ich das wenn die ID zwei Byte gross ist ? Ein push E21B gibt es ja nicht.

Das ist ja auch egal, denn LoadString erwartet ja auch ein UINT, was ein DWORD groß ist, wenn es eine x86 Executable ist.

LoadString function (Windows)

(Ein PUSH WORD gibt es aber tatsächlich auch mittels dem Operandengrößen-Prefix 0x66)

Hier wurde der String "Wahr" mit der ID EF42 aus dem StringTable geholt, ich erkenne aber nicht wo die ID im Code an die Funktion übergeben wurde !! X(

Schau dir doch mal die Stelle an von wo die Funktion aufgerufen wurde. Die RETN Adresse liegt ja ganz oben auf dem Stack, wenn du am Anfang der Funktion breakst.
Oder am besten drückst du bei aktiviertem Breakpoint so lange F9 bis dein gesuchter String geladen wird und steppst weiter bis die Funktion zurückkehrt und dann bist du an der Stelle im Programm, wo der String geladen wurde.
 
Kann es sein, dass ich mit dem was ich grade versuche komplett auf dem Holzweg bin, und so mein Ziel nie erreiche ??? :rolleyes:

In den vorherigen Versionen war es ja so, dass der Text "Die maximale Anzahl von Dokumenten in der Demoversion wurde erreicht", welcher beim Versuch ein weiteres Dokument anzulegen in eines Messagebox erschien, in den Stringreferenzen von Olly zu finden war. Innerhalb der Stringreferenzen hab ich mich mit STRG+L durchgehangelt, den String doppeltgeklickt und dann im Codefenster etwas hochgescrollt. Hier gab es dann einige wenige Zeilen über dem String eine Abfrage z.B. je ..... die ich in jmp ..... geändert habe. Die Anzeige der Messagebox wurde dann immer übersprungen und ein neues Dokument wurde angelegt. Hiervon gab es bisher immer ca. 10 Stellen, für verschiedene Arten von Dokumenten, die ich auf die Weise ändern musste. Danach funktionierte das Programm ohne Einschränkungen.

In der aktuellen Version wurden aber sämliche Strings als Resource abgelegt, wohl um das Programm demnächst mehrsprachig zu machen. Den String "Die maximale Anzahl von Dokumenten in der Demoversion wurde erreicht" gibts nun im Stringtable 3 mal.

Nun war mein Gedanke, such die Stellen wo die Strings aus dem Stringtable geholt werden, das müsste dann ja wie in den Vorgängerversionen ca. 10 mal der Fall sein, und ändere die darüber liegende Sprungbedingung wie zuvor.

Ich finde vergleichbare Stellen aber trotz deiner Hinweise nicht. Ist es denn für meinen Fall der richtige Weg einen Breakpoint auf LoadStringW zu setzen ?? So sieht das aus wenn Olly breakt
Code:
0140AA7C  /$ 55             PUSH EBP
0140AA7D  |. 8BEC           MOV EBP,ESP
0140AA7F  |. 81C4 F0FBFFFF  ADD ESP,-410
0140AA85  |. 33C9           XOR ECX,ECX
0140AA87  |. 898D F0FBFFFF  MOV DWORD PTR SS:[EBP-410],ECX
0140AA8D  |. 8955 F8        MOV DWORD PTR SS:[EBP-8],EDX
0140AA90  |. 8945 FC        MOV DWORD PTR SS:[EBP-4],EAX
0140AA93  |. 33C0           XOR EAX,EAX
0140AA95  |. 55             PUSH EBP
0140AA96  |. 68 D2AB4001    PUSH MeinProgramm.0140ABD2
0140AA9B  |. 64:FF30        PUSH DWORD PTR FS:[EAX]
0140AA9E  |. 64:8920        MOV DWORD PTR FS:[EAX],ESP
0140AAA1  |. 837D FC 00     CMP DWORD PTR SS:[EBP-4],0
0140AAA5  |. 0F84 0E010000  JE MeinProgramm.0140ABB9
0140AAAB  |. 8B45 FC        MOV EAX,DWORD PTR SS:[EBP-4]
0140AAAE  |. 8178 04 000001>CMP DWORD PTR DS:[EAX+4],10000
0140AAB5  |. 7C 13          JL SHORT MeinProgramm.0140AACA
0140AAB7  |. 8B45 F8        MOV EAX,DWORD PTR SS:[EBP-8]
0140AABA  |. 8B55 FC        MOV EDX,DWORD PTR SS:[EBP-4]
0140AABD  |. 8B52 04        MOV EDX,DWORD PTR DS:[EDX+4]
0140AAC0  |. E8 2BC1FFFE    CALL MeinProgramm.00406BF0
0140AAC5  |. E9 D1000000    JMP MeinProgramm.0140AB9B
0140AACA  |> 803D 64CAB401 >CMP BYTE PTR DS:[1B4CA64],0
0140AAD1  |. 75 3A          JNZ SHORT MeinProgramm.0140AB0D
0140AAD3  |. 68 00040000    PUSH 400
0140AAD8  |. 8D85 F4FBFFFF  LEA EAX,DWORD PTR SS:[EBP-40C]
0140AADE  |. 50             PUSH EAX
0140AADF  |. 8B45 FC        MOV EAX,DWORD PTR SS:[EBP-4]
0140AAE2  |. 8B40 04        MOV EAX,DWORD PTR DS:[EAX+4]
0140AAE5  |. 50             PUSH EAX
0140AAE6  |. 8B45 FC        MOV EAX,DWORD PTR SS:[EBP-4]
0140AAE9  |. 8B00           MOV EAX,DWORD PTR DS:[EAX]
0140AAEB  |. 8B00           MOV EAX,DWORD PTR DS:[EAX]
0140AAED  |. E8 2ED5FFFE    CALL MeinProgramm.00408020
0140AAF2  |. 50             PUSH EAX                                 ; |hInst
0140AAF3  |. E8 30FCFFFE    CALL <JMP.&user32.LoadStringA>           ; \LoadStringA
0140AAF8  |. 8BC8           MOV ECX,EAX
0140AAFA  |. 8D95 F4FBFFFF  LEA EDX,DWORD PTR SS:[EBP-40C]
0140AB00  |. 8B45 F8        MOV EAX,DWORD PTR SS:[EBP-8]
0140AB03  |. E8 18C0FFFE    CALL MeinProgramm.00406B20
0140AB08  |. E9 8E000000    JMP MeinProgramm.0140AB9B
0140AB0D  |> 8B45 F8        MOV EAX,DWORD PTR SS:[EBP-8]
0140AB10  |. E8 9FBFFFFE    CALL MeinProgramm.00406AB4
0140AB15  |. 33C0           XOR EAX,EAX
0140AB17  |. 8945 F4        MOV DWORD PTR SS:[EBP-C],EAX
0140AB1A  |. EB 65          JMP SHORT MeinProgramm.0140AB81
0140AB1C  |> 8B45 F8        /MOV EAX,DWORD PTR SS:[EBP-8]
0140AB1F  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB21  |. E8 02C2FFFE    |CALL MeinProgramm.00406D28
0140AB26  |. 85C0           |TEST EAX,EAX
0140AB28  |. 75 0F          |JNZ SHORT MeinProgramm.0140AB39
0140AB2A  |. 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
0140AB2D  |. BA 00040000    |MOV EDX,400
0140AB32  |. E8 61C5FFFE    |CALL MeinProgramm.00407098
0140AB37  |. EB 16          |JMP SHORT MeinProgramm.0140AB4F
0140AB39  |> 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
0140AB3C  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB3E  |. E8 E5C1FFFE    |CALL MeinProgramm.00406D28
0140AB43  |. 8BD0           |MOV EDX,EAX
0140AB45  |. 03D2           |ADD EDX,EDX
0140AB47  |. 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
0140AB4A  |. E8 49C5FFFE    |CALL MeinProgramm.00407098
0140AB4F  |> 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
0140AB52  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB54  |. E8 CFC1FFFE    |CALL MeinProgramm.00406D28
0140AB59  |. 50             |PUSH EAX
0140AB5A  |. 8B45 F8        |MOV EAX,DWORD PTR SS:[EBP-8]
0140AB5D  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB5F  |. E8 B4C1FFFE    |CALL MeinProgramm.00406D18
0140AB64  |. 50             |PUSH EAX
0140AB65  |. 8B45 FC        |MOV EAX,DWORD PTR SS:[EBP-4]
0140AB68  |. 8B40 04        |MOV EAX,DWORD PTR DS:[EAX+4]
0140AB6B  |. 50             |PUSH EAX
0140AB6C  |. 8B45 FC        |MOV EAX,DWORD PTR SS:[EBP-4]
0140AB6F  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB71  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB73  |. E8 A8D4FFFE    |CALL MeinProgramm.00408020
0140AB78  |. 50             |PUSH EAX                                ; |hInst
0140AB79  |. E8 B2FBFFFE    |CALL <JMP.&user32.LoadStringW>          ; \LoadStringW
0140AB7E  |. 8945 F4        |MOV DWORD PTR SS:[EBP-C],EAX
0140AB81  |> 8B45 F8         MOV EAX,DWORD PTR SS:[EBP-8]
0140AB84  |. 8B00           |MOV EAX,DWORD PTR DS:[EAX]
0140AB86  |. E8 9DC1FFFE    |CALL MeinProgramm.00406D28
0140AB8B  |. 3B45 F4        |CMP EAX,DWORD PTR SS:[EBP-C]
0140AB8E  |.^74 8C          \JE SHORT MeinProgramm.0140AB1C
Breakpoints hab ich auf 140AB79 und 140AA7C, letzteres hab ich als Anfang der Funktion gedeutet. Wenn Olly da breakt steht da immer
Code:
EBP=0018F734
Local call from 0140AA1D
egal welche StringID geladen wird.

Was mach ich da falsch ?? Und bitte bitte etwas dauhafter erklären, ich bin kein Profi. ;)
 
Du machst gar nichts so wirklich falsch. Du musst dich eigentlich nur ein bisschen reindenken in die Funktionsweise des Programms und dann die Spuren weiterfolgen.

Fassen wir noch einmal zusammen. Die Strings werden also alle an der selben Stelle geladen. Das heißt das Programm hat eine Funktion, die dafür zuständig ist und das ist wohl die 0x0140AA7C.
Außerdem müssen die Strings ja irgendwo hin. Wir erinnern uns:

Das dritte Argument der Funktion LoadString() ist der Buffer, wo der String reinkommt. Der geht ja nicht ins Nirvana:

_Out_ LPTSTR lpBuffer

Deine Aufgabe:
Wo wird das dritte Argument auf den Stack geschoben? Das müsste ja ein Pointer zu einem Buffer sein, der den String am Ende enthält. Alles was später diesen String in diesem Buffer benutzt ist ja die Stelle die du suchst. Beispielsweise könntest du es dir bequem machen und dir ein Hardware Breakpoint auf diesen Buffer setzen.

Also. Du hast doch die Stelle wo der String geladen wird. Jetzt verfolge die Benutzung dieses Strings. Das wäre wohl die einfachste Möglichkeit.
 
Du machst gar nichts so wirklich falsch. Du musst dich eigentlich nur ein bisschen reindenken in die Funktionsweise des Programms und dann die Spuren weiterfolgen.
Das hab ich ja gemacht. Das Programm ist mit Delphi geschrieben, hatte ich noch nicht erwähnt, vielleicht spielt es eine Rolle.

Bisher war es so
Code:
If not (IsFullversion) then
   Begin
     Messagebox("... maximale Anzahl in der Demo wurde erreicht ...");
     Break;
   end
else
   ErstelleNeuesDokument;
Hier habe ich im Olly einfach nach den Strings "... maximale Anzahl in der Demo wurde erreicht ..." gesucht und dafür gesorgt, dass immer zu ErstelleNeuesDokument gesprungen wird. Das war recht simpel und musste ca. an 10 Stellen im Code gemacht werden.

Nun läuft es scheinbar so
Code:
If not (IsFullversion) then
   Begin
     Messagebox(ReadFromStringTable($E21B));
     Break;
   end
else
   ErstelleNeuesDokument;
In der Funktion ReadFromStringTable wird geprüft mit welcher Sprache das System läuft und dann der String mit der übergebenen ID aus dem StringTable zurückgegeben. Derzeit ist da nur deutsch enthalten.

Fassen wir noch einmal zusammen. Die Strings werden also alle an der selben Stelle geladen. Das heißt das Programm hat eine Funktion, die dafür zuständig ist und das ist wohl die 0x0140AA7C.
Genau darum dachte ich ja, dass ich an dieser Stelle auf dem falschen Weg bin !
Außerdem müssen die Strings ja irgendwo hin.
Ja, und die Erkenntniss welcher String durch die Funktion aus dem StringTable gelesen werden soll ja irgendwo herkommen. Nach meinem Verständniss müsste die programmeigene Funktion ja, wie in meinem obigen Beispiel, mit einem Parameter ID aufgerufen werden. Wenn ich nun in den StringTables 3 ID´s gefunden habe die für besagte Meldungen genutzt werden können, müsste ich im Code doch nur die Stellen finden wo diese ID´s übergeben werden oder ??

Ich habe ein paar tuts im Netz gefunden wo das so gemacht wird aber dort sind die Id´s immer 1 Byte gross. Was muss ich denn im Olly machen um die Stelle zu finden wo diese ID 57883d bzw. E21Bh generiert und an eine Funktion übergeben wird ???? Wie gesagt, nach Push E21B suchen funktioniert ja nicht. Die Id´s sind ja nicht verschlüsselt und werden nicht per Zufall generiert, also müssen sie doch irgendwo im Code auftauchen.Aber wo und in welcher Form ???

Wir erinnern uns:

Das dritte Argument der Funktion LoadString() ist der Buffer, wo der String reinkommt. Der geht ja nicht ins Nirvana:

_Out_ LPTSTR lpBuffer

Deine Aufgabe:
Wo wird das dritte Argument auf den Stack geschoben? Das müsste ja ein Pointer zu einem Buffer sein, der den String am Ende enthält. Alles was später diesen String in diesem Buffer benutzt ist ja die Stelle die du suchst. Beispielsweise könntest du es dir bequem machen und dir ein Hardware Breakpoint auf diesen Buffer setzen.

Also. Du hast doch die Stelle wo der String geladen wird. Jetzt verfolge die Benutzung dieses Strings. Das wäre wohl die einfachste Möglichkeit.
Du meinst damit ich sollte einen Breakpoint auf den Speicherbereich setzen in dem der String nach auslesen durch die Funktion steht und dann rückwärts verfolgen wo der String benutzt wird ?? Zäume ich da nicht das Pferd von hinten auf ?? Wenn ich den Weg dann für ca. 10 Stellen gehen muss ist das ziemlich umständlich.
 
So ich kann Erfolg melden !

Ich bin die Sache wie von dir beschrieben rückwärts angegangen und habe, vielleicht auch etwas durch Zufall, die Stellen gefunden an denen auf die Strings zugegriffen wird. Anders als von mir erwartet waren nurnoch 3 Stellen zu ändern, demnach hat sich doch etwas mehr am Programm geändert.

Dennoch hätt ich die Fragen von oben gerne noch irgendwie beantwortet. Wie und wo wird die ID festgelegt ?? Ich seh in der Nähe, dort wo ich die Sprungbedingungen geändert habe, nix was ich mit den ID´s in Verbindung bringen könnte.
 
Zäume ich da nicht das Pferd von hinten auf ??

Ich wusste von dem kleinen Ausschnitt ja nicht ob du dich bereits im "nesting" des Badboy-Calls befindest.
Das war anscheinend der Fall und dann musste man eben nur ein paar Calls im Stack "hochklettern" um zur Bedingungsüberprüfung zu gelangen, wie du es wohl gemacht hast. :wink:

Wie und wo wird die ID festgelegt ?? Ich seh in der Nähe, dort wo ich die Sprungbedingungen geändert habe, nix was ich mit den ID´s in Verbindung bringen könnte.

Bei Delphi gillt fastcall. Das erste Argument ist EAX.
Wenn ich in dem Dump oben schaue, dann wird die ID in [EAX+4] übergeben.

0140AA90 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
[...]
0140AAE2 |. 8B40 04 MOV EAX,DWORD PTR DS:[EAX+4]
0140AAE5 |. 50 PUSH EAX

Das erste Argument ist also ein Pointer zu einer Struktur deren 2. Element die ID ist.
Das müsste sich ja weiterverfolgen lassen.

Was übergibt der Caller der Funktion über EAX?
 
Bei Delphi gillt fastcall. Das erste Argument ist EAX.
Wenn ich in dem Dump oben schaue, dann wird die ID in [EAX+4] übergeben.

0140AA90 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
[...]
0140AAE2 |. 8B40 04 MOV EAX,DWORD PTR DS:[EAX+4]
0140AAE5 |. 50 PUSH EAX
Dieser Bereich ist doch aber innerhalb, verglichen mit meinem Beispiel aus meinem vorletzten Post, der Funktion ReadFromStringTable(ID). Also mittendrin, da ist die ID ja schon längst bekannt und wird für die Funktion LoadStringW bereitgestellt.
,Das erste Argument ist also ein Pointer zu einer Struktur deren 2. Element die ID ist.
Das müsste sich ja weiterverfolgen lassen.

Was übergibt der Caller der Funktion über EAX?
Also da wo der Call zu der Funktion (Auszug Dump) erfolgt und auch schon einige Zeilen zuvor finde ich in EAX nix was mit ner ID zu tun hat. Und hier müsste, meinem Verständis nach, doch die ID an die Funktion übergeben werden.

Soweit ich dich jetzt verstanden habe steht die ID bei einem Delphi-Programm eh nicht so im disassembliertem Code wie ich mir das gedacht bzw. gewünscht hatte, und ich muss den umständlicheren Weg gehen, den ich nun gegangen bin. Richtig ??
 
Also da wo der Call zu der Funktion (Auszug Dump) erfolgt und auch schon einige Zeilen zuvor finde ich in EAX nix was mit ner ID zu tun hat. Und hier müsste, meinem Verständis nach, doch die ID an die Funktion übergeben werden.

Schau doch noch einmal genau, was ich geschrieben habe. Nicht in EAX selbst, sondern in [EAX+4].
EAX ist also ein Pointer auf eine Struktur und das zweite DWORD davon ist die ID.
Du müsstest das also zurückverfolgen. Also wo und an welcher Stelle dieses DWORD geschriebn wird. Du gehst immer davon aus das sei irgendwie in der "Nähe" oder nur eine Funktion drüber. Verfolge die Struktur doch einfach mal bis ganz zurück? Wann wird EAX übergeben? Wo stand der Pointer vorher? Wer schreibt in die Struktur rein? Usw. Man nennt es doch nicht umsonst Reverse Engineering.

Soweit ich dich jetzt verstanden habe steht die ID bei einem Delphi-Programm eh nicht so im disassembliertem Code wie ich mir das gedacht bzw. gewünscht hatte

Naja doch. Könnte doch sein. Vlt. steht ja irgendwo mal ein MOV [EAX+4], $E21B oder sowas ähnlices. Oder es wird aus .data geladen. Oder irgendwie zusammengerechnet: $E000 (Stringbase) + $21B (Stringindex).

Oder auch beliebt: Es wird aus einem Language-File geladen. Damit für jede Sprache die richtigen Strings genommen werden.

Das kann also alles mögliche sein. Nicht jedes Programm ist gleich.

, und ich muss den umständlicheren Weg gehen, den ich nun gegangen bin. Richtig ??

Welchen Weg du gehen musst, entscheidet sich meistens mittendrin. Man fängt einfach mit Anhaltspunkten an, die man hat. In diesem Fall Stringressourcen. Die hätten z.B. auch nicht unbedingt mit LoadString() geladen werden müssen. Aber es ist eine praktische Vermutung.
 
Zurück
Oben