Simple DIY proxy dll

It took me to intercept calls to 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 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.

Also popular now: