WinBugs
-----------------------------------------------------------------------------------------------
This text tries to describe not only bugs but also various interesting things in MS Windows
operating systems.
Explore, discover, find, describe and contribute, please!
Last update: Aug-21-2001 Can be updated without notice!
-----------------------------------------------------------------------------------------------
TRUE vs. FALSE
--------------
Author: EliCZ
Date: Sep-24-2000
OS: All
BOOL is sometimes BYTE, sometimes DWORD. It means sometimes can be FALSE == n * 0x100.
For example if you return 0xFFFFFF00 from DllMain(,DLL_PROCESS_ATTACH,), the DLL is unmapped
because you've returned FALSE.
Solution:
Work with BOOL as with BYTE.
-----------------------------------------------------------------------------------------------
WriteProcessMemory can return NTSTATUS instead of BOOL
------------------------------------------------------
Author: EliCZ
Date: Sep-24-2000
OS: NT 4.0, Windows 2000
When writing to PAGE_EXECUTE or PAGE_EXECUTE_READ region fails,
WriteProcessMemory returns STATUS_ACCESS_VIOLATION instead of FALSE
(maybe VC++ compiler bug?).
Solution:
Result = WriteProcessMemory(...);
if((Result == STATUS_ACCESS_VIOLATION) || (Result == FALSE))
OutputDebugString("WriteProcessMemory failed!");
-----------------------------------------------------------------------------------------------
Remote thread into not-yet-initialized process causes various failures in that process
--------------------------------------------------------------------------------------
Author: EliCZ
Date: Sep-24-2000
OS: Windows 2000
Proof[WinBugs-RemThread.zip - MISSING]
Not-yet-initialized process is process which was created with suspended primary thread
that was not resumed yet (such process contains main module and ntdll.dll only). Process
is initialized (~DllMains are called with DLL_PROCESS_ATTACH, etc..) by the first
nonsuspended thread. Such a thread can be remote only. What it can cause depends on "thread
LPC registration" (via CsrClientConnectToServer) in LPC server (typically csrss.exe process):
A) Remote thread was not "LPC registered"
Examples: pure Nt/ZwCreateThread;
RtlCreateUserThread;
RtlQueryProcessDebugInformation;
CreateToolhelp23Snapshot(TH32CS_SNAPMODULE,)
Here is it clear:
Initialization of KERNEL32.dll (or USER32.dll) fails because thread can't connect
to LPC server
{Nt/ZwSecureConnectPort(,"\\Windows\\ApiPort",...) returns STATUS_PORT_CONNECTION_REFUSED
because thread was not "LPC registered"}
in its DllMain during DLL_PROCESS_ATTACH. Then DllMain returns FALSE and
EXCEPTION_DLL_INIT_FAILED (or EXCEPTION_APP_INIT_FAILURE) is raised. What happens
later (crash) is not important.
B) Remote thread was "LPC registered"
Examples: CreateRemoteThread
DLL initialization is OK (but DLLs are initialized by remote thread), some DLLs
created new window classes (COMCTL32.dll created SysHeader*, SysList*, SysTab*,..
classes). Then thread executes the routine which was specified as lpStartAddress
in call to CreateRemoteThread. Then thread terminates. Alas! It took all newly
created window classes (USER32.dll and WIN2K.sys classes are preserved) out!
Later when application wants to create window or dialog containing erased classes
(e.g. SysListView32), window creation fails.
Symptoms: Application can't invoke common dialog boxes; TaskMgr can crash;....
(both common dialogs and dialogs in TaskMgr use/include window classes created by
COMCTL32.dll).
After remote thread termination, you will resume suspended primary thread. But this
primary thread brings DLL_THREAD_ATTACH notification to DLLs and TLS callbacks. As you
can see there was total role exchange: primary thread <-> remote thread. Hence, remote
thread into not-yet-initialized process should not be used at all.
Solution:
If you really need remote thread into not-yet-initialized process, you should use
CreateRemoteThread function and you should ensure the thread never terminates (at the end
of thread routine put while(TRUE);).
-----------------------------------------------------------------------------------------------
DPMI service "Set Debug Watchpoint" (0B00h) doesn't return handle
-----------------------------------------------------------------
Author: EliCZ
Date: Sep-24-2000
OS: Windows 2000
Proof[WinBugs-DPMISDW.zip - MISSING]
This DPMI service should return handle to debug watchpoit in BX register if it was
succesful (CF is clear). Unfortunately it doesn't change BX register at all.
Solution:
Valid handles are 0..3. Work with hardcoded values:
CDW(0); CDW(1); CDW(2); CDW(3);
if(SDW(...)) HandleA = 0; if(SDW(...)) HandleB = 1; if(SDW(...)) HandleC = 2;
CDW(HandleB); if(SDW(...)) HandleD = 1; if(SDW(...)) HandleE = 3;
CDW(HandleA); if(SDW(...)) HandleF = 0;
-----------------------------------------------------------------------------------------------
LDT descriptor can be initialized via INT 0x2A
----------------------------------------------
Author: EliCZ
Date: Sep-24-2000
OS: NT 4.0, Windows 2000
Interrupt 0x2A is normally used as KiGetTickCount but it can be also used in DPMI (DOS or
Win16) applications for fast setting of LDT selectors (NTVDM uses it). Allowed are DPL3 code
or data descriptors (forget call/int gates) with limit <= HighestUserAddress.
;Make new (LDT) selector
MOV AX, 0
INT 31H
JC DPMIFailed
;Now set the descriptor to writeable DATA 0..3FFH
MOV EBP, 0F0F0F0F1H ;magic
MOV BX, AX ;new selector
MOV ECX, 0000003FFH ;low part of descriptor
MOV EDX, 00000F200H ;high part of descriptor
MOV EAX, EBP ;magic
INT 2AH
JC DPMIFailed
-----------------------------------------------------------------------------------------------
API forwarding is API hooking (DLL redirection method) provided by NTDLL/KERNEL32
---------------------------------------------------------------------------------
Author: EliCZ
Date: Sep-24-2000
OS: All
Proof[WinBugs-DetectHooks.zip - MISSING]
=> primitive API hooking (DLL redirection) detections can give false positive results.
Examples of bad detections:
VirtualQuery method, comparing highest bytes of API address with module base
and other module walking based detections.
Note for NT:
Modules containing "backwarded" APIs are loaded dynamically on demand (when thread
needs address of forwarded API).
Limits of API forwarding:
Module name must have ".dll" extension (it can't be .exe, .drv, ...).
Limits of API forwarding in 9X:
First, it was nice surprise I've found that 9X supports API forwarding partially.
But module containing "backwarded" API must be preloaded and ordinals forwarding
(e.g. MyDll.#12) is not supported.
-----------------------------------------------------------------------------------------------
User thread's environment block (TEB) is freed when thread terminates even if there exist
-----------------------------------------------------------------------------------------
references to this thread
-------------------------
Author: EliCZ
Date: Dec-10-2000
OS: Windows 2000 (NT 4.0 ?)
Proof[WinBugs-NoTEB.zip - MISSING]
That's because RtlQueryProcessDebugInformation (CreateToolhelp23Snapshot(TH32CS_SNAPMODULE,))
sometimes mayn't free new thread's stack. That's because RtlFreeUserStack can't free stack of
terminated thread (stack section is in TEB.E0C) or may crash running thread by freeing its
stack.
Solution to free user thread's stack:
Use ThreadFunc which terminates via RET 4 or via ExitThread (stack is then freed implicitly).
In case thread is terminated via NtTerminateThread:
a) create thread suspended
b) get thread TEB and from it thread's stack section
c) resume thread.
-----------------------------------------------------------------------------------------------
Situation:
Windows NT, 4.00.1381, Workstation, Service Pack 6a
connted via Terminal client, 5.0.2221.1, 128 Bit
to Windows 2000, 5.00.2195, Advanced Server.
Used API function:
NtQueryObject in ntdll.dll called from user mode
with OBJECT_INFORMATION_CLASS of ObjectAllTypesInformation.
Result:
The function returns the right object types
but only the count of object types wich are
available on Windows NT 4.0
Result Windows NT 4.0
------------------- -----------------
1, Type 1, Type
2, Directory 2, Directory
3, SymbolicLink 3, SymbolicLink
4, Token 4, Token
5, Process 5, Process
6, Thread 6, Thread
7, Job 7, Event
8, Event 8, EventPair
9, EventPair 9, Mutant
10, Mutant 10, Semaphore
11, Callback 11, Timer
12, Semaphore 12, Profile
13, Timer 13, WindowStation
14, Profile 14, Desktop
15, WindowsStation 15, Section
16, Desktop 16, Key
17, Section 17, Port
18, Key 18, Adapter
19, Port 19, Controller
20, WaitablePort 20, Device
21, Adapter 21, Driver
22, Controller 22, IoCompletion
23, Device 23, File
(24, Driver - not returned)
(25, IoCompletion - not returned)
(26, File - not returned)
Bug info:
29 Jan 2001, NicoDE (nico@bendlins.de)
----------------------------------------
And EliCZ adds (see also LocPInfo.zip\readme.txt) :
When querying \Device\NamedPipe\net\NtControlPipeX, the querying
thread may be blocked. Try for example SysInternals HandleEx and
try to view Properties/Security of NtControlPipeX.
BTW: The new HandleEx uses INT 1, when things go wrong in NT.
-----------------------------------------------------------------------------------------------
You can't open Desktop when you don't have DESKTOP_READOBJECTS and DESKTOP_WRITEOBJECTS
-----------------------------------------------------------------------------------------
access allowed
-------------------------
Author: EliCZ
Date: Aug-21-2001
OS: Windows NT
That's because Win32K.sys!xxxOpenDesktop adds to access mask, you've passed in a call to
OpenDesktop, 0x81 (DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS). For example, even if you're
trying to open a deskktop for READ_CONTROL only and you don't have D_RO and D_WO granted,
call doesn't succeed.
-----------------------------------------------------------------------------------------------
APC oddities
------------
Author: EliCZ
Date: Aug-21-2001
OS: All
In Windows 9x aren't APCs queued before thread begins to execute spawned before thread
begins to execute (like in NT). They are spawned when thread enters an alertable state only.
In Windows NT are APCs queued before thread begins to execute spawned before thread begins
to execute, but in RANDOM order (not in FIFO order).
-----------------------------------------------------------------------------------------------
Bug/trick/interesting thing
---------------------------
Author: <You>
Date: <soon>
OS: ?
???
-----------------------------------------------------------------------------------------------