Simple DIY proxy dll
It took me to intercept calls to GDS32.DLL. I decided to write a proxy dll.
The first thing we need is to get a list of all exported functions from this dll.
Let's do it with the following code:
There seem to be no difficulties. We get sequentially to the export table (line 19) of pointers to an array of names (NamesCursor) and an array of numbers (OrdinalCursor) and read the function by function, names and numbers. The number of functions is in the NumberOfNames field. This code was obtained on the Internet, then finalized and simplified.
Consider our test dll.
There are no difficulties either. We export two functions - addition and subtraction.
we’ll have a list of exported functions and numbers:
myAdd = 2
mySub = 1
Listing 3 The
compiler assigned these numbers. Why exactly these? I do not know this.
Now let's focus on the addition function. Let's see in which code its call was compiled, for this we will call it and look in the debugger.
Everything is simple here. We get the address of the function and call it. I will only explain that in the second parameter GetProcAddress a pointer to the function name, but this is if it is greater than $ FFFF, if it is less than or equal, then it is perceived as the function number in the export table. That is, we can call a function by number or by name.
Now let's see how the result of addition is entered into a variable, namely the operation of line 13.
1. TestCall.dpr.13: N: = myAdd (1,2);
2. push $ 02
3. push $ 01
4. call dword ptr [$ 0040cba4]
5. mov [$ 0040cbac], eax
Listing 5
And then everything is simple, put on the stack, two (2) and one (3), we call our function (4), the result of the addition is placed by the compiler in the register eax, and then from the register we copy the result into the variable N (5).
Here it is before you a common function call from Dll. Arguments are pushed onto the stack, a call is made, and the results are read from the registers (or stack).
My idea is that when instead of the real one my fake dll lies, then it first intercepts the inputs of the function and the name of the function, then it calls the real function and as if there was nothing.
Writing a fake Dll.
So, we have a list of functions and numbers, but each exported function must have some code. Which one. That's for the sake of this and everything is written. Those examples that I saw on the Internet, in them the useful code for each intercepted function is cloned, and you also need to know the export parameters of the function in order to call the real one with the same parameters. I was too lazy to do such painstaking work (to find a description of all the GDS32 functions and duplicate on delphi) this time. Still, cloning useful code is "not our method." The idea is this: we want the application to run our code after calling the function. Since the code is the same, well, let's create a separate procedure with useful code - ProxyProc. And every fake procedure will just have to call ProxyProc. Next, the proxy procedure must somehow find out exactly which procedure caused it. After thinking, I came to the conclusion that the ideal option is to put the number of the function on the stack. We also need to save the state of the registers and flags, because they can affect the execution of the procedure in this DLL. In total, we get four lines of code for each exported function. And yes, since we intervene in the underlying mechanisms of Windows, in order to be sure of what and where we messed up, we will write in assembler.
1. pushfd // the same for each function
2. pushad // the same for each function
3. push 2 // the number for each function is changed
4. call ProxyProc // the same for each function
Listing 6
And here is the code.
Everything is simple here. We export two fake procedures, and give them the names and numbers the same as in the real dll.
The trickiest part is the proxy procedure itself. What should it consist of.
1. Perform some useful operations with the function number and input parameters
2. Find out the address of this function
3. Return all the registers to their original state
4. Transfer control to the address of this procedure, as if there was nothing.
Accordingly, its code may be as follows.
Now when we compile this code, we get 'minilib2.dll'. Rename it to "minilib.dll" and replace it, and "minilib.dll" rename it accordingly to "minilib_.dll"
TestCall.dpr.13: N: = myAdd (1,2);
1. push $ 02
2. push $ 01
3. call dword ptr [$ 0040cba4] // call myAdd, but we get into fake
4. mov [$ 0040cbac], eax
Listing 9
In Listing 9, the part of the already seen code that calls the function from Dll and in the table below the state of the stack and registers after getting into the fake procedure, that is, after entering the call on line 3
Next, we see on the left the code of our four-line fake procedure and on the right the state of the stack after getting into proxyproc, that is, after entering the call on line 4
Further, we see the proxy procedure code on the left and the state of the stack on the right after receiving the address of the true procedure after line 6. We see that the return address to the fakeProc0002 fake procedure was removed from the stack and the function number was removed from the stack, but the address of the real function appeared on the stack.
Further we see the state of the registers in the table on the left and the state of the stack before jmp on the true procedure, that is, before executing line 9 of Listing 14. As you can see, the state of the stack and registers is identical to the state immediately after entering the fake procedure (listings 10 and 11), and We hope the true DLL procedure does not feel the difference. (28 in hexadecimal is 40 in decimal, that is, 10 times 4 bytes each is exactly the place on the stack where we have the address of the true procedure (Listing 17)).
And finally, the developer's procedure.
In this procedure, it is no longer necessary to write in assembler. Here we actually can intercept, without harming the contents of the registers and the stack.
For example, a simple code to output to a file all the numbers of called functions may be like this.
On line 7, the base pointer was entered into the variable _ebp
on line 9, the variable F was connected with the file
on line 10, the file was opened for addition
On line 11, the current date and time were recorded, and the number of the called function
We must add 4 bytes three times to the database pointer, therefore that there are three pointers in the stack after the function number: 1. Pointer to return to the fake procedure, 2. Pointer to return to the proxy procedure, and 3. Pointer to the stack placed by the compiler (push ebp). The pointer type PAnsiChar was chosen because addition and subtraction operations with numbers are allowed.
Line 12 closed the file.
Download examples here .
PS The proxy-GDS32.Dll was successfully compiled, the program using it did not produce any errors in operation, all calls were intercepted into the log file, failed sql queries were caught and optimized.
PPS The author of this article is not responsible for the use of information and material in this article. All information is for educational purposes only.
We write a research stand
The first thing we need is to get a list of all exported functions from this dll.
Let's do it with the following code:
1. program GetFuncsDll;
2. {$APPTYPE CONSOLE}
3. uses Windows;
4. var
5. ImageBase: DWORD; //адрес образа dll
6. pNtHeaders: PImageNtHeaders; // PE заголовок dll
7. IED: PImageExportDirectory; // адрес таблицы экспорта
8. ExportAddr: TImageDataDirectory; // таблица экспорта
9. I: DWORD; // переменная для цикла
10. NamesCursor: PDWORD; // указатель на адрес имени функции
11. OrdinalCursor: PWORD; // указатель на адрес номера функции
12. LIB_NAME:AnsiString; // имя dll
13. BEGIN
14. LIB_NAME:='MiniLib.dll';
15. loadlibraryA(PAnsiChar(LIB_NAME));
16. ImageBase := GetModuleHandleA(PAnsiChar(LIB_NAME));
17. pNtHeaders := Pointer(ImageBase + DWORD(PImageDosHeader(ImageBase)^._lfanew));
18. ExportAddr := pNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
19. IED := PImageExportDirectory(ImageBase+ExportAddr.VirtualAddress);
20. NamesCursor := Pointer(ImageBase + DWORD(IED^.AddressOfNames));
21. OrdinalCursor := Pointer(ImageBase + DWORD(IED^.AddressOfNameOrdinals));
22. For I:=0 to Integer(IED^.NumberOfNames-1) do begin
23. WriteLn(output,PAnsiChar(ImageBase + PDWORD(NamesCursor)^),'=',OrdinalCursor^ + IED^.Base);
24. Inc(NamesCursor);
25. Inc(OrdinalCursor);
26. end;
27. Readln;
28. end.
Листинг 1
There seem to be no difficulties. We get sequentially to the export table (line 19) of pointers to an array of names (NamesCursor) and an array of numbers (OrdinalCursor) and read the function by function, names and numbers. The number of functions is in the NumberOfNames field. This code was obtained on the Internet, then finalized and simplified.
Consider our test dll.
1. Library MiniLib;
2. function myAdd(a,b:integer): integer; stdcall;
3. begin
4. result:=a+b;
5. end;
6. function mySub(a,b:integer): integer; stdcall;
7. begin
8. result:=a-b;
9. end;
10. exports
11. myAdd,
12. mySub;
13. begin
14. end.
Листинг 2
There are no difficulties either. We export two functions - addition and subtraction.
we’ll have a list of exported functions and numbers:
myAdd = 2
mySub = 1
Listing 3 The
compiler assigned these numbers. Why exactly these? I do not know this.
Now let's focus on the addition function. Let's see in which code its call was compiled, for this we will call it and look in the debugger.
1. program TestCall;
2. {$APPTYPE CONSOLE}
3. uses Windows;
4. var
5. myAdd: function (a,b:integer): integer; stdcall;
6. Handle:HMODULE;
7. N:Integer;
8. begin
9. Handle := loadlibrary('MiniLib.dll');
10. @myAdd := GetProcAddress(Handle, 'myAdd');
11. //пример получения адреса функции по индексу
12. //@myAdd := GetProcAddress(Handle, PChar(2));
13. N:=myAdd(1,2);
14. writeLn(N);
15. readln;
16. end.
Листинг 4
Everything is simple here. We get the address of the function and call it. I will only explain that in the second parameter GetProcAddress a pointer to the function name, but this is if it is greater than $ FFFF, if it is less than or equal, then it is perceived as the function number in the export table. That is, we can call a function by number or by name.
Now let's see how the result of addition is entered into a variable, namely the operation of line 13.
1. TestCall.dpr.13: N: = myAdd (1,2);
2. push $ 02
3. push $ 01
4. call dword ptr [$ 0040cba4]
5. mov [$ 0040cbac], eax
Listing 5
And then everything is simple, put on the stack, two (2) and one (3), we call our function (4), the result of the addition is placed by the compiler in the register eax, and then from the register we copy the result into the variable N (5).
Here it is before you a common function call from Dll. Arguments are pushed onto the stack, a call is made, and the results are read from the registers (or stack).
Idea
My idea is that when instead of the real one my fake dll lies, then it first intercepts the inputs of the function and the name of the function, then it calls the real function and as if there was nothing.
Writing a fake Dll.
So, we have a list of functions and numbers, but each exported function must have some code. Which one. That's for the sake of this and everything is written. Those examples that I saw on the Internet, in them the useful code for each intercepted function is cloned, and you also need to know the export parameters of the function in order to call the real one with the same parameters. I was too lazy to do such painstaking work (to find a description of all the GDS32 functions and duplicate on delphi) this time. Still, cloning useful code is "not our method." The idea is this: we want the application to run our code after calling the function. Since the code is the same, well, let's create a separate procedure with useful code - ProxyProc. And every fake procedure will just have to call ProxyProc. Next, the proxy procedure must somehow find out exactly which procedure caused it. After thinking, I came to the conclusion that the ideal option is to put the number of the function on the stack. We also need to save the state of the registers and flags, because they can affect the execution of the procedure in this DLL. In total, we get four lines of code for each exported function. And yes, since we intervene in the underlying mechanisms of Windows, in order to be sure of what and where we messed up, we will write in assembler.
1. pushfd // the same for each function
2. pushad // the same for each function
3. push 2 // the number for each function is changed
4. call ProxyProc // the same for each function
Listing 6
We realize the idea
And here is the code.
1. Library minilib2;
2.
3. Uses Windows;
4.
5. Procedure ProxyProc; assembler;
6. asm
7. end;
8.
9. Procedure FakeProc0001; assembler;
10. asm
11. pushfd
12. pushad
13. push 000000001
14. call ProxyProc
15. end;
16.
17. Procedure FakeProc0002; assembler;
18. asm
19. pushfd
20. pushad
21. push 000000002
22. call ProxyProc
23. end;
24.
25. Exports
26. FakeProc0001 index 1 name 'mySub',
27. FakeProc0002 index 2 name 'myAdd';
28. Begin
29. End.
Листинг 7
Everything is simple here. We export two fake procedures, and give them the names and numbers the same as in the real dll.
The trickiest part is the proxy procedure itself. What should it consist of.
1. Perform some useful operations with the function number and input parameters
2. Find out the address of this function
3. Return all the registers to their original state
4. Transfer control to the address of this procedure, as if there was nothing.
Accordingly, its code may be as follows.
1. const LibName:pAnsiChar = 'MiniLib_.DLL'#0;
2. Procedure DeveloperProc;
3. // процедура разработчика
4. begin
5. end;
6. Procedure ProxyProc; assembler;
7. asm
8. call DeveloperProc; // Взываем процедуру, в которой читаем в стеке
// и регистрах, всё что хотели перехватить
9. add esp,4 // убираем адрес возврата в фейковую функцию
10. push LibName // помещаем адрес имени истинной dll
11. call LoadLibraryA // загружаем dll в память, узнаем адрес
12. push eax // помещаем этот адрес в стек
13. call GetProcAddress // номер функции же уже в стеке. узнаем адрес функции
14. mov [esp-4], eax // отмечаем в стеке этот адрес,
// хотя моя версия винды уже его там отметила
15. popad // восстанавливаем регистры
16. popfd // восстанавливаем флаги
17. jmp [esp-40] // сделали свое грязное дело,
// регистры и стек вернули к исходному состоянию
// передаем управление настоящей функции
18. end;
Листинг 8
Now when we compile this code, we get 'minilib2.dll'. Rename it to "minilib.dll" and replace it, and "minilib.dll" rename it accordingly to "minilib_.dll"
Now let's see how it works
TestCall.dpr.13: N: = myAdd (1,2);
1. push $ 02
2. push $ 01
3. call dword ptr [$ 0040cba4] // call myAdd, but we get into fake
4. mov [$ 0040cbac], eax
Listing 9
In Listing 9, the part of the already seen code that calls the function from Dll and in the table below the state of the stack and registers after getting into the fake procedure, that is, after entering the call on line 3
EAX 00364434 EBX 7FFDA000 ECX 00000000 EDX 00000003 ESI 16A1F224 EDI 13D84260 EBP 0012FFC0 ESP 0012FFA4 EIP 00364434 EFL 00000246 Listing 10 | 0012FFAC 00000002 // second argument 0012FFA8 00000001 // first argument -> 0012FFA4 0040811A // return address to executable Listing 11 |
Next, we see on the left the code of our four-line fake procedure and on the right the state of the stack after getting into proxyproc, that is, after entering the call on line 4
minilib2.myAdd: // aka fakeProc0002 1. pushfd 2. pushad 3. push $ 02 4. call $ 00364408 // call proxyProc Listing 12 | 0012FFAC 00000002 // second argument 0012FFA8 00000001 // first argument 0012FFA4 0040811A // return address to executable 0012FFAO 00000346 // flag register 0012FF9C 00364434 // register ЕАХ 0012FF98 00000000 // register ECX 0012FF94 00000003 // register EDX 0012FF90 7 register EBX 0012FF90 7 0012FF8C 0012FFAO // register the ESP 0012FF88 0012FFC0 // register the EBP 0012FF84 16A1F224 // register ESI 0012FF80 13D84260 // register EDI 0012FF7C 00000002 // function (02) -> 0012FF78 0036443D // return address feykovye procedure fakeProc0002 Listing 13 |
Further, we see the proxy procedure code on the left and the state of the stack on the right after receiving the address of the true procedure after line 6. We see that the return address to the fakeProc0002 fake procedure was removed from the stack and the function number was removed from the stack, but the address of the real function appeared on the stack.
minilib2.ProxyProc: 1. add esp, $ 04 2. push dword ptr [$ 0036782c] 3. call $ 00364394 // this is LoadLibrary 4. push eax 5. call $ 00364384 // this is GetProcAdress 6. mov [esp- $ 04], eax 7 . popad 8. popfd 9. jmp dword ptr [esp- $ 28] Listing 14 | 0012FFAC 00000002 // second argument 0012FFA8 00000001 // first argument 0012FFA4 0040811A // return address to executable 0012FFAO 00000346 // flag register 0012FF9C 00364434 // register ЕАХ 0012FF98 00000000 // register ECX 0012FF94 00000003 // register EDX 0012FF90 7 register EBX 0012FF90 7 0012FF8C 0012FFAO // register ESP 0012FF88 0012FFC0 // register EBP 0012FF84 16A1F224 // register ESI -> 0012FF80 13D84260 // register EDI 0012FF7C 0037437C // address of this procedure in this dll Listing 15 |
Further we see the state of the registers in the table on the left and the state of the stack before jmp on the true procedure, that is, before executing line 9 of Listing 14. As you can see, the state of the stack and registers is identical to the state immediately after entering the fake procedure (listings 10 and 11), and We hope the true DLL procedure does not feel the difference. (28 in hexadecimal is 40 in decimal, that is, 10 times 4 bytes each is exactly the place on the stack where we have the address of the true procedure (Listing 17)).
EAX 00364434 EBX 7FFDA000 ECX 00000000 EDX 00000003 ESI 16A1F224 EDI 13D84260 EBP 0012FFC0 ESP 0012FFA4 EIP 00364422 EFL 00000246 Listing 16 | 0012FFAC 00000002 // second argument 0012FFA8 00000001 // first argument -> 0012FFA4 0040811A // return address to executable 1. 0012FFAO 00000346 // flag register 2. 0012FF9C 00364434 // register EXA 3. 0012FF98 00000000 // register ECX 4. 0012FF94 00000003 // register EDX 5. 0012FF90 7FFDA000 // register EBX 6. 0012FF8C 0012FFAO // register ESP 7. 0012FF88 0012FFC0 // register EBP 8. 0012FF84 16A1F224 // register ESI 9. 0012FF80 13D84260 // register EDI 10. 0012FF7C 0037437C // address of this procedure in this dll Listing 17 |
And finally, the developer's procedure.
In this procedure, it is no longer necessary to write in assembler. Here we actually can intercept, without harming the contents of the registers and the stack.
For example, a simple code to output to a file all the numbers of called functions may be like this.
1. Procedure DeveloperProc;
2. var
3. F:text;
4. _ebp:PAnsiChar; //указатель на стек
5.begin
6. asm
7. mov _ebp,ebp;
8. end;
9. assignfile(F,'G:\Projects\dllproxy\logdll.txt');
10. append(F);
11. writeln(F,DateTimeToStr(now),': ',PDWORD(_ebp+3*4)^);
12. closefile(F);
13.end;
Листинг 18
On line 7, the base pointer was entered into the variable _ebp
on line 9, the variable F was connected with the file
on line 10, the file was opened for addition
On line 11, the current date and time were recorded, and the number of the called function
We must add 4 bytes three times to the database pointer, therefore that there are three pointers in the stack after the function number: 1. Pointer to return to the fake procedure, 2. Pointer to return to the proxy procedure, and 3. Pointer to the stack placed by the compiler (push ebp). The pointer type PAnsiChar was chosen because addition and subtraction operations with numbers are allowed.
Line 12 closed the file.
Download examples here .
PS The proxy-GDS32.Dll was successfully compiled, the program using it did not produce any errors in operation, all calls were intercepted into the log file, failed sql queries were caught and optimized.
PPS The author of this article is not responsible for the use of information and material in this article. All information is for educational purposes only.