In this video, I’m looking at access masks: what they are, where they appear in Windows, and how you can inspect them with a few practical tools.
An access mask is one of those things that’s easy to ignore until something doesn’t work. A good example is when an API call fails with Access Denied, even though you think you have a valid handle. In many cases, the missing piece is simply that the handle doesn’t have the rights that specific call requires. So the goal here is to make access masks less mysterious and more observable.
What An Access Mask Is
An access mask is a compact way to represent access rights for an object. It’s a 32-bit value, where different bits have different meanings. Those bits represent what someone can do with the object.
Access masks typically show up in two locations:
- In A Handle
Every time you open a handle to an object, you must specify what access you want. If the security checks succeed, Windows gives you a handle and stores the granted access mask inside that handle. This also means that even if multiple handles refer to the same object, they’re not necessarily equal—some handles are more powerful than others, depending on the access mask that was used to open them. - In An ACE (Access Control Entry)
Access masks also appear in ACEs, which are part of an ACL (Access Control List). The ACL answers “who can do what” with an object. The “what” part is represented by the access mask stored in each ACE.
So, the access mask is a core part of both “what access did I actually get on this handle?” and “what access is allowed according to the ACL?”
Looking At Access Masks In Process Explorer
A quick way to see access masks on handles is Process Explorer. Pick any handle, and you’ll see each handle’s properties: the handle value, object type, object name (if it has one), and the Access column.
That Access value is the access mask—a set of bits corresponding to a 32-bit number. Since an access mask is just a number, you usually need Windows headers or documentation to translate it into something meaningful.
To make this easier, there’s also a Decoded Access column that shows the access mask in a readable form. That way, you don’t have to go hunting through headers every time you want to understand what a handle can actually do.
For example:
- An unnamed event might show EVENT_ALL_ACCESS, meaning the handle can do anything with that event.
- A file handle might decode to something like SYNCHRONIZE and EXECUTE.
“Execute” means you can execute the file (or, for a directory, list contents). “Synchronize” means you can wait on the object—what that means depends on the object type (for a file it often relates to I/O completion behavior).
Another useful example is a registry key handle opened with READ_CONTROL and KEY_READ only. If code tries to write to that registry key using this handle, it will fail with Access Denied, because the handle simply doesn’t have write permissions.
One detail that trips people up: if you double-click a handle in Process Explorer, the dialog you get is about the object, not about the handle. The handle-specific access is what you see in the handle list itself.
Seeing Access Masks In The Security Dialog
Another view is the classic Windows security dialog (for example, via the Security tab and then Advanced).
In the Advanced view you’ll see the list of ACEs. Each ACE is essentially a triplet:
- the ACE type (allow/deny),
- the user or group it applies to,
- and the access (the access mask).
Sometimes the UI shows “Special.” That doesn’t mean it’s unusual—it usually means there isn’t a single neat label for that exact combination of bits. If you click Edit or View, you can see the detailed permissions behind that entry (including things like delete, change permissions, change owner, and others).
Seeing The Same Data In A Kernel Debugger
You can also view the same information through a kernel debugger. The underlying idea is:
- every object has an object header,
- the object header contains a pointer to a security descriptor,
- and the security descriptor contains the ACL/ACEs.
When I do this locally in a debugger, I can locate the object, then look at the object header structure. One practical detail is that the object header is 48 bytes before the object.
From there, you can grab the security descriptor pointer and use the !sd command to dump it. If you get an error related to flags, one fix is to zero out the last digit of the address—on 64-bit Windows, object addresses are 16-byte aligned, so the lower four bits are always zero.
Once you dump the security descriptor, you’ll see the ACEs listed with SIDs and access masks (in a more raw form). The ordering and identities match what you see in the Security UI, but here you’ll also see the numeric masks directly.
$1,478
$1182 or $120 X 10 payments
Windows Master Developer
Takes you from a “generic” C programmer to a master Windows programmer in user mode and kernel mode.
What Access Masks Are Made Of
A 32-bit access mask includes several categories of rights:
Specific Rights
The lower 16 bits are specific rights, and these depend on the object type. A process has different specific rights than a file, thread, desktop, and so on. There can be no more than 16 object-specific bits.
Standard Rights
There are also standard rights that apply broadly across object types:
- DELETE (meaningful for some objects like files)
- READ_CONTROL (read the DACL)
- WRITE_DAC (write/modify the DACL)
- WRITE_OWNER (change the owner)
Ownership matters because the owner always has a certain level of control—objects should not become permanently inaccessible due to a bad DACL. And if you have the Take Ownership privilege (administrators typically do by default), you can change the owner even without having “write owner” granted through the normal access bits.
SYNCHRONIZE
SYNCHRONIZE is the right to wait on an object. It only makes sense for objects where “signaled” has meaning (processes, threads, events, mutexes, semaphores, and similar). A process, for example, becomes signaled when it terminates.
MAXIMUM_ALLOWED
MAXIMUM_ALLOWED means “give me the maximum access I can get without failing.” Whether that’s enough for what you want to do depends on your use case.
Generic Rights
Finally, there are generic rights:
- GENERIC_READ
- GENERIC_WRITE
- GENERIC_EXECUTE
- GENERIC_ALL
These are translated behind the scenes into object-specific rights. One way to see what they translate to is by looking at object-type mappings in Object Explorer (for example, for process objects: generic read maps to a specific set of rights such as VM read and query information).
In general, if you’re doing programming work, I recommend being explicit rather than using generic rights. Generic rights can request more than you actually need—and if you ask for more than you need, you may end up getting nothing.
A Practical Example: OpenProcess And The “ALL_ACCESS” Trap
Let’s say you want to terminate a process. You call OpenProcess, and the first parameter is the desired access mask—what you want to be able to do if the call succeeds.
A lazy approach is to request PROCESS_ALL_ACCESS. That’s usually a bad idea:
- it requests more than you need,
- it’s often “all or nothing” (if you can’t get all the requested rights, the call fails),
- it increases the attack surface because a successful handle is very powerful,
- and it’s slightly slower because more bits must be checked.
If your goal is termination, you should request PROCESS_TERMINATE. If you also want to wait for the process to actually terminate, add SYNCHRONIZE.
Now here’s the catch: if you then try to call GetProcessTimes using that handle, it will fail with Access Denied. Why? Because GetProcessTimes requires PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION, and you didn’t ask for that access—so you don’t have it in this handle.
The fix isn’t “use ALL_ACCESS.” The fix is to request exactly what you need:
- PROCESS_TERMINATE
- SYNCHRONIZE (if you plan to wait)
- PROCESS_QUERY_LIMITED_INFORMATION (if you plan to query basic details/times)
That’s the larger point: the access mask stored in the handle directly controls what you can successfully do with it.
$1300
$1040 or $104 X 10 payments
Windows Internals Master
Broadens and deepens your understanding of the inner workings of Windows.
Why This Matters For TrainSec Students
Access masks show up everywhere once you start paying attention. They’re in the handles you open, and they’re in the ACEs that decide who gets what access.
If you can read access masks, you stop guessing:
- You can understand why a handle can terminate a process but can’t query information from it.
- You can look at an ACL and understand what a user or group is actually allowed to do.
- You can make cleaner decisions when writing code by requesting only the rights you really need.
That’s a practical skill in Windows internals work, and it also shows up in security contexts where you’re trying to understand what a process can actually do with the objects it has open.
Keep Learning With TrainSec
If you want more material like this, browse the free TrainSec Knowledge Library:
https://trainsec.net/library/
And if you’re following along, try inspecting access masks in two places for the same system: a handle view (Process Explorer) and an ACE view (Security → Advanced). Seeing both perspectives is usually where the concept clicks.
































