{"id":14036,"date":"2026-03-23T16:10:14","date_gmt":"2026-03-23T15:10:14","guid":{"rendered":"https:\/\/www.corelan.be\/?p=14036"},"modified":"2026-04-11T03:55:39","modified_gmt":"2026-04-11T01:55:39","slug":"debugging-windbg-windbgx-fundamentals","status":"publish","type":"post","link":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/","title":{"rendered":"Debugging - WinDBG &#038; WinDBGX Fundamentals"},"content":{"rendered":"\n<h2>Introduction<\/h2>\n<p>Is AI an evolution or a revolution? Or both? Those are interesting questions.<\/p>\n<p>Speaking of AI - even ChatGPT and Grok agree: A debugger is the one of the most (if not the most) important tool for exploit developers, malware analysts, and reverse engineers. Exploit development, malware analysis and reverse engineering all ultimately share certain core activities, including <strong>figuring out and observing what a process does at runtime.<\/strong><br \/>\nA debugger is the tool that facilitates that. It's like turning an application into a fish bowl, giving you some sort of control over the fish as well:<\/p>\n<ul>\n<li><strong>Exploit developers<\/strong> tend to look at memory corruptions, look at the state of registers, memory contents and layouts. They search for certain instructions, gadgets, primitives and essentially build an exploit step by step, using the debugger as their companion.<\/li>\n<li><strong>Malware analysts<\/strong> may use a debugger to unpack malware, bypass anti-analysis tricks, dump content from memory, and so on.<\/li>\n<li><strong>Reverse engineers<\/strong> use debuggers to follow execution paths, understand logic, confirm static analysis hypotheses. It tells you what actually happens.<\/li>\n<\/ul>\n<p>(Of course, tools such as <a href=\"https:\/\/github.com\/NationalSecurityAgency\/ghidra\" target=\"_blank\" rel=\"noopener\">Ghidra<\/a>, <a href=\"https:\/\/hex-rays.com\/ida-pro\" target=\"_blank\" rel=\"noopener\">IDA Pro<\/a> and <a href=\"https:\/\/binary.ninja\/\" target=\"_blank\" rel=\"noopener\">Binary Ninja<\/a> play an important role in the world of malware analysis, reverse engineering and exploit development as well, but I'll stick to the debugger for now.)<\/p>\n<p>Debuggers are powerful utilities, because they allow us to see what is going on, they allow us to intervene, inspect registers, memory and execution state, and basically slow down the execution of an application all the way to a single-CPU-instruction-level. Once you have a debugger connected to an application, far fewer things remain invisible.  There might still be obfuscation and anti-debugging or other things that can make your life difficult. But at least you're closer to seeing what happens under the hood.<\/p>\n<p>Unlike what some people believe, a debugger is not an application that is inserted between the CPU and the application, and it's not a proxy.<br \/>\nA debugger is typically a separate process that interacts with the OS debugging subsystem, which delivers debug events and allows the debugger to influence execution of the target process.  <\/p>\n<p>An application runs because its threads are scheduled by the kernel to execute on the CPU.<br \/>\nUnder normal circumstances, execution proceeds uninterrupted. However, when certain events occur\u2014such as exceptions, breakpoints, or thread creation\u2014the kernel generates debug events.<br \/>\nIf a debugger is attached to the process, these events are delivered to the debugger instead of being handled immediately by the application. The debugger can then inspect the process state (registers, memory, stack) and decide how execution should continue.<br \/>\nIn this model, the debugger does not sit between the application and the CPU. Execution still happens natively on the processor. The debugger operates by reacting to events and controlling execution through the operating system.<\/p>\n<p>A debugger gives you two core capabilities: <strong>visibility<\/strong> (seeing state) and <strong>control<\/strong> (influencing execution). Everything else builds on that.<\/p>\n<p>Anyway, the more fluent you become at operating a debugger, the closer you'll get to thinking in the language of the machine, and the easier (crash) analysis, debugging, vulnerability research and exploit development process will become.<\/p>\n<p>In today's post, I'm going to walk you through the basics of using Microsoft's free debugger: WinDBG (Classic) and WinDBGX.   We're basically going to learn <em>how the Debugger works<\/em> and how we can use it.  We'll cover how to install and configure them and how to perform basic elementary tasks using a simple demo application. In later posts, we'll go over some more advanced features, basically <em>learning how to make the Debugger work for us.<\/em><\/p>\n<p>Maybe you got here because you're preparing to take a <a href=\"https:\/\/www.corelan-training.com\" target=\"_blank\" rel=\"noopener\">Corelan class<\/a>. Or maybe you're here because you're just learning stuff on your own. Whatever it may be, welcome! We'll take this slowly, one step at a time.<\/p>\n<p>If you have any questions, feel free to join our <a href=\"\/index.php\/discord\/\" target=\"_blank\" rel=\"noopener\">Discord server<\/a> and reach out.<\/p>\n<h2>Why WinDBG?<\/h2>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/windows-hardware\/drivers\/debuggercmds\/windbg-overview\" target=\"_blank\" rel=\"noopener\">WinDBG(X)<\/a> is not the only debugger out there, and it's not necessarily the best one.<br \/>\nIts default GUI is rather \"basic\", and if you're new to the craft, you may end up fighting the debugger as well as the application\/memory corruption.<br \/>\nWinDbg is powerful, but it does have a learning curve, and out of the box it may not be the easiest debugger for beginners.<\/p>\n<p>Nevertheless, it does have a few interesting features that make the learning curve worth while:<\/p>\n<ul>\n<li>It has extensions\/plugins that will assist with examining the Windows heap, amongst others.<\/li>\n<li>It has powerful scripting and automation capabilities<\/li>\n<li>It has the ability to open crash dump files<\/li>\n<li>You can use it to debug the Windows Kernel as well as applications<\/li>\n<li>It has native support for symbols. (I'll explain what symbols are in a moment).<\/li>\n<li>Through its symbol support and availability of extensions, it provides insight in certain Windows internals.<\/li>\n<li>WinDBGX, the newer version of WinDBG, has a neat feature called Time Travel Debugging (TTD), which records execution of an application and it allows you to step backwards in time. For complex bugs and root cause analysis, this can be pretty powerful.<\/li>\n<\/ul>\n<h3>Symbols?<\/h3>\n<p>Symbols are generated during the build process (typically at link time). A symbol file (with extension <mono>.pdb<\/mono>) contains human-readable elements from the source code, mapped to addresses and structures in the compiled binary. For example: function names, variable names, data types, and so on.<br \/>\nThere are 2 main types: 'Public' and 'Private' Symbols. Private symbols provide more details than public symbols (for instance, local variable names).<br \/>\nSymbols are not required to run the application, but it is very useful to have when you are debugging an application.<\/p>\n<p>When you have the source code of an application, it makes sense to generate the corresponding symbols yourself. If WinDBG is able to locate the matching <mono>.pdb<\/mono> file (for example, because it's found in the same folder, or it is retrieved via the symbol path configuration), it will automatically parse it, giving you names (of functions etc) as opposed to numbers\/addresses.<br \/>\nMicrosoft decided to release (public) symbols for some of its software, including the Operating System itself, Office Products, web browser, etc.<br \/>\n(And some other developers did the same - Mozilla, Chromium, etc).<\/p>\n<blockquote><p>If you're a bit familiar with Visual Studio, you may have noticed already that a symbol .pdb file is created automatically for Visual C++ projects. You can find the debug information options in the project properties, under the \"Linker\" - \"Debugging\" settings.  The default setting is \/DEBUG, but you can change it to \/DEBUG:FASTLINK and \/DEBUG:FULL. The latter will create full (or 'private') symbols.\n<\/p><\/blockquote>\n<h2>Installing WinDBG Classic and WinDBGX<\/h2>\n<p>I'm going to use a Windows 11 Virtual Machine, but you can use Windows 10 as well.<br \/>\nWinDBG Classic has versions that work on older Windows versions as well, the newer WinDBGX debugger only runs on newer Windows versions. (Win10 and up).<\/p>\n<h3>Why do we need both?<\/h3>\n<p>WinDBG Classic does not get updated automatically, WinDBGX is under active development.<br \/>\nThat's great, but I have noticed that some updates introduce features or change behaviour of existing commands.<br \/>\nSome of the commands I rely on in WinDBG Classic no longer work in WinDBGX. Likewise, there are features in WinDBGX that simply don't exist in Classic WinDBG.<br \/>\nThat's why I usually install and use both on my research virtual machines. I basically switch back and forth based on the task I'm trying to do. I know that WinDBG Classic will behave consistently (because it won't get updated mid-session)<br \/>\nAt the same time, it means it may have outdated logic.<\/p>\n<p>Luckily, we can run both debuggers on the same machine. They're fundamentally different applications and don't overwrite one another. That said, I don't think it's a good idea to connect both of them to the same application at the same time.<br \/>\nIf you would like to have a debugger for certain tasks, and you want some other tool to monitor or trace things, then you may want to consider a instrumentation platform such as <a href=\"https:\/\/frida.re\/\" target=\"_blank\" rel=\"noopener\">Frida<\/a>.<br \/>\nAnyway, I digress, maybe I'll talk a bit about Frida in another post.<\/p>\n<p>let's start by installing both WinDBG Classic and WinDBGX on our system.<\/p>\n<h3>Scripted\/automated installation<\/h3>\n<p>Students that are taking one of the <a href=\"https:\/\/www.corelan-training.com\" target=\"_blank\" rel=\"noopener\">Corelan classes<\/a> are requested to bring a few Virtual Machines that contains some applications and configurations. In order to streamline the installation &amp; configuration process, I decided to create a PowerShell script that will download and install most of the required components, including WinDBG Classic and WinDBGX. The script includes installers for other components as well, such as Python, Visual Studio Express Edition, etc.<\/p>\n<p>If you're a fan of automation and don't mind installing those other components as well (or if you are preparing for class), feel free to open our <a href=\"https:\/\/github.com\/corelan\/CorelanTraining\" target=\"_blank\" rel=\"noopener\">CorelanTraining repository on GitHub<\/a> and download <span class=\"corelan-mono\">CorelanWin11VMInstall.ps1<\/span> to your system.<\/p>\n<p>Next, open a PowerShell Terminal, with administrator privileges:<\/p>\n<ul>\n<li>Click on the \u201cStart\u201d icon, start typing <span class=\"corelan-mono\">powershell<\/span>. The \u201cBest match\u201d section should show \u201cWindows PowerShell\u201d.<\/li>\n<li>Right click \u201cWindows PowerShell\u201d and choose \u201cRun as administrator\u201d<\/li>\n<\/ul>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/PowerShell.png\" style=\"display: block; margin-left: auto; margin-right: auto\" alt=\"PowerShell\" title=\"PowerShell.png\" border=\"0\" width=\"314\" height=\"232\"><\/p>\n<p>In the administrator PowerShell Terminal, first run the following statement to allow the downloaded PowerShell script to execute:<\/p>\n<pre><cmd>Set-ExecutionPolicy RemoteSigned<\/cmd><\/pre>\n<p>(if that doesn't work, you may have to open it even further)<\/p>\n<pre><cmd>Set-ExecutionPolicy Unrestricted<\/cmd><\/pre>\n<p>Next, just to be sure, verify that your VM has a working internet connection.<br \/>\nAlso, please make sure your laptop is connected to a power supply, and is not installing Windows updates or any other software installers at this point.<\/p>\n<p>In the administrator PowerShell Terminal, navigate to the folder that contains the downloaded PowerShell script, run the script and lean back:<\/p>\n<pre><cmd>.\\CorelanWin11VMInstall.ps1<\/cmd><\/pre>\n<p>The script will download and run various installers (including WinDBG Classic and WinDBGX).  The last application in the list will be Visual Studio Express Edition.  This step requires a bit of manual user interaction (such as clicking \u201cContinue\u201d and \u201cInstall\u201d).  The Visual Studio Express Edition installer will download a few Gigabytes of  components, so please be patient.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/corelanvminstall.png\" alt=\"corelanvminstall\" title=\"corelanvminstall.png\" border=\"0\" width=\"600\" height=\"321\" \/><\/p>\n<p>Finally, check the output of the <span class=\"corelan-mono\">CorelanWin11VMInstall.ps1<\/span> script for errors and, after verifying that everything looks ok, reboot your VM.<br \/>\nWait for Windows updates to install (if needed).<\/p>\n<p>You're all set, you can continue with \"Verify if both versions work\"<\/p>\n<p>Of course, if you prefer to just install WinDBG Classic and WinDBGX by hand, following the steps below:<\/p>\n<h3>Manual Installation<\/h3>\n<h4>Classic WinDBG<\/h4>\n<ol>\n<li>Download the Windows 10 SDK from <a href=\"https:\/\/developer.microsoft.com\/windows\/downloads\/windows-10-sdk\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.microsoft.com\/windows\/downloads\/windows-10-sdk<\/a>. (version 10.0.183621 is known to work well)<\/li>\n<li>Launch the installer with administrator privileges (right-click on the file and choose \u2018Run as administrator\u2019)<\/li>\n<li>During installation, only select \u201cDebugging tools for Windows\u201d. Deselect the other options<\/li>\n<li>Install in the default path. <span class=\"corelan-mono\">(C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\...)<\/span><\/li>\n<\/ol>\n<h4>WinDBGX<\/h4>\n<ol>\n<li>Open a PowerShell terminal with administrator privileges<\/li>\n<li>Run the following command<\/li>\n<\/ol>\n<pre><cmd>winget install Microsoft.WinDbg --silent --accept-package-agreements<\/cmd><\/pre>\n<p>Once installed, you can check for updates via the following command:<\/p>\n<pre><cmd>winget upgrade Microsoft.WinDbg<\/cmd><\/pre>\n<h3>Microsoft Symbol Server<\/h3>\n<p>Since we're working on Windows, we'll go ahead and already configure our system to connect to Microsoft's Symbol Server, which contains the .pdb files for Operating System components, Office Products, etc.<\/p>\n<p>There are a few ways to configure WinDBG(X) to connect to the right server, but I prefer to use the following system-wide configuration.<br \/>\nFrom an administrator command prompt, run the following command:<\/p>\n<pre><cmd>setx \/m _NT_SYMBOL_PATH \"srv*c:\\symbols*https:\/\/msdl.microsoft.com\/download\/symbols\"<\/cmd>\n<\/pre>\n<p>This will create a Systemwide environment variable <span class=\"corelan-mono\">_NT_SYMBOL_PATH<\/span><br \/>\nIts value is then set to <span class=\"corelan-mono\">\"srv*c:\\symbols*https:\/\/msdl.microsoft.com\/download\/symbols\"<\/span>.<br \/>\nWinDBG(X) and other applications that rely on the same system environment variable, will now connect to Microsofts Symbol Server, and download relevant .pdb files to folders inside <span class=\"corelan-mono\">c:\\symbols<\/span>.<br \/>\n(Of course, feel free to change the local folder\/ path as you wish. WinDBG(X) will create the folder if needed).<\/p>\n<p>From this point forward, make sure your machine has an active internet connection, as that is required to download symbols from Microsoft's Symbol Server.<\/p>\n<h3>Verify if both versions work<\/h3>\n<p>Open an administrator command prompt and navigate to the folder that contains the WinDBG Classic installation:<\/p>\n<pre>\n<cmd>c:<\/cmd>\n<cmd>cd \"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\"<\/cmd>\n<\/pre>\n<p>Inside that folder, you'll find a bunch of folders, including some folders that correspond with the 4 architectures that are supported by WinDBG Classic: x86, x64, arm and arm64<br \/>\nLet's run the version for 32-bit. Enter the x86 folder and run the windbg.exe in that folder<\/p>\n<pre>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers&gt;<cmd>cd x86<\/cmd>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86&gt;<cmd>windbg.exe<\/cmd>\n<\/pre>\n<blockquote><p>I'll refer to this folder as the \"WinDBG Program Folder\" from this point forward.<\/p><\/blockquote>\n<p>If all goes well, you should see something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDBG-Classic.png\" style=\"display: block; margin-left: auto; margin-right: auto\" alt=\"WinDBG Classic\" title=\"WinDBG Classic.png\" border=\"0\" width=\"600\" height=\"366\"><\/p>\n<p>Great! The GUI itself feels a bit \"empty\", but at least WinDBG is running.<\/p>\n<blockquote><p>Please take note of the fact that WinDBG Classic has a separate executable for each architecture. Make sure to run the binary that matches with the architecture of the application you're going to work with<\/p>\n<\/blockquote>\n<p>Close WinDBG.<\/p>\n<p>From the same command prompt, run <span class=\"corelan-mono\">windbgx.exe<\/span> (just add an 'x' after 'windbg').<\/p>\n<pre>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86&gt;<cmd>windbgx.exe<\/cmd>\n<\/pre>\n<p>Although WinDBGX is installed inside its own folder structures, you can actually launch it from any location. If WinDBGX was installed correctly, you'll see something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDBGX.png\" style=\"display: block; margin-left: auto; margin-right: auto\" alt=\"WinDBGX\" title=\"WinDBGX.png\" border=\"0\" width=\"599\" height=\"314\"><\/p>\n<p>We see that the GUI has a bigger ribbon bar and 3 panels. But it's all still very empty.<\/p>\n<p>Anyway, we now know how to launch WinDBG Classic and WinDBGX.<br \/>\nThat's great, but they won't do much just running by themselves.<br \/>\nThey're designed to operate, attached to another process.<br \/>\nLet's see what that looks like next.<\/p>\n<h2>Connecting to a process<\/h2>\n<p>There are a few ways to have a debugger connect itself to another process. The 2 most important techniques are:<\/p>\n<ol>\n<li><strong>'Opening'<\/strong> or <strong>'launching'<\/strong> an executable. The debugger will then create that application, and it will be connected to it from the start, leaving it paused before it executes application logic.<\/li>\n<li><strong>'Attaching'<\/strong> to an already running process. In this case, it will \"intervene\" and pause the application, regardless of where it was\/is.<\/li>\n<\/ol>\n<p>Both can be done from within the debugger, as well as from the command line.<\/p>\n<p>What is the difference between these 2 techniques, which one should you use, and how do we \"open\" and \"attach\" in WinDBG(X)?<\/p>\n<h3>Open executable vs Attach<\/h3>\n<p>Both techniques ('opening\/launching' or 'attaching') ultimately rely on the use of the Windows debugging subsystem. A debugged process has a debug object associated with it. The process' Kernel structure <span class=\"corelan-mono\">EPROCESS<\/span> includes a <span class=\"corelan-mono\">DebugPort<\/span>, which points to a <span class=\"corelan-mono\">DebugObject<\/span>, etc.   If non-null, it means the process is actively being debugged.<br \/>\n(Check out the <a href=\"https:\/\/www.vergiliusproject.com\/\" target=\"_blank\" rel=\"noopener\">Vergilius Project<\/a> website for more information about this kind of Kernel Structures - f.i. <a href=\"https:\/\/www.vergiliusproject.com\/kernels\/x64\/windows-11\/25h2\/_EPROCESS\" target=\"_blank\" rel=\"noopener\">this Windows 11 version of EPROCESS<\/a>)<br \/>\nThe Kernel has a few routines that are accessible via System calls:<br \/>\n<span class=\"corelan-mono\">NtCreateDebugObject, NtDebugActiveProcess, NtWaitForDebugEvent, NtDebugContinue<\/span><br \/>\nThe Userland side of the Operating System has wrappers functions to use them. <\/p>\n<blockquote><p>If all of this is too much detail for you at this time, don't worry. You don't need to remember all of this to use the debugger. It's just here FYI.<\/p><\/blockquote>\n<p>Nevertheless, there are still differences between 'opening' and executable and 'attaching' to a process, related with: <\/p>\n<ol>\n<li>The \"initial break\" - the place\/moment during the execution of the application when the debugger is \"attached\" to the process, and takes control<\/li>\n<li>The effect to \"Debug Flags\"<\/li>\n<\/ol>\n<p>Lets have a quick look at what these difference are:<\/p>\n<h4>Initial break:<\/h4>\n<h5>Opening\/Launching<\/h5>\n<p>When <strong>'opening\/launching'<\/strong> an executable, the process will be created from the start, with the debugger attached to it.  You're technically able to see everything from process creation, the initialization routines, TLS Callbacks, etc.<br \/>\nThe process will be created with a special flag <span class=\"corelan-mono\">DEBUG_ONLY_THIS_PROCESS<\/span>, telling the kernel to route all debug events to the debugger. I.e. the debugger has control right away.  The debugger now enters a loop involving <span class=\"corelan-mono\">WaitForDebugEvent()<\/span> and <span class=\"corelan-mono\">ContinueDebugEvent()<\/span><br \/>\nThe process will be paused at the so-called \"initial breakpoint\", which is triggered by the Windows Loader routines.<\/p>\n<h5>Attaching<\/h5>\n<p>When <strong>\"attaching\"<\/strong>, the debugger needs to connect itself to a process that already exists. Technically, there is an <strong>invasive<\/strong> and <strong>non-invasive<\/strong> way for a debugger to attach itself.  By default, debuggers use the \"invasive\" method.  It means that it uses <span class=\"corelan-mono\">DebugActiveProcess(pid)<\/span>, which is a routine that will call the <span class=\"corelan-mono\">NtDebugActiveProcess<\/span> system call to: <\/p>\n<ul>\n<li>Associate the debugger with the debug port of that process<\/li>\n<li>Suspend all threads in the process<\/li>\n<li>Some synthetic debug events (simulating the events that the debugger would have received if it was attached from the start, so it can catch up with the environment)<\/li>\n<li>Finally, cause a break-in exception, so the debugger has control.<\/li>\n<\/ul>\n<p>With an invasive attach, the debugger gets full debugging control, allowing you to: <\/p>\n<ul>\n<li>set breakpoints (INT3)<\/li>\n<li>single-step execution<\/li>\n<li>receive exceptions<\/li>\n<li>suspend\/resume threads<\/li>\n<li>modify registers<\/li>\n<li>control execution flow<\/li>\n<\/ul>\n<p>The <mono>DebugActiveProcess()<\/mono> technique involves creating a new thread inside the process, thus technically modifying it, and running code inside the process that performs the steps listed above.  If you let the process continue running (after it paused initially), you'll see that it \"begins\" by exiting\/cleaning up the thread that it used to \"attach\" itself.  In other words, don't freak out if you see that a thread gets terminated. You didn't break anything, it's just cleaning up itself.<br \/>\nWhen attaching to a process, the \"initial break\" happens wherever the program currently is the moment of attach.<\/p>\n<h5>Attaching: invasive vs non-invasive<\/h5>\n<p>An <strong>invasive attach<\/strong> means that the debugger will become the process's official debugger in the Windows debugging subsystem. Windows only allows one debugger to own the DebugPort of the process.<\/p>\n<p>A <strong>non-invasive attach<\/strong> means the debugger does not register itself as the process debugger.  Instead of using <span class=\"corelan-mono\">DebugActiveProcess()<\/span>, it uses <span class=\"corelan-mono\">OpenProcess()<\/span> - just like other tools would do when it wants to connect to a process and look\/analyse things.  You can watch, but you cannot set breakpoints, perform single-step execution, intercept exceptions or control execution.  Because it's not really acting as a \"debugger\", but more as a \"spectator\", it's not often used.  In fact, not all debuggers support the non-invasive attach to begin with.<\/p>\n<h4>Debug Flags<\/h4>\n<h5>Opening\/Launching<\/h5>\n<p>When you open\/launch an executable in the debugger, the debugger will automatically (and without telling you) activate a certain flag, a value that is part of a broader set of 'Global flags' (<span class=\"corelan-mono\">NTGlobalFlag<\/span>) that, amongst others, affect how the Windows heap manager operates.<br \/>\nWhen the Debugger does this, it basically updates a field in the Process Environment Block (PEB).  <\/p>\n<p>Technically, you could also set GlobalFlags up front by creating a GlobalFlag key with a value that corresponds with the flag(s) that you wish to activate.<br \/>\nLet's say you're working with an application called <span class=\"corelan-mono\">corelanapp1.exe<\/span>, then you'd have to set the following key:<\/p>\n<pre>\nHKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\corelanapp1.exe\\GlobalFlag\n<\/pre>\n<p>That registry will affect any new instance of the corelanapp1.exe process, regardless of whether you're opening it in the debugger or running it outside a debugger. The key persists across reboots, so be careful when you make this type of changes.<\/p>\n<p>Anyway, when you open an executable in WinDBG Classic, it activates the following 3 flags:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">FLG_HEAP_ENABLE_TAIL_CHECK (0x10)<\/span><\/li>\n<li><span class=\"corelan-mono\">FLG_HEAP_ENABLE_FREE_CHECK (0x20)<\/span><\/li>\n<li><span class=\"corelan-mono\">FLG_HEAP_VALIDATE_PARAMETERS (0x40)<\/span><\/li>\n<\/ul>\n<p>The values get summed up, so if the GlobalFlag indicates 0x70, it means those 3 options are active.<br \/>\nSome popular GlobalFlag options include:<\/p>\n<pre>\nFlag\t\t\t\t\t\t\t\tValue\t\tDescription\nFLG_HEAP_ENABLE_TAIL_CHECK\t\t\t0x10\t\tDetect overwrite past allocation\nFLG_HEAP_ENABLE_FREE_CHECK\t\t\t0x20\t\tDetect use-after-free\nFLG_HEAP_VALIDATE_PARAMETERS\t\t0x40\t\tValidate heap calls\nFLG_HEAP_VALIDATE_ALL\t\t\t\t0x80\t\tAggressive heap validation\nFLG_APPLICATION_VERIFIER\t\t\t0x100\t\tEnables AppVerifier\nFLG_HEAP_PAGE_ALLOCS\t\t\t\t0x02000000\tFull page heap\n<\/pre>\n<blockquote><p>Note: WinDBG doesn't create the registry key, it just updates the NtGlobalFlag field in the PEB directly.<\/p><\/blockquote>\n<p>'Opening' an executable in a debugger also activates certain flags in the PEB that make it obvious that the process runs with a debugger attached to it.<br \/>\nBoth features (heap and debug flags) may affect your debugging session:<\/p>\n<ul>\n<li>It would be trivial for anti-debugging logic to detect that the application is being debugged.<\/li>\n<li>Furthermore, key core features in the heap will behave differently, changing sizes, layouts, relative distances etc.  You're essentially running in an environment that does not resemble reality, and you may end up building an exploit that is based on behaviour and calculations made in a 'debugging' context.<\/li>\n<li>Even if all of that doesn't play a role for your exploit, the application developer may have taken the decision to take a different code path when it detects it is being debugged. For instance, it may print out debug information, and the fact that it runs additional or different code, may have an impact on when\/where\/how the application processes your input.    <\/li>\n<\/ul>\n<p>Long story short, running an executable in a debugger is not the same thing as running it outside a debugger.<\/p>\n<p>We'll look at an example in a moment, and I'll also explain how to overrule this default behaviour at that time.  Let's look at the effect of \"attaching\" first.<\/p>\n<h5>Attaching<\/h5>\n<p>When attaching to a process, the process will use whatever Global Flags were present already (none by default).  In other words, the application will behave just like it would without a debugger, because there was no debugger present at process creation.<br \/>\nFrom an anti-debugging perspective, the fact that some obvious flags won't be present, doesn't mean the application won't be able to detect that it is being debugged.  There are many anti-debugging and anti-anti-debugging techniques, but I rarely see those being used in commercial 'productivity' software. In my personal experience, these techniques are more frequently found present in malware and games. <\/p>\n<h3>Connecting to a process: exercises<\/h3>\n<p>Let's do a few exercises to practice connecting a debugger to a process, either by opening it or by attaching.<\/p>\n<p>Go to <a href=\"https:\/\/github.com\/corelan\/blogposts\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/corelan\/blogposts<\/a>, open the \"debugging\" folder, then open the \"corelanapp1\" folder and download the corelanapp1.exe binary from inside the \"Release\" folder.<br \/>\nYou can access the .exe file directly <a href=\"https:\/\/github.com\/corelan\/blogposts\/raw\/refs\/heads\/main\/debugging\/corelanapp1\/Release\/corelanapp1.exe\" target=\"_blank\" rel=\"noopener\">here<\/a><br \/>\nFrom the same folder, please download the <span class=\"corelan-mono\">corelanapp1.pdb<\/span> file and store it next to the .exe file.<\/p>\n<p>When running the application outside of a debugger, you'll see something like this:<\/p>\n<pre>\nWelcome to CorelanApp1!\nwww.corelan.be\nAlloc 1 : 0x00F6DE20\nAlloc 2 : 0x00F72E28\nThe distance from Alloc 1 to Alloc 2 is 0x5008 bytes\n<\/pre>\n<p>The application will pause, waiting for you to press return, and then terminate.<br \/>\nThe values printed after 'Alloc 1' and 'Alloc 2' may be different, that's ok.  Under normal circumstances (and unless the Heap manager had to take a different decision), the distance between Alloc 1 and Alloc 2 will be 0x5008 bytes.  Remember that number.<\/p>\n<p>Let's see if we can connect WinDBG and WinDBGX to this sample application, by \"opening\" and \"attaching\", both through the GUI and the command line.<\/p>\n<h4>WinDBG Classic, 'Open executable'<\/h4>\n<p>Open an administrator command prompt and launch windbg classic from within the WinDBG Program Folder:<\/p>\n<pre>\nMicrosoft Windows [Version 10.0.26200.7623]\n(c) Microsoft Corporation. All rights reserved.\n\nC:\\Windows\\System32><cmd>cd \"C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\"<\/cmd>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86><cmd>windbg.exe<\/cmd>\n<\/pre>\n<p>From the menu, choose 'File' and then click 'Open Executable'.  <\/p>\n<p>Navigate to the folder that contains the <span class=\"corelan-mono\">corelanapp1.exe<\/span> binary:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbgclassic-openexecutable.png\" alt=\"Open Executable in WinDBG Classic\" title=\"windbgclassic-openexecutable.png\" border=\"0\" width=\"583\" height=\"590\" \/><\/p>\n<p>If the application would require command line argument(s), you can enter them in the \"Arguments\" field.  (Not needed in this case).<br \/>\nClick 'Open' to start the debugging session.<br \/>\nThe process gets created, the WinDBG \"Command\" window appears and shows some text output<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-openexecutable-started.png\" alt=\"WinDBG Open Executable \" title=\"windbg-openexecutable-started.png\" border=\"0\" width=\"599\" height=\"328\" \/><\/p>\n<p>It is showing what modules (.exe and .dll) were loaded, it shows the state of registers, and it finally shows this:<\/p>\n<pre>\n(13a8.5f4): Break instruction exception - code 80000003 (first chance)\neax=00000000 ebx=00000000 ecx=e02a0000 edx=00000000 esi=00e22d28 edi=00be8000\neip=77bf80c8 esp=00cff66c ebp=00cff698 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!LdrpDoDebuggerBreak+0x2b:\n77bf80c8 cc              int     3\n<\/pre>\n<p>Right below the \"Command window\", we can see an input field. This is WinDBG's Command line.  It is enabled (i.e. we can enter text), which indicates that the process exists, the debugger is attached to it, but the application is not in an active running state.  The debugger is now waiting for our input to do something.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-command-line.png\" alt=\"windbg command line\" title=\"windbg command line.png\" border=\"0\" width=\"599\" height=\"342\" \/><\/p>\n<p>In other words, when we open an executable in a debugger, it creates the process but - by default - it will end up in a paused state.<br \/>\nNone of the application code has run at this point.  We're right at the end of the OS logic that creates the process, but before any of the application logic itself has executed. <\/p>\n<p>We can now issue WinDBG commands, for instance to allow the process to continue running.<br \/>\nThe command <span class=\"corelan-mono\">g<\/span> (or shortkey <span class=\"corelan-mono\">F5<\/span>) will do that.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-running.png\" alt=\"windbg-running\" title=\"windbg-running.png\" border=\"0\" width=\"598\" height=\"163\" \/><\/p>\n<p>The command line indicates \"Busy\" and \"Debuggee is running...\" and we can no longer type commands.   If we open the Window for corelanapp1.exe, we can see that it has executed code and is now waiting for a key press before it terminates.<\/p>\n<p>For example:<\/p>\n<pre>\nWelcome to CorelanApp1!\nwww.corelan.be\nAlloc 1 : 0x00C3D000\nAlloc 2 : 0x00C42018\nThe distance from Alloc 1 to Alloc 2 is 0x5018 bytes\n<\/pre>\n<blockquote><p>Note that the distance from Alloc 1 to Alloc 2 is now 0x5018 bytes instead of 0x5008.  When we ran the application outside the debugger, it was showing a distance of 0x5008 bytes.<br \/>\nThis is one of the effects of the NtGlobalFlags. The relative position of the heap allocations is different now. If you wouldn't be aware of this, you might be building an exploit that is based on calculations made in a modified context.<\/p><\/blockquote>\n<p>Just like most command line statements, the \"g\" (F5) command works the same in both WinDBG Classic and WinDBGX<\/p>\n<p>We can \"break\" the process (basically pause the application regardless of where it is) and give control back to the debugger by opening the \"Debug\" menu and choosing \"Break\".<br \/>\nIf you happen to have a \"Break\" button on your keyboard, you can also press the CTRL+Break combination.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-break.png\" alt=\"windbg-break\" title=\"windbg-break.png\" border=\"0\" width=\"437\" height=\"435\" \/><\/p>\n<p>Last but not least, we can also click the tiny little \"pause\" button in the toolbar:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-pause.png\" alt=\"windbg-pause\" title=\"windbg-pause.png\" border=\"0\" width=\"600\" height=\"130\" \/><\/p>\n<p>Regardless of the method you decided to use, it will trigger a \"break\" instruction, the process will pause and you'll be able to issue WinDBG command line statements again.<\/p>\n<pre>\n(13a8.2370): Break instruction exception - code 80000003 (first chance)\neax=00a02000 ebx=00000000 ecx=77badcb0 edx=77badcb0 esi=77badcb0 edi=77badcb0\neip=77b5b400 esp=0113f964 ebp=0113f990 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!DbgBreakPoint:\n77b5b400 cc              int     3\n<\/pre>\n<p>We can now invoke an extension that shows the NTGlobalFlag value for the current process. Running a plugin\/extension requires an exclamation point followed by the extension name, in this case <span class=\"corelan-mono\">!gflag<\/span>:<\/p>\n<pre>\n0:001> <cmd>!gflag<\/cmd>\nCurrent NtGlobalFlag contents: 0x00000070\n    htc - Enable heap tail checking\n    hfc - Enable heap free checking\n    hpc - Enable heap parameter checking\n<\/pre>\n<p>This confirms that some GlobalFlags have been activated. Not by us, but by the debugger.<br \/>\nAs explained earlier, these flags are stored in the PEB. We have 2 ways to query the PEB for a process, either by using the <span class=\"corelan-mono\">!peb<\/span> extension, or by asking WinDBG to dump the contents of memory at the location of the PEB and organizing the bytes based on its understanding of the PEB datastructure.<\/p>\n<p>Here we can see the !peb extension at work.  I truncated the output, but you should be able to see the contents of the NtGlobalFlag field:<\/p>\n<pre>\n0:001> <cmd>!peb<\/cmd>\nPEB at 00be8000\n    InheritedAddressSpace:    No\n    ReadImageFileExecOptions: No\n    BeingDebugged:            Yes\n    ImageBaseAddress:         00750000\n    <high>NtGlobalFlag:             70<\/high>\n    NtGlobalFlag2:            0\n    Ldr                       77c17340\n    Ldr.Initialized:          Yes\n    Ldr.InInitializationOrderModuleList: 00e244e8 . 00e37b28\n    Ldr.InLoadOrderModuleList:           00e245f0 . 00e37b18\n    Ldr.InMemoryOrderModuleList:         00e245f8 . 00e37b20\n...\n<\/pre>\n<p>We can also perform a typed dump (dt) of the PEB using the following command:<\/p>\n<pre>\n<cmd>dt nt!_PEB @$peb<\/cmd>\n<\/pre>\n<p>(The output will show that the NtGlobalFlag field sits at offset +0x68)<\/p>\n<p>If we're only interested in one specific field (for instance, the NtGlobalFlag), we can get its contents right away using the following command:<\/p>\n<pre>\n0:001> <cmd>dt nt!_PEB @$peb NtGlobalFlag<\/cmd>\nntdll!_PEB\n   +0x068 NtGlobalFlag : 0x70\n<\/pre>\n<p>Finally, to terminate the WinDBG session (which will also terminate the process it is attached to), use the \"q\" command and press return.<br \/>\nThe \"Command\" window will close, WinDBG itself will continue running.  I do recommend closing it as well in between debugging sessions, as it may be caching some extension related stuff.<\/p>\n<p>We can automate creating a process in WinDBG Classic from the command line as well.  WindBG takes a bunch of command line options, one of which is the full path to the executable you'd wish to \"open\" in the debugger.  You can also specify some command line flags to modify windbg behaviour, as well as command lines arguments to the binary you're trying to run.<br \/>\nThe basic syntax is<\/p>\n<pre>\n<cmd>windbg.exe [windbg args] path\/to\/application.exe [app arguments]<\/cmd>\n<\/pre>\n<p>Let's keep this simple for now, we'll simply run windbg.exe, followed by the path to the corelanapp1 binary.<br \/>\nOn my system, the corelanapp1.exe is stored under <span class=\"corelan-mono\">g:\\blogposts\\debugging\\corelanapp1\\Release<\/span>,<br \/>\nOpen an administrator command prompt, go to the folder that contains windbg.exe and run the following command <\/p>\n<pre><cmd>windbg.exe g:\\blogposts\\debugging\\corelanapp1\\Release\\corelanapp1.exe<\/cmd>\n<\/pre>\n<p>This will open WinDBG and then open the executable as if you have selected it from the \"File\" - \"Open executable\" menu option yourself.<br \/>\nThe process is paused and you can now type commands at the WinDBG Command Line.  For instance, if you run <span class=\"corelan-mono\">!gflag<\/span>, you'll see that the 3 NTGlobalFlags have been activated again.<br \/>\nYou can execute \"g\" to let the process run, you can execute \"q\" to make it stop.<\/p>\n<p>What if you don't want the NtGlobalFlag to be set?   There is a WinDBG command line flag <span class=\"corelan-mono\">-hd<\/span> that allows you to overrule this behaviour, but it only works if you run windbg.exe from the command line and specify the application to run as well.<br \/>\nRunning <span class=\"corelan-mono\">windbg.exe -hd<\/span> without specifying the application .exe and then selecting it yourself from \"File\" - \"Open executable\" won't actually turn off the NTGlobalFlags.<\/p>\n<p>So, the correct syntax on my system would be<\/p>\n<pre>\n<cmd>windbg.exe -hd g:\\blogposts\\debugging\\corelanapp1\\Release\\corelanapp1.exe<\/cmd>\n<\/pre>\n<p>The output of !gflag will now indicate that none of the flags were activated:<\/p>\n<pre>\n0:000> <cmd>!gflag<\/cmd>\nCurrent NtGlobalFlag contents: 0x00000000\n<\/pre>\n<h4>WinDBGX, 'Launch executable'<\/h4>\n<p>With WinDBGX, Microsoft has made substantial changes to the end-user experience.  The GUI now features a larger ribbon bar with easy-to-use buttons. The menu layout has changed as well.<br \/>\nTo open an executable in WinDBGX (or to start a debugging session in general), we can simply click \"File\". WinDBGX will automatically open the \"Start debugging\" submenu. <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbgx-start.png\" alt=\"windbgx-start\" title=\"windbgx-start.png\" border=\"0\" width=\"511\" height=\"354\" \/><\/p>\n<p>From the \"Start debugging\" submenu, we can now choose \"Launch executable\" or \"Launch executable (advanced)\".  With the former, you simply select the executable.  With the latter, you get the option to specify command line arguments to the executable you're trying to launch.<\/p>\n<p>For the sake of this exercise, feel free to try both options.  Select the corelanapp1.exe file and click \"debug\" to start the process with the debugger attached to it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbgx-startproc.png\" alt=\"windbgx-startproc\" title=\"windbgx-startproc.png\" border=\"0\" width=\"599\" height=\"399\" \/><\/p>\n<p>Although the GUI is slightly different, the basic principles still apply.  The process is created but is paused, the Command window shows output, and we get the opportunity to issue commands using the WinDBGX Command Line input box.  Similarly to WinDBG Classic, we can now type \"g\" and press return, or use F5 to let the process continue running.<\/p>\n<p>Interrupting (breaking) a running process can be done by clicking the larger \"Break\" (pause) button at the left hand side of the \"Home\" tab of the ribbon.  <\/p>\n<p>Unlike WindBG Classic, WinDBGX only activates one NTGlobalFlag:<\/p>\n<pre>\n0:003> <cmd>!gflag<\/cmd>\nCurrent NtGlobalFlag contents: 0x00000010\nCurrent NtGlobalFlag2 contents: 0x00000000\n    htc - Enable heap tail checking\n<\/pre>\n<p>We can launch the executable from the command line as well.  The -hd flag serves its purpose as well, it allows us to overrule the debuggers default behaviour related with activating NTGlobalFlags.<\/p>\n<pre>\n<cmd>windbgx.exe -hd g:\\blogposts\\debugging\\corelanapp1\\Release\\corelanapp1.exe<\/cmd>\n<\/pre>\n<h4>WinDBG Classic, 'Attach'<\/h4>\n<p>I usually recommend my students to attach to an already running process (unless you won't get the chance to attach to it without triggering a vulnerability).<br \/>\nThis avoids having to consider NTGlobalFlags, and any initial anti-debugging checks may have passed already before your debugger is \"invading\" the process and attaching itself to it.<\/p>\n<p>We can \"attach\" using the GUI, or using the command line (by either providing the process ID or the prooces name if there is only one process with the same name).<\/p>\n<p>Attaching using the GUI works as follows:<\/p>\n<ol>\n<li>Start the application you'd like to debug<\/li>\n<li>Launch WinDBG<\/li>\n<li>Click \"File\" - \"Attach to a process\" or use F6<\/li>\n<li>WinDBG will show a list of processes, sorted in the order that they were created. (Old to new). This default sort order may be a bit annoying, because you're quit likely going to attach to one of the last processes that was created, which means you'll have to scroll down almost every time)<\/li>\n<li>Select the process you would like to debug. Note: WinDBG provides the ability to perform a non-invasive attach.<\/li>\n<\/ol>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-processes.png\" alt=\"windbg-processes\" title=\"windbg-processes.png\" border=\"0\" width=\"485\" height=\"600\" \/><\/p>\n<p>Click \"OK\" to start the debugging session.<br \/>\nThe Command window now appears, and the process will be paused.  The debugger has created a new thread in the process and told it to run the required code for the debugger to be connected to the process, pause all the threads, and give the debugger control.<br \/>\nAfter attaching to a process, it will be paused (wherever it was executing code from).<br \/>\nFrom this point forward, everything works exactly the same as if you had opened the executable in the debugger.  Of course, there won't be NtGlobalFlags set by the debugger, but you can now interact with the process in the same way.  You can let the process continue running with <span class=\"corelan-mono\">g<\/span>or F5. You can break, you can look at memory contents, and so on.<\/p>\n<p>Attaching to an already running process from the command line requires a bit of interaction first. You'll have to either find the PID (process ID) of the process you'd like to attach to.  If there is only one instance of the application running, then you may be able to attach to it using its process name as well.<br \/>\nLet's say I launched corelanapp1.exe already, and there's only one instance of an application running with that image name (corelanapp1.exe). I can use something like tasklist to get the PID:<\/p>\n<pre>\n<cmd>tasklist \/FI \"IMAGENAME eq corelanapp1.exe\"<\/cmd>\n\nImage Name                     PID Session Name        Session#    Mem Usage\n========================= ======== ================ =========== ============\ncorelanapp1.exe              11016 Console                    1      5.132 K\n<\/pre>\n<p>And then I can attach WinDBG to it using the reported PID:<\/p>\n<pre>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86><cmd>windbg.exe -p 11016<\/cmd>\n<\/pre>\n<p>Or, if there is only one process running with that image name, I can tell WinDBG to connect to it:<br \/>\nFirst, let's confirm there is only one:<\/p>\n<pre>\n<cmd>tasklist \/FI \"IMAGENAME eq corelanapp1.exe\"<\/cmd>\n\nImage Name                     PID Session Name        Session#    Mem Usage\n========================= ======== ================ =========== ============\ncorelanapp1.exe              11016 Console                    1      5.156 K\n<\/pre>\n<p>and then connect to it:<\/p>\n<pre>\nC:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86><cmd>windbg.exe -pn corelanapp1.exe<\/cmd>\n<\/pre>\n<p>If there was more than one process with this process name, you'd get something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-multiple.png\" alt=\"windbg-multiple\" title=\"windbg-multiple.png\" border=\"0\" width=\"411\" height=\"169\" \/><\/p>\n<h4>WinDBGX, 'Attach'<\/h4>\n<p>In WinDBGX, attaching to an already running process can be done by clicking the \"File\" menu and then choosing \"Attach to process\", or by simply pressing F6 (just like WinDBG Classic).<br \/>\nWinDBGX lists the processes in the order that they are created, but with the newest on top. In other words, you usually won't have to scroll down to find the application that you just launched.<br \/>\nSelect the process from the list, and click \"Attach\" to start the debugging session.<\/p>\n<p>Attaching from the command line can be done using the same CLI arguments:<br \/>\n<span class=\"corelan-mono\">-p <pid><\/span> and <span class=\"corelan-mono\">-pn <imagename><\/span> <\/p>\n<p>Interestingly enough, if you're trying to attach to a processname that isn't unique, the resulting error message in WinDBGX won't be as clear as the one we got earlier in WinDBG Classic:<br \/>\n<img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbgx-multiple.png\" alt=\"windbgx-multiple\" title=\"windbgx-multiple.png\" border=\"0\" width=\"492\" height=\"176\" \/><\/p>\n<h2>Running WinDBG(X) - Command Line Arguments<\/h2>\n<p>We've used WinDBG to open an executable and to attach to a process using the <span class=\"corelan-mono\">-p<\/span> flag. I've demonstrated how to use the <span class=\"corelan-mono\">-hd<\/span> flag to disable activating the NTGlobalFlags.<br \/>\nThose are just a few of the available command line options. Of course, you can find the full list of available options in the WinDBG Help, but I'll take a moment to discuss the ones I use the most:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">-g<\/span> \u2192 Do not break on initial execution. This flag allows you to automatically run the process once the debugger attaches or creates the process.  It's useful when scripting<\/li>\n<li><span class=\"corelan-mono\">-G<\/span> \u2192 Exit WinDBG when the process terminates. Similar to -g, it's a useful CLI option when scripting<\/li>\n<li><span class=\"corelan-mono\">-Q<\/span> \u2192 WinDBG Classic only - Do not ask to save the workspace. Similar to -g and -G, it's a useful CLI option when scripting<\/li>\n<li><span class=\"corelan-mono\">-o<\/span> \u2192 Follow child processes<\/li>\n<li><span class=\"corelan-mono\">-logo path\/to\/logfile<\/span> \u2192 Write the output of the entire debugging session into a log file<\/li>\n<li><span class=\"corelan-mono\">-xd sov<\/span> \u2192 Ignore \"StackOverflow\" exceptions<\/li>\n<li><span class=\"corelan-mono\">-xi eh<\/span> \u2192 Ignore C++ EH exceptions<\/li>\n<li><span class=\"corelan-mono\">-WF path\/to\/workspacefile<\/span> \u2192 WinDBG Classic only - make WinDBG load a \"workspace\" file, which allows you to customize the GUI. We'll talk more about this in the next chapter<\/li>\n<li><span class=\"corelan-mono\">-c \"windbg commands\"<\/span>: Execute the commands (semi-colon separated) between the double quotes the moment the debugger breaks.  You can use this to either execute commands at WinDBG startup (and by adding <span class=\"corelan-mono\">;g<\/span> at the end, WinDBG will mimic what command line option -g does.)  Or you can use it in combination with -g to have WinDBG run certain commands when the debugging session stops (which is typically when it hits an access violation.<\/li>\n<\/ul>\n<h2>Customizing the GUI<\/h2>\n<h3>Customizing the WinDBG Classic GUI<\/h3>\n<h4>The WinDBG Classic Workspace<\/h4>\n<p>WinDBG Classic's GUI is a bit ... basic.  You'll get to see text based output in Command Window and you'll need to instruct WinDBG to show you what you want to see.<br \/>\nPeople coming from visual debuggers such as x64dbg, Immunity, etc  might feel a bit frustrated by the lack of visuals. People new to exploit development may not know yet what to look for, so the lack of visuals is not exactly helping either.<\/p>\n<p>We have the option to customize the look & feel. We can open some additional windows\/views and arrange them in such a way that it feels more intuitive (for those that come from x64dbg\/...), by kind of mimicking a layout that would be similar to the majority of visual debuggers.<br \/>\nThat means, showing:<\/p>\n<ul>\n<li>A disassembly view, showing CPU instructions around EIP<\/li>\n<li>The general purpose registers<\/li>\n<li>The stack, showing pointers instead of bytes (little-endian)<\/li>\n<li>The output in the Command window<\/li>\n<li>An window that allows us to see any location in memory<\/li>\n<\/ul>\n<p>The look&feel of WinDBG Classic is called a \"Workspace\". It includes what windows are visible, their position, the font & font sizes, colors used for certain things, the process it is attached to, etc.  <\/p>\n<p>The active workspace can be stored in a workspace file, and we can tell WinDGB (from the GUI or via a CLI argument) to load a workspace from file.<\/p>\n<blockquote><p>Saving the workspace to a file requires a bit of attention.  As explained, it contains the process it's attached to. If you were to save the active workspace (i.e. save the workspace from your active debugging session) to a workspace file, and your session is attached to a process, that information will be included in the file. Consequently, WinDBG will automatically try to attach itself to the process in that workspace file next time you open the workspace file, yes even if you were already attached to another process.<br \/>\nThat's probably not what we want.<br \/>\nOn the flipside, in order to see the effect of the changes that you're making, it helps if WinDBG is actually attached to something.<br \/>\nIn other words, right before saving a workspace to file, we should detach WinDBG from the process it is connected to and then save the workspace.<\/p>\n<p>Or, alternatively, you can customize the workspace, save it to file, and use a second debugger to load the workspace, attach itself to a process and see what the updated workspace looks like. That's what I usually do. That way, I won't forget to detach, and I can still see the effect of the changes I made in real time using the second debugger instance.  Make changes in the empty (non-connected) debugger, and use a real session on the side to see the effect.<br \/>\nWhatever scenario. you do, please  be careful not to save the workspace when WinDBG is actively debugging a process.\n<\/p><\/blockquote>\n<p>I have included 2 workspace files in the GitHub repository. You can find them inside the <a href=\"https:\/\/github.com\/corelan\/blogposts\/tree\/main\/debugging\/workspace\" target=\"_blank\" rel=\"noopener\">workspace<\/a> folder:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">dark.wew<\/span> \u2192 A 'dark' theme workspace file that I found somewhere on the internet, and<\/li>\n<li><span class=\"corelan-mono\">corelan.wew<\/span> \u2192 A 'light' theme workspace file that I created myself.<\/li>\n<\/ul>\n<p>Feel free to download both files and store them in your WinDBG Program Folder.  We'll discuss how to use them next.<\/p>\n<p>Before we do that, please note that the concept of workspace files no longer plays a role in WinDBGX.  It has become a lot easier to customize the GUI, and WinDBG automatically remembers the GUI you have created.  And if you don't like what you did,  you can reset the Windows and start over.<\/p>\n<p>Anyway, let's begin in WinDBG Classic.<\/p>\n<h4>Loading a workspace file in WinDBG Classic<\/h4>\n<p>Let's tell WinDBG to open a workspace file.  <\/p>\n<p>We can do this from the GUI and by using a CLI argument.<br \/>\nThe latter is obviously the easiest solution, as it would be entirely scriptable.<br \/>\nIn fact, whenever I set up an exploit dev\/research machine, I usually create a batch file and store it inside the WinDBG Program Folder.<br \/>\nThe batch file invokes <span class=\"corelan-mono\">windbg.exe<\/span> with a number of CLI arguments (such as <span class=\"corelan-mono\">-hd<\/span> as well as the <span class=\"corelan-mono\">-WF<\/span> argument which will allow me to load a workspace file), and then feeds it any other arguments I am passing to the batch file, if any.<\/p>\n<p>Students of one of the <a href=\"https:\/\/www.corelan-training.com\" target=\"_blank\" rel=\"noopener\">Corelan classes<\/a> will find a <span class=\"corelan-mono\">w.bat<\/span> file in their 'Tools\/Windbg' folder.  Blog readers can copy it from the code box below. <\/p>\n<p>Let's say we want windbg.exe to automatically open the dark.wew workspace file, the corresponding <span class=\"corelan-mono\">w.bat<\/span> file would look like this:<\/p>\n<pre>\n@echo off\nREM ==========================================\nREM Run WinDBG with optional arguments\nREM Corelan Stack \/ Heap Training\nREM www.corelan-training.com\nREM ==========================================\n\nREM Define base command (adjust path to wew file as needed)\nset \"WINDBG_CMD=windbg.exe -hd -WF dark.wew\"\n\n%WINDBG_CMD% %*\n<\/pre>\n<p>If you now run w.bat from within the WinDBG Program folder, the GUI will look like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-darkwew.png\" alt=\"windbg-darkwew\" title=\"windbg-darkwew.png\" border=\"0\" width=\"600\" height=\"327\" \/><\/p>\n<p>Doing the same thing, but opening the corelan.wew workspace, the GUI will show this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-corelanwew.png\" alt=\"windbg-corelanwew\" title=\"windbg-corelanwew.png\" border=\"0\" width=\"600\" height=\"317\" \/><\/p>\n<p>Of course, since we're not attached to a process yet, the actual windows are still empty. But at least we get to see a certain layout that goes beyond the \"Command\" window.<\/p>\n<p>Both workspaces have a couple of views in common:<\/p>\n<ul>\n<li>The <strong>Disassembly view <\/strong>(Upper left).  This will show the CPU instructions (bytes and corresponding assembly statements) and their positions in memory.  By default, the view will show the instructions around register EIP\/RIP<\/li>\n<li>A <strong>Memory View<\/strong> (Upper middle). This view allows you to look at any location in memory.  You can change the Display format<\/li>\n<li>The <strong>registers<\/strong> (Upper right). This view shows the state of the registers. I'll talk a bit more about this view later on, because I made a modification to the default settings.<\/li>\n<li>In the lower half, on the left we see the <strong>Command view<\/strong> showing. This is where we will see all kinds of output: Debugger messages, alerts, output of commands we run, etc.<\/li>\n<li>In the lower right, we see the <strong>stack<\/strong> for the current thread, organized to show the contents around esp\/rsp; and displaying the contents in 'Pointer + symbols' format.<\/li>\n<\/ul>\n<p>For most people, those are the most important views to work with on an ongoing basis.  Of course, there are additional views you can add\/open if you'd like.<\/p>\n<p>If you were to run windbg.exe just by itself, you have the option to just load a Workspace file from the 'File' menu as well.<br \/>\nChoose the 'Open Workspace in File' option, and select the workspace file you'd like to use.  You can do this before or after you've connected to a process, the result should be the same.<\/p>\n<h4>Creating a custom workspace file for WinDBG Classic<\/h4>\n<p>You can create your very own custom workspace from scratch. Of course, you could also load an existing workspace file and start modifying it. In any case, the idea is that you configure all windows, their position, layout, the font used, and font colors for the current debugging session.<br \/>\nYou'd then detach from the process and - once detached - save the workspace to a file.<\/p>\n<p>The elements that may need some customization are:<\/p>\n<ul>\n<li><strong>The active windows\/views<\/strong>: This involves opening the views that you need an drag\/dropping them into the right position.  You can find the available views by clicking on 'View' in the top menu.<\/li>\n<li><strong>Fonts<\/strong> (and font sizes): this can be done via 'View' - 'Font'.  I kind of like the Consolas monospace font, and I usually use a size that makes the experience somewhat pleasant for your eyes.<\/li>\n<li><strong>Colors<\/strong>: this is part of the Options ('View' - 'Options'). In addition to configuring the Colors, this is also where you can configure some other behavioural aspects of WinDBG.  You can check the options in the corelan.wew file to see how I tend to configure my WinDBG session.<\/li>\n<\/ul>\n<p>When done, (and detached from any process), you can now use 'File' - 'Save workspace to File' to write the current settings to your very own .wew file. <\/p>\n<p>Enjoy! <\/p>\n<h4>The Register view<\/h4>\n<p>I'd like to share a quick note about the Registers view. You can open the view by clicking 'View' and then choosing 'Registers'.  By default, it shows 2 columns: the register (Reg) and its value: <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-registers1.png\" alt=\"windbg-registers1\" title=\"windbg-registers1.png\" border=\"0\" width=\"579\" height=\"303\" \/><\/p>\n<p>When attached to a process, you can click the Customize... button to see (and change) what registers you'd like to see (and in what order)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-registers2.png\" alt=\"windbg-registers2\" title=\"windbg-registers2.png\" border=\"0\" width=\"599\" height=\"343\" \/><\/p>\n<p>By default, WinDBG begins by showing a mix of segment registers and multi-purpose registers, in this order:<\/p>\n<pre>\ngs fs es ds edi esi ebx edx ecx eax ebp eip cs efl esp ss\n<\/pre>\n<p>It's worth noting that CPU instructions such as <span class=\"corelan-mono\">PUSHAD<\/span> and <span class=\"corelan-mono\">POPAD<\/span> actually access the registers in a different order.  In other to match their modus operandi with what I see, I prefer to change the order to this:<\/p>\n<pre>\neax ecx edx ebx esp ebp esi edi eip gs fs es ds cs efl ss\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-registers3-1.png\" alt=\"windbg-registers3\" title=\"windbg-registers3.png\" border=\"0\" width=\"445\" height=\"329\" \/><\/p>\n<h3>Customizing the WinDBGX GUI<\/h3>\n<p>With WinDBGX, Microsoft let go of the idea of using Workspace files.   <\/p>\n<p>The GUI now consists of \"Layouts\" (that can be selected via 'View' - 'Layouts', or just created by drag\/dropping views in place), and some settings that can be found via 'File' - 'Settings', including Light\/Dark Theme and Fonts\/Sizes in the 'General' section.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbgx-general.png\" alt=\"windbgx-general\" title=\"windbgx-general.png\" border=\"0\" width=\"599\" height=\"233\" \/><\/p>\n<p>When you open a new view<\/p>\n<p>WinDBGX remembers the changes you have made, and will automatically load the GUI you have created.<\/p>\n<h2>Debugging basics<\/h2>\n<h3>WinDBG Command Line Interface<\/h3>\n<p>In this chapter, we're going to cover the basic operations in a debugger.  We'll learn how to slow down the execution to the individual instruction level.  We'll learn how to use breakpoints to create control.  We'll look at memory contents and perform searches, and we'll cover how to run <span class=\"corelan-mono\">mona.py<\/span> from within WinDBG(X).<\/p>\n<p>Although WinDBG (and especially WinDBGX) has some buttons you can click, the real power of driving WinDBG is its command line interface.<\/p>\n<p>WinDBG(X) has 3 main types of commands:<\/p>\n<ol>\n<li><strong>Regular commands<\/strong>: allowing us to interact with the process that is being debugged<\/li>\n<li><strong>Meta commands<\/strong>: allowing us to interact with the debugger (gui). These commands start with a dot <span class=\"corelan-mono\">.<\/span> <\/li>\n<li><strong>Extensions<\/strong>: Invoking plugins (extensions) is done using the exclamation point <span class=\"corelan-mono\">!<\/span><\/li>\n<\/ol>\n<p>Most of the commands we'll need are regular commands, but we'll introduce a few meta commands and extension as well.<\/p>\n<p>Unless specified otherwise, all commands that are discussed in this chapter will work on WinDBG Classic and WinDBGX<\/p>\n<p>Let's get started.<\/p>\n<h3>Execution Control<\/h3>\n<p>In this chapter, we'll study 4 frequently used techniques to control the execution of a process in a debugger:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">g<\/span> \u2192 continue (go)<\/li>\n<li><span class=\"corelan-mono\">t<\/span> \u2192 step into<\/li>\n<li><span class=\"corelan-mono\">p<\/span> \u2192 step over<\/li>\n<li><span class=\"corelan-mono\">gu<\/span> \u2192 step out (go up)<\/li>\n<\/ul>\n<p>Let's begin by running windbg.exe and opening our corelanapp1.exe application. We'll use the <span class=\"corelan-mono\">-hd<\/span> flag to avoid activating NtGlobalFlags.<\/p>\n<p>From WinDBG Program Folder, run:<\/p>\n<pre>\n<cmd>windbg.exe -hd -WF \"corelan.wew\" g:\\blogposts\\debugging\\corelanapp1\\Release\\corelanapp1.exe<\/cmd>\n<\/pre>\n<p>or, if you have created the w.bat file, you can just run<\/p>\n<pre>\n<cmd>w g:\\blogposts\\debugging\\corelanapp1\\Release\\corelanapp1.exe<\/cmd>\n<\/pre>\n<p>As seen earlier, the process gets created, but hasn't run any of the application logic yet.  WinDBG has control (you can type commands at the Command Line), and the Command Window shows something like this:<\/p>\n<pre>\n(1c54.1e98): Break instruction exception - code 80000003 (first chance)\neax=00000000 ebx=00000000 ecx=479b0000 edx=00000000 esi=01216650 edi=00e23000\neip=76f78218 esp=00ddf9c4 ebp=00ddf9f0 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!LdrpDoDebuggerBreak+0x2b:\n76f78218 cc    \n<\/pre>\n<p>We could type <span class=\"corelan-mono\">g<\/span> to let the process run, but we'll do something different this time.<\/p>\n<p>When a process gets created, the first code that gets executed from the application binary can be found at the <span class=\"corelan-mono\">AddressOfEntryPoint<\/span> field in the PE Header.  That field specifies the relative virtual address (RVA) - in other words, the offset from the start - of the place in the application where the first instructions resides.  (You can find more information about the PE format <a href=\"https:\/\/corkamiwiki.github.io\/PE\" target=\"_blank\" rel=\"noopener\">here<\/a>)<br \/>\nWe could query that field, but WinDBG has a 'pseudo-register' that actually has the address.  It's called <span class=\"corelan-mono\">$exentry<\/span><\/p>\n<p>We'll talk about breakpoints and pseudo-registers in more detail later on, but let's go ahead and put a breakpoint already at the <span class=\"corelan-mono\">AddressOfEntryPoint<\/span>, by typing the following command at the WinDBG Command Line:<\/p>\n<pre>\n<cmd>bp @$exentry<\/cmd>\n<\/pre>\n<p>You won't see any output, but we can use the <span class=\"corelan-mono\">bl<\/span> command to list the current breakpoints, and we should see that a breakpoint was activated.  If you look at the end of the line (and if you have downloaded & stored the <span class=\"corelan-mono\">corelanapp1.pdb<\/span> file next to the <span class=\"corelan-mono\">corelanapp1.exe<\/span> file earlier on), you should see a reference to <span class=\"corelan-mono\">corelanapp1!mainCRTStartup<\/span>.  This is the symbol name, found in the <span class=\"corelan-mono\">corelanapp1.pdb<\/span> file, that corresponds with the start of the routine that sits at the <span class=\"corelan-mono\">AddressOfEntryPoint<\/span>:<\/p>\n<pre>\n0:000> <cmd>bp @$exentry<\/cmd>\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  00701887     0001 (0001)  <high>0:**** corelanapp1!mainCRTStartup<\/high>\n<\/pre>\n<p>With a breakpoint active at this location, we can now let the process run.  Type <span class=\"corelan-mono\">g<\/span> and press return, or just use the <span class=\"corelan-mono\">F5<\/span> key. You should see something like this:<\/p>\n<pre>\n0:000> <cmd>g<\/cmd>\n<high>Breakpoint 0 hit<\/high>\neax=00ddff38 ebx=00e23000 ecx=00701887 edx=00701887 esi=00701887 edi=00701887\neip=00701887 esp=00ddfee4 ebp=00ddfef0 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\ncorelanapp1!__scrt_common_main [inlined in corelanapp1!mainCRTStartup]:\n00701887 e8cf020000      call    corelanapp1!__security_init_cookie (<high>00701b5b<\/high>)\n<\/pre>\n<p>The process changes to a running state for a brief moment, and then we see the message <span class=\"corelan-mono\">Breakpoint 0 hit<\/span>, followed by a dump of the registers and information about the instruction at EIP.  That instruction, the <span class=\"corelan-mono\">call    corelanapp1!__security_init_cookie (00701b5b)<\/span> in my example above, has not been executed yet.<\/p>\n<p>Good.  We're now at the start of the code referred to by the <span class=\"corelan-mono\">AddressOfEntryPoint<\/span><\/p>\n<p>We're going to use this context to practice 2 important debugger mechanics:<\/p>\n<ul>\n<li><strong>Stepping into (<span class=\"corelan-mono\">t<\/span> + return, or <span class=\"corelan-mono\">F11<\/span>)<\/strong> (The 1 in F11 kind of looks like the i in 'Into')<\/li>\n<li><strong>Stepping over (<span class=\"corelan-mono\">p<\/span> + return, or <span class=\"corelan-mono\">F10<\/span>)<\/strong> (The 0 in F10 kind of looks like the O in 'Over')<\/li>\n<\/ul>\n<h4>Stepping into<\/h4>\n<p><strong>Stepping into<\/strong> is a technique that allows us to execute one CPU instruction at a time, allowing us to see its effect.<br \/>\nWe can \"<strong>step into<\/strong>\" using the <span class=\"corelan-mono\">t<\/span> command + return, or we can simply press the <span class=\"corelan-mono\">F11<\/span> shortkey.<br \/>\n(I'm a big fan of using the shortkeys, as I won't have to press return every single time)<\/p>\n<p>When you step into (single step), the debugger will show the execution of the next CPU instruction. You'll see the instruction, a dump of the registers and you can observe the effect of that instruction to the process in real time.<\/p>\n<p>For example, when we would perform a step into in our current debugging session, it would execute the <span class=\"corelan-mono\">call  corelanapp1!__security_init_cookie (00701b5b)<\/span> instruction.  A <span class=\"corelan-mono\">call<\/span> instruction will take the CPU to the start of a (child) function. In this case, the child function sits at address 00701b5b. (this address will very likely be different on your system, due to address randomization (ASLR))<\/p>\n<p>When telling the debugger to step into, you're telling the debugger to have it execute just that instruction and then stop. As expected, the <span class=\"corelan-mono\">CALL<\/span> tells the CPU to go to the destination of that <span class=\"corelan-mono\">CALL<\/span> (<span class=\"corelan-mono\">00701b5b<\/span>) and then stop.<br \/>\nAnd indeed - that's exactly where the debugging session is stopped now: <\/p>\n<pre>\n0:000> <cmd>t<\/cmd>\neax=00ddff38 ebx=00e23000 ecx=00701887 edx=00701887 esi=00701887 edi=00701887\neip=<high>00701b5b<\/high> esp=00ddfee0 ebp=00ddfef0 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\ncorelanapp1!__security_init_cookie:\n<high>00701b5b<\/high> 8b0d18407000    mov ecx,dword ptr [corelanapp1!__security_cookie (00704018)] ds:002b:00704018=bce68da3\n<\/pre>\n<p>Going forward, at that location, we see the next instruction:<\/p>\n<pre>mov ecx,dword ptr [corelanapp1!__security_cookie (00704018)] ds:002b:00704018=<high>bce68da3<\/high><\/pre>\n<p>Because we have symbols, it provides us with some meaningful information about what it's going to do.  Based on the <span class=\"corelan-mono\">__security_cookie<\/span> symbol name, we know that this instruction is going to copy the random master security cookie into <span class=\"corelan-mono\">ecx<\/span>, most likely preparing to generate a thread-specific cookie to store it on the stack.  Again, values will be different on your system.  <\/p>\n<p>The point is, we can observe what is in ecx before executing that <span class=\"corelan-mono\">mov<\/span> instruction, and we can see what is in ecx after it has been executed. We can basically see all the changes that are being made to registers, to memory, as it happens. <\/p>\n<p>Before executing the <mono>mov<\/mono>, and based on the output above, we see that <span class=\"corelan-mono\">ecx<\/span> contains <span class=\"corelan-mono\">00701887<\/span>.  If I let the debugger step into (and thus execute that mov instruction), I get to see the effect of it:<\/p>\n<pre>\n0:000> <cmd>t<\/cmd>\neax=00ddff38 ebx=00e23000 ecx=<high>bce68da3<\/high> edx=00701887 esi=00701887 edi=00701887\neip=00701b61 esp=00ddfee0 ebp=00ddfef0 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\ncorelanapp1!__security_init_cookie+0x6:\n00701b61 56              push    esi\n<\/pre>\n<p><span class=\"corelan-mono\">ecx<\/span> now contains <span class=\"corelan-mono\">bce68da3<\/span><\/p>\n<p>You can now continue stepping into, basically executing one instruction at a time, and see what happens every step of the way.<\/p>\n<p>Keep stepping all the way until the next time you see a <span class=\"corelan-mono\">CALL<\/span> instruction, but don't execute it yet.<\/p>\n<p>On my system, the next CALL I got to see is this:<\/p>\n<pre>\n...\n0:000> <cmd>t<\/cmd>\neax=00ddff38 ebx=00e23000 ecx=4319725c edx=00701887 esi=00701887 edi=00701887\neip=<high>0070170c<\/high> esp=00ddfedc ebp=00ddfef0 iopl=0         nv up ei ng nz na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286\ncorelanapp1!__scrt_common_main_seh+0x7:\n<high>0070170c e81f070000      call    corelanapp1!__SEH_prolog4 (00701e30)<\/high>\n<\/pre>\n<p>Ok good.  Hold on for a moment, it's time to talk about \"stepping over\".<\/p>\n<h4>Stepping over<\/h4>\n<p>Stepping into is very powerful because it allows you to see every single step.   It's also very annoying, because you're seeing every single step.<\/p>\n<p>Let's say you have already analysed a part of the application, more specifically some function that is being used from time to time.  You've seen it, you documented it, you understand it.<br \/>\nNext time the application wants to use (or <span class=\"corelan-mono\">CALL<\/span>) that function, maybe you don't want to see the details of that function anymore.<br \/>\nThat's where <strong>stepping over<\/strong> comes into play.<\/p>\n<p>If you were following along, the debugger is about to execute a <span class=\"corelan-mono\">CALL<\/span>.  What if I just want that CALL to happen (and with that, anything that happens inside the corresponding child function), and when it's done, make it stop.   <\/p>\n<p>That's exactly what <strong>stepping over<\/strong> does.  It's super helpful in relation with <span class=\"corelan-mono\">CALL<\/span> instructions.<\/p>\n<p>Let's apply that to our exercise. <\/p>\n<p>We're here:<\/p>\n<pre>\ncorelanapp1!__scrt_common_main_seh:\n00701705 6a14            push    14h\n00701707 6838367000      push    offset corelanapp1!__rtc_tzz+0x60 (00703638)\n<high>0070170c<\/high> e81f070000      call    corelanapp1!__SEH_prolog4 (00701e30) <high>\/\/ ** EIP is here now<\/high>\n00701711 6a01            push    1 <high>\/\/ ** When the CALL is done, we expect it to resume execution here<\/high>\n00701713 e8ef010000      call    corelanapp1!__scrt_initialize_crt (00701907)\n00701718 59              pop     ecx\n00701719 84c0            test    al,al\n<\/pre>\n<p>Type <span class=\"corelan-mono\">p<\/span> and hit return or just press the <span class=\"corelan-mono\">F10<\/span> shortkey.<br \/>\nThe debugger will now execute the CALL and anything that happens, until the child function returns back to its caller, and resumes execution right after the call.<br \/>\nIn your example, that's the <span class=\"corelan-mono\">push 1<\/span> instruction just beneath it.<\/p>\n<p>So, this is where we are right now:<\/p>\n<pre>\n0:000> <cmd>t<\/cmd>\neax=00ddff38 ebx=00e23000 ecx=4319725c edx=00701887 esi=00701887 edi=00701887\neip=<high>0070170c<\/high> esp=00ddfedc ebp=00ddfef0 iopl=0         nv up ei ng nz na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286\ncorelanapp1!__scrt_common_main_seh+0x7:\n0070170c e81f070000      call    corelanapp1!__SEH_prolog4 (00701e30)\n<\/pre>\n<p>and if we now perform the <strong>step over<\/strong>, we get this:<\/p>\n<pre>\n0:000> <cmd>p<\/cmd>\neax=00ddfed0 ebx=00e23000 ecx=4319725c edx=00701887 esi=00701887 edi=00701887\neip=<high>00701711<\/high> esp=00ddfeac ebp=00ddfee0 iopl=0         nv up ei ng nz na po nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000282\ncorelanapp1!__scrt_common_main_seh+0xc:\n00701711 6a01            push    1\n<\/pre>\n<p>Great!  <\/p>\n<h4>Stepping Source vs Assembly<\/h4>\n<p>When the debugger is able to find the source code of the application you're debugging, then it will very likely activate stepping \"source\" instead of \"assembly\". In other words, when you perform a step, it becomes a \"single line of source code\", which means it's very likely going to step more than one assembly instruction.<br \/>\nIf that is the case, don't forget to turn of \"Source mode\" <\/p>\n<p>WinDBG: 'Debug' - Uncheck 'Source mode'<br \/>\nWinDBGX: 'Home' - 'Preferences' - Select 'Assembly' instead of 'Source'<\/p>\n<h4>Stepping out (go up)<\/h4>\n<p>The last command I'd like to review in this chapter, is \"stepping out\". It's a mechanism that allows you to continue running the current function, but making the execution stop when it's done. It's particularly useful if you have accidentally stepped into a function, or if you just want to get out of the current function and resume stepping in the parent function.<\/p>\n<p>The command <span class=\"corelan-mono\">gu<\/span> allows you to do so.  It executes the rest of the function, until (and including) the RETN at the end of the function.  It will go back to the parent and stop right there.<\/p>\n<h3>Unassemble: u, ub and uf<\/h3>\n<p>In the previous chapter, I provided the disassembly listing at the start of the <span class=\"corelan-mono\">corelanapp1!__scrt_common_main_seh<\/span> function:<\/p>\n<pre>\ncorelanapp1!__scrt_common_main_seh:\n00701705 6a14            push    14h\n00701707 6838367000      push    offset corelanapp1!__rtc_tzz+0x60 (00703638)\n0070170c e81f070000      call    corelanapp1!__SEH_prolog4 (00701e30)\n00701711 6a01            push    1 \n00701713 e8ef010000      call    corelanapp1!__scrt_initialize_crt (00701907)\n00701718 59              pop     ecx\n00701719 84c0            test    al,al\n<\/pre>\n<p>How can we produce a disassembly listing like this in WinDBG?<\/p>\n<p>First of all, there is a dedicated Disassembly view.<br \/>\nIf you are using the dark.wew or corelan.wew workspaces,  you should already have it on your screen.  If not, you can open it by clicking 'View' and then chosing 'Disassembly'.<br \/>\nBy default, the <span class=\"corelan-mono\">Offset:<\/span> field at the top of the view is set to <span class=\"corelan-mono\">@scopeip<\/span>, which means it will show the instructions around EIP.  (In fact, I copied the disassembly listing from that view.)<\/p>\n<p>That said, we can use the <span class=\"corelan-mono\">u<\/span> command (unassemble forward) in WinDBG to produce a disassemble listing from any location.<br \/>\nWe just need to provide the memory location after the <span class=\"corelan-mono\">u<\/span> and we'll get to see the instructions from that point forward.<\/p>\n<p>For example:<\/p>\n<pre>\n0:000> <cmd>u eip<\/cmd>\ncorelanapp1!__scrt_common_main_seh+0xc [d:\\agent\\_work\\3\\s\\src\\vctools\\crt\\vcstartup\\src\\startup\\exe_common.inl @ 237]:\n00701711 6a01            push    1\n00701713 e8ef010000      call    corelanapp1!__scrt_initialize_crt (00701907)\n00701718 59              pop     ecx\n00701719 84c0            test    al,al\n0070171b 0f8450010000    je      corelanapp1!__scrt_common_main_seh+0x16c (00701871)\n00701721 32db            xor     bl,bl\n00701723 885de7          mov     byte ptr [ebp-19h],bl\n00701726 8365fc00        and     dword ptr [ebp-4],0\n<\/pre>\n<p>Of course, you can also show the instructions before a certain point, using <span class=\"corelan-mono\">ub location<\/span> (unassemble backward).  <\/p>\n<p>For instance <span class=\"corelan-mono\">ub eip<\/span>:<\/p>\n<pre>\n0:000> <cmd>ub eip<\/cmd>\ncorelanapp1!pre_cpp_initialization+0x5 [d:\\agent\\_work\\3\\s\\src\\vctools\\crt\\vcstartup\\src\\startup\\exe_common.inl @ 222]:\n007016f8 e8a9040000      call    corelanapp1!_matherr (00701ba6)\n007016fd 50              push    eax\n007016fe e8f20a0000      call    corelanapp1!set_new_mode (007021f5)\n00701703 59              pop     ecx\n00701704 c3              ret\ncorelanapp1!__scrt_common_main_seh [d:\\agent\\_work\\3\\s\\src\\vctools\\crt\\vcstartup\\src\\startup\\exe_common.inl @ 236]:\n00701705 6a14            push    14h\n00701707 6838367000      push    offset corelanapp1!__rtc_tzz+0x60 (00703638)\n0070170c e81f070000      call    corelanapp1!__SEH_prolog4 (00701e30)\n<\/pre>\n<p>Just like the u command earlier on, what you'll get is just a linear translation of the bytes at that location in memory.  It does not actually follow the code flow.  What you see is not necessarily the sequence of execution.<\/p>\n<p>In both cases, you see that the <span class=\"corelan-mono\">u<\/span> and <span class=\"corelan-mono\">ub<\/span> commands produce a given number of lines of output.<br \/>\nWith pretty much any command that shows contents (display memory, disassembly of instructions), you can direct WinDBG how many entities of output you'd like to see.<br \/>\nTo do so, you can use <span class=\"corelan-mono\">L number<\/span>.  The 'number' indicate how many entities you'd like to see.<\/p>\n<p>When asking for a disassembly listing, the \"entity\" is an instruction. In this case, the <span class=\"corelan-mono\">L<\/span> specifier will affect how many lines you get to see.<\/p>\n<p>For example:<\/p>\n<pre>\n0:000> <cmd>u eip L 4<\/cmd>\ncorelanapp1!__scrt_common_main_seh+0xc [d:\\agent\\_work\\3\\s\\src\\vctools\\crt\\vcstartup\\src\\startup\\exe_common.inl @ 237]:\n00701711 6a01            push    1\n00701713 e8ef010000      call    corelanapp1!__scrt_initialize_crt (00701907)\n00701718 59              pop     ecx\n00701719 84c0            test    al,al\n<\/pre>\n<p>4 lines of instructions, as expected.<\/p>\n<p>In addition to <span class=\"corelan-mono\">u<\/span> and <span class=\"corelan-mono\">ub<\/span>, there is another interesting command from the u-range: <span class=\"corelan-mono\">uf<\/span> (unassemble function).<br \/>\nThis command will disassemble an entire function (or as close as possible) instead of a certain range of instructions.   <span class=\"corelan-mono\">uf<\/span> will use symbols & heuristics to determine the boundaries of the function and will then spit out the full disassembly listing for that function.   As a result of the nature of the command, it should be clear that <span class=\"corelan-mono\">uf<\/span> is mostly useful when you run it against the start of a function.  You can use the function address or the symbol name associated with a function.<\/p>\n<p>For example:<\/p>\n<p><span class=\"corelan-mono\">u ntdll!RtlAllocateHeap<\/span>: prints out just a few lines at the start of the function<br \/>\n<span class=\"corelan-mono\">uf ntdll!RtlAllocateHeap<\/span>: prints out the entire function<\/p>\n<pre>\n0:000> <cmd>u ntdll!RtlAllocateHeap<\/cmd>\nntdll!RtlAllocateHeap:\n76e9f8a0 8bff            mov     edi,edi\n76e9f8a2 55              push    ebp\n76e9f8a3 8bec            mov     ebp,esp\n76e9f8a5 6afe            push    0FFFFFFFEh\n76e9f8a7 68b8dbf776      push    offset ntdll!SbGetContextDetailsById+0x70b (76f7dbb8)\n76e9f8ac 68b01dee76      push    offset ntdll!_except_handler4 (76ee1db0)\n76e9f8b1 64a100000000    mov     eax,dword ptr fs:[00000000h]\n76e9f8b7 50              push    eax\n<\/pre>\n<pre>\n0:000> <cmd>uf ntdll!RtlAllocateHeap<\/cmd>\nFlow analysis was incomplete, some code may be missing\nntdll!RtlAllocateHeap:\n76e9f8a0 8bff            mov     edi,edi\n76e9f8a2 55              push    ebp\n76e9f8a3 8bec            mov     ebp,esp\n76e9f8a5 6afe            push    0FFFFFFFEh\n76e9f8a7 68b8dbf776      push    offset ntdll!SbGetContextDetailsById+0x70b (76f7dbb8)\n76e9f8ac 68b01dee76      push    offset ntdll!_except_handler4 (76ee1db0)\n76e9f8b1 64a100000000    mov     eax,dword ptr fs:[00000000h]\n76e9f8b7 50              push    eax\n76e9f8b8 83ec08          sub     esp,8\n76e9f8bb 53              push    ebx\n76e9f8bc 56              push    esi\n76e9f8bd 57              push    edi\n76e9f8be a1e0c3f976      mov     eax,dword ptr [ntdll!__security_cookie (76f9c3e0)]\n76e9f8c3 3145f8          xor     dword ptr [ebp-8],eax\n76e9f8c6 33c5            xor     eax,ebp\n76e9f8c8 50              push    eax\n76e9f8c9 8d45f0          lea     eax,[ebp-10h]\n76e9f8cc 64a300000000    mov     dword ptr fs:[00000000h],eax\n76e9f8d2 8965e8          mov     dword ptr [ebp-18h],esp\n76e9f8d5 8b7508          mov     esi,dword ptr [ebp+8]\n76e9f8d8 85f6            test    esi,esi\n76e9f8da 750e            jne     ntdll!RtlAllocateHeap+0x4a (76e9f8ea)  Branch\n...\n76ea01a1 8be5            mov     esp,ebp\n76ea01a3 5d              pop     ebp\n76ea01a4 c21000          ret     10h\n<\/pre>\n<h3>Breakpoints (bp, ba)<\/h3>\n<p>Debuggers provide the ability to create breakpoints, and breakpoints are what will give you control.<br \/>\nThere are 2 types of breakpoints:<\/p>\n<ul>\n<li><strong>Software<\/strong> breakpoints<\/li>\n<li><strong>Hardware<\/strong> breakpoints<\/li>\n<\/ul>\n<p>The difference lies in how the CPU is made to interrupt the execution.<\/p>\n<p><span style='text-decoration:underline;'><strong>Software breakpoints:<\/strong><\/span><\/p>\n<p>When you set a software breakpoint at a certain address, you're telling the debugger you want to make the CPU stop when it tries to execute the instruction at that location.<br \/>\nWhen creating a breakpoint at that address, the debugger replace the first byte in memory at that location with the <span class=\"corelan-mono\">CC<\/span> byte, wwhich corresponds with the <span class=\"corelan-mono\">INT3<\/span> instruction.<br \/>\nWhen the CPU reaches that address and executes it, it will raise a <span class=\"corelan-mono\">STATUS_BREAKPOINT exception<\/span>, which gets picked up by the debugger, and that makes the execution stop.<br \/>\nAs the debugger remembers what the original byte was (before it replaced it with <span class=\"corelan-mono\">CC<\/span>, it's able to show you the original byte, and with that, the original instruction.<\/p>\n<p>You can pretty much create as many software breakpoints as you want, but please keep in mind that it involves changing memory.<\/p>\n<p><span style='text-decoration:underline;'><strong>Hardware Breakpoints<\/strong><\/span><\/p>\n<p>Hardware breakpoints use CPU debug registers (<span class=\"corelan-mono\">DR0<\/span> to <span class=\"corelan-mono\">DR3<\/span>). Additionally, register <span class=\"corelan-mono\">DR7<\/span> is used for flags (type and size).   As the hardware breakpoints use registers, they are thread specific, not process wide.  <\/p>\n<p>You can set 4 hardware breakpoints at a time, and for each of them, you have to indicate not just the address, but also the type of access you'd like the CPU to break on (Read, Write or Execute) and the size (number of bytes to monitor)<br \/>\nThe CPU will monitor access\/execution and will trigger a <span class=\"corelan-mono\">STATUS_SINGLE_STEP exception<\/span> when a condition is met.<br \/>\nIn short: hardware breakpoints require an address, access type, and size because the CPU debug registers must know what to monitor, how to monitor it, and how many bytes are involved.<\/p>\n<h4>Software breakpoints<\/h4>\n<p>Breakpoints are managed via a series of commands that begin with <span class=\"corelan-mono\">b<\/span>:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">bp<\/span> : create a breakpoint<\/li>\n<li><span class=\"corelan-mono\">bl<\/span> : show all breakpoints<\/li>\n<li><span class=\"corelan-mono\">bc<\/span> : clear a breakpoint<\/li>\n<li><span class=\"corelan-mono\">be<\/span> : enable a breakpoint<\/li>\n<li><span class=\"corelan-mono\">bd<\/span> : disable a breakpoint<\/li>\n<\/ul>\n<p>In its most basic form, creating a breakpoint is a simple as typing the <span class=\"corelan-mono\">bp<\/span> command, followed by the location where you'd like to put the breakpoint.<\/p>\n<p>We can see the list of breakpoints using the <span class=\"corelan-mono\">bl<\/span> command.<\/p>\n<p>Open a new debugging session using the corelanapp1.exe binary, but don't let the application run yet.  In other words, open the executable in the debugger and don't anything yet at the initial break.  You should see something like this:<\/p>\n<pre>\n(1d30.1ed8): Break instruction exception - code 80000003 (first chance)\neax=00000000 ebx=00000000 ecx=f7780000 edx=00000000 esi=00e36628 edi=0080d000\neip=76f78218 esp=00b6f38c ebp=00b6f3b8 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!LdrpDoDebuggerBreak+0x2b:\n76f78218 cc              int     3\n<\/pre>\n<p>Let's create a breakpoint at the RtlAllocateHeap function in ntdll:<\/p>\n<p><span class=\"corelan-mono\">bp ntdll!RtlAllocateHeap<\/span><\/p>\n<p>If all goes well, you won't see output.<\/p>\n<p>We can now use the <span class=\"corelan-mono\">bl<\/span> command to see all breakpoints:<\/p>\n<pre>\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  76e9f8a0     0001 (0001)  0:**** ntdll!RtlAllocateHeap\n<\/pre>\n<p>Let's examine what we see:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">0<\/span>: this is the internal breakpoint ID, it's a unique number assigned by WinDBG. Technically, we can define a number yourself, I'll explain in a moment how to do so<\/li>\n<li><span class=\"corelan-mono\">e<\/span>: this indicates that the breakpoint is enabled.  We have the ability to enable and disable breakpoints<\/li>\n<li><span class=\"corelan-mono\">Disable<\/span> : this clickable link allows you to Disable an enabled breakpoint. If the breakpoint was disabled, you'd see <span class=\"corelan-mono\">Enable<\/span> instead.  If you hover the mouse over the clickable link, you'll see the <span class=\"corelan-mono\">be 0<\/span> command in WinDBG's status tray, just below the Command Line input box.<\/li>\n<li><span class=\"corelan-mono\">Clear<\/span> : this clickable link allows you to clear (remove) the breakpoint. Hold the mouse pointer over the link, and you'll see the <span class=\"corelan-mono\">bc 0<\/span> command in the status tray.<\/li>\n<li><span class=\"corelan-mono\">76e9f8a0<\/span> : although we used a symbol name as destination, WinDBG has resolved it into the corresponding absolute address.<\/li>\n<li><span class=\"corelan-mono\">0001<\/span> : this indicates the current remaining count, related with the pass count (see next bullet point).  If you would configure a pass count higher than 1, you can see this counter decrementing until it gets to 1<\/li>\n<li><span class=\"corelan-mono\">(0001)<\/span> : this is the pass count. A count of 1 means the breakpoint will hit the first time the CPU goes to that location. I'll take about pass count in a moment.<\/li>\n<li><span class=\"corelan-mono\">0:****<\/span> : this is the thread\/process applicability field. First part (before the colon) is the process (0 = internal sequence number in WinDBG).  **** means all threads.<\/li>\n<li><span class=\"corelan-mono\">ntdll!RtlAllocateHeap<\/span> : WinDBG prints the symbol name that corresponds with the address<\/li>\n<\/ul>\n<h5>Enable (be), Disable (bd), Clear (bc)<\/h5>\n<p>Once we have the ID, we can interact with a breakpoint. We can disable it, enable it, clear it.<\/p>\n<p><span class=\"corelan-mono\">be 0<\/span> enables breakpoint with id 0<br \/>\n<span class=\"corelan-mono\">bd 0<\/span> disables breakpoint with id 0<br \/>\n<span class=\"corelan-mono\">bc 0<\/span> clears breakpoint with id 0<\/p>\n<p>We can also clear all breakpoints using <span class=\"corelan-mono\">bc *<\/span><\/p>\n<h5>Specifying a custom ID to a breakpoint<\/h5>\n<p>As indicated earlier, we have the ability to specify an ID right away.  In order to do so, we have to glue the desired ID number to the <span class=\"corelan-mono\">bp<\/span> command, followed by the desired location.<\/p>\n<p>For instance, let's say I want to create breakpoint with ID 666 on <mono>ntdll!RtlFreeHeap<\/mono>:<\/p>\n<pre>\n0:000> <cmd>bp666 ntdll!RtlFreeHeap<\/cmd>\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  76e9f8a0     0001 (0001)  0:**** ntdll!RtlAllocateHeap\n    666 e Disable Clear  76e9ee30     0001 (0001)  0:**** ntdll!RtlFreeHeap\n<\/pre>\n<blockquote><p>Take note: the ID\/number needs to be glued to <mono>bp<\/mono>. No spaces!!<\/p><\/blockquote>\n<p>Now I don't have to look up the ID for this breakpoint. I know my breakpoint has ID 666, allowing me to very easily enable, disable or clear it.<\/p>\n<p>With the 2 breakpoints active, let the process run (type <span class=\"corelan-mono\">g<\/span>) and see what happens.<\/p>\n<pre>\n0:000> <cmd>g<\/cmd>\n<high>Breakpoint 0 hit<\/high>\neax=007f0008 ebx=00000000 ecx=00e30000 edx=000000c4 esi=00000010 edi=00000000\neip=76e9f8a0 esp=00b6f138 ebp=00b6f14c iopl=0         nv up ei pl nz na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206\nntdll!RtlAllocateHeap:\n76e9f8a0 8bff            mov     edi,edi\n<\/pre>\n<p>Apparently the CPU ended up going to the RtlAllocateHeap function, hitting breakpoint 0, and making the process stop.  As expected.<\/p>\n<h5>Pass count<\/h5>\n<p>When we examined the output of <span class=\"corelan-mono\">bl<\/span>, I indicated that we have the ability to specify a \"pass count\".  A pass count allows me to specify how many times a certain breakpoint needs to be hit before I want the debugger to actually stop.<br \/>\nLet's say I want to ignore the first 29 iterations of RtlAllocateHeap, and only stop the 30th time, I can specify the pass count after the address in the <span class=\"corelan-mono\">bp<\/span> statement: <\/p>\n<p><span class=\"corelan-mono\">bp ntdll!RtlAllocateHeap 30<\/span><\/p>\n<pre>\n0:000> <cmd>bp ntdll!RtlAllocateHeap 30<\/cmd>\nbreakpoint 0 redefined\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  76e9f8a0     <high>0030 (0030)<\/high>  0:**** ntdll!RtlAllocateHeap\n    666 e Disable Clear  76e9ee30     0001 (0001)  0:**** ntdll!RtlFreeHeap\n<\/pre>\n<p>Here we see the pass count is set to (0030), and the remaining counter is set to 0030 as well.  Every time the ntdll!RtlAllocateHeap function gets used, it will decrement the remaining counter.  When that counter becomes 1, the process will stop.<\/p>\n<p>Let the process continue running:<\/p>\n<pre>\n0:000> <cmd>g<\/cmd>\n<high>Breakpoint 666 hit<\/high>\neax=0080d000 ebx=00e3d4a8 ecx=00000000 edx=00e3d49c esi=00e3c400 edi=00e3d4a8\neip=76e9ee30 esp=00b6f17c ebp=00b6f18c iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!RtlFreeHeap:\n76e9ee30 8bff            mov     edi,edi\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  76e9f8a0     <high>0029 (0030)<\/high>  0:**** ntdll!RtlAllocateHeap\n    666 e Disable Clear  76e9ee30     0001 (0001)  0:**** ntdll!RtlFreeHeap\n<\/pre>\n<p>The breakpoint on RtlFreeHeap got triggered, and that gives us the opportunity to examine the pass count\/remaining counter setting for RtlAllocateHeap.  In the example above, we see that RtlAllocateHeap was actually used once already, the remaining counter is now set to 0029.  We'll just have to wait until it decrements to 1 before the breakpoint on RtlAllocateHeap will hit again.<br \/>\nSo, let the process continue running again:<\/p>\n<pre>\n0:000> <cmd>g<\/cmd>\n<high>Breakpoint 0 hit<\/high>\neax=00000001 ebx=00000023 ecx=00000022 edx=0000000e esi=00000023 edi=00e3dbc1\neip=76e9f8a0 esp=00b6f16c ebp=00b6f184 iopl=0         nv up ei pl nz na po nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202\nntdll!RtlAllocateHeap:\n76e9f8a0 8bff            mov     edi,edi\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  76e9f8a0     <high>0001 (0030)<\/high>  0:**** ntdll!RtlAllocateHeap\n    666 e Disable Clear  76e9ee30     0001 (0001)  0:**** ntdll!RtlFreeHeap\n<\/pre>\n<p>This time, breakpoint 0 got hit, and we can see that the Remaining Counter has become 1. In fact, it will remain 1.  From this point forward, the breakpoint will hit every single time.<\/p>\n<p>Let's wrap up the exercise.  Clear all breakpoints with <span class=\"corelan-mono\">bc *<\/span> and confirm they're gone.<\/p>\n<h5>Setting mass-breakpoints using symbol wildcards<\/h5>\n<p>The <span class=\"corelan-mono\">bm<\/span> command allows us to set breakpoints on multiple addresses in one shot, and you can use wildcards to find the locations you'd like to break on.  Let's say you want to put a breakpoint on al functions that contain the word \"heap\" in kernel32.<\/p>\n<p>First of all, we can do a symbol search using the <span class=\"corelan-mono\">x<\/span> command:<\/p>\n<p>The syntax is quite simple: <span class=\"corelan-mono\">modulename (or wildcard) !  symboldname (or wildcard)<\/span><br \/>\nSo if you'd like to find all symbol names that contain the word heap inside kernel32, then the search would be<br \/>\n<span class=\"corelan-mono\">kernel32!*heap*<\/span><\/p>\n<p>Let's try:<\/p>\n<pre>\n0:000> <cmd>x kernel32!*heap*<\/cmd>\n757e4082          KERNEL32!WerpGetHeapHandle (void)\n757e49b0          KERNEL32!WerpHeapUnLock (void)\n757e48f1          KERNEL32!WerpHeapGetBlockFromFreeList (void)\n757e4986          KERNEL32!WerpHeapLock (void)\n757e44cb          KERNEL32!WerpHeapAddBlockToTail (void)\n757f13f0          KERNEL32!HeapDestroyStub (no parameter info)\n757d41b0          KERNEL32!HeapFreeStub (no parameter info)\n75841430          KERNEL32! api-ms-win-core-heap-obsolete-l1-1-0_NULL_THUNK_DATA = <no type information>\n757f1470          KERNEL32!HeapUnlockStub (no parameter info)\n758413fc          KERNEL32!_imp__HeapValidate = <no type information>\n758413e8          KERNEL32!_imp__HeapFree = <no type information>\n758413d4          KERNEL32!_imp__HeapQueryInformation = <no type information>\n...\n<\/pre>\n<p>The <span class=\"corelan-mono\">x<\/span> command only performs a search and we get to see the output.<br \/>\nIt doesn't set a breakpoint yet.   That's what the <span class=\"corelan-mono\">bm<\/span> command can do.<\/p>\n<p>Simply replace <span class=\"corelan-mono\">x<\/span> with <span class=\"corelan-mono\">bm<\/span> and see what happens:<\/p>\n<pre>\n0:000> <cmd>bm kernel32!*heap*<\/cmd>\n  1: 757e4082          @!\"KERNEL32!WerpGetHeapHandle\"\n  2: 757e49b0          @!\"KERNEL32!WerpHeapUnLock\"\n  3: 757e48f1          @!\"KERNEL32!WerpHeapGetBlockFromFreeList\"\n  4: 757e4986          @!\"KERNEL32!WerpHeapLock\"\n  5: 757e44cb          @!\"KERNEL32!WerpHeapAddBlockToTail\"\n  6: 757f13f0          @!\"KERNEL32!HeapDestroyStub\"\n  7: 757d41b0          @!\"KERNEL32!HeapFreeStub\"\n  8: 757f1470          @!\"KERNEL32!HeapUnlockStub\"\n  9: 757f1430          @!\"KERNEL32!HeapQueryInformationStub\"\n 10: 757e4748          @!\"KERNEL32!WerpHeapCreate\"\n 11: 75820930          @!\"KERNEL32!Heap32ListFirst\"\n 12: 757e4500          @!\"KERNEL32!WerpHeapAlloc\"\n 13: 757f1490          @!\"KERNEL32!HeapValidateStub\"\n 14: 757ddf54          @!\"KERNEL32!RtlFreeHeap\"\n 15: 75820a80          @!\"KERNEL32!Heap32Next\"\n 16: 757ddf48          @!\"KERNEL32!RtlAllocateHeap\"\n 17: 757dd210          @!\"KERNEL32!HeapCreateStub\"\n 18: 757f14b0          @!\"KERNEL32!HeapWalkStub\"\n 19: 757f0f30          @!\"KERNEL32!GetProcessHeapsStub\"\n 20: 757e49d4          @!\"KERNEL32!WerpValidateHeapSignature\"\n 21: 757de1cc          @!\"KERNEL32!RtlSizeHeap\"\n 22: 757f13d0          @!\"KERNEL32!HeapCompactStub\"\n 23: 757dd5b0          @!\"KERNEL32!HeapSetInformationStub\"\n 24: 757e3390          @!\"KERNEL32!GetProcessHeap\"\n 25: 758209e0          @!\"KERNEL32!Heap32ListNext\"\n 26: 757f1450          @!\"KERNEL32!HeapSummaryStub\"\n 27: 757e47bb          @!\"KERNEL32!WerpHeapFree\"\n 28: 757f1410          @!\"KERNEL32!HeapLockStub\"\n 29: 758206c0          @!\"KERNEL32!Heap32First\"\n<\/pre>\n<h4>Hardware breakpoints<\/h4>\n<p>You can set a hardware breakpoint with the <span class=\"corelan-mono\">ba<\/span> command. The basic syntax is:<\/p>\n<pre>\nba type+size address\n<\/pre>\n<p>There are 4 types:<\/p>\n<ul>\n<li><span class=\"corelan-mono\">r<\/span>\tread<\/li>\n<li><span class=\"corelan-mono\">w<\/span>\twrite<\/li>\n<li><span class=\"corelan-mono\">rw<\/span>\tread or write<\/li>\n<li><span class=\"corelan-mono\">e<\/span>\texecute<\/li>\n<\/ul>\n<p>The sizes are CPU\/architecture dependent, but they typically are 1, 2, 4 and 8 (for 64bit)<\/p>\n<p>So, for example, let's say you want the CPU to break when any code makes a change to any of the 4 bytes beginning at address 0x12345678, the breakpoint statement would be<\/p>\n<pre>\n<cmd>ba w4 0x12345678<\/cmd>\n<\/pre>\n<p>If you'd like to break on execution at 0x00400234, you can do:<\/p>\n<pre>\n<cmd>ba e1 0x00400234<\/cmd>\n<\/pre>\n<p>Hardware breakpoints will show up in the output of <span class=\"corelan-mono\">bl<\/span> and you can use the <span class=\"corelan-mono\">be<\/span>, <span class=\"corelan-mono\">bd<\/span> and <span class=\"corelan-mono\">bc<\/span> commands to enable, disable and clear them, just like software breakpoints.<\/p>\n<h4>From breakpoint to dynamic logging system<\/h4>\n<p>In the past 2 chapters, we've explored in great detail how we can create and manage software and hardware breakpoints.  Up until this point, we've been using breakpoints to interrupt the execution of the process.  But in reality, WinDBG allows us to execute WinDBG statements whenever a breakpoint gets hit.  And that allows us to turn the breakpoint system into a dynamic logger.<\/p>\n<p>Before looking at how to do this, let's put a few facts on the table.<\/p>\n<ol>\n<li>We have the ability to put multiple WinDBG commands on one line, simply separate them by a semi-colon.<\/li>\n<li>We can print information using the <mono>.printf<\/mono> statement.  This meta-command takes text and variables, between double quotes.  We can use C-style format specifiers to format the variables.<\/li>\n<\/ol>\n<p>The basic format to link one or more WinDBG statements to a breakpoint is this:<\/p>\n<pre><cmd>bp[id] address [passcount] \"windbgstatement1;statement2;gc\"<\/cmd><\/pre>\n<p>Putting a <span class=\"corelan-mono\">gc<\/span> as the last instruction, will turn the traditional \"break\" point into a mechanism that can print context-specific information and then simply continue running.<\/p>\n<blockquote><p>According to MS documentation, using <mono>gc<\/mono> as the final command in the breakpoint action (instead of <mono>g<\/mono>) might be slightly faster.<\/p><\/blockquote>\n<p>Printing context-specific information to the screen (register, memory contents, etc) requires some sort of print statement.   The meta-command to do so, taking variables, is <span class=\"corelan-mono\">.printf<\/span>.   It takes double quotes and C-style variables\/format specifiers.   The entire <mono>.print<\/mono> statement will sit inside the double quotes that are required in the bp statement.  With double quotes inside double quotes, we'll need to escape the inner double quotes with a backslash.<\/p>\n<p>Let's look at a simple example:  Let's say you want to print the value in <mono>eax<\/mon> as a pointer, and the value in ecx as a hex number to the screen.<br \/>\nThe corresponding <span class=\"corelan-mono\">.printf<\/span> statement would be<\/p>\n<pre><cmd>.printf \"eax: 0x%p, ecx: 0x%x\\n\", @eax, @ecx<\/cmd><\/pre>\n<p>To embed this inside a WinDBG breakpoint, we have to escape the double quotes with a backslash.  We also have to escape the backslash that was used for the newline.  The correct bp statement looks like this:<\/p>\n<pre><cmd>bp address \".printf \\\"eax: 0x%p, ecx: 0x%x\\\\n\\\", @eax, @ecx\"<\/cmd><\/pre>\n<p>This breakpoint will print the information and then stop.  If we want to turn it into a dynamic logger, we have to instruct it to continue running after printing the requested information.  We can do that by adding <span class=\"corelan-mono\">;g<\/span>at the end, inside the outer double quotes:<\/p>\n<pre><cmd>bp address \".printf \\\"eax: 0x%p, ecx: 0x%x\\\\n\\\", @eax, @ecx ; gc\"<\/cmd><\/pre>\n<p>Check the <a href=\"#Commands_overview\">\"Commands Overview\"<\/a> table at the end of this document for more information about some of the available format specifiers.<\/p>\n<h4>ASLR-friendly breakpoints<\/h4>\n<p>In an ALSR environment, setting (and documenting) breakpoint on addresses that are impacted by ASLR, may very likely invalidate the breakpoint statement as soon you close the process.  The absolute address will likely be different in the next run. In other works, you can't copy\/paste breakpoints that are set on absolute addresses.<\/p>\n<p>It's a good habit to use breakpoint statements that don't use hardcoded addresses , but rather use a relative offset from something that can be referenced reliably.  For example, the start of the module a certain address belongs to.<\/p>\n<h5>From ASLR-enabled absolute address to ASLR-friendly relative address<\/h5>\n<p>Run the corelanapp1.exe binary in a debugger and let's take ntdll's <mono>RtlAllocateHeap<\/mono> function as an example.<\/p>\n<p>It's pretty easy to locate that function if we have symbols.  <\/p>\n<pre>\n0:000> <cmd>x ntdll!RtlAllocateHeap<\/cmd>\n773bf8a0          ntdll!RtlAllocateHeap (no parameter info)\n<\/pre>\n<p>And with symbols, we can put a breakpoint at ntdll!RtlAllocateHeap (instead of the address). WinDBG will resolve it, and the breakpoint will land in the right place every time.<br \/>\nEven if you reboot your machine, the symbol name will resolve to the correct position.<\/p>\n<pre>\n0:000> <cmd>bp ntdll!RtlAllocateHeap<\/cmd>\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  <high>773bf8a0<\/high>     0001 (0001)  0:**** ntdll!RtlAllocateHeap\n<\/pre>\n<p>But what if you'd like to put a breakpoint somewhere in the middle of some random function.<br \/>\nOr in a function for a module that doesn't have symbols?<\/p>\n<p>In the example above, RtlAllocateHeap sits at <span class=\"corelan-mono\">773bf8a0<\/span>.<br \/>\nFor the sake of the exercise, let's use that address as an example. <\/p>\n<p>We know the function resides in ntdll.<br \/>\nInstead of using the function symbol name, I can also set a breakpoint at an offset from the start of ntdll.<\/p>\n<p>Unless the version of ntdll changes, the offset from the start of the module, to that position, will always be the same.<\/p>\n<p>Even without symbols, I can always use the name of a module to make a calculation.<\/p>\n<p>First, I have to determine the offset from the start of ntdll to my chosen address.   I can use WinDBG's \"MASM\" expression evaluator to do this: <span class=\"corelan-mono\">?<\/span><br \/>\nThe syntax is quite simple:<\/p>\n<p><span class=\"corelan-mono\">? address - module_it_belongs_to<\/span><\/p>\n<p>Example:<\/p>\n<pre>\n0:000> <cmd>?773bf8a0-ntdll<\/cmd>\nEvaluate expression: 260256 = 0003f8a0\n<\/pre>\n<p>On my system, my address sits at offset <span class=\"corelan-mono\">0003f8a0<\/span> from the start of ntdll.<\/p>\n<p>I can now make a breakpoint at that location exactly, without having to hardcode its address, nor rely on symbols:<\/p>\n<pre>\n0:000> <cmd>bc *<\/cmd>\n0:000> <cmd>bp ntdll+0003f8a0<\/cmd>\n0:000> <cmd>bl<\/cmd>\n     0 e Disable Clear  <high>773bf8a0<\/high>     0001 (0001)  0:**** ntdll!RtlAllocateHeap\n<\/pre>\n<p>In short, you can set a breakpoint at a Symbol name (the name of a function, and thus the start of a function), and at an offset from the start of a module.<br \/>\nIn my experience, it may not be a good idea to set breakpoints at an offset from a symbol name.<\/p>\n<p>So, this is reliable:<\/p>\n<pre>\n<cmd>bp ntdll!RtlFreeHeap<\/cmd>\n<cmd>bp ntdll+0x1234<\/cmd>\n<\/pre>\n<p>but this breakpoint may not end up where you want or expect it to be.<\/p>\n<pre><cmd>bp ntdll!RtlAllocateHeap+0x168<\/cmd><\/pre>\n<h5>MASM Expression evaluator (?)<\/h5>\n<p>I'll go a little deeper into WinDBG's expression evaluators in a future post.  <\/p>\n<p>For now, please remember that you can invoke the MASM expression evaluator using the question mark, and you can use it to perform arithmetic operations.<br \/>\nYou can use numbers, symbols names, pseudo-registers, real registers, the name of a module (which works even without symbols).  You can dereference an address using the <mono>poi()<\/mono> function.<br \/>\nWhen using registers, it's recommended to prefix the register name with <mono>@<\/mono>. That way, you're telling the expression evaluator that you want the value of the register. <\/p>\n<p>MASM is the default expression evaluator used in:<\/p>\n<ul>\n<li><mono>? expression<\/mono> statements, <\/li>\n<li>breakpoint conditions (see next chapter), <\/li>\n<li><mono>.if<\/mono> \/ <mono>.for<\/mono> \/ <mono>.foreach<\/mono> routines (I'll talk more about that in a future post),<\/li>\n<li>display\/dump commands (<mono>d<\/mono>-commands)<\/li>\n<li>unassemble commands (<mono>u<\/mono>-commands) <\/li>\n<li>etc<\/li>\n<\/ul>\n<h4>Conditional breakpoints<\/h4>\n<p>The last thing I'd like to share about breakpoints today, is that we have the ability to define conditional breakpoints.<br \/>\nIn other words, we can have a breakpoint do something if a certain condition is met, and do something else otherwise.  If you're using breakpoints for dynamic logging, don't forget to let the breakpoint continue running in both the then and else branch.  According to document, you're supposed to use <mono>gc<\/mono> (continue from condition) instead of <mono>g<\/mono><\/p>\n<p>the basic syntax is this:<\/p>\n<pre>\nbp address \"j (Condition) 'OptionalCommands; gc'; 'gc';\"\nbp address \".if (Condition) {OptionalCommands; gc} .else {gc}\"\n<\/pre>\n<h3>.printf Debugger Markup Language (DML)<\/h3>\n<p>I'd like to add another cool trick to our toolbox.  With <mono>.printf<\/mono>, we have the ability to use DML, or Debugger Markup Language.<br \/>\nThis allows you to create clickable links, colorize output, make output bold.<\/p>\n<p>You have to specify the <mono>\/D<\/mono> flag, which enables the feature. Without <mono>\/D<\/mono>, the rest is considered raw text.<\/p>\n<p>A quick overview:<\/p>\n<p><strong>Clickable command<\/strong><br \/>\n<mono>&lt;link cmd=\"...\"&gt;...&lt;\/link&gt;<\/mono> : Runs a debugger command (Disable breakpoint: <mono>bd 42<\/mono>)<\/p>\n<p><strong>Clickable script execution<\/strong><br \/>\n<mono>&lt;link cmd=\"$&lt;file\"&gt;...&lt;\/link&gt;<\/mono> : Runs a script file (Load command file: <mono>$&lt;c:\\temp\\script.txt<\/mono>)<\/p>\n<p><strong>Highlight text (color)<\/strong><br \/>\n<mono>&lt;col fg=\"...\" bg=\"...\"&gt;...&lt;\/col&gt;<\/mono> : Adds foreground\/background color (Highlight warning text)<\/p>\n<p><strong>Bold \/ emphasis<\/strong><br \/>\n<mono>&lt;b&gt;...&lt;\/b&gt;<\/mono> : Makes text bold (Emphasize breakpoint hit)<\/p>\n<p><strong>Disassembly shortcut<\/strong><br \/>\n<mono>&lt;link cmd=\"u addr\"&gt;...&lt;\/link&gt;<\/mono> : Disassembles code at address (Jump to <mono>@rip<\/mono>)<\/p>\n<p><strong>Memory view<\/strong><br \/>\n<mono>&lt;link cmd=\"db addr\"&gt;...&lt;\/link&gt;<\/mono> : Dumps memory at address (Inspect buffer at <mono>@rip<\/mono>)<\/p>\n<p><strong>Register view<\/strong><br \/>\n<mono>&lt;link cmd=\"r\"&gt;...&lt;\/link&gt;<\/mono> : Displays registers (Quick state check with <mono>r<\/mono>)<\/p>\n<p><strong>Stack trace<\/strong><br \/>\n<mono>&lt;link cmd=\"k\"&gt;...&lt;\/link&gt;<\/mono> : Shows call stack (Execution context via <mono>k<\/mono>)<\/p>\n<p><strong>Conditional execution<\/strong><br \/>\n<mono>&lt;link cmd=\"j (cond) 'cmd1'; 'cmd2'\"&gt;...&lt;\/link&gt;<\/mono> : Executes logic based on condition (Check register value using <mono>j (@rax==0) '.echo zero'; '.echo nonzero'<\/mono>)<\/p>\n<p><strong>Multiple commands<\/strong><br \/>\n<mono>&lt;link cmd=\"cmd1;cmd2\"&gt;...&lt;\/link&gt;<\/mono> : Runs multiple commands sequentially (Disassemble and stack trace using <mono>u @rip;k<\/mono>)<\/p>\n<p>You can't use dynamic formatting inside attributes though.  <mono>&lt;link cmd=\"bd %d\"&gt;<\/mono> won't work, the string must be fully resolved.<\/p>\n<p>Some examples of clickable links and formatting:<\/p>\n<p>Disable a breakpoint<\/p>\n<pre><cmd>bp666 ntdll+0x1234 \".printf \/D \\\"&lt;b&gt;bp666 hit at %y&lt;\/b&gt; &lt;link cmd=\\\\\\\"bd 666\\\\\\\"&gt;[disable]&lt;\/link&gt;\\\\n\\\", $ip; gc\"<\/cmd><\/pre>\n<p>Disassemble current location<\/p>\n<pre><cmd>.printf \/D \"Execute at &lt;link cmd='u @rip'&gt;%p&lt;\/link&gt;\\n\", @rip<\/cmd><\/pre>\n<p>Dump memory at current instruction<\/p>\n<pre><cmd>.printf \/D \"Memory &lt;link cmd='db @rip'&gt;[dump @rip]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Show registers on demand<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='r'&gt;[show registers]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Show stack trace<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='k'&gt;[show stack]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Quick triage (disasm + call stack)<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='u @rip; kb'&gt;[analyze here]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Jump to function<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='uf kernel32!CreateFileW'&gt;[CreateFileW]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Conditional check (quick sanity)<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd=&quot;j (@rax==0) '.echo zero'; '.echo nonzero'&quot;&gt;[check rax]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Run helper script<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='$&lt;c:\\temp\\triage.txt'&gt;[run triage]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>List \/ clear breakpoints<\/p>\n<pre><cmd>.printf \/D \"&lt;link cmd='bl'&gt;[list bp]&lt;\/link&gt; &lt;link cmd='bc *'&gt;[clear all]&lt;\/link&gt;\\n\"<\/cmd><\/pre>\n<p>Color-highlighted hit<\/p>\n<pre><cmd>.printf \/D \"&lt;col fg='black' bg='yellow'&gt;HOT PATH&lt;\/col&gt; at %p\\n\", @rip<\/cmd><\/pre>\n<h3>Looking at memory (d)<\/h3>\n<p>The <mono>d<\/mono>-series of commands allow us to inspect the contents of memory.  We obviously need to provide the address we'd like to inspect\/display\/dump, and we can also control the output formatting.<\/p>\n<p>There are a few ways of looking at memory:<\/p>\n<ul>\n<li>Raw: We can \"dump\" the raw contents and simply format the output<\/li>\n<li>Typed: We can take the contents and apply a datastructure\/prototype over it, asigning bytes to field contents<\/li>\n<li>As a variation to the previous item, we can use it to construct linked list by reading values and considering them elements in a list<\/li>\n<\/ul>\n<h4>Raw dump + formatting<\/h4>\n<p>Dumping or displaying memory and formatting the output can be done by simply providing an output formatting indicator to the <mono>d<\/mono> command.<br \/>\nPopular d commands that use specific format types are:<\/p>\n<ul>\n<li><mono>db<\/mono> : bytes<\/li>\n<li><mono>dw<\/mono> : words<\/li>\n<li><mono>dd<\/mono> : double words<\/li>\n<li><mono>dq<\/mono> : quad words<\/li>\n<li><mono>dp<\/mono> : pointers (architecture-aware)<\/li>\n<li><mono>dps<\/mono> : pointers + symbol names<\/li>\n<li><mono>dpa<\/mono> : pointers + ascii string\/characters found at that location<\/li>\n<li><mono>da<\/mono> : ansi string (null terminated)<\/li>\n<li><mono>du<\/mono> : unicode string (double null terminated)<\/li>\n<li><mono>dc<\/mono> : double words + ansi values<\/li>\n<li><mono>dyb<\/mono> : bits<\/li>\n<\/ul>\n<p>Let's look at a few basic examples.  Open the corelanapp1.exe binary, let it run, and attach WinDBG to it.<\/p>\n<p>We're going to look at the stack in a few different ways.  The <mono>esp<\/mono> register points at the top of the stack consumption.  We'll use WinDBG to look at the contents at <mono>esp<\/mono>.<\/p>\n<p>Type the following command and see what happens:<\/p>\n<pre>\n0:003> <cmd>db esp<\/cmd>\n00e2fc98  39 de 44 77 08 5f 90 94-00 de 44 77 00 de 44 77  9.Dw._....Dw..Dw\n00e2fca8  00 00 00 00 9c fc e2 00-00 00 00 00 1c fd e2 00  ................\n00e2fcb8  b0 1d 40 77 34 44 3b e3-00 00 00 00 d4 fc e2 00  ..@w4D;.........\n00e2fcc8  49 5d 00 75 00 00 00 00-30 5d 00 75 2c fd e2 00  I].u....0].u,...\n00e2fcd8  1b d8 3e 77 00 00 00 00-e0 5e 90 94 00 00 00 00  ..>w.....^......\n00e2fce8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................\n00e2fcf8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................\n00e2fd08  00 00 00 00 00 00 00 00-00 00 00 00 e0 fc e2 00  ................\n<\/pre>\n<blockquote><p>of course, the actual addresses and contents will be different on your system. We're merely looking at how the contents are presented, not what they mean<\/p><\/blockquote>\n<p><mono>db<\/mono> prints the contents, one byte at a time.   It's worth noting that, with every <mono>d<\/mono> command, we can tell WinDBG how many lines of entities we want to see, by adding <mono>L number<\/mono> to the statement.<\/p>\n<p>In the case of <mono>db<\/mono>, the entity is a byte.<\/p>\n<pre>\n0:003> <cmd>db esp L 4<\/cmd>\n00e2fc98  39 de 44 77                                      9.Dw\n<\/pre>\n<p>Suppose we want to output the values on the stack as pointers.  We can use the dp or the dps command to do so.  In this case, the entities are pointers, so the L argument will affect how many pointers we'll see:<\/p>\n<pre>\n0:003> <cmd>dp esp L 6<\/cmd>\n00e2fc98  7744de39 94905f08 7744de00 7744de00\n00e2fca8  00000000 00e2fc9c\n\n0:003> <cmd>dps esp L 6<\/cmd>\n00e2fc98  7744de39 ntdll!DbgUiRemoteBreakin+0x39\n00e2fc9c  94905f08\n00e2fca0  7744de00 ntdll!DbgUiRemoteBreakin\n00e2fca4  7744de00 ntdll!DbgUiRemoteBreakin\n00e2fca8  00000000\n00e2fcac  00e2fc9c\n<\/pre>\n<p>I'll let you experiment with the other output types and the effect of the <mono>L<\/mono> argument. <\/p>\n<h4>Typed display\/dump<\/h4>\n<p>The <mono>dt<\/mono> command allows us to display the contents at a certain address, but organize the output based on a certain datastructure.<\/p>\n<p>The theory is quite simple:<\/p>\n<ol>\n<li>We're going to dump memory at a certain location.  Of course, you have to be sure that you're really providing the exact location of the datastructure. After all, <mono>dt<\/mono> is dumb, it won't complain if you're telling to display memory at some random location. It will happily overlay the bytes at any location with the datastructure fields.<\/li>\n<li>We also need to provide the corresponding datastructure\/type name.  That implies you know what the actual type name is.<\/li>\n<\/ol>\n<p>To be more specific, the syntax is <mono>dt type location<\/mono><\/p>\n<p>How do I know what types even exist?<\/p>\n<p>Well, datastructures\/types can be found in symbols.<br \/>\nMicrosoft provides symbols for its Operating Systems, so we can find\/use lower level OS datastructures that are documented in the symbols for ntdll, amongst others.<\/p>\n<p>Of coure, we can perform a symbol search using the <mono>x<\/mono> command.<br \/>\nThe problem is that we may not have filters available that only show types.  <mono>x<\/mono> will basically search all symbols, regardless of what they are.<br \/>\nYou can produce a list of ALL symbols (functions, global variables as well as types (structs, unions, etc) in ntdll using the following command:<\/p>\n<pre>\n<cmd>x ntdll!*<\/cmd>\n<\/pre>\n<p>You'll notice that this list is way too long to be useable. But ok, it would be a starting point.<br \/>\nMaybe you already know what kind of information you're trying to investigate and are just looking for the correct spelling.<br \/>\nIt may be related with HEAP. Or TEB, or PEB.  In any case, you may be able to come up with some keywords, and use those to perform a more specific search using <mono>dt<\/mono>:<\/p>\n<p>A few examples of useful searches include:<\/p>\n<pre><cmd>dt -v ntdll!_HEAP*\ndt -v ntdll!_TEB* \ndt -v ntdll!_PEB* <\/cmd>\n<\/pre>\n<p>Still, it would be nice to have the full list of types without having to make any assumptions in terms of keywords.<br \/>\nWe may be able to to create something that gets close to that relatively easy.<\/p>\n<p>In fact, the output of <mono>dt -v<\/mono> shows an address when it finds a function, and leaves the address field empty for datatypes.<br \/>\nFor example:<\/p>\n<pre>\n0:003> <cmd>dt -v ntdll!*HEAP<\/cmd>\nEnumerating symbols matching ntdll!*HEAP\nAddress   Size Symbol\n           258 ntdll!_HEAP\n           6c0 ntdll!_SEGMENT_HEAP\n           7e8 ntdll!_LFH_HEAP\n774286c1   0c4 ntdll!pqdownheap\n7747ab4c   0d8 ntdll!RtlpHeapTrkTrackRemoveHeap\n773d431f   240 ntdll!RtlpExtendHeap\n773b5949   000 ntdll!LdrProtectMrdataHeap (no type info)\n<\/pre>\n<p>The first 3 lines of output show types, followed by some functions.<\/p>\n<p>In other words, if you were to print out all symbol names with <mono>dt -v<\/mono>, but only keep the ones that begin with <mono>           <\/mono> (11 spaces), maybe you'll get close to the actual list of types.  <\/p>\n<p>We can take the output of a WinDBG command and pipe it into a shell command such as <mono>findstr<\/mono>:<\/p>\n<pre>\n<cmd>0:003> .shell -ci \"dt -v ntdll!*\" findstr -C:\"       \"<\/cmd>\n           010 ntdll!LIST_ENTRY64\n           008 ntdll!LIST_ENTRY32\n           004 ntdll!_ARM64_FPCR_REG\n           004 ntdll!_ARM64_FPSR_REG\n           004 ntdll!_AMD64_MXCSR_REG\n           004 ntdll!SE_WS_APPX_SIGNATURE_ORIGIN\n           004 ntdll!_PS_MITIGATION_OPTION\n           018 ntdll!_PS_MITIGATION_OPTIONS_MAP\n           018 ntdll!_PS_MITIGATION_AUDIT_OPTIONS_MAP\n           00c ntdll!_KSYSTEM_TIME\n           004 ntdll!_NT_PRODUCT_TYPE\n           004 ntdll!_ALTERNATIVE_ARCHITECTURE_TYPE\n...\n.shell: Process exited\n<\/pre>\n<p>Assuming that you have found the type you need, you can now run <mono>dt TYPE location<\/mono>.<br \/>\nFor instance, if you would like to see the contents of the PEB, you can run this:<\/p>\n<pre>\n0:003> <cmd>dt _PEB @$peb<\/cmd>\nntdll!_PEB\n   +0x000 InheritedAddressSpace : 0 ''\n   +0x001 ReadImageFileExecOptions : 0 ''\n   +0x002 BeingDebugged    : 0x1 ''\n   +0x003 BitField         : 0x4 ''\n   +0x003 ImageUsesLargePages : 0y0\n   +0x003 IsProtectedProcess : 0y0\n   +0x003 IsImageDynamicallyRelocated : 0y1\n   +0x003 SkipPatchingUser32Forwarders : 0y0\n   +0x003 IsPackagedProcess : 0y0\n   +0x003 IsAppContainer   : 0y0\n   +0x003 IsProtectedProcessLight : 0y0\n   +0x003 IsLongPathAwareProcess : 0y0\n   +0x004 Mutant           : 0xffffffff Void\n   +0x008 ImageBaseAddress : 0x00350000 Void\n   +0x00c Ldr              : 0x774b7340 _PEB_LDR_DATA\n   +0x010 ProcessParameters : 0x009f6650 _RTL_USER_PROCESS_PARAMETERS\n   +0x014 SubSystemData    : (null) \n   +0x018 ProcessHeap      : 0x009f0000 Void\n   +0x01c FastPebLock      : 0x774b7240 _RTL_CRITICAL_SECTION\n   +0x020 AtlThunkSListPtr : (null) \n   +0x024 IFEOKey          : (null) \n   ...\n<\/pre>\n<p>Or if you'd like to look at the header of a heap:<\/p>\n<pre>\n0:003> <cmd>dt _HEAP 0x009f0000<\/cmd>\nntdll!_HEAP\n   +0x000 Segment          : _HEAP_SEGMENT\n   +0x000 Entry            : _HEAP_ENTRY\n   +0x008 SegmentSignature : 0xffeeffee\n   +0x00c SegmentFlags     : 2\n   +0x010 SegmentListEntry : _LIST_ENTRY [ 0x9f00a4 - 0x9f00a4 ]\n   +0x018 Heap             : 0x009f0000 _HEAP\n   +0x01c BaseAddress      : 0x009f0000 Void\n   +0x020 NumberOfPages    : 0xff\n   +0x024 FirstEntry       : 0x009f04a8 _HEAP_ENTRY\n   +0x028 LastValidEntry   : 0x00aef000 _HEAP_ENTRY\n   +0x02c NumberOfUnCommittedPages : 0xe1\n   +0x030 NumberOfUnCommittedRanges : 1\n   +0x034 SegmentAllocatorBackTraceIndex : 0\n   +0x036 Reserved         : 0\n   +0x038 UCRSegmentList   : _LIST_ENTRY [ 0xa0dff0 - 0xa0dff0 ]\n   +0x040 Flags            : 2\n   +0x044 ForceFlags       : 0\n   +0x048 CompatibilityFlags : 0\n   +0x04c EncodeFlagMask   : 0x100000\n   +0x050 Encoding         : _HEAP_ENTRY\n   +0x058 Interceptor      : 0\n   +0x05c VirtualMemoryThreshold : 0xfe00\n   +0x060 Signature        : 0xeeffeeff\n   +0x064 SegmentReserve   : 0x100000\n   +0x068 SegmentCommit    : 0x2000\n   +0x06c DeCommitFreeBlockThreshold : 0x800\n   +0x070 DeCommitTotalFreeThreshold : 0x2000\n   +0x074 TotalFreeSize    : 0x5ba\n   +0x078 MaximumAllocationSize : 0x7ffdefff\n   ...\n<\/pre>\n<p>In the PEB example above, I have used <mono>@$peb<\/mono>.  That's a pseudo-register. I'll talk about them in a few moments.<\/p>\n<h4>Linked Lists<\/h4>\n<p>We can also use a <mono>d<\/mono> command to follow linked lists.<br \/>\nSegments in the NT heap, for instance, form a linked list<br \/>\nWell technically, it's a doubly linked list... but single linked lists and doubly linked list usually begins with a flink (forward link, i.e. the address of the next element). <\/p>\n<p>Let's say we have a 32bit NT heap at 00690000. On Windows 11, the SegmentList Head is stored at offset 0xa4 in the header of the Heap:<\/p>\n<pre>\n0:002> <cmd>dt _HEAP 00690000<\/cmd> \nntdll!_HEAP\n   +0x000 Segment          : _HEAP_SEGMENT\n   +0x000 Entry            : _HEAP_ENTRY\n   +0x008 SegmentSignature : 0xffeeffee\n   +0x00c SegmentFlags     : 2\n   +0x010 SegmentListEntry : _LIST_ENTRY [ 0xbb0010 - 0x6900a4 ]\n\t...\n   <high>+0x0a4 SegmentList      : _LIST_ENTRY [ 0x690010 - 0x3a50010 ]<\/high>\n\t...\n<\/pre>\n<p>The primary command to display linked lists is <mono>dl<\/mono><br \/>\nIt walks a linked list and expects each node to being with a  flink.<br \/>\nYou'd simply run it against an address.<br \/>\nFor example, with the ListHead at <mono>006900a4<\/mono> in the example above, we can run<\/p>\n<pre>0:002> <cmd>dl 006900a4<\/cmd>\n006900a4  00690010 03a50010 00000000 00000000\n00690010  00bb0010 006900a4 00690000 00690000\n00bb0010  00cb0010 00690010 00690000 00bb0000\n00cb0010  00eb0010 00bb0010 00690000 00cb0000\n00eb0010  012b0010 00cb0010 00690000 00eb0000\n012b0010  01ab0010 00eb0010 00690000 012b0000\n01ab0010  02a80010 012b0010 00690000 01ab0000\n02a80010  03a50010 01ab0010 00690000 02a80000\n03a50010  006900a4 02a80010 00690000 03a50000\n<\/pre>\n<p>As you can see, the <mono>dl<\/mono> command prints out the entire linked list.<br \/>\nIt starts at 006900a4 and finds 00690010.<br \/>\nAt 00690010, it finds the address of the next one in the list: 00bb0010.<br \/>\nAnd so on. <\/p>\n<pre>\n006900a4 -> 00690010 -> 00bb0010 -> 00cb0010 -> 00eb0010 -> 012b0010 -> 01ab0010 -> 02a80010 -> 03a50010 -> 006900a4\n<\/pre>\n<p>You can basically see the entire list, until it eventually finds an element in the list that has 006900a4 (the address of the ListHead), which indicates that this is the last element in the list.<\/p>\n<p>You can now us a typed dump <mono>dt<\/mono> on each element, and it will print out the information in nicely annotated format:<\/p>\n<pre>0:002> <cmd>dt _LIST_ENTRY 006900a4<\/cmd>\nntdll!_LIST_ENTRY\n [ 0x690010 - 0x3a50010 ]\n   +0x000 Flink            : 0x00690010 _LIST_ENTRY [ 0xbb0010 - 0x6900a4 ]\n   +0x004 Blink            : 0x03a50010 _LIST_ENTRY [ 0x6900a4 - 0x2a80010 ]\n0:002> <cmd>dt _LIST_ENTRY 00690010<\/cmd>\nntdll!_LIST_ENTRY\n [ 0xbb0010 - 0x6900a4 ]\n   +0x000 Flink            : 0x00bb0010 _LIST_ENTRY [ 0xcb0010 - 0x690010 ]\n   +0x004 Blink            : 0x006900a4 _LIST_ENTRY [ 0x690010 - 0x3a50010 ]\n0:002> <cmd>dt _LIST_ENTRY 00bb0010<\/cmd>\nntdll!_LIST_ENTRY\n [ 0xcb0010 - 0x690010 ]\n   +0x000 Flink            : 0x00cb0010 _LIST_ENTRY [ 0xeb0010 - 0xbb0010 ]\n   +0x004 Blink            : 0x00690010 _LIST_ENTRY [ 0xbb0010 - 0x6900a4 ]<\/pre>\n<p>and so on.<\/p>\n<p>Perfectly scriptable:<\/p>\n<pre><cmd> r $t0 = poi(0x006900a4)\n.for ( ; @$t0 != 0x006900a4 ; r $t0 = poi(@$t0) ) { dt _LIST_ENTRY @$t0 }<\/cmd><\/pre>\n<p>You could even go as far as getting the address of the default process heap itself from the peb and make this little script that gets the segments associated with the default heap:<\/p>\n<p>x86-only:<\/p>\n<pre><cmd>r $t0 = poi(@$peb+0x18)\nr $t1 = $t0+0xa4\nr $t2 = poi($t1)\n.for ( ; @$t2 != @$t1 ; r $t2 = poi(@$t2) ) { .printf \"entry=%p\\n\", @$t2; dt _LIST_ENTRY @$t2 }<\/cmd><\/pre>\n<p>or, more generic, for both x86 and x64 (Windows 11 - and providing that the default process heap is NT and not Segment):<\/p>\n<pre><cmd>.printf \"ptrsize=%d\\n\", @$ptrsize;\n.if (@$ptrsize == 4) { r $t9 = 0x18; r $t8 = 0xA4 } .else { r $t9 = 0x30; r $t8 = 0x120 };\nr $t0 = poi(@$peb + @$t9);\nr $t1 = @$t0 + @$t8;\n.printf \"PEB=%p  ProcessHeap=%p  SegmentListHead=%p\\n\", @$peb, @$t0, @$t1;\ndt _LIST_ENTRY @$t1;\nr $t2 = poi(@$t1);\n.for ( ; @$t2 != @$t1 ; r $t2 = poi(@$t2) ) { .printf \"LIST_ENTRY=%p  Flink=%p  Blink=%p  Parent?=%p\\n\", @$t2, poi(@$t2), poi(@$t2+@$ptrsize), @$t2-0x10; }<\/cmd><\/pre>\n<p>output:<\/p>\n<pre>\nLIST_ENTRY=00690010  Flink=00bb0010  Blink=006900a4  Parent?=00690000\nLIST_ENTRY=00bb0010  Flink=00cb0010  Blink=00690010  Parent?=00bb0000\nLIST_ENTRY=00cb0010  Flink=00eb0010  Blink=00bb0010  Parent?=00cb0000\nLIST_ENTRY=00eb0010  Flink=012b0010  Blink=00cb0010  Parent?=00eb0000\nLIST_ENTRY=012b0010  Flink=01ab0010  Blink=00eb0010  Parent?=012b0000\nLIST_ENTRY=01ab0010  Flink=02a80010  Blink=012b0010  Parent?=01ab0000\nLIST_ENTRY=02a80010  Flink=03a50010  Blink=01ab0010  Parent?=02a80000\nLIST_ENTRY=03a50010  Flink=006900a4  Blink=02a80010  Parent?=03a50000\n<\/pre>\n<p>If you wanted to dump the list for a heap other than the default, you can simply set <mono>$t0<\/mono> to the address of that heap (instead of getting a value from the peb)<br \/>\nFor example:<\/p>\n<pre>0:010> <cmd>!heap<\/cmd>\n        Heap Address      NT\/Segment Heap\n\n    0000017463000000         Segment Heap\n    <high>0000017462270000<\/high>              NT Heap\n    0000017463400000         Segment Heap\n    00000174623c0000              NT Heap\n    0000017463600000         Segment Heap<\/pre>\n<pre><cmd>.printf \"ptrsize=%d\\n\", @$ptrsize;\n.if (@$ptrsize == 4) { r $t9 = 0x18; r $t8 = 0xA4 } .else { r $t9 = 0x30; r $t8 = 0x120 };\nr $t0 = <high>0000017462270000<\/high>;\nr $t1 = @$t0 + @$t8;\n.printf \"PEB=%p  Heap=%p  SegmentListHead=%p\\n\", @$peb, @$t0, @$t1;\ndt _LIST_ENTRY @$t1;\nr $t2 = poi(@$t1);\n.for ( ; @$t2 != @$t1 ; r $t2 = poi(@$t2) ) { .printf \"LIST_ENTRY=%p  Flink=%p  Blink=%p  Parent?=%p\\n\", @$t2, poi(@$t2), poi(@$t2+@$ptrsize), @$t2-0x10; }<\/cmd><\/pre>\n<p>Just one:<\/p>\n<pre>\nLIST_ENTRY=0000017462270018  Flink=0000017462270120  Blink=0000017462270120  Parent?=0000017462270008\n<\/pre>\n<h3>Searching memory (s)<\/h3>\n<p>When a debugger is attached to a process, we have the ability to query the entire process space.  Debugger have search functionality that, unfortunately, is usually rather limited in terms of meaningful scope.  You can most likely define the start and end position for the search, and identify what you'd like to find.  But searches that require more intelligence will require some sort of scripting.<br \/>\nThat's why I very rarely use the built-in search.<br \/>\nNevertheless, the <mono>s<\/mono> command allows you to search the virtual memory space from within WinDBG.<br \/>\nThe basic syntax is one of the following variations:<\/p>\n<pre>\ns type startaddress L?distance searchpattern\ns type startaddress endaddres searchpattern\n<\/pre>\n<p>Common types are:<\/p>\n<ul>\n<li><mono>-a<\/mono> : search for an ascii string (raw sequence)<\/li>\n<li><mono>-sa<\/mono> : search for an ascii string (null byte terminated)<\/li>\n<li><mono>-u<\/mono> : search for a unicode string (raw sequence)<\/li>\n<li><mono>-su<\/mono> : search for a unicode string (null bytes terminated)<\/li>\n<li><mono>-b<\/mono> : search for a byte sequence<\/li>\n<li><mono>-d<\/mono> : search for a dword value<\/li>\n<li><mono>-q<\/mono> : search for a qword value<\/li>\n<li><mono>-v<\/mono> : search for C++ vftables<\/li>\n<\/ul>\n<p>A few examples:<\/p>\n<pre>\n0:003> <cmd>s -a 0x00000000 L?0x7fffffff \"AAAAAAAA\"<\/cmd>\n77398581  41 41 41 41 41 41 41 41-42 42 42 42 42 42 42 42  AAAAAAAABBBBBBBB\n\n0:003> <cmd>s -d 0 0x7FFFFFF 0x008CFF40<\/cmd>\n003cfed4  008cff40 008c0000 003cff24 00ab17ff  @.......$.<.....\n\n0:003> <cmd>s -b 0 0x7FFFFFF CC CC CC CC<\/cmd>\n00ab100a  cc cc cc cc cc cc 55 8b-ec 8b 45 14 50 8b 4d 10  ......U...E.P.M.\n00ab100b  cc cc cc cc cc 55 8b ec-8b 45 14 50 8b 4d 10 51  .....U...E.P.M.Q\n00ab100c  cc cc cc cc 55 8b ec 8b-45 14 50 8b 4d 10 51 8b  ....U...E.P.M.Q.\n00ab103a  cc cc cc cc cc cc 55 8b-ec 83 ec 08 8d 45 0c 89  ......U......E..\n00ab103b  cc cc cc cc cc 55 8b ec-83 ec 08 8d 45 0c 89 45  .....U......E..E\n00ab103c  cc cc cc cc 55 8b ec 83-ec 08 8d 45 0c 89 45 fc  ....U......E..E.\n00ab107b  cc cc cc cc cc 55 8b ec-83 ec 10 8b 45 08 89 45  .....U......E..E\n00ab107c  cc cc cc cc 55 8b ec 83-ec 10 8b 45 08 89 45 f8  ....U......E..E.\n<\/pre>\n<p>As explained, when performing searches, I tend to use a script that has a bit more filters and search capabilities.<br \/>\nFor example, <mono>mona.py<\/mono><\/p>\n<h3>Editing memory<\/h3>\n<p>Debugger provide the ability to edit memory and registers.  I have a bit of a love-hate relationship with this, because the fact that a Debugger can change memory doesn't mean your exploit will be able to do so.  Debuggers will happily overrule access controls, so please be careful when you make changes in memory yourself.<\/p>\n<h4>Editing memory contents<\/h4>\n<p>There are 2 main ways to edit memory. You can use the command line, or you can edit memory directly in a memory view window.<\/p>\n<p>From the WinDBG Command Line, you can use the <mono>e<\/mono> commands. You'll have to specify the format\/size of the change you're going to make and the location, and of course you'll need to provide the new value.<\/p>\n<p>Let's look at a few examples.  I'll use <mono>0x12345678<\/mono> (x86) or <mono>0xda`12345678<\/mono> (x64) as the placeholder for the location where you want to make a change in memory. <\/p>\n<ul>\n<li><mono>eb location newvalue<\/mono> : change one byte at location: <mono>eb 0x12345678 41<\/mono><\/li>\n<li><mono>ed location newvalue<\/mono> : change a dword at location: <mono>eb 0x12345678 41424344<\/mono><\/li>\n<li><mono>eq location newvalue<\/mono> : change a qword at location: <mono>eb 0x12345678 4142434445464748<\/mono><\/li>\n<li><mono>ep location newvalue<\/mono> : change a pointer (architecture-aware) at location: <mono>ep 0x12345678 41424344<\/mono> (x86) or <mono>ep 0xda`12345678 4142434445464748<\/mono> (x64)<\/li>\n<li><mono>ea location newvalue<\/mono> : write an ansi string to location <mono>ea 0x12345678 \"ABCDEFGH\"<\/mono><\/li>\n<li><mono>eu location newvalue<\/mono> : write a unicode string to location <mono>eu 0x12345678 \"ABCD\"<\/mono> (this will write <mono>0041004200430044<\/mono>)<\/li>\n<li><mono>eza location newvalue<\/mono> : write a null-terminated ansi string to location <mono>ep 0x12345678 \"ABCD\"<\/mono> (a null byte will be appended automatically)<\/li>\n<li><mono>ezu location newvalue<\/mono> : write a null-terminated unicode string to location <mono>ep 0x12345678 \"ABC\"<\/mono> (unicode, and a double null will be appended automatically)<\/li>\n<\/ul>\n<p>Of course, you can also open a Memory view, go to the location you're trying to edit, double-click in the memory view at that location and just start typing the new value.<br \/>\nIf you notice that this doesn't work in WinDBG Classic, you may want to check if 'QuickEdit Mode' is enabled.<br \/>\nClick 'View', choose 'Options' and verify that \"QuickEdit Mode\" is on.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" style=\"display:block; margin-left:auto; margin-right:auto;\" src=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/windbg-quickedit.png\" alt=\"windbg-quickedit\" title=\"windbg-quickedit.png\" border=\"0\" width=\"430\" height=\"339\" \/><\/p>\n<h4>Fill memory<\/h4>\n<p>A variation on the concept of 'editing' memory, is 'filling' memory.  The mechanism is based on an action that takes 3 variables: a location, a length and a pattern to write.<br \/>\nBasic syntax:<\/p>\n<p><mono>f location L?length pattern<\/mono><\/p>\n<p>The pattern can be one byte or multiple bytes (space separated). In the latter, the pattern will repeat until the total length becomes the length specified after L?.<\/p>\n<p>Let's look at a few examples.  I'll just fill memory at esp and then show the contents after each fill:<\/p>\n<pre>\n0:000> <cmd>f esp L?4 41<\/cmd>\nFilled 0x4 bytes\n0:000> <cmd>dp esp L 8<\/cmd>\n00cff444  <high>41414141<\/high> 00b0a000 00fa6650 00000000\n00cff454  00401db0 00cff444 fffffffe 00cff668\n\n0:000> <cmd>f esp L?4 41 42<\/cmd>\nFilled 0x4 bytes\n0:000> <cmd>dp esp L 8<\/cmd>\n00cff444  <high>42414241<\/high> 00b0a000 00fa6650 00000000\n00cff454  00401db0 00cff444 fffffffe 00cff668\n<\/pre>\n<h4>Editing registers<\/h4>\n<p>The <mono>r<\/mono> command will show the current registers (or at least a subset of all available registers).  <\/p>\n<pre>\n0:000> <cmd>r<\/cmd>\neax=00000000 ebx=00000000 ecx=8e7f0000 edx=00000000 esi=011f6650 edi=00e96000\neip=77498218 esp=010ff3bc ebp=010ff3e8 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!LdrpDoDebuggerBreak+0x2b:\n77498218 cc              int     3\n<\/pre>\n<p><mono>r<\/mono> followed by a register will show the contents of that register <\/p>\n<pre>\n0:000> <cmd>r @ecx<\/cmd>\necx=8e7f0000\n0:000> <cmd>r @dr0<\/cmd>\ndr0=00000000\n<\/pre>\n<p>You can change a register by assigning a value to it:<\/p>\n<pre>\n0:000> <cmd>r @ecx=41414141<\/cmd>\n0:000> <cmd>r<\/cmd>\neax=00000000 ebx=00000000 ecx=<high>41414141<\/high> edx=00000000 esi=011f6650 edi=00e96000\neip=77498218 esp=010ff3bc ebp=010ff3e8 iopl=0         nv up ei pl zr na pe nc\ncs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246\nntdll!LdrpDoDebuggerBreak+0x2b:\n77498218 cc              int     3\n<\/pre>\n<h3>mona.py<\/h3>\n<h4>Installing mona.py in WinDBG Classic<\/h4>\n<p>Although <mono>mona.py<\/mono> was originally written for Immunity Debugger and heavily relies on Immunity Debugger's API, I ended up porting that API to a WinDBG\/PyKD-compatible library called <mono>windbglib<\/mono>. The combination PyKD, Python2 and windbglib allows us to run <mono>mona.py<\/mono> inside WinDBG and WinDBGX.<\/p>\n<p>If you have used the <mono>CorelanWin11VMInstall.ps1<\/mono> script to install the debuggers, then <mono>mona.py<\/mono> and <mono>windbglib<\/mono> are already installed.<br \/>\nIf not, please check out the installation instructions here: <a href=\"https:\/\/github.com\/corelan\/windbglib#windows-7-and-up-64bit-os-32bit-windbg\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/corelan\/windbglib#windows-7-and-up-64bit-os-32bit-windbg<\/a><br \/>\nMake sure the default Python version on your system is Python 2.7 (v14 or higher), 32bit.  If not, pykd\/windbglib\/mona will fail to work. <\/p>\n<h4>Running mona.py - WinDBG Classic<\/h4>\n<p>In WinDBG Classic, you have to load the <mono>pykd.pyd<\/mono> extension first, and then you can invoke <mono>mona.py<\/mono> through PyKD:<\/p>\n<pre>\n0:003> <cmd>.load pykd.pyd<\/cmd>\n0:003> <cmd>!py mona<\/cmd>\nHold on...\n[+] Command used:\n!py mona.py\n     'mona' - Exploit Development Swiss Army Knife - WinDBG (32bit)\n     Plugin version : 2.0 r643\n     Python version : 2.7.18 (v2.7.18:8d21aa21f2, Apr 20 2020, 13:19:08) [MSC v.1500 32 bit (Intel)]\n     PyKD version 0.2.0.29\n     Written by Corelan - https:\/\/www.corelan.be\n     Project page : https:\/\/github.com\/corelan\/mona\n     ...\n<\/pre>\n<p>It's always a good idea to check for updates on a regular basis. If an update is available, <mono>mona.py<\/mono> will update in-place.<\/p>\n<pre>\n0:003> <cmd>!py mona up<\/cmd>\nHold on...\n[+] Command used:\n!py mona.py up\n[+] Version compare :\n    Current Version : '2.0', Current Revision : 643\n    Latest Version : '2.0', Latest Revision : 643\n[+] You are running the latest version\n[+] Locating windbglib path\n[+] Checking if C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\windbglib.py needs an update...\n[+] Version compare :\n    Current Version : '1.0', Current Revision : 151\n    Latest Version : '1.0', Latest Revision : 151\n[+] You are running the latest version\n\n[+] This mona.py action took 0:00:03.198000\n<\/pre>\n<h3>Log files<\/h3>\n<p>We can instruct WinDBG to write all output that you see in the Command view (including the commands that you've typed) to a log file.<br \/>\nIf your WinDBG session is already open, you can open a new logfile with the <mono>.logopen<\/mono> command:<\/p>\n<pre>0:013> <cmd>.logopen c:\\logs\\windbg.log<\/cmd>\nOpened log file 'c:\\logs\\windbg.log'\n<\/pre>\n<p>You also have the option to launch windbg with the <mono>-logo<\/mono> flag, followed by the full path to the log file that you want to create\/use for the debugging session.<br \/>\nIf you open a different logfile in the same session, it will close the open log file, if any.<br \/>\nWhen specifying the logfile path, make sure the path exists and is accessible:<\/p>\n<pre>0:013> <cmd>.logopen c:\\nonexisting\\blah.log<\/cmd>\nLog file could not be opened\n<\/pre>\n<p>In any case, don't forget to close the logfile again with <mono>.logclose<\/mono> at the end of your debugging session.<br \/>\nIf you close WinDBG, and if it stil had a log file open, it might clear the logfile at the start of the next WinDBG session.<\/p>\n<h3>Pseudo-registers<\/h3>\n<p>The last topic I would like to discuss in this post, is pseudo-registers.   They are like variables that you can use in your debugging session.<br \/>\nWinDBG has 2 types of pseudo-registers: read-only (with specific variable names) and read-write (with somewhat cryptic names).<\/p>\n<p>The read-only pseudo-registers include:<\/p>\n<ul>\n<li><mono>$ea<\/mono> : Effective address of last instruction that was executed<\/li>\n<li><mono>$ea2<\/mono> : Second effective address of last instruction that was executed<\/li>\n<li><mono>$exp<\/mono> : Last expression that was evaluated<\/li>\n<li><mono>$ra<\/mono> : Return address currently on the stack<\/li>\n<li><mono>$ip<\/mono> : EIP on x86, RIP on x64<\/li>\n<li><mono>$sp<\/mono> : ESP on x86, RSP on x64<\/li>\n<li><mono>$bp<\/mono> : EBP on x86, RBP on x64<\/li>\n<li><mono>$eventip<\/mono> : Instruction pointer at the moment the debug event occurred (may differ from current $ip)<\/li>\n<li><mono>$previp<\/mono> : EIP at the time of the previous event (debugger break counts as an event)<\/li>\n<li><mono>$retreg<\/mono> : Primary return value register (eax on x86)<\/li>\n<li><mono>$retval<\/mono> : Function return value<\/li>\n<li><mono>$p<\/mono> : Result of the last expression used by certain commands<\/li>\n<li><mono>$bp(id)<\/mono> : Address of breakpoint identified by its ID (no space between $bp and (id))<\/li>\n<li><mono>$dbgtime<\/mono> : Current time<\/li>\n<li><mono>$exentry<\/mono> : AddressOfEntrypoint of first executable in process (bp $exentry)<\/li>\n<li><mono>$peb<\/mono> : Address of the PEB<\/li>\n<li><mono>$teb<\/mono> : Address of the TEB<\/li>\n<li><mono>$proc<\/mono> : Current process<\/li>\n<li><mono>$thread<\/mono> : Current thread<\/li>\n<li><mono>$exceptioncode<\/mono> : Last exception code<\/li>\n<li><mono>$exceptionaddress<\/mono> : Address where exception occurrred<\/li>\n<li><mono>$ptrsize<\/mono> : A value indicating the architecture. 4 = 32bit, 8 = 64bit<\/li>\n<\/ul>\n<p>You can use those variable names in all kind of commands (breakpoints, .printf statements, etc).<br \/>\nJust keep in mind that they are read-only, WinDBG makes sure they have the correct content.<br \/>\nThe notations above are pseudo-register names.  It is recomended, when you want to access their value, to prefix the name with <mono>@<\/mono>.<br \/>\nWithout the <mono>@<\/mono>, WinDBG may interpret something like <mono>$peb<\/mono> as a symbol name or a literal. The <mono>@<\/mono> fixes that.  <\/p>\n<p>In other words, <mono>$peb<\/mono> is the name of the variable, and <mono>@$peb<\/mono> is how you access its value.<\/p>\n<p>For example:<\/p>\n<pre>\n<cmd>dt _PEB @$peb<\/cmd>\n<\/pre>\n<p>Good.  <\/p>\n<p>WinDBG has 20 read-write user-definable pseudo-registers. You don't get to choose the variable names, they are predefined as <mono>$t0, $t1, $t2<\/mono>, ... up to <mono>$t19<\/mono><\/p>\n<p>On x86, you can store a 32bit value in the register.  On x64, it's a 64bit value.<br \/>\nYou can use a register to hold on to a value, make calculations, keep track of things.<\/p>\n<p>Let's consider the following example:<\/p>\n<p>Let's say you want to keep track of how many times <mono>ntdll!RtlAllocateHeap<\/mono> is being used\/called.<br \/>\nWe'd put a breakpoint at the start of that function, and we can use one of the user-defined pseudo-registers to serve as a counter.<br \/>\nThe breakpoint would then simply increment the value of the counter, and print the current value to the screen. <\/p>\n<pre>\n0:000> <cmd>r @$t0=0<\/cmd>\n0:000> <cmd>bp ntdll!RtlAllocateHeap \"r @$t0=@$t0+1; .printf \\\"RtlAllocateHeap called %d times so far\\\\n\\\", @$t0;g\"<\/cmd>\n0:000> <cmd>g<\/cmd>\nRtlAllocateHeap called 1 times so far\nRtlAllocateHeap called 2 times so far\nRtlAllocateHeap called 3 times so far\nRtlAllocateHeap called 4 times so far\nRtlAllocateHeap called 5 times so far\nRtlAllocateHeap called 6 times so far\nRtlAllocateHeap called 7 times so far\n<\/pre>\n<p>As you can see in the above example, I have also prefixed the <mono>$t0<\/mono> pseudo-register with the <mono>@<\/mono>.  It would work without, but it might\/would be slower, almost as if WinDBG is struggling a bit trying to determine what it actually is.  With the <mono>@<\/mono>, it becomes crystal clear that we're referring to the value of the pseudo-register.<\/p>\n<h2>Common WinDBG Commands<\/h2>\n<p>The following table shows a quick overview of the most important WinDBG commands:<\/p>\n<table class=\"corelan-table\">\n<thead>\n<tr>\n<th>Command<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">Execution control<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">g<\/span><\/td>\n<td>Continue running the process (go) - shortkey <mono>F5<\/mono><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">gN<\/span><\/td>\n<td>Invoke exception handler<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">t<\/span> \/ <span class=\"corelan-mono\">F11<\/span><\/td>\n<td>Step into (single trace)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">p<\/span> \/ <span class=\"corelan-mono\">F10<\/span><\/td>\n<td>Step over<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">tt<\/span><\/td>\n<td>Trace (single step) until next RETN<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">pt<\/span><\/td>\n<td>Step over until next RETN<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">tc<\/span><\/td>\n<td>Trace until next CALL<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">pc<\/span><\/td>\n<td>Step until next CALL<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">wt<\/span><\/td>\n<td>Trace & watch - execute & show all CALLs<\/td>\n<\/tr>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">Breakpoints<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">bp<\/span><\/td>\n<td>Set a software breakpoint  <span class=\"corelan-mono\">bp[id] address [passcount] \"windbgcmd;windbgcmd;g\"<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">ba<\/span><\/td>\n<td>Set a hardware breakpoint  <span class=\"corelan-mono\">ba e1 address<\/span> \/ <span class=\"corelan-mono\">ba r2 address<\/span> \/ <span class=\"corelan-mono\">ba w4 address<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">bm<\/span><\/td>\n<td>Mass software breakpoints based on wildcard symbols<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">bl<\/span><\/td>\n<td>Show all breakpoints<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">bc<\/span><\/td>\n<td>Clear a breakpoint<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">be<\/span><\/td>\n<td>Enable breakpoint<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">bd<\/span><\/td>\n<td>Disable breakpoint<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">br<\/span><\/td>\n<td>Renumber a breakpoint<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">j<\/span><\/td>\n<td>Condition method #1 <span class=\"corelan-mono\">bp address \"j (Condition) 'OptionalCommands; gc'; 'gc';\"<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">.if<\/span><\/td>\n<td>Condition method #2 <span class=\"corelan-mono\">bp address \".if (Condition) {OptionalCommands; gc} .else {gc}\"<\/span><\/td>\n<\/tr>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">Unassemble<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">u<\/span><\/td>\n<td>Unassemble forward<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">ub<\/span><\/td>\n<td>Unassemble backward<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">uf<\/span><\/td>\n<td>Unassemble function<\/td>\n<\/tr>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">Showing information<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">r<\/span><\/td>\n<td>Show registers, or set a register to a value<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">d<\/span><\/td>\n<td>Display memory<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">db<\/span><\/td>\n<td>Display memory (bytes)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">dp<\/span><\/td>\n<td>Display memory (pointers)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">dps<\/span><\/td>\n<td>Display memory (pointers + symbol name)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">dpa<\/span><\/td>\n<td>Display memory (pointers + ascii characters at that location)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">dt<\/span><\/td>\n<td>Typed display<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">dl<\/span><\/td>\n<td>Display linked list<\/td>\n<\/tr>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">.printf format specifiers<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%p<\/span><\/td>\n<td>Pointer<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%x<\/span> \/ <span class=\"corelan-mono\">%X<\/span> <\/td>\n<td>hex value (without leading null padding) - lowercase \/ uppercase<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%I64x<\/span><\/td>\n<td>64bit hex value (without leading null padding) - avoids truncation<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%y<\/span><\/td>\n<td>Pointer + symbol name<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%ly<\/span><\/td>\n<td>Pointer + symbol name (safe for 64bit)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%d<\/span><\/td>\n<td>Decimal<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%I64d<\/span><\/td>\n<td>64bit Decimal<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%c<\/span><\/td>\n<td>Character<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%N<\/span><\/td>\n<td>Number with commas<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%f<\/span><\/td>\n<td>Float<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%e<\/span><\/td>\n<td>Scientific<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%g<\/span><\/td>\n<td>Compact<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%ma<\/span><\/td>\n<td>ASCII C string (null terminated)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%mu<\/span><\/td>\n<td>WIDE C string (null terminated)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%msa<\/span><\/td>\n<td>ANSI_STRING*<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">%msu<\/span><\/td>\n<td>UNICODE_STRING**<\/td>\n<\/tr>\n<tr class=\"corelan-subheader\">\n<td colspan=\"2\">Various<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">lm<\/span><\/td>\n<td>Show loaded modules (and symbol file, if present\/downloaded)<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">?<\/span><\/td>\n<td>MASM Expression evaluator <span class=\"corelan-mono\">?esp<\/span> \/ <span class=\"corelan-mono\">?module<\/span> \/ <span class=\"corelan-mono\">?address-module<\/span> \/ <span class=\"corelan-mono\">?@eax<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">x<\/span><\/td>\n<td>Search (examine) symbols <span class=\"corelan-mono\">x module!*wildcard*<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">;<\/span><\/td>\n<td>Command separator<\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">poi<\/span><\/td>\n<td>Dereference <span class=\"corelan-mono\">?poi(@esp)<\/span><\/td>\n<\/tr>\n<tr>\n<td><span class=\"corelan-mono\">Alt+Del<\/span><\/td>\n<td>Interrupt WinDBG is it's busy<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2>Resources<\/h2>\n<p>Here are some other resources on WinDBG that are worth checking out:<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/windows-hardware\/drivers\/debuggercmds\/windbg-overview\" target=\"_blank\" rel=\"noopener\">https:\/\/learn.microsoft.com\/en-us\/windows-hardware\/drivers\/debuggercmds\/windbg-overview<\/a><\/li>\n<li><a href=\"https:\/\/codemachine.com\/articles\/windbg_quickstart.html\" target=\"_blank\" rel=\"noopener\">https:\/\/codemachine.com\/articles\/windbg_quickstart.html<\/a><\/li>\n<\/ul>\n<p>If you have other quality resources on WinDBG, feel free to let me know, I'll gladly add them to the list here.<\/p>\n<h2>Key Takeaways & learnings<\/h2>\n<ul>\n<li>Debugging fluency is essential. practice attaching, stepping, inspecting, and taking control to lean on the tool instead of getting crushed by the tool.<\/li>\n<p><\/p>\n<li>Prefer attaching over launching to avoid NtGlobalFlag changes (heap validation, layout shifts) that break exploits or trigger anti-debug.<\/li>\n<p><\/p>\n<li>Symbols provide insigts, but are a luxury. Configure the Microsoft symbol server early for readable function\/struct names instead of raw addresses.<\/li>\n<p><\/p>\n<li>ASLR makes absolute addresses unreliable \u2192 always use symbolic breakpoints (bp ntdll!RtlAllocateHeap) or module-relative (ntdll+0x3f8a0) for resilience.<\/li>\n<p><\/p>\n<li>Breakpoints are versatile: use them for stopping or silent logging (commands + gc to continue).<\/li>\n<p><\/p>\n<li>Pseudo-registers are your variables: @ prefix for system values (e.g., @$peb), $t0\u2013$t19 for counters\/state in scripts\/conditional bps.<\/li>\n<p><\/p>\n<li>WinDbg's rough GUI is worth customizing (workspaces\/layouts) \u2014 fight the interface once, then focus on the target.<\/li>\n<p><\/p>\n<li>Extend built-in limits with tools like mona.py when needed (better search, pattern creation).<\/li>\n<p>\n<\/ul>\n<h2>Outro<\/h2>\n<p>At its core, debugging is about building understanding.<\/p>\n<p>Every time you step through code, inspect memory, or analyze a crash, you\u2019re training your intuition about how software behaves under the hood. Over time, that intuition becomes one of your most valuable assets.<\/p>\n<p>WinDbg might feel rough at first, but stick with it. It gives you the tools to build that understanding, even if it takes a bit of effort to get comfortable with it. The more fluent you become, the closer you get to thinking in instructions instead of assumptions. And that\u2019s where things start to click.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Is AI an evolution or a revolution? Or both? Those are interesting questions. Speaking of AI - even ChatGPT and Grok agree: A debugger is the one of the most (if not the most) important tool for exploit developers, malware analysts, and reverse engineers. Exploit development, malware analysis and reverse engineering all ultimately share &hellip; <a href=\"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> \"Debugging - WinDBG &#038; WinDBGX Fundamentals\"<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":17578,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Corelan is BACK! \ud83d\ude80\n\nAfter 7 long years without new posts, we\u2019re finally publishing fresh content again \u2014 and we\u2019re kicking things off strong.\n\nToday: Debugging - WinDBG & WinDBGX Fundamentals (March 23, 2026)\n\nA practical, hands-on tutorial covering WinDbg Classic + the modern WinDbgX: symbol setup, launching vs attaching, core commands, breakpoints (including conditional\/logging with j + gc), memory inspection, pseudo-registers, basic scripting, heap\/PEB traversal examples, and ASLR-resilient techniques. \n\nPerfect for exploit developers, malware analysts, and reverse engineers who want runtime control and intuition.\n\nFull post here: https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/\n\nExcited to be back in the game \u2014 feedback, questions, or your favorite WinDbg tricks? \n\nDrop them below!\n\nFollow to be among the first:\nhttps:\/\/www.corelan.be\/index.php\/contact ","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[3708,244,2561],"tags":[3773,3758,3733,2576,2327,2124,285],"class_list":["post-14036","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-debugging","category-exploit-writing-tutorials","category-malware-and-reversing","tag-tutorial","tag-windbgx","tag-exploit-development-tutorial","tag-debugging","tag-breakpoint","tag-debugger","tag-windbg"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Debugging - WinDBG &amp; WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research<\/title>\n<meta name=\"description\" content=\"Learn how to leverage WinDBG &amp; WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Debugging - WinDBG &amp; WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research\" \/>\n<meta property=\"og:description\" content=\"Learn how to leverage WinDBG &amp; WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/\" \/>\n<meta property=\"og:site_name\" content=\"Corelan | Exploit Development &amp; Vulnerability Research\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/corelanconsulting\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-23T15:10:14+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-11T01:55:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png\" \/>\n\t<meta property=\"og:image:width\" content=\"256\" \/>\n\t<meta property=\"og:image:height\" content=\"256\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"corelanc0d3r\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@corelanc0d3r\" \/>\n<meta name=\"twitter:site\" content=\"@corelanc0d3r\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"TechArticle\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/\"},\"author\":{\"name\":\"corelanc0d3r\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#\\\/schema\\\/person\\\/3be5542b9b0a0787893db83a5ad68e8f\"},\"headline\":\"Debugging - WinDBG &#038; WinDBGX Fundamentals\",\"datePublished\":\"2026-03-23T15:10:14+00:00\",\"dateModified\":\"2026-04-11T01:55:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/\"},\"wordCount\":15200,\"commentCount\":2,\"publisher\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/WinDbg_256.png\",\"keywords\":[\"tutorial\",\"windbgx\",\"exploit development tutorial\",\"debugging\",\"breakpoint\",\"debugger\",\"windbg\"],\"articleSection\":[\"Debugging\",\"Exploit Writing Tutorials\",\"Malware and Reversing\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/\",\"url\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/\",\"name\":\"Debugging - WinDBG & WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/WinDbg_256.png\",\"datePublished\":\"2026-03-23T15:10:14+00:00\",\"dateModified\":\"2026-04-11T01:55:39+00:00\",\"description\":\"Learn how to leverage WinDBG & WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/WinDbg_256.png\",\"contentUrl\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/WinDbg_256.png\",\"width\":256,\"height\":256},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/2026\\\/03\\\/23\\\/debugging-windbg-windbgx-fundamentals\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.corelan.be\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Debugging - WinDBG &#038; WinDBGX Fundamentals\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#website\",\"url\":\"https:\\\/\\\/www.corelan.be\\\/\",\"name\":\"Corelan CyberSecurity Research\",\"description\":\"Corelan publishes in-depth tutorials on exploit development, Windows exploitation, vulnerability research, heap internals, reverse engineering and security tooling used by professionals worldwide.\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.corelan.be\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#organization\",\"name\":\"Corelan CyberSecurity Research\",\"url\":\"https:\\\/\\\/www.corelan.be\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/corelanlogo2_small-20.png\",\"contentUrl\":\"https:\\\/\\\/www.corelan.be\\\/wp-content\\\/uploads\\\/2026\\\/03\\\/corelanlogo2_small-20.png\",\"width\":200,\"height\":200,\"caption\":\"Corelan CyberSecurity Research\"},\"image\":{\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/corelanconsulting\",\"https:\\\/\\\/x.com\\\/corelanc0d3r\",\"https:\\\/\\\/x.com\\\/corelanconsulting\",\"https:\\\/\\\/instagram.com\\\/corelanconsult\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.corelan.be\\\/#\\\/schema\\\/person\\\/3be5542b9b0a0787893db83a5ad68e8f\",\"name\":\"corelanc0d3r\",\"pronouns\":\"he\\\/him\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x\",\"caption\":\"corelanc0d3r\"},\"description\":\"Peter Van Eeckhoutte is the founder of Corelan and a globally recognized expert in exploit development and vulnerability research. With over two decades in IT security, he built Corelan into a respected platform for deep technical research, hands-on training, and knowledge sharing. Known for his influential exploit development tutorials, tools, and real-world training, Peter combines a strong research mindset with a passion for education\u2014helping security professionals understand not just how exploits work, but why.\",\"sameAs\":[\"https:\\\/\\\/www.corelan-training.com\",\"https:\\\/\\\/instagram.com\\\/corelanc0d3r\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/petervaneeckhoutte\\\/\",\"https:\\\/\\\/x.com\\\/corelanc0d3r\"],\"url\":\"https:\\\/\\\/www.corelan.be\\\/index.php\\\/author\\\/admin0\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Debugging - WinDBG & WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research","description":"Learn how to leverage WinDBG & WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/","og_locale":"en_US","og_type":"article","og_title":"Debugging - WinDBG & WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research","og_description":"Learn how to leverage WinDBG & WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.","og_url":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/","og_site_name":"Corelan | Exploit Development &amp; Vulnerability Research","article_publisher":"https:\/\/www.facebook.com\/corelanconsulting","article_published_time":"2026-03-23T15:10:14+00:00","article_modified_time":"2026-04-11T01:55:39+00:00","og_image":[{"width":256,"height":256,"url":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","type":"image\/png"}],"author":"corelanc0d3r","twitter_card":"summary_large_image","twitter_creator":"@corelanc0d3r","twitter_site":"@corelanc0d3r","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#article","isPartOf":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/"},"author":{"name":"corelanc0d3r","@id":"https:\/\/www.corelan.be\/#\/schema\/person\/3be5542b9b0a0787893db83a5ad68e8f"},"headline":"Debugging - WinDBG &#038; WinDBGX Fundamentals","datePublished":"2026-03-23T15:10:14+00:00","dateModified":"2026-04-11T01:55:39+00:00","mainEntityOfPage":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/"},"wordCount":15200,"commentCount":2,"publisher":{"@id":"https:\/\/www.corelan.be\/#organization"},"image":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#primaryimage"},"thumbnailUrl":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","keywords":["tutorial","windbgx","exploit development tutorial","debugging","breakpoint","debugger","windbg"],"articleSection":["Debugging","Exploit Writing Tutorials","Malware and Reversing"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/","url":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/","name":"Debugging - WinDBG & WinDBGX Fundamentals - Corelan | Exploit Development &amp; Vulnerability Research","isPartOf":{"@id":"https:\/\/www.corelan.be\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#primaryimage"},"image":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#primaryimage"},"thumbnailUrl":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","datePublished":"2026-03-23T15:10:14+00:00","dateModified":"2026-04-11T01:55:39+00:00","description":"Learn how to leverage WinDBG & WinDBGX for effective debugging. Essential tutorial for exploit development and analysis.","breadcrumb":{"@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#primaryimage","url":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","contentUrl":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","width":256,"height":256},{"@type":"BreadcrumbList","@id":"https:\/\/www.corelan.be\/index.php\/2026\/03\/23\/debugging-windbg-windbgx-fundamentals\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.corelan.be\/"},{"@type":"ListItem","position":2,"name":"Debugging - WinDBG &#038; WinDBGX Fundamentals"}]},{"@type":"WebSite","@id":"https:\/\/www.corelan.be\/#website","url":"https:\/\/www.corelan.be\/","name":"Corelan CyberSecurity Research","description":"Corelan publishes in-depth tutorials on exploit development, Windows exploitation, vulnerability research, heap internals, reverse engineering and security tooling used by professionals worldwide.","publisher":{"@id":"https:\/\/www.corelan.be\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.corelan.be\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.corelan.be\/#organization","name":"Corelan CyberSecurity Research","url":"https:\/\/www.corelan.be\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.corelan.be\/#\/schema\/logo\/image\/","url":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/corelanlogo2_small-20.png","contentUrl":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/corelanlogo2_small-20.png","width":200,"height":200,"caption":"Corelan CyberSecurity Research"},"image":{"@id":"https:\/\/www.corelan.be\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/corelanconsulting","https:\/\/x.com\/corelanc0d3r","https:\/\/x.com\/corelanconsulting","https:\/\/instagram.com\/corelanconsult"]},{"@type":"Person","@id":"https:\/\/www.corelan.be\/#\/schema\/person\/3be5542b9b0a0787893db83a5ad68e8f","name":"corelanc0d3r","pronouns":"he\/him","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x","url":"https:\/\/secure.gravatar.com\/avatar\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/3783bed6acd72d7fa5bb2387d88acbb9a3403e7cada60b2037e1cbb74ad451f9?s=96&d=mm&r=x","caption":"corelanc0d3r"},"description":"Peter Van Eeckhoutte is the founder of Corelan and a globally recognized expert in exploit development and vulnerability research. With over two decades in IT security, he built Corelan into a respected platform for deep technical research, hands-on training, and knowledge sharing. Known for his influential exploit development tutorials, tools, and real-world training, Peter combines a strong research mindset with a passion for education\u2014helping security professionals understand not just how exploits work, but why.","sameAs":["https:\/\/www.corelan-training.com","https:\/\/instagram.com\/corelanc0d3r","https:\/\/www.linkedin.com\/in\/petervaneeckhoutte\/","https:\/\/x.com\/corelanc0d3r"],"url":"https:\/\/www.corelan.be\/index.php\/author\/admin0\/"}]}},"views":11129,"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.corelan.be\/wp-content\/uploads\/2026\/03\/WinDbg_256.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/posts\/14036","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/comments?post=14036"}],"version-history":[{"count":258,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/posts\/14036\/revisions"}],"predecessor-version":[{"id":19337,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/posts\/14036\/revisions\/19337"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/media\/17578"}],"wp:attachment":[{"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/media?parent=14036"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/categories?post=14036"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.corelan.be\/index.php\/wp-json\/wp\/v2\/tags?post=14036"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}