Entwickler-Ecke

Windows API - API hook


uall@ogc - Do 26.08.04 13:59
Titel: API hook
hab hier mal nen kleinen API hook geschrieben:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
var nextMessageboxA: function(hWnd: HWND; lpText, lpCaption: pchar; uType: cardinal): UINT; stdcall;
    oldMessageboxA: function(hWnd: HWND; lpText, lpCaption: pchar; uType: cardinal): UINT; stdcall;

function myMessageboxA(hWnd: HWND; lpText, lpCaption: pchar; uType: cardinal): UINT; stdcall;
begin
  result := nextMessageboxA(hWnd,pchar(lpText+' hooked!!!'),lpCaption,uType);
end;

procedure HookApi(my, old: pointer; var next: pointer); stdcall;
asm
  push PAGE_EXECUTE_READWRITE
  push MEM_COMMIT or MEM_RESERVE
  push 64+5*4
  push 0
  call VirtualAlloc
  push eax
  mov [eax], $C7241C8Badd eax, 4
  mov [eax], $00002404add eax, 4
  mov [eax], $058b0000add eax, 4
  mov [eax], $00000000add eax, 4
  mov [eax], $0000158Badd eax, 4
  mov [eax], $10890000add eax, 4
  mov [eax], $0000158Badd eax, 4
  mov [eax], $50890000add eax, 4
  mov [eax], $50E0FF04add eax, 4
  mov [eax], $00058B52add eax, 4
  mov [eax], $8B000000add eax, 4
  mov [eax], $00000015add eax, 4
  mov [eax], $8B108900add eax, 4
  mov [eax], $00000015add eax, 4
  mov [eax], $04508900add eax, 4
  mov [eax], $E3FF5A58add eax, 4
  pop eax
  push eax

  mov ebx, eax
  add eax, 6
  add ebx, $23
  mov [eax], ebx

  add eax, 6
  add ebx, $40-$23
  mov [eax], ebx

  add eax, 6
  add ebx, 4
  mov [eax], ebx

  add eax, 8
  add ebx, 4
  mov [eax], ebx

  add eax, 13
  sub ebx, 8
  mov [eax], ebx

  add eax, 6
  add ebx, 12
  mov [eax], ebx

  add eax, 8
  add ebx, 4
  mov [eax], ebx

  push eax
  push esp
  push PAGE_EXECUTE_READWRITE
  push 8
  push [old]
  call VirtualProtect

  pop eax
  pop eax
  push eax
  add eax, $40
  mov ebx, [old]
  mov [eax], ebx
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx
  add ebx, 4
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx

  mov eax, [old]
  mov byte ptr [eax], $E9
  add eax, 1
  mov ebx, [my]
  sub ebx, [old]
  sub ebx, 5
  mov [eax], ebx

  pop eax
  push eax
  add eax, $40
  add eax, 12
  mov ebx, [old]
  mov ecx, [ebx]
  mov [eax], ecx
  add eax, 4
  add ebx, 4
  mov ecx,[ebx]
  mov [eax], ecx

  mov eax, [next]
  pop edx
  mov [eax], edx
end;

procedure test;
begin
  @oldMessageBoxA := GetProcAddress(LoadLibrary('user32.dll'),'MessageBoxA');
  HookApi(@myMessageboxA,@oldMessageBoxA,@nextMessageboxA);
  oldMessageboxA(0,'hallo :) ',nil,0);
end;


das is nen bsp mit MessageBoxA aus der user32.dll
anstatt den text 'hallo :)' auszugeben wird 'hallo :) hooked!!!' ausgegeben
wenn jemand genauer daran interessiert wie der hook funzt kann ich ihm das erklären :)


Sprint - Do 26.08.04 16:19

Hallo,

beschäfitge mich auch gerade mit dem hooken von API's. Hab unter anderem madCodeHook [http://www.madshi.net/], afxCodeHook [http://www.iamaphex.net] und die HookImport Funktion aus dem JEDI Project [http://www.delphi-jedi.org/] getestet.
Ich finde das madCodeHook am stabilsten läuft. Naja, aber US $125 wollte ich eigentlich nicht ausgeben. Und ich möchte natürlich auch, das ich meine verwendeten Komponenten, als Quellcode hab.

Ich finde das gut, das du hier eine OpenSource Möglichkeit gepostet hast.
Ich suche noch ein Beispiel, wie ich meine DLL in ein Prozess injecten kann.
Zurzeit benutze ich CreateRemoteThread. Das funktioniert unter NT-basierten Systerm auch sehr gut. Nur auf Win9x Rechnern geht das nicht.
Also wenn du da noch irgendwo 'nen Quelltext liegen hast... Das würde mich sehr freuen, wenn du den hier auch noch veröffentlich kannst. :D


uall@ogc - Do 26.08.04 16:30



uall@ogc - Do 26.08.04 16:38

hier hab nochmal schnell nen nen bsp für ELIRT gefunden

http://www.arschvoll.net/elirtbsp.zip

(btw alle programme / codes sin von mir ausser ELIRT!)


uall@ogc - Do 26.08.04 17:19

naja ich beschreib mal was der apiHook (in meinem #1 post von dem thread) genau macht / was für probleme aufgetreten sind usw.

zuerst wird speicher reserviert


Delphi-Quelltext
1:
2:
3:
4:
5:
  push PAGE_EXECUTE_READWRITE
  push MEM_COMMIT or MEM_RESERVE
  push 64+5*4
  push 0
  call VirtualAlloc


danach wird eine funktion in den speicher geschrieben:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
  mov [eax], $C7241C8B; add eax, 4
  mov [eax], $00002404; add eax, 4
  mov [eax], $058b0000; add eax, 4
  mov [eax], $00000000; add eax, 4
  mov [eax], $0000158B; add eax, 4
  mov [eax], $10890000; add eax, 4
  mov [eax], $0000158B; add eax, 4
  mov [eax], $50890000; add eax, 4
  mov [eax], $50E0FF04; add eax, 4
  mov [eax], $00058B52; add eax, 4
  mov [eax], $8B000000; add eax, 4
  mov [eax], $00000015; add eax, 4
  mov [eax], $8B108900; add eax, 4
  mov [eax], $00000015; add eax, 4
  mov [eax], $04508900; add eax, 4
  mov [eax], $E3FF5A58; add eax, 4


später zu der funktion mehr :)


nachdem wir die funktion in den speicher geschriben haben müssen wir noch nen paar adresse patchen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
  mov ebx, eax
  add eax, 6
  add ebx, $23
  mov [eax], ebx

  add eax, 6
  add ebx, $40-$23
  mov [eax], ebx

  add eax, 6
  add ebx, 4
  mov [eax], ebx

  add eax, 8
  add ebx, 4
  mov [eax], ebx

  add eax, 13
  sub ebx, 8
  mov [eax], ebx

  add eax, 6
  add ebx, 12
  mov [eax], ebx

  add eax, 8
  add ebx, 4
  mov [eax], ebx


dann wird VirtualProtect auf die original funktion aufgerufen um dort schreib und leserechte zu bekommen und unseren jmop zu setzen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
  push eax
  push esp
  push PAGE_EXECUTE_READWRITE
  push 8
  push [old]
  call VirtualProtect


jetzt speicher wir die 8 bytes die an der funktion stehen (die später mit einem jp überschriben werden)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  push eax
  add eax, $40
  mov ebx, [old]
  mov [eax], ebx
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx
  add ebx, 4
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx


dann schreiben wir den jmp zu unserer funktion


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  mov eax, [old]
  mov byte ptr [eax], $E9
  add eax, 1
  mov ebx, [my]
  sub ebx, [old]
  sub ebx, 5
  mov [eax], ebx


und speicher dann diesen auch nochmal ab


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
  add eax, $40
  add eax, 12
  mov ebx, [old]
  mov ecx, [ebx]
  mov [eax], ecx
  add eax, 4
  add ebx, 4
  mov ecx,[ebx]
  mov [eax], ecx


zum schluss wird die NEXT funktion gesetzt, diese liegt ja an dem speicher den wir reserviert haben und mit unserer funktion beschriben haben


Delphi-Quelltext
1:
2:
3:
  mov eax, [next]
  pop edx
  mov [eax], edx


sinn des api hooks ist es das ein jmp an $E9 xx xx xx xx an den beginn der API geschriebn wird und dieser jmp zu der eigen funktion springt, das ist eigentlich kein problem, nur wenn man die original funktion wieder aufrüfen würde, ensteht eine endlosschleife da der jmp immer wieder ausgeführt wird
also muss man den code bevor man die original funktion aufruft wieder herstellen
dies macht die funktion nextMessageBoxA

nun schaunen wir uns diese mal genauer an, wir haben sie an den reservierten speicher geschrieben

diese funktion sieht im speicher folgendermaßen aus:
[img]
http://www.arschvoll.net/apihook01.jpg
[/img]

diese funktion ist die "nextMessageBoxA" funktion und man kann sie in delphi auch so schreiben:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
function nextMessageboxA(hWnd: HWND; lpText, lpCaption: pchar; uType: cardinal): UINT; stdcall;
asm
  // replace JMP
  pop ebp                     // delphi stdcall
  mov ebx, [esp]              // save old return address
  mov [esp], offset @lala     // set new return address


  mov eax, [offset @save]     // unhook function
  mov edx, [offset @save+4]
  mov [eax], edx
  mov edx, [offset @save+8]
  mov [eax+4], edx

  jmp eax                     // jmp to orig function
  @lala:                      // here is new return address


  // set jmp
  push eax                    // hook function
  push edx
  mov eax, [offset @save]
  mov edx, [offset @save+12]
  mov [eax], edx
  mov edx, [offset @save+16]
  mov [eax+4], edx
  pop eax
  pop edx

  jmp ebx                     // jmp back to old saved return address

  @save:
  fnopfnop // old address
  fnopfnop // orig data 1st 4 byte
  fnopfnop // orig data 2nd 4 byte
  fnopfnop // jmp data 1st 4 byte
  fnopfnop // jmp data 2nd 4 byte
end;


bei @save werden folgende werte gespeichert:
1.) 4 bytes für die addresse wo die original API funktion beginnt
2.) 2*4 bytes für die original daten die an der funktion stehen
3.) 2*4 bytes fpr den jmp den man an die original funktion setzt

wenn nun die nextMessageBoxA funktion aufgerufen wird, werden die 8 originalen bytes an die "oldMessageBoxA" funktion geschrieben und diese funktion wird dann aufgerufen

hier gab es nun ein problem, und zwar wenn man die original funktion callt, liegt aufm dem stack als 1. die return adresse und die ganzen parameter (im bsp sind es 4)

das programm ruft die original funktion auf:
auf dem Stack liegt dann

4.parameter
3.parameter
2.parameter
1.parameter
return adresse

nun haben wir ja bei der original funktion einen jmp gesetzt dieser springt zu unserer funktion

die funtkion macht dann irgendwas und will nun NEXT funktion aufrufen

also legt unsere funktion wiederum alle parameter + return adresse auf den stack

4.parameter
3.parameter
2.parameter
1.parameter
return adresse - bei dem aufruf der orig funktion
4.parameter
3.parameter
2.parameter
1.parameter
return adresse - irgendwo in meiner funktion

jetzt sind wir in der next funktion, diese soll ja den jmp bei der originalen funktion entfernen um dann wiederum die originale funktion aufzurufen
das entfernen des jumps ist nicht das problem, aber wird müssen nun entweder alle parameter wieder auf den stack legen um die originale funktion zu callen, wenn wir das nicht machen würden, würde der stack nach dem call so aussehen:


4.parameter
3.parameter
2.parameter
1.parameter
return adresse - bei dem aufrud der orig funktion
4.parameter
3.parameter
2.parameter
1.parameter
return adresse - irgendwo in meiner funktion
return adresse - irgendwo in der next funktion

die parameter liegen alle falsch :/
nun kennen wir aber nicht die anzahl der parameter, aber wir wissen das ein
call nichts anderes macht als:

push eip + 10
jmp function

(5 bytes für push adresse und 5 für den jmp / bei nem call würde nur EIP+5 aufn stack gelegt)

wie könne wir das nun nutzen?

wir wissen das

4.parameter
3.parameter
2.parameter
1.parameter
return adresse - bei dem aufrud der orig funktion
4.parameter
3.parameter
2.parameter
1.parameter
return adresse - irgendwo in meiner funktion

auf dem stack liegt, also warum ändern wir nicht einfach die 2. return adresse ab
und springen einfach zu der originalen funktion?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
  mov [esp], offset @lala     return adresse abändern (neue return bei lala)

  mov eax, [offset @save]     unhooken der funktion (jmp removen)
  mov edx, [offset @save+4]
  mov [eax], edx
  mov edx, [offset @save+8]
  mov [eax+4], edx

  jmp eax                     zu der original funktion springen
  @lala:                      hier landen wir dann nach dem jmp


nachdem wir nun die original funktion aufgerufen haben müssen wir natürlich wieder den jmp hinsetzen damit wir bei einem 2. aufruf auch wieder den call anfangen können:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
  // set jmp
  push eax                    // hook function
  push edx
  mov eax, [offset @save]
  mov edx, [offset @save+12]
  mov [eax], edx
  mov edx, [offset @save+16]
  mov [eax+4], edx
  pop eax
  pop edx

  jmp ebx                     // jmp back to old saved return address


wichtig dabei ist es, EAX zu sichern, falls es sich bei der gehookten API um eine funktion handelt die einen rückgabewert hat (dieser steht immer in EAX)

schließlich müssen wir noch zu der retrun adresse zurücksprignen die wir überschriben haben

is bisl kompliziert, wer net so dne plan von asm hatwird da wohl auch net durchblicken aber für diejenigen dies könen nisses sehr interessant


uall@ogc - Fr 27.08.04 14:49

neue version des API hooks

kleine verbesserungen, return adresse wird nicht mehr in EBX gespeichert, funzt nun auch bei APIs die EBX verändern, ausserdem wurde aus der procedure eine funktion gemacht die bei fehlern beim hook false zurückgibt

wäre nett wenn die funktion genutzt das man mir das mitteilt ;>
ausserdem wünsch ich mir das mal jemand hier antwortet damit ich weiß das überhapt interesse daran besteht THX

EDIT: hab nen kleinen bug behoben das immer TRUE zurückgegeben wurde
und hab noch ne UnhookAPI funktion geschrieben bei der man einfach NEXT funktion angeben muss
diese gibt auch TRUE/FALSE zurück wenns das unhooken funzt oder net


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
function HookApi(my, old: pointer; var next: pointer): boolean; stdcallassembler;
asm
  push PAGE_EXECUTE_READWRITE         // speicher enthält code und daten
  push MEM_COMMIT or MEM_RESERVE
  push $4C+6*4
  push 0
  call VirtualAlloc
  cmp eax, 0                          // speicher freimachen hat net gefunzt
  je @endef

  mov ebx, eax
  mov [ebx], $8924048Badd ebx, 4
  mov [ebx], $00000005add ebx, 4
  mov [ebx], $2404c700add ebx, 8
//  mov [ebx], $00000000; add ebx, 4  // unnötig VirtualAlloc initialisiert speicher mit #0
  mov [ebx], $0000058badd ebx, 4
  mov [ebx], $158b0000add ebx, 4
  mov [ebx], $00000000add ebx, 4
  mov [ebx], $158b1089add ebx, 8
//  mov [ebx], $00000000; add ebx, 4
  mov [ebx], $ff045089add ebx, 4
  mov [ebx], $8b5250e0add ebx, 4
  mov [ebx], $00000005add ebx, 4
  mov [ebx], $00158b00add ebx, 4
  mov [ebx], $89000000add ebx, 4
  mov [ebx], $00158b10add ebx, 4
  mov [ebx], $89000000add ebx, 4
  mov [ebx], $5a580450add ebx, 4
  mov [ebx], $000025ff;// add ebx, 4
//  mov [ebx], $00000000; add ebx, 4

  mov ecx, eax; add ecx, 5
  mov ebx, eax; add ebx, $4a+20
  mov [ecx], ebx

  mov ecx, eax; add ecx, 12
  mov ebx, eax; add ebx, $29
  mov [ecx], ebx

  mov ecx, eax; add ecx, 18
  mov ebx, eax; add ebx, $4a+0
  mov [ecx], ebx

  mov ecx, eax; add ecx, 24
  mov ebx, eax; add ebx, $4a+4
  mov [ecx], ebx

  mov ecx, eax; add ecx, 32
  mov ebx, eax; add ebx, $4a+8
  mov [ecx], ebx

  mov ecx, eax; add ecx, 45
  mov ebx, eax; add ebx, $4a+0
  mov [ecx], ebx

  mov ecx, eax; add ecx, 51
  mov ebx, eax; add ebx, $4a+12
  mov [ecx], ebx

  mov ecx, eax; add ecx, 59
  mov ebx, eax; add ebx, $4a+16
  mov [ecx], ebx

  mov ecx, eax; add ecx, 70
  mov ebx, eax; add ebx, $4a+20
  mov [ecx], ebx

  mov ebx, eax
  push eax                          // etwas sinnloses auf den stack legen
  push esp                          // und als variable für die alten
  push PAGE_EXECUTE_READWRITE       // schreibrechte einfach ESP angeben
  push 8
  push [old]
  call VirtualProtect
  pop ecx                           // das sinnlose vom stack nehmen
  cmp eax, 0                        // virtualprotect hat net gefunzt
  je @endef

  mov eax, ebx
  push eax
  add eax, $4a
  mov ebx, [old]
  mov [eax], ebx
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx
  add ebx, 4
  add eax, 4
  mov ecx, [ebx]
  mov [eax], ecx

  mov eax, [old]               // hier wird der jmp an die original
  mov byte ptr [eax], $E9      // API gesetzt = $E9 = assembler jmp anweisung
  add eax, 1
  mov ebx, [my]                // jmp to adresse - jmp from adresse - 5
  sub ebx, [old]
  sub ebx, 5
  mov [eax], ebx

  pop eax
  push eax

  add eax, $4a
  add eax, 12
  mov ebx, [old]
  mov ecx, [ebx]
  mov [eax], ecx
  add eax, 4
  add ebx, 4
  mov ecx,[ebx]
  mov [eax], ecx

  mov eax, [next]
  pop edx
  mov [eax], edx
  mov eax, 1     // eax = 1 = true = kein fehler
  pop ebp
  ret 3*4

  @endef:
  xor eax, eax   // eax = 0 = false = fehler
  pop ebp
  ret 3*4
end;

function UnhookApi(next: pointer): boolean; stdcallassembler;
asm
  push eax
  push esp
  push PAGE_EXECUTE_READWRITE
  push $4C+6*4
  push [next]
  call VirtualProtect
  pop ebx
  cmp eax, 0
  je @endef
  cmp ebx, PAGE_EXECUTE_READWRITE
  jne @endef

  mov eax, [next]
  add eax, $4a
  mov ebx, [eax]

  push eax
  push ebx

  push ecx
  push esp
  push PAGE_EXECUTE_READWRITE
  push 8
  push ebx
  call VirtualProtect
  pop ecx
  cmp eax, 0
  je @endef

  pop ebx
  pop eax

  cmp byte ptr [ebx], $E9
  jne @endef

  add eax, 4
  mov ecx, [eax]
  mov [ebx], ecx
  add eax, 4
  add ebx, 4
  mov ecx, [eax]
  mov [ebx], ecx

  mov eax, 1
  pop ebp
  ret 4
  @endef:
  xor eax, eax
  pop ebp
  ret 4
end;


opfer.der.genauigkeit - Fr 27.08.04 17:26

Ziemlich geniale Sache.

Wenn ich mal n bißchen Zeit habe, kann ich mir das genauer reinziehen und meine
Assemblerkenntnisse verbessern.. who knows ;)


BenBE - Fr 27.08.04 20:15

Kleiner Tipp:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
//Speicher reservieren ...
  push PAGE_EXECUTE_READWRITE         // speicher enthält code und daten
  push MEM_COMMIT or MEM_RESERVE
  push $4C+6*4
  push 0
  call VirtualAlloc
//  cmp eax, 0                          // speicher freimachen hat net gefunzt
  TEST EAX, EAX
  je @endef

MOV EDI, EBX
LEA ESI, @@ASMRoutinenSource
MOV ECX, @@ASMRoutinenSource_Ende - @@ASMRoutinenSource
REPNZ MOVSB

//Anderer Quelltext  ...
//  mov ecx, eax; add ecx, 5
//  mov ebx, eax; add ebx, $4a+20
//  mov [ecx], ebx

//Dabei kann die Addressierung über 
//  MOV [EAX+@@ASMRoutinenLabelToModify-@@ASMRoutinenSource+ OffsetImBefehl ], EBX
//gepatcht werden. Der Compiler übernimmt dann die Offset-Berechnungen.

  LEA EBX, [EAX + $4a + 20]  //Displacement (Offset-Addresse) berechnen lassen
  MOV DWORD PTR [EAX + $0000005], EBX  //Wert speichern

...

JMP @@Ret

@@ASMRoutineenSource:
  //Hier der Klartext-Source deiner Routine
@@ASMRoutineenSource_Ende:

@@Ret:

end;


Damit ist dein Source etwas einfacher zu Verstehen, da die wenigsten Opcodes aus dem Kopf interpretieren können.


uall@ogc - Fr 27.08.04 20:35

jo is dann wirklich bisl einfacher zu verstehen, ich kann das ja mal so umbauen, glaub zwar eh das kaum jemand daran interessiert sit zu gucken ob das funzt sondern die meisten das einfach benutzen wollen

aber thx für info BenBE, ich werds ändern


uall@ogc - Sa 28.08.04 13:11

hab hier nochmal ne verbesserte funktion (nu hook funktion nicht unhook)
hab mal comments beigeschriebn damit man sich nen überblick über die funktions weise machen kann

bevor jemand was dazu postet:
ich weiß das man sich das

pop eax; push eax
sparen kan nwenn man einfach nen anderes register nimmt
oder man z.b. net jedes mal die größe der NEXT-funkton berechnen muss
werde das auch noch ändern

zu BenBE: die berechnungne mit LEA funktionieren nicht wenn man 2 labels angibt :/

EDIT: hab auch kommentare zu UnhookApi geschreiben, diese gibt nun auch wieder den speicher frei, desweiteren werden bei HookApi in der Next-funktion nun auch vor dem aufruf der original api alle register gespeichert, und der jmp gfeht nicht mer per jmp EAX sondern wird vorher auch noch in eine variabel geschrieben

