Skip to content
 
 

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Broken Stack Guard Page

These demo programs show how thread stack expansion works in Windows and how it's surprisingly fragile so that an application can even break other program's stack guard page that can cause clueless crash sometime later.

Note

  • How to compile and Run demo program

    1. Open Visual C++ command prompt.
    2. To compile, go to Src folder and enter cl.exe /Ox /EHsc demoN.cpp (N is number and currently 1, 2 or 3)
    3. Enter demoX.exe to run the demoX.
  • As a first step, all demo programs get stack top address of main thread from TIB (Thread Information Block).

Demo 1

  • In this demo, AccessStackGuardMemory function is simply trying to read memory address at the beginning of the one page (4KiB) above the current stack top address. (Remember stack grows to lower address from higher address).

  • Use this command line to compile cl.exe /Ox /EHsc demo1.cpp /link /stack:262144 to set max stack size as 256KiB.

  • It continues to move the memory pointer up by 1 page (in other words, subtract 0x1000 from the current address) and try to read int (4 bytes) value. You can hit any key other than ESC to continue to move to the next pages.

  • This essentially simulating nested function calls and each function allocate or use 4KiB stack memory probably for local variables.

  • Stack guard page is set up above the current stack top. Whenever this program is trying to access (read) a memory in the guard page area, STATUS_GUARD_PAGE_VIOLATION exception occurs. This exception is caught by the Windows Kernel exception handler and it'll expand or commit current stack by 1 page.

  • When you keep hold a non-ESC key for a while, this program eventually hit stackoverflow exception (0xC00000FD) because it'll hit the maximum stack size which is specified at link time. (Default is 1MiB)

    Demo1 output

  • There is linker option /STACK:reserve[,commit] sets the size of the stack. Default reserve size is 1 MiB. On my Windows 10, the minimum reserved stack size seems 256 KiB because the value is ignored if I specify smaller than 256 KiB.

  • While running, launch Vmmap.exe and select demo1.exe

    VMMAP

  • Hit key and on the command window where demo1.exe is running to expand the stack then go back to the vmmap and refresh. You can see the commit memory is growing.

Demo 2

  • This program tries to just READ main thread's stack guard pages from a different thread in same process.
  • What this program does:
    1. Set up unhandled exception filter to catch and write to console when Windows structured exception is thrown.
    2. Create a new thread and passes main thread's stack limit address (stack top)
    3. In a new thread, try to read above the stack top address where main thread's stack guard page exists.
  • You can see STATUS_GUARD_PAGE_VIOLATION (OurUnhandledExceptionFilter receives the exception) is thrown whenever try to access stack guard page. Normally when thread stack is growing and touching guard page this exception is handled by Kernel code you won't see the exception. However, in this demo, the guard page is touched by other thread then no stack expansion happens. If you continue to hit key, it eventually tries to access above the stack guard pages and you will see ACCESS_VIOLATION exception.
  • As far as I remember, in previous versions of Windows (Maybe including Windows 7 but not 100% sure), Windows didn't even throw the STATUS_GUARD_PAGE_VIOLATION exception so it just blew up the guard pages. This will lead to an obscure access violation crash some time later.

Demo 3

  • Modified Demo 2 a little bit. ThreadProc accesses (3 pages) above the current stack top to break all 3 stack guard pages. Then calling Crash() function which allocates a large size local variable.
  • The Crash() function throws ACCESS_VIOLATION and never returns.
  • This program shows that random memory read on other thread stack's guard pages can cause access violation later. Although, this kind of crash should be very rare and probably almost never happen if your program is well-written.

Demo 4

This is last demo showing the most interesting scenario. A malicious process just doing READ access on other process thread's stack guard page causes access violation crash of the victim process. The important thing is the access violation does not happen immediately after stack guard page broken. It'll happen later when the thread start using more stack memory, so the crash will be mostly clueless and crash dump doesn't tell much about the story.

  • Malicious process needs following privileges to target victim process

    • PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_CREATE_THREAD
  • Executing IsBadCodePtr from the threads in the victim process.

  • Let's crash Chrome browser:

    1. Compile command line is slightly different for this demo

      • Use cl.exe /Ox /EHsc demo4.cpp Shlwapi.lib
    2. Launch Chrome and load couple of tabs.

    3. Run demo4.exe like below on command line.

      • demo4.exe chrome.exe
      • I opened 3 YouTube tabs.

      Chrome Loaded 3 YouTube Tabs

    4. Go back to the Chrome browser click individual tab.

      • You are likely see some of tabs are crashed. If it's not crashing, you can try again or load other more expensive web site.

      Crashed Tabs

    5. Sometimes main Chrome process is killed that also kill all of its children processes that causes Windows Error Reporting dialog pops up.

      Chrome WER

    6. When you attach debugger, you will see something like below. It's access violation in random function. Unfortunately it won't tell what really happened.

      Postmortem debugger VS Postmortem debugger ntsd

  • If run demo4 process at low integrity level, it cannot open chrome processes running at medium level. However, there are still chrome processes running at low and untrusted integrity level and demo4 be able to blow their stack guard pages. Although it can't crash all chrome since the parent chrome process is running at medium level.

    • Run this command to change integrity level (from Administrator command prompt) icacls demo4.exe /setintegritylevel low

    Chrome process integrity level

Conclusion

  • It shows how stack growing mechanism is fragile in especially multi-threaded environment. A subtle bug in one thread that reads other thread's stack guard page area can crash the application.
  • See this Mark's blog for thread stack expansion details.
  • Allowing just PROCESS_VM_READ right can lead serious security issue like allowing crash your application from any other apps. See this Blog and IsBadxxxPtr APIs are dangerous
  • .NET commits whole thread stack memory (no run-time growing). They've chosen reliability over more memory consumption.
  • When I working on servers, I always committed whole thread stack memory at the thread initialization time especially for the worker threads in game server. I think eliminating run-time stack growing will give tiny bit of perf benefits as well. :)

About

Windows Broken Stack Guard Page

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages