📜 ⬆️ ⬇️

Simple proxy DLL do it yourself

It took me to intercept the calls of GDS32.DLL. I decided to write a proxy dll.

We write a research stand


The first thing we need is to get a list of all exported functions from a real dll.
Let's do this 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 are no difficulties here. 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 produced on the Internet, then refined 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:=ab; 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.
The list of exported functions and numbers will be as follows:

myAdd = 2
mySub = 1
Listing 3

Such numbers are assigned by the compiler. Why precisely such? I do not know this.
Now focus on the addition function. Let's look at what code its call was compiled for, for this we will call it and see 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 


It's simple. We get the address of the function and call it. I will only explain that in the second parameter, GetProcAddress is a pointer to the name of the function, but this is if it is greater than $ FFFF, if less than or equal, then it is taken 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 adding to the variable is entered, 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, we put the stack, the two (2) and the unit (3), 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 results are read from the registers (or stack).

Idea


My idea is that when, instead of the present, my fake dll lies, then at first it intercepts the inputs of the function and the name of the function, then calls the real function and as if there was nothing.

We write fake Dll.

So, we have a list of functions and numbers, but some exported code must correspond to each exported function. What a. That's what this is written for. Those examples that I saw on the Internet, in them the useful code for each intercepted function is cloned, and moreover you need to know the parameters of the export function to call this with the same parameters. I was too lazy to carry out such hard work (to find the description of all the functions of GDS32 and duplication on Delphi) this time. Still, cloning a useful code is “not our method.” The idea is as follows - we want the application to execute our code after the function is called. Once the code is the same, well, let's make a separate procedure with a useful code - ProxyProc. And every fake procedure should just call ProxyProc. Next, the proxy procedure must somehow find out exactly what procedure caused it. After deliberation, I came to the conclusion that the ideal option is to push the number of the function onto the stack. We also need to preserve the state of the registers and flags, because they can affect the execution of the procedure in this DLL. Total we get for each exported function four lines of code. And yes, since we interfere with the underlying mechanisms of the work of Windows, in order to be sure of what and where we have messed up, we will write in assembler.

1. pushfd // the same for each function
2. pushad // same for each function
3. push 2 // changing the number for each function
4. call ProxyProc // the same for each function
Listing 6

We implement 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 


It's simple. We export two fake procedures, and give them the names and numbers the same as in this dll.
The next trickiest part is the proxy procedure itself. What it should consist of.

1. Perform some useful operations with the number of the function and input parameters.
2. Find out the address of this function
3. Return all registers to their original state.
4. Transfer control to the address of this procedure, as if nothing had happened.

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 substitute, and "minilib.dll" rename 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 get into fake
4. mov [$ 0040cbac], eax
Listing 9
In Listing 9, the part of the already seen code that calls the function from the Dll and in the table below the state of the stack and the 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 in the executable
Listing 11


Further 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: // it is also 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 in the executable
0012FFAO 00000346 // flag register
0012FF9C 00364434 // register EAX
0012FF98 00000000 // ESC register
0012FF94 00000003 // EDX register
0012FF90 7FFDA000 // EBX register
0012FF8C 0012FFAO // ESP register
0012FF88 0012FFC0 // EBP register
0012FF84 16A1F224 // ESI register
0012FF80 13D84260 // EDI register
0012FF7C 00000002 // function number (02)
-> 0012FF78 0036443D // fakeProc0002 fake procedure return address
Listing 13


Then we see the proxy procedure code on the left and the state of the stack after receiving the true procedure address after executing line 6 on the right.

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 in the executable
0012FFAO 00000346 // flag register
0012FF9C 00364434 // register EAX
0012FF98 00000000 // ESC register
0012FF94 00000003 // EDX register
0012FF90 7FFDA000 // EBX register
0012FF8C 0012FFAO // ESP register
0012FF88 0012FFC0 // EBP register
0012FF84 16A1F224 // ESI register
-> 0012FF80 13D84260 // EDI register
0012FF7C 0037437C // the address of this procedure in this dll
Listing 15


Then we see in the table on the left the state of the registers and on the right the state of the stack before jmp for 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) hope the true dll procedure doesn’t feel the difference. (28 in hexadecimal is 40 in decimal, that is, 10 times 4 bytes 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 in the executable
1. 0012FFAO 00000346 // flag register
2. 0012FF9C 00364434 // register EAX
3. 0012FF98 00000000 // ESC register
4. 0012FF94 00000003 // register EDX
5. 0012FF90 7FFDA000 // EBX register
6. 0012FF8C 0012FFAO // ESP register
7. 0012FF88 0012FFC0 // EBP register
8. 0012FF84 16A1F224 // ESI register
9. 0012FF80 13D84260 // EDI register
10. 0012FF7C 0037437C // address of this procedure in this dll
Listing 17


Finally, the developer procedure.

In this procedure, it is not necessary to write in assembly language. Here we actually can do the interception, without harming the contents of the registers and the stack.

For example, a simple code to display in the file all the numbers of called functions can 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 in the variable _ebp brought the pointer base
on line 9 linked variable F to the file
on line 10 opened the file to add
On line 11 recorded the current date and time, and the number of the function called
We must add 4 bytes three times to the base pointer, because there are three pointers on 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.
On line 12, they closed the file.

Examples download here .

PS Proxy-GDS32.Dll was successfully compiled, the program using it did not issue any errors in its work, 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 given solely for educational purposes.

Source: https://habr.com/ru/post/215389/


All Articles