EDIT2: nen bug bei der UnhookApi funktion entfernt... ret 4 anstatt ret 4*3 weil nur 1 parameter, hatte aus der HookApi kopiert aber nicht geändert...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
function HookApi(my, old: pointer; var next: pointer): boolean; stdcallassembler;
const varcount = 6;                      //anzahl der variablen die wir benötigen
asm
  mov ecx, offset @@endasmfunction       // größe unserer NEXT-funktion ausrechnen
  sub ecx, offset @@beginasmfunction     // ECX = funktionsgröße

  push ecx                               // ECX aufn stack
  add ecx, varcount*4                    // zu ECX den speicher für variablen hinzurechnen
  push PAGE_EXECUTE_READWRITE            // speicher enthält code und daten
  push MEM_COMMIT or MEM_RESERVE         // speicher reserviern und mit #0 initialisieren
  push ecx                               // größe des speichers der zu reservieren ist
  push 0                                 // egal wo wie speicher bekommen
  call VirtualAlloc                      // speicher reservieren
  test eax, eax                          // speicher freimachen hat net gefunzt
  pop ecx                                // ECX vom stack, ECX = größe der NEXT-funktion
  je @endef
                                         // ECX = anzahl der zu kopierenden bytes
  mov edi, eax                           // EAX = adresse unseres speichers
  mov esi, offset @@beginasmfunction     // ESI = adresse der zu kopierenden bytes
  repz movsb                             // bytes kopieren bis ECX = 0

  mov ebx, eax                           // EBX = EAX, EBX = adresse des reservierten speichers
  add ebx, offset @@endejmp              //  --\
  sub ebx, offset @@beginasmfunction     //  --- berechnen der return adresse
  mov [EAX + 16], ebx                    // setzen der return adresse nach dem aufruf der
                                         // originalen API in der NEXT-funktion

  mov ebx, eax                           // EBX = EAX, EBX = adresse des reservierten speichers
  add ebx, offset @@endasmfunction       // --\
  sub ebx, offset @@beginasmfunction     // addieren der größe der NEXT-function

  add [EAX +  8], ebx                    // ---
  add [EAX + 22], ebx                    //
  add [EAX + 28], ebx                    //
  add [EAX + 36], ebx                    //
  add [EAX + 47], ebx                    // patchen der variablen adressen
  add [EAX + 55], ebx                    //
  add [EAX + 61], ebx                    //
  add [EAX + 69], ebx                    //
  add [EAX + 80], ebx                    // ---

  mov ebx, eax
  push ecx                          // etwas sinnloses auf den stack legen
  push esp                          // und als variable für die alten
                                    // schreibrechte einfach ESP angeben
  push PAGE_EXECUTE_READWRITE       // schreib/lese/ausführrechte ...
  push 8                            // ... für 8 bytes ...
  push [old]                        // ... an der orignalen API ...
  call VirtualProtect               // ... festlegen
  pop ecx                           // das sinnlose vom stack nehmen
  cmp eax, 0                        // speicherrechte festlegen hat net gefunzt
  je @endef                         // zum ende springen

  mov eax, ebx                      // EAX = EBX, da VirtualProtect EAX geändert hat
  push eax                          // EAX aufn stack, EAX & EBX = adresse des reservierten speichers
  add eax, offset @@endasmfunction  // --\
  sub eax, offset @@beginasmfunction // -- addieren der größe der NEXT-funktion
                                    // EAX = ende der NEXT-funktion, anfang der vaiablen
  mov ebx, [old]                    // EBX = adresse der originalen API
  mov [eax], ebx                    // 1. variable setzen = adresse der origianl API
  add eax, 4                        // EAX = EAX+4, adresse der 2. variablen
  mov ecx, [ebx]                    // ECX = die 4 bytes die an der originalen API stehen
  mov [eax], ecx                    // 2. variable setzen mit diesen 4 bytes
  add ebx, 4                        // EBX = EBX+4
  add eax, 4                        // EAX = EAX+4, adresse der 3. variablen
  mov ecx, [ebx]                    // ECX = die nächsten 4 bytes die an der originalen API stehen
  mov [eax], ecx                    // 2. variable setzen mit diesen nächsten 4 bytes

                                    // wir haben nun die 8 bytes gesichert die wir
                                    // jetzt mit einem JMP überschreiben
                                    // es hätte gereicht wenn man 5 bytes sicher
                                    // da der jmp aus $E9 und der distanz die zu
                                    // springen ist besteht

  mov eax, [old]               // EAX = adresse der original API
  mov byte ptr [eax], $E9      // $E9 = assembler jmp anweisung an die adresse schreiben
  add eax, 1                   // EAX = EAX+1
  mov ebx, [my]                // EBX = adresse der my-funktion
  sub ebx, [old]               // subtrahieren der adresse von der original API
  sub ebx, 5                   // subtrahieren von 5 (größe des jmps)
  mov [eax], ebx               // EBX entählt die sprung distanz, diese an die original API
                               // nach $E9 schreiben

  pop eax                      // EAX vom stack, da wir eax verändert haben
  push eax                     // EAX wieder auf den stack, EAX = adresse des reservierten speichers

  add eax, offset @@endasmfunction    // --\
  sub eax, offset @@beginasmfunction  // --- addieren zu EAX, die größe der NEXT-funktion
                                      // wir sind am ende der NEXT-funktion, und am anfang
                                      // der adresse der variablen
  add eax, 12                         // EAX = EAX+12, an adresse der 4. variable springen
  mov ebx, [old]                      // EBX = adresse der original API
  mov ecx, [ebx]                      // ECX = ersten 4 bytes der API
                                      // ($E9 und 3 bytes der distanz, da wir das
                                      // ja da hingeschriebn haben)
  mov [eax], ecx                      // 4. variable mit den 4 bytes beschreiben
  add eax, 4                          // EAX = EAX+4, EAX = adresse der 5. variable
  add ebx, 4                          // EBX = EBX+4, die nächsten 4 bytes die an der original funktion stehen
  mov ecx,[ebx]                       // in ECX schreiben
  mov [eax], ecx                      // setzen der 5. varable mit den 4 bytes

                                      // es ist kein fehler aufgetreten
                                      // wir können nun NEXT unseren reservierten speicher
                                      // zuweisen und "true" zurückgeben
  mov eax, [next]                     // EAX = adresse der variable NEXT steht
  pop edx                             // adresse vom stack nehmen und in EDX schreiben
  mov [eax], edx                      // setzen der next variable, mit EDX = reservierter speicher
  mov eax, 1                          // EAX = 1 = true = kein fehler, RESULT = TRUE
  pop ebp                             // EBP vom stack nehmen
  ret 3*4                             // RETURN 12, 12 bytes vom stack nehmen aus der funktion springen
                                      // 12 bytes da 3 parameter, jeder parameter besteht immer aus 4 bytes = dword

  @endef:                             // hier wir hingesprungen wenn fehler aufgetreten ist
  xor eax, eax                        // EAX = 0 = false = fehler, RESULT = FALSE
  pop ebp                             // EBP vom stack nehmen
  ret 3*4                             // RETURN 12, 12 bytes vom stack nehmen aus der funktion springen
                                      // 12 bytes da 3 parameter, jeder parameter besteht immer aus 4 bytes = dword

  @@beginasmfunction:                 // unsere next funktion die wir kopieren
                                      // diese wird dann aufgerufen wenn man die gehookte
                                      // API aufrufen will
                                      // sie entfernt den JMP ruft dann die original
                                      // funktion auf und schreibt den jmp wieder rein
                                      // da sie keine feste adresse hat und in den
                                      // reservierten speicher geschriebn wird, muss man
                                      // alle variablen und jmps ($00000000) selbst ausfüllen

  push eax                            // EAX aufn stack
  push edx                            // EDX aufn stack
  mov eax, [esp+8]                    // ESP = adresse der aktuallen stack position
                                      // da 2 mal etwas auf dem stack gelegt wurde
                                      // ist in [ESP+8] die return adresse
                                      // diese wird in EAX geschrieben
  mov [$00000000+20], eax             // eax an variable 5 schreiben (20 / 4 bytes)
  mov [esp+8], $00000000              // setzen der neuen return adresse
                                      // diese ist bei @@endejump:

  mov eax, [$00000000+0]              // 1. variable in EAX, EAX = adresse der original API
  mov edx, [$00000000+4]              // 2. variable in EDX, 4 bytes die normal bei der API stehen
  mov [eax], edx                      // überschreiben des jmps mit den 4 bytes
  mov edx, [$00000000+8]              // 3. variable in EDX, die nächsten 4 bytes
  mov [eax+4], edx                    // und wieder überschreiben
  pop edx                             // EDX vom stack
  pop eax                             // EAX vom stack

                                      // alles wurde wieder hergestellt
                                      // ausser der return adresse gab es keine änderungen
                                      // wir können die ungehookte original API wieder aufrufen
                                      // wir jmp zu der funktion anstatt sie zu callen
                                      // dafür müssen wir dann nicht die parameter pushen
                                      // ändern der return adresse hat zu folge
                                      // Das wir nach dem jmp wieder bei @@nedejmp:
                                      // landen, also kaum ein unterschied zum call ist

  jmp [$00000000+0]                   // jmp zur originalen ungehookten API
  @@endejmp:                          // hier landen wir nach dem aufruf
                                      // wird benötigt um die adresse zu berechnen

                                      // alle register die wir verändern
                                      // werden wieder auf den stack gelegt
                                      // falls die original API änderungen virgenommen hat
                                      // z.b. an EAX, falls die API eine funktion war
                                      // ist dort jetzt das ergebnis zu finden
  push eax                            // EAX auf stack
  push edx                            // EDX auf stack
  mov eax, [$00000000]                // in EAX adresse der original API
  mov edx, [$00000000+12]             // in EDX, die 4. variable, EDX = jmp infos
  mov [eax], edx                      // setzen des jmps an die originale API
  mov edx, [$00000000+16]             // in EDX, die 5. variable, (da jmp asu 5 bytes besteht)
  mov [eax+4], edx                    // und auch hier wieder zurückschreiben
  pop eax                             // EAX vom stack
  pop edx                             // EDX vom stack

  jmp [$00000000+20]                  // die gesicherte return adrese springen

  @@endasmfunction:                   // ende der NEXT funktion
                                      // wird benötigt um die größe zu berechnen
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
function UnhookApi(next: pointer): boolean; stdcallassembler;
const varcount = 6;                       // anzahl der variablen die wir benötigen
asm
  mov ebx, offset @@endasmfunction        // berechnen der größe der NEXT-funktion
  sub ebx, offset @@beginasmfunction      // und in EBX schreiben
  add ebx, 4*varcount                     // addieren der variablen größe zu EBX

  push eax                                // EAX aufn stack
  push esp                                // ESP auf den stack, dort werden
                                          // die alten schreibrechte gespeichert
  push PAGE_EXECUTE_READWRITE             // lese/schreib/ausführrechte
  push ebx                                // für die berehcnete größe
  push [next]                             // bei der NEXT-funktion
  call VirtualProtect                     // aufrufen von VirtualProtect
  pop ebx                                 // 4 bytes vom stack nehmen
  cmp eax, 0                              // schauen on ändern der speicherrechte gefunzt hat
  je @endef                               // wenn nicht springe zum ende

  cmp ebx, PAGE_EXECUTE_READWRITE         // schauen ob die alten speicherrechte ebenfalls
                                          // lese/schreib/ausführen waren
                                          // muss so sein da wir bei HookApi
                                          // so speicher reserviert haben
  jne @endef                              // wenn nicht springe zum ende

  mov ebx, offset @@endasmfunction        // --\
  sub ebx, offset @@beginasmfunction      // --- berechnen der größe der NEXT-funktion

  mov eax, [next]                         // EAX = adresse der NEXT-funktion
  add eax, ebx                            // addieren der größe -> adresse der 1. variable
  mov ebx, [eax]                          // EBX = 1. variable = adresse der original funktion

  push eax                                // EAX auf den stack
  push ebx                                // EBX auf den stack

  push ecx                                // ECX auf den stack
  push esp                                // ESP auf den stack, für die alten schreibrechte
  push PAGE_EXECUTE_READWRITE             // lese/schreib/ausführrechte
  push 8                                  // für die 8 bytes
  push ebx                                // von der originalen API setzen
  call VirtualProtect                     // aufruf der funktion
  pop ecx                                 // ale schreibrechte vom stack nehmen und in ECX
  test eax, eax                           // funktion war nicht erfolgreich? EAX = 0
  je @endef                               // wenn EAX = 0 dann zum ende springen

  pop ebx                                 // EBX vom stack
  pop eax                                 // EAX vom stack

  cmp byte ptr [ebx], $E9                 // steht an der originalen API $E9 für jmp?
  jne @endef                              // wenn nicht dann zum ende springen

  add eax, 4                              // EAX = EAX+4, adresse der 2. variable
  mov ecx, [eax]                          // diese in EAX schreiben
  mov [ebx], ecx                          // diese wiederum an die original API
  add eax, 4                              // EAX = EAX+4, adresse der 3. variable
  add ebx, 4                              // EBX = EBX+4
  mov ecx, [eax]                          // 3. variable in ECX
  mov [ebx], ecx                          // diese an die nächsten 4 bytes der original API
                                          // der JMP hook wurde überschrieben mit den richtigen daten

  push MEM_RELEASE                        // art der speicher freigabe
  push 0                                  // bei MEM_RELEASE muss speichergröße 0 sein
  push [next]                             // adresse des speichers der freigegeben werden soll
  call VirtualFree                        // aufrufen der API zum speicher freigeben
                                          // resultat muss nicht auf wahr/falsch
                                          // geprüft werden da der hook entfernt wurde
                                          // egal ob speicher freigegeben werden konnte
                                          // oder nicht

  mov eax, 1                          // EAX = 1 = true = kein fehler, RESULT = TRUE
  pop ebp                             // EBP vom stack nehmen
  ret 4                               // RETURN 12, 12 bytes vom stack nehmen aus der funktion springen
                                      // 12 bytes da 3 parameter, jeder parameter besteht immer aus 4 bytes = dword

  @endef:                             // hier wir hingesprungen wenn fehler aufgetreten ist
  xor eax, eax                        // EAX = 0 = false = fehler, RESULT = FALSE
  pop ebp                             // EBP vom stack nehmen
  ret 4                               // RETURN 12, 12 bytes vom stack nehmen aus der funktion springen
                                      // 12 bytes da 3 parameter, jeder parameter besteht immer aus 4 bytes = dword

  @@beginasmfunction:                 // unsere next funktion die wir kopieren
                                      // diese wird dann aufgerufen wenn man die gehookte
                                      // API aufrufen will
                                      // sie entfernt den JMP ruft dann die original
                                      // funktion auf und schreibt den jmp wieder rein
                                      // da sie keine feste adresse hat und in den
                                      // reservierten speicher geschriebn wird, muss man
                                      // alle variablen und jmps ($00000000) selbst ausfüllen

  push eax                            // EAX aufn stack
  push edx                            // EDX aufn stack
  mov eax, [esp+8]                    // ESP = adresse der aktuallen stack position
                                      // da 2 mal etwas auf dem stack gelegt wurde
                                      // ist in [ESP+8] die return adresse
                                      // diese wird in EAX geschrieben
  mov [$00000000+20], eax             // eax an variable 5 schreiben (20 / 4 bytes)
  mov [esp+8], $00000000              // setzen der neuen return adresse
                                      // diese ist bei @@endejump:

  mov eax, [$00000000+0]              // 1. variable in EAX, EAX = adresse der original API
  mov edx, [$00000000+4]              // 2. variable in EDX, 4 bytes die normal bei der API stehen
  mov [eax], edx                      // überschreiben des jmps mit den 4 bytes
  mov edx, [$00000000+8]              // 3. variable in EDX, die nächsten 4 bytes
  mov [eax+4], edx                    // und wieder überschreiben
  pop edx                             // EDX vom stack
  pop eax                             // EAX vom stack

                                      // alles wurde wieder hergestellt
                                      // ausser der return adresse gab es keine änderungen
                                      // wir können die ungehookte original API wieder aufrufen
                                      // wir jmp zu der funktion anstatt sie zu callen
                                      // dafür müssen wir dann nicht die parameter pushen
                                      // ändern der return adresse hat zu folge
                                      // Das wir nach dem jmp wieder bei @@nedejmp:
                                      // landen, also kaum ein unterschied zum call ist

  jmp [$00000000+0]                   // jmp zur originalen ungehookten API
  @@endejmp:                          // hier landen wir nach dem aufruf
                                      // wird benötigt um die adresse zu berechnen

                                      // alle register die wir verändern
                                      // werden wieder auf den stack gelegt
                                      // falls die original API änderungen virgenommen hat
                                      // z.b. an EAX, falls die API eine funktion war
                                      // ist dort jetzt das ergebnis zu finden
  push eax                            // EAX auf stack
  push edx                            // EDX auf stack
  mov eax, [$00000000]                // in EAX adresse der original API
  mov edx, [$00000000+12]             // in EDX, die 4. variable, EDX = jmp infos
  mov [eax], edx                      // setzen des jmps an die originale API
  mov edx, [$00000000+16]             // in EDX, die 5. variable, (da jmp asu 5 bytes besteht)
  mov [eax+4], edx                    // und auch hier wieder zurückschreiben
  pop eax                             // EAX vom stack
  pop edx                             // EDX vom stack

  jmp [$00000000+20]                  // die gesicherte return adrese springen

  @@endasmfunction:                   // ende der NEXT funktion
                                      // wird benötigt um die größe zu berechnen
end;


BenBE - Sa 28.08.04 23:08

Warum nutzt du in der Unhook-Funktion im Displacement $00000000 als Basis-Adresse? Ich denk mal, das wird zu ner AV führen ... (Oder welchen Grund gibt es dafür beim Unhooken das System zu crashen???)

Ooops, hat sich erledigt, hab übersehen, dass die Routine in der Unhook-Funktion nicht geschrieben werden. Self-Modifying Code ist aber auch immer Kompliziert :D :oops:


uall@ogc - So 29.08.04 12:12

nene beim unhook ist die funktion nur zum berechnen der größe da, damit ich weiß wo die variablen beginnen (di stehen hinter der funktion die in in den reservierten speicher kopiere) die erste variable ist die adresse der original API wo ich den JMP hinsetze, diese lese ich aus schau ob der jmp da ist und überschreibe das einfach mit den originalen 8 bytes (5 bytes für den jmp) dann gebe ich den reservierten speicher wieder frei, die funktion mit den $00000000 rufe ich nie auf, sie ist halt nur dafür da um zu berechnen wo die variablen beginnen :)


BenBE - So 29.08.04 20:28

Beim Studieren des Codes von afx ist mir noch ne Idee gekommen, wie du nur einmal den Code für Hook und Unhook brauchst und trotzdem einfach die Größe berechnen kannst:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure HookCode;assembler;
asm
//Assembler-Code zum Hooken, der in den anderen Prozess geschrieben wird
end;

procedure HookCodeEnd;assembler;
asm;
end;


Die Größe erhälst du dann mit DWORD(@HookCodeEnd) - DWORD(HookCode) und den HookCode selber mit @HookCode.

in ASM also:
LEA ECX, [HookCodeEnd]
LEA ESI, [HookCode] //Offset
SUB ECX, ESI //Größe


uall@ogc - So 29.08.04 20:54


Delphi-Quelltext
1:
DWORD(@HookCodeEnd) - DWORD(HookCode)                    


fehlt nen @ = DWORD(@HookCode)

oder man nimmt


Delphi-Quelltext
1:
2:
MOV ECX, offset HookCodeEnd
SUB ECX, offset HookCode


;>


BenBE - So 29.08.04 21:33

Wobei die Möglichkeit über LEA den Vorteil hat, dass schon alles (Größe und Offset) in den Richtigen Registern stehen.


BenBE - Mi 08.09.04 22:28

Ich wollte mal, weil mir das nicht so richtig klar geworden ist, anfragen, in wie weit es Einschränkungen bei den API Hooks gibt, weil bestimmte Berechtigungen erforderlich sind?

Also, z.B. funzt das als Otto Normal-User ohne Extra-Privilegien?
(P.S.: Das es mit Umwegen funzt, wenn man den SMC steuern kann, hat Assabad ja schon bewiesen ;-), das mein ich aber nicht)

Ich mein im Speziellen, ob ein normaler User, der Benutzer-Gruppe, sich z.B. in den Explorer, oder PID 8 einklinken könnte, d.h. in Prozesse auch von anderen Usern.

TIA.


Sprint - Mi 08.09.04 23:01

BenBE hat folgendes geschrieben:
Ich mein im Speziellen, ob ein normaler User, der Benutzer-Gruppe, sich z.B. in den Explorer, oder PID 8 einklinken könnte, d.h. in Prozesse auch von anderen Usern.

Ich weiß jetzt nicht was du mit PID 8 meinst. Aber beim Explorer und so, da kannst du dich so auch ganz leicht einklicken.
Zum Beispiel installierst z.B. ein MsgHook systemweit und wenn deine DLL in Prozess vom Explorer geladen wird, kannst du dort die API's gegen deine Funktionen austauschen.


BenBE - Do 09.09.04 09:28

Sprint hat folgendes geschrieben:
BenBE hat folgendes geschrieben:
Ich mein im Speziellen, ob ein normaler User, der Benutzer-Gruppe, sich z.B. in den Explorer, oder PID 8 einklinken könnte, d.h. in Prozesse auch von anderen Usern.

Ich weiß jetzt nicht was du mit PID 8 meinst.

PID 8 ist der System Prozess (Kernel) von Windows 2000.

Sprint hat folgendes geschrieben:
Aber beim Explorer und so, da kannst du dich so auch ganz leicht einklicken.
Zum Beispiel installierst z.B. ein MsgHook systemweit und wenn deine DLL in Prozess vom Explorer geladen wird, kannst du dort die API's gegen deine Funktionen austauschen.
Schon klar, aber ich glaub nicht, dass der Explorer meine DLL ohne Grund laden wird ;-) Und außerdem wolte ich in einen anderen Prozess rein, der nicht durch ein paar simple RegKeys dazu gebracht werden kann, eine DLL zu laden...

Und das es als Admin geht, weiß ich ... Jedoch nicht, ob's auch als Normal-User geht ...


Sprint - Do 09.09.04 12:59

BenBE hat folgendes geschrieben:
Schon klar, aber ich glaub nicht, dass der Explorer meine DLL ohne Grund laden wird ;-)

Auf NT-basierten Betriebssystemen kannst du ja auch CreateRemoteThread benutzen. Das funktioniert wunderbar.
Und mit einem systemweitern MsgHook (SetWindowsHookEx) wird deine DLL auch in den Prozess des Explores geladen.

Zitat:
Und außerdem wolte ich in einen anderen Prozess rein, der nicht durch ein paar simple RegKeys dazu gebracht werden kann, eine DLL zu laden...

Weiß ich nicht was du damit meinst? Von RegKeys habe ich nicht geredet. Geht ja auch nur, wenn die Anwendug die user32dll geladen hat. So macht das z.B. Window Blinds.


uall@ogc - Do 09.09.04 16:22

@BenBE

suchst vill sowas:

function GetDebugPrivilege: boolean;
{unser prozess bekomm debug previlegien,
benötigt um einen prozess handle von einem systemprozess zu bekommen}



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function GetDebugPrivilege: boolean;
var hToken,rel: cardinal;
    tkp: TOKEN_PRIVILEGES;
    luid: int64;
begin
  result := false;
  if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
  begin
    if LookupPrivilegeValue(nil'SeDebugPrivilege', luid) then
    begin
      tkp.PrivilegeCount            := 1;
      tkp.Privileges[0].Attributes  := SE_PRIVILEGE_ENABLED;
      tkp.Privileges[0].Luid        := luid;
      result := AdjustTokenPrivileges(hToken, FALSE, tkp, sizeof(tkp), nil, rel);
    end;
    CloseHandle(hToken);
  end;
end;


BenBE - Do 09.09.04 16:59

Ne, das Debug-Priviledge ist nur für Admins verfügbar (oder wenn man's anderen Usern gibt. Bei Delphi funzt das Debuggen auch nur, da die Prozesse die Security-Einstellungen der IDE erben und damit die IDE das Recht hat drauf zuzugreifen.

Um meine Frage mal umzuformulieren: Ich suche was, was es mir erlaubt, die Funktionalität von Assabad's CMD_SVC direkt über nen DLL Injection auszuführen.
Hintergrund: Möchte paar Klassenkammeraden mal etwas verarschen, indem ihre Rechner ferngesteuert runterfahren. Funzt als Admin auf meinem Sys auch ganz fein, jedoch nur, wenn ich mich auf dem Remote-System als Admin authentifiziere (oder anderer Account mit RemoteShutdownPriv) -> Ich muss lokal einen Account haben, der bei Remote-Anmeldung die SeRemoteShutdownPriv hat. Und das ist eben entweder LocalSystem oder Admin. Und da ich den Admin-Account nicht knacken will oder sonstewie die Konfig ändern darf, bleibt nur der Weg über die System-Prozesse.


BenBE - Fr 10.09.04 20:28

Also gut, hab nochmal einen anderen Ansatz versucht, der theoretisch funktionieren sollte, jedoch an den Speicher-Berechtigungen scheitert.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
Function HookApi(my, old: Pointer; Var Next: Pointer): Boolean; StdcallAssembler;
Asm
  //Prüfen, ob die Function über statisch per JMP DWORD PTR [] eingebunden ist
  MOV    EAX, DWORD PTR [Old]
  CMP   WORD PTR [EAX], $25FF
  JNZ    @@RetFailure

  //Größe der Routine berechnen
  MOV   ECX, OFFSET @@ASMHookLoader_End
  SUB   ECX, OFFSET @@ASMHookLoader_Start

  //Größe Speichern
  PUSH  ECX

  //Speicher reservieren:
  PUSH  PAGE_EXECUTE_READWRITE      //flProtect
  PUSH   MEM_COMMIT or MEM_RESERVE    //flAllocationType
  PUSH  ECX                //dwSize
  PUSH   $00000000            //lpDesiredAddress
  CALL  VirtualAlloc

  //Größe wieder in Speicher holen
  POP    ECX
  
  TEST   EAX, EAX
  JZ    @@Ret              //EAX = $00000000, daher einfach beenden

  //Routine an entsprechende Stelle im Speicher kopieren
  MOV   EDI, EAX            //Zieladresse setzen
  LEA   ESI, @@ASMHookLoader_Start    //Quelldaten laden
  REPNZ   MOVSB              //Block kopieren

//  //Speicher-Schreibschutz entfernen
//  PUSH  PAGE_EXECUTE_READWRITE      //Vollzugriff auf den Speicherbereich
//  PUSH  MEM_COMMIT AND MEM_RESERVE
//  PUSH  $00000006            //Nur die Jump-Instruction
//  PUSH  DWORD PTR [Old]
//  CALL   VirtualAlloc
//  TEST  EAX, EAX                        //Auf Erfolg prüfen
//  JZ    @@RetProtectionError      ///Fehler bei Zugriffssteuerung
  
  //Zeiger-Daten neu initialisieren
  MOV    EDI, EAX            //Routinen-Block laden
  MOV    ESI, DWORD PTR [Old]      //Jump-Instruction der alten Routine laden
  
  //Zeiger auf alte Prozedur lesen und neu setzen
  MOV    ECX, OFFSET @@ASMHookLoader_OldProc
  SUB    ECX, OFFSET @@ASMHookLoader_Start
  MOV    EDX, DWORD PTR [ESI+$00000002]  //Quellpointer lesen
  MOV   DWORD PTR [ESI+$00000002], EAX  //Neue Funktionsadresse schreiben
  MOV    DWORD PTR [EDI+ECX], EDX    //Zeiger zur alten Prozedur schreiben

//  //Speicher-Schutz zurücksetzen
//  PUSH  MEM_DECOMMIT OR MEM_RELEASE    //Speicher wieder freigeben
//  PUSH  $00000006            //Nur die Jump-Instruction
//  PUSH  DWORD PTR [Old]          //Speicher von LibPtr
//  CALL   VirtualFree                     //Speicher wieder freigeben.
  
  //Zeiger auf neue Prozedure setzen
  MOV    ECX, OFFSET @@ASMHookLoader_ProcPtr
  SUB    ECX, OFFSET @@ASMHookLoader_Start
  MOV    EDX, DWORD PTR [My]        //Zeiger auf neue Procedure lesen
  MOV    DWORD PTR [EDI+ECX], EDX    //Zeiger auf neue Procedure schreiben
  
  //Functionsadresse für Aufruf der alten Funktion zurückgeben.
  MOV    ECX, OFFSET @@ASMHookLoader_JumpInstruction
  SUB    ECX, OFFSET @@ASMHookLoader_Start
  LEA    EDX, DWORD PTR [EDI+ECX]    //Offset ermitteln
  MOV   DWORD PTR [Next], EAX      //Zeiger

  //Set Last Error Code
  PUSH  ERROR_SUCCESS
  CALL  SetLastError

  //Return True
  XOR   EAX, EAX            
  INC    EAX
  JMP   @@Ret

@@ASMHookLoader_Start:
  PUSH  EAX                //Wert von EAX sichern
  CALL  @@ASMHookLoader_GetMemPos    //Aktuelle Position im Speicher lesen
@@ASMHookLoader_Relative:          //Bezugslabel für alle Offsets
  ADD   EAX, OFFSET @@ASMHookLoader_ProcPtr  //Displacement von ProcPtr berechnen
  SUB   EAX, OFFSET @@ASMHookLoader_Relative
  MOV   EAX, DWORD PTR [EAX]            //Adresse der gehookten Funktion lesen
  XCHG  EAX, [ESP]            //Wert von EAX wiederherstellen, Zieladresse für RET setzen
  RET
@@ASMHookLoader_JumpInstruction:      //Daten für die Jump-Instruction für CallNext
  PUSH  EAX                //Wert von EAX sichern
  CALL  @@ASMHookLoader_GetMemPos    //Aktuelle Position im Speicher lesen
@@ASMHookLoader_OldRelative:          //Bezugslabel für alle Offsets
  ADD   EAX, OFFSET @@ASMHookLoader_OldProc  //Displacement von ProcPtr berechnen
  SUB   EAX, OFFSET @@ASMHookLoader_OldRelative
  MOV   EAX, DWORD PTR [EAX]            //Adresse der gehookten Funktion lesen
  XCHG  EAX, [ESP]            //Wert von EAX wiederherstellen, Zieladresse für RET setzen
  RET
@@ASMHookLoader_ProcPtr:          //Zeiger auf neue Procedure\Function
  DD    $00000000
@@ASMHookLoader_OldProc:          //Zeiger auf die alte Procedure
  DD    $00000000
@@ASMHookLoader_GetMemPos:          //Legt die Rücksprungadresse in EAX
  POP   EAX
  PUSH   EAX
  RET
@@ASMHookLoader_End:

@@OldLibraryProtection:            //Variable, die den alten ProtectionStatus
  DD    $00000000            //Des Imports enthält

@@RetProtectionError:
  //Set Last Error Code
  PUSH  ERROR_ACCESS_DENIED
  CALL  SetLastError
  XOR   EAX, EAX
  JMP    @@Ret

@@RetFailure:                //Signal an error
  //Set Last Error Code
  PUSH  ERROR_NOT_SUPPORTED
  CALL  SetLastError
  XOR   EAX, EAX
@@Ret:                    //Ende Routine
End;


Die Auskommentierten Zeilen sind meine Versuche, Windows doch dazu zu bewegen, mir Schreibzugriff auf DWORD PTR [ESI+$02] zu geben, die aber leider Erfolglos blieben.
Die Routine funktioniert nur mit statisch importierten Funktionen (wenn es funzen würde) und behebt ein Problem vom oben aufgeführten API-Hook von uall@ogc, der bei diesen Funktionen zu AccessViolations führt (zumindest bei mir):

Testcode mit meinem Versuch GetLastError etwas Windows-Gerechter zu Schreiben
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TForm1.Button1Click(Sender: TObject);
type
  TGetLastErrorProc = function:DWORD;stdcall;
var
  OldGetLastErr:TGetLastErrorProc;
  function MyGetLastError:DWORD;stdcall;
  begin
    Result := Random(65536);
  end;
begin
  HookApi(@MyGetLastError, @GetLastError, @OldGetLastErr);
  Caption := IntToStr(GetLastError);
  UnHookApi(@OldGetLastErr);
end;

AV kommt, wenn Delphi versucht mit Call TComponent.SetCaption den Text zuzuweisen. Außerdem wird im Import-Modul die nächste Funktion durch die ASM-Routine überschrieben.

Wäre dankbar, wenn sich das mal jemand angucken könnte, um mir Tips zu geben, was ich vor dem Zugriff auf den LibPtr noch beantragen muss.


uall@ogc - Sa 11.09.04 11:35

Naja zu deiner Methode:

1. hat sie mit meiner fast net mehr soviel gemeinsam, deine kann "nur" statische imports hooken, meine sollte (funzt in deinem bsp net) alle APIs hooken können :)

2. crasht deine routine bei mir genauso wie meine ^^

3. solltest auf die OLD funktion am anfang nen virtualprotect machen, kann ja sein das jemand die lese/schreibrechte auf die page genommen hat

4. wünsch ich mir mal nen ASM forum

5. wenn ich falsch liegen sollte dann plz schreiben! ;>

EDIT:
naja wenn ich mir zuerst mal deinen text dazu durchgelesen hätte und ent auf die funktion geguckt hätte, wüßte ich das du shcon auf punkt 1/2 eingegangen bist - *aufschlägewart*

EDIT2:

gnaa wasn das bidde?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
//   //Speicher-Schreibschutz entfernen 
//   PUSH   PAGE_EXECUTE_READWRITE         //Vollzugriff auf den Speicherbereich 
//   PUSH   MEM_COMMIT AND MEM_RESERVE 
//   PUSH   $00000006                  //Nur die Jump-Instruction 
//   PUSH   DWORD PTR [Old] 
//   CALL    VirtualAlloc 
//   TEST   EAX, EAX                        //Auf Erfolg prüfen 
//   JZ      @@RetProtectionError         ///Fehler bei Zugriffssteuerung


wie willst denn auf nen reservierten speicher nochmal den speicher reservieren?

willst net lieber VirtualProtect nehmen? ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//   PUSH   EAX
//   PUSH   ESI
//   PUSH   PAGE_EXECUTE_READWRITE         //Vollzugriff auf den Speicherbereich 
//   PUSH   $00000006                  //Nur die Jump-Instruction 
//   PUSH   DWORD PTR [Old] 
//   CALL   VirtualProtect
//   TEST   EAX, EAX                        //Auf Erfolg prüfen 
//   POP    EAX
//   JZ      @@RetProtectionError         ///Fehler bei Zugriffssteuerung



btw. solltest das schon direkt am anfang machen bevor du überhaupt guckst ob die old funktion nen statischer import ist!


BenBE - Sa 11.09.04 18:47

Werd mir das mit dem VirtualProtect mal ansehen. Meld mich evtl. nochmal.


uall@ogc - So 12.09.04 14:40

nur zur info:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
{excpetion, da keine schreibrechte}
procedure _exception;
var p: pointer;
    old: cardinal;
    b: ^byte;
begin
  p := virtualalloc(nil,$1000,MEM_COMMIT OR MEM_RESERVE,PAGE_NOACCESS);
  b := pointer(integer(p)+10);
  b^ := $50;
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
{keine exception obwohl EXECUTE_READ_WRITE rechte nur auf
  den ersten 6 bytes, windows arbeitet mit seiten (pages)
  die immer $1000 groß sind, von daher ist
  bei virtualprotect(p,6,PAGE_EXECUTE_READWRITE,old);
  die ganze seite beschreibbar}

procedure _noexception;
var p: pointer;
    old: cardinal;
    b: ^byte;
begin
  p := virtualalloc(nil,$1000,MEM_COMMIT OR MEM_RESERVE,PAGE_NOACCESS);
  virtualprotect(p,6,PAGE_EXECUTE_READWRITE,old);
  b := pointer(integer(p)+10);
  b^ := $50;
end;


Kedariodakon - Mi 19.01.05 10:38

morgen :D
Wollte mich mal bedanken für den souce...
Bin gerade dabei das alles zu verstehen :shock:

Aber frage mich gerade, was die Funktion überhaupt bekommt, da sie ja 3 Addressen will...


Delphi-Quelltext
1:
function HookApi(my, old: pointer; var next: pointer): boolean; stdcallassembler;                    

Und die zum unhooken will auch eine :roll:

Delphi-Quelltext
1:
UnhookApi(next: pointer): boolean; stdcallassembler;                    


Was bekommen die da übergeben?
Brauch ich noch mehr als die beiden?

Arbeite bisjetzt nur mit der Mad-Komponente, da is das ein klein wenig anders...
Möchte sie aber gern gegen Source ersetzen...

Bin leider kein Licht in Assembeler...

Wie testet ihr die Funktionen? Runtime-debugger? könnt ihr da einen empfehlen?
Hatte früher mal Softice gehabt, war net schlecht, glaub ich zumindest ^^
Aber was nimmt man heute zu WinXP-SP2 Zeiten?


Bye


BeautifulRuby - Do 20.01.05 01:53

Kann mir einer ein Beispiel geben, wie man Systemweit ein Hook macht?

Einfach eine veränderte MessageBox-Funktion, wo egal wer MessageBox aufruft, der Inhalt verändert dargestellt wird, also aus "Wollen Sie die Datei speichern?" wird "Wollen Sie die Datei speichern? - TEST!"

Die Demos die ich im Internet gefunden habe, funktionierten bei mir nicht: entweder brauchte man fremdkomponente oder Unit fehlte oder auch System stürzte ab.


Delete - Do 20.01.05 02:46

BenBE hat folgendes geschrieben:
Ne, das Debug-Priviledge ist nur für Admins verfügbar (oder wenn man's anderen Usern gibt. Bei Delphi funzt das Debuggen auch nur, da die Prozesse die Security-Einstellungen der IDE erben und damit die IDE das Recht hat drauf zuzugreifen.

Und woher erbt das Token des IDE-Prozesses das Privileg? Richtig, von der Logon-Session. Und wenn du als einfacher Benutzer angemeldet bist, läuft die IDE mit deinen Rechten und Privilegien. Und eigene Prozesse darfst du immer debuggen. Ich kann nämlich sehr wohl als benutzer mit dem Delphi Debugger meine Anwendungen debuggen. Mit dem Debug-Privileg darfst du das eben auch für Prozesse, die nicht von dir sind.


uall@ogc - Do 20.01.05 13:40

@Kedariodakon:

function HookApi(my, old: pointer; var next: pointer): boolean; stdcall; assembler;

my = eigene funktion die aufgerufen werden soll
old = funktion die gehookt werden soll
next = neue funktion die man in der my funktion aufrufen kann

bsp:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
var oldMessageBoxA: function (i: integer; a,b: pchar; j: integer): integer; stdcall;
    nextMessageBoxA: function(i: integer; a,b: pchar; j: integer): integer; stdcall;

function myMessageBoxA(i: integer; a,b: pchar; j: integer): integer; stdcall;
begin
  result := nextMessageBoxA(i,pchar(a+' test'),b,j);
end;

procedure InstallHook;
begin
  @oldMessageBoxA := getprocaddress(getmodulehandle('user32.dll'),'MessageBoxA');
  HookAPI(@myMessageBoxA,@oldMessageBoxA,@nextMessageBoxA);
end;

bei der oldMessageBoxA wird ein jump gesetzt zu myMessageBoxA und damit keine endlosscheife auftritt muss man nextMessageBoxA aufrufen