TrainSec https://trainsec.net Cybersecurity online training Mon, 16 Mar 2026 17:19:14 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://trainsec.net/wp-content/uploads/2024/05/[email protected] TrainSec https://trainsec.net 32 32 Hiding a Service with C++ https://trainsec.net/library/windows-internals/hiding-a-service-with-c/ Mon, 16 Mar 2026 17:19:14 +0000 https://trainsec.net/?p=10732 In the previous video I showed how to hide a Windows service using Process Explorer: a few clicks, deny read access to Administrators, and the service disappears from Services.msc and sc query.

A reasonable follow-up question is: how do you do the same thing programmatically?

This post shows one way to do it in C++ using the Windows security APIs. It’s not “hard,” but it is easy to get lost if you haven’t worked with security descriptors and ACLs before. The key is understanding what you’re modifying and what format it’s in.

What We Are Actually Changing

A service has a security descriptor that controls who can do what with it. For our purposes, the important part is the DACL (Discretionary Access Control List).

The DACL contains ACEs (Access Control Entries). Each ACE is essentially:

  • Allow or deny
  • Which user/group (SID)
  • Which access mask (rights)

To “hide” a service from standard enumeration, we deny the relevant “read/query” access to the principal doing the enumeration (for example, Administrators). The service can still exist and run; we’re just making it harder to query through the Service Control Manager (SCM) APIs.

First: Observe What The UI Does

If you want to reverse engineer what a tool is doing, start with Process Monitor.

When you change service permissions in Process Explorer, the actual change is not done by Process Explorer itself. The change is performed through the SCM, and you’ll see activity involving the service configuration in the registry.

You’ll find a key like:

HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>\Security

…and a value (often also named “Security”) that contains a binary blob. That blob is a security descriptor (it can be represented as SDDL text, but it’s stored as binary).

Important detail: editing the registry directly is not enough for immediate effect. The SCM does not necessarily re-read that value on every change. If you only edit the registry, the change may not apply until reboot. If you want it to take effect immediately, use the service security APIs.

The APIs We Need

Core service/security calls:

  • OpenSCManager
  • OpenService
  • QueryServiceObjectSecurity
  • SetServiceObjectSecurity

Supporting security calls:

  • ConvertSecurityDescriptorToStringSecurityDescriptor (optional, for visibility/debugging)
  • CreateWellKnownSid (for the Administrators SID)
  • GetSecurityDescriptorDacl
  • SetEntriesInAcl (build a new DACL that can grow)
  • MakeAbsoluteSD (convert from self-relative to absolute)
  • SetSecurityDescriptorDacl

Step 1: Open The SCM And The Service

Typical flow:

  1. OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS)
  2. OpenService(hScm, serviceName, SERVICE_ALL_ACCESS)

In theory you can request a smaller access mask. In practice, when you’re building/testing, using SERVICE_ALL_ACCESS avoids “access denied” surprises while you’re still wiring everything up.

Step 2: Query The Service Security Descriptor

Call QueryServiceObjectSecurity requesting DACL_SECURITY_INFORMATION.

You’ll typically call it once with a small/NULL buffer to get the required size, then allocate and call again.

At this point it’s useful to convert what you got to SDDL text, just so you can see what you’re working with. Use ConvertSecurityDescriptorToStringSecurityDescriptor. Remember to free the returned string with LocalFree.

Also: save a recovery path before you experiment. One simple option is:

  • sc sdshow <service> to capture the current SDDL
  • sc sdset <service> <sddl> to restore it later

Do this in a VM if you can. Deny ACEs can lock you out in annoying ways.

Step 3: Get The Administrators SID

Use CreateWellKnownSid(WinBuiltinAdministratorsSid, ...).

SIDs are variable length, so either size properly or just allocate a buffer of SECURITY_MAX_SID_SIZE and pass its size in/out.

Step 4: Why “AddAccessDeniedAce” Fails

The obvious approach is:

  • get the existing DACL via GetSecurityDescriptorDacl
  • call AddAccessDeniedAce to add a deny ACE (for GENERIC_READ against Administrators)

This often fails with an error about allotted space / security information. That’s because the security descriptor returned by QueryServiceObjectSecurity is typically in self-relative form: a compact blob with offsets, not pointers, and there’s no spare room to grow the DACL inside that blob.

So you need to build a new DACL rather than trying to extend the existing one in-place.

Step 5: Build A New DACL With SetEntriesInAcl

Use SetEntriesInAcl with a single EXPLICIT_ACCESS entry:

  • Access mode: DENY_ACCESS
  • Access permissions: GENERIC_READ (good enough for the demo)
  • Trustee: the Administrators SID

SetEntriesInAcl returns an error code directly. If it fails, don’t waste time with GetLastError—use the returned error. If it succeeds, you now have newDacl that contains the original entries plus your new deny ACE.

You’ll typically free that allocated ACL later with LocalFree.

Step 6: Attach The New DACL (And Why It Fails Again)

You might think you can just call:

  • SetSecurityDescriptorDacl(sd, TRUE, newDacl, FALSE)

…and then SetServiceObjectSecurity.

But this can fail with Invalid security descriptor. The reason is the same theme: the original descriptor is self-relative, and you’re trying to attach pointers/structures in a way that doesn’t match the format.

The fix is to convert to absolute form first.

Step 7: Convert To Absolute With MakeAbsoluteSD

Use MakeAbsoluteSD to convert the descriptor from self-relative to absolute.

Absolute descriptors contain pointers to their components (owner, group, DACL, SACL). Once you have an absolute descriptor, attaching a new DACL works as expected.

So:

  1. Convert to absolute (MakeAbsoluteSD)
  2. Attach the DACL (SetSecurityDescriptorDacl)
  3. Apply to the service (SetServiceObjectSecurity)

If everything worked, you’ll see the effect immediately: the service disappears from enumeration for callers that no longer have query/read access.ear” from Services.msc and sc query for callers that no longer have the needed query rights.

$1300

$1040 or $104 X 10 payments

Windows Internals Master

Broadens and deepens your understanding of the inner workings of Windows.

Cleanup Notes

In real code, don’t forget to close handles and free allocations:

  • CloseServiceHandle for service/SCM handles
  • LocalFree for SDDL strings returned by conversion APIs
  • LocalFree for ACLs allocated by SetEntriesInAcl

For a short-lived demo program, the process exit will clean up memory, but it’s still good practice to do it properly.

Why This Matters For TrainSec Students

This is a practical lesson in how Windows visibility depends on API paths and security descriptors, not just “what exists on disk.”

  • Services.msc and sc.exe enumerate services through the SCM and enforce the service DACL. If you can’t query it, it may not appear in your list at all.
  • Registry-based tools can still see the service configuration because they enumerate via a different path.

If you’re doing DFIR, threat hunting, or systems triage, the takeaway is simple: when tools disagree, don’t stop at “it isn’t there.” Ask which path the tool is using (SCM vs registry), and check the security descriptor when enumeration results don’t make sense.

For builders and researchers, this is also a good reminder that Windows security APIs can be unforgiving: self-relative vs absolute formats matter, and “I can’t append an ACE” often means “this structure can’t grow in-place,” not that the idea is wrong.

Liked the content?

Subscribe to the free TrainSec knowledge library, and get insider access to new content, discounts and additional materials.

Keep Learning with TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals, malware analysis, and reverse engineering. Subscribe for free and continue learning with us:

https://trainsec.net/library

]]>
Hiding A Windows Service From Enumeration https://trainsec.net/library/windows-internals/hiding-a-windows-service/ Thu, 12 Mar 2026 11:58:19 +0000 https://trainsec.net/?p=10566 If a service doesn’t show up in Services.msc, it’s easy to assume it isn’t installed or it isn’t running. But there’s a third possibility: you’re not allowed to see it.

In this post I’m looking at a simple Windows detail: a service can be running normally and still “disappear” from standard service enumeration. Nothing magical is happening. It’s just security. If a caller can’t query a service, the Service Control Manager (SCM) won’t show it to that caller.

Where Services Are Registered

Service configuration is stored in the system Registry under:

HKLM\SYSTEM\CurrentControlSet\Services

That key contains both services and device drivers (you can distinguish them using the Type value). Because services are registry-backed, many people assume that service visibility is purely a registry permission problem. That assumption is only sometimes true.

Why Services.msc and sc.exe Can Miss A Service

Services.msc (running inside MMC) uses the service control APIs to enumerate services. The same is true for sc.exe when you run commands like sc query.

Those APIs talk to the SCM’s internal database. They are not simply walking the Services registry key. And most importantly, enumeration is gated by access checks.

If the SCM can’t open a service with the access required for “read/query,” that service can drop out of the list for that caller. The service can still be running; you just won’t see it through the normal enumeration path.

Service Objects Have Security Descriptors

A Windows service is an object managed by the SCM, and it has a security descriptor (with a DACL) that controls who can query it and manage it.

If the relevant query/read permissions are denied for your security context, standard enumeration won’t show the service. In the video I use Process Explorer as a convenient way to view and edit a service’s permissions. The important concept is the DACL, not the tool.

How To Prove The Service Is Still Running

One reason this technique is confusing is that “hidden” does not mean “stopped.”

If you suspect a service is present but not visible through the usual UI, try to confirm it from a different angle:

  • Task Manager can show you services and the PID hosting them. If the service is hosted inside an svchost instance, you can jump from the service name to the process that’s running it.
  • In Process Explorer, a process that hosts services (like the very common svchost) can list the services it hosts. That’s a useful cross-check when a service isn’t appearing where you expect.

The point is not that these tools are “better.” The point is that they’re giving you different views of the same system.

Windows master developer badge 1

$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 “Hidden” Looks Like In Practice

When service permissions block enumeration, you typically see:

  • In Services.msc, the service is not listed (even if it is running).
  • In sc query, the service doesn’t show up in the full list.
  • If you already know the exact service name and query it directly, you get Access Denied (not “not found”).

That last point matters. “Not found” means the SCM can’t locate it. “Access denied” means the SCM found it, but you’re not allowed to query it.

Why “Admin” Visibility Is Not Guaranteed

A detail that surprises people is that this can happen even when you run the management tool “as administrator.”

Being elevated does not automatically mean you can query every service. The SCM evaluates the service’s DACL against your token, and explicit deny entries take precedence over allow entries. Windows even warns about this: deny ACEs are evaluated before allow ACEs.

There’s also a practical escape hatch: ownership. The owner of an object is in a privileged position and can recover access by changing the DACL. That’s why these changes are reversible if you still have sufficient rights.

Why Some Tools Still Find The Service

Not every tool discovers services through the SCM APIs. Autoruns is a good example: it can enumerate services by reading the registry directly.

So a service that disappears from Services.msc can still appear in Autoruns, because Autoruns is looking at HKLM\SYSTEM\CurrentControlSet\Services rather than relying on SCM enumeration.

This is the defensive lesson: don’t trust a single enumeration path.

$1300

$1040 or $104 X 10 payments

Windows Internals Master

Broadens and deepens your understanding of the inner workings of Windows.

Defensive Checks That Actually Help

Compare SCM Enumeration With Registry Enumeration

Enumerate services via the SCM APIs (Services.msc, sc.exe, PowerShell), and separately enumerate the Services registry key. A service present in the registry but missing from SCM results is a strong signal that permissions (or corruption) are involved.

Inspect The Service DACL

Look for nonstandard DACLs, especially explicit deny entries against principals that normally have query access. Comparing against a known-good baseline for similar services on the same OS build is usually faster than guessing.

Use Error Semantics As A Clue

Pay attention to the difference between:

  • “service does not exist”
  • “access is denied”

That distinction alone often tells you whether you’re dealing with visibility/permissions versus a missing service.

Why This Matters For TrainSec Students

Windows visibility depends on access checks, and different tools see different slices of the system depending on which APIs they use.

When tools disagree, don’t stop at “it isn’t there.” Ask what path the tool is using (SCM APIs or Registry) and what permissions that path requires.ion cleanly, it’s a short step to building more advanced tooling.

Liked the content?

Subscribe to the free TrainSec knowledge library, and get insider access to new content, discounts and additional materials.

Keep Learning with TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals, malware analysis, and reverse engineering. Subscribe for free and continue learning with us:

https://trainsec.net/library

]]>
Windows System Programming In Rust https://trainsec.net/library/programming/windows-system-programming-in-rust/ Thu, 12 Mar 2026 11:28:22 +0000 https://trainsec.net/?p=10559 Rust is a system programming language, but it’s also a modern language with a strong safety story. That combination is the reason it keeps showing up in places where C and C++ used to be the only viable option.

In this post I’m going to show how to get started with Windows system programming in Rust. The assumption is that you know a bit of Rust already, but I’ll keep the Rust-specific parts simple and focus on what changes when you start calling Win32 APIs.

The example I’m going to build is a classic: enumerate processes using the Toolhelp APIs (CreateToolhelp32Snapshot, Process32First, Process32Next). It’s simple enough to be a first project, but it forces us to deal with the key topics: crates, features, unsafe, error handling, and UTF-16 strings.

Safe Rust And Unsafe Rust

Rust’s safety guarantees apply to safe Rust: you don’t get use-after-free bugs, garbled pointers, or the usual memory hazards that come from manual lifetime management. But the moment you start interacting with an operating system API that was designed for C, you’re going to cross into unsafe territory.

That’s not a Rust limitation. It’s just reality: the OS APIs are mostly C-style interfaces. Rust can’t prove at compile time that you’re using them correctly, so you explicitly mark the call site as unsafe. Think of it as telling the compiler: “I’m taking responsibility for this part.”4).

Choosing Windows Bindings: windows-sys vs windows

For Windows specifically, you have a few options in the ecosystem, but Microsoft maintains two crates that matter here:

  • windows-sys: low-level bindings, close to the raw Win32 types and functions.
  • windows: higher-level wrappers built on top of windows-sys, generally more ergonomic for Rust code.

For this example, I use the windows crate. It still exposes the Win32 calls, but it’s a better starting point if you want code that feels like Rust rather than a direct mechanical translation of C headers.

Creating The Project

Start a new executable project:

  • cargo new (I call the sample project ProcList)
  • Open it in your IDE of choice

Most people use VS Code with the Rust Analyzer plugin, which is fine. I’m using RustRover from JetBrains in the video, but the tooling choice isn’t the point—Cargo and the crate setup are what matter.

You’ll see two things immediately:

  • Cargo.toml (your project metadata and dependencies)
  • main.rs (the default “Hello world”)

Adding The windows Crate And Enabling Features

Add the windows crate to Cargo.toml. The key detail is features.

Win32 is huge. You do not want to enable everything. The correct approach is to enable the minimal set of modules you actually use.

For Toolhelp process enumeration, you’ll need the module that contains the Toolhelp APIs, and typically you’ll also need Foundation types.

A practical tip: don’t guess feature names. Use the windows-rs documentation tooling that maps API names to features. If you search for a function like CreateToolhelp32Snapshot, it tells you which feature to enable. This saves time, and it avoids the “why won’t this import compile?” loop.

Calling Win32 APIs From Rust

Once you import the right modules, you can start calling Win32.

The first Toolhelp call is CreateToolhelp32Snapshot.

Two things you’ll notice immediately:

  1. It’s unsafe
    All Win32 calls will require unsafe { ... }. That’s normal, as these are C APIs, which are unsafe by definition.
  2. You’ll get Rust-style Results
    Instead of manually calling GetLastError after every step, many of the bindings surface results as Rust Result<> values.

For a first demo, it’s common to use unwrap() to keep the example focused. That means: if something fails, crash and show the error. For production code, you’ll usually do proper error handling, but for learning the mechanics, unwrap() keeps the noise down.

$1300

$1040 or $104 X 10 payments

Windows Internals Master

Broadens and deepens your understanding of the inner workings of Windows.

Enumerating Processes With Toolhelp

The Toolhelp enumeration pattern is the same as in C and C++:

  1. Create a snapshot
  2. Initialize a PROCESSENTRY32 structure
  3. Call Process32First
  4. Loop with Process32Next

In Rust, there are a few details you must get right.

Using The Wide (UTF-16) Variants

On Windows, the “real” string API surface is UTF-16. For Toolhelp, that means using the wide types and functions:

  • PROCESSENTRY32W
  • Process32FirstW
  • Process32NextW

The process name field is a fixed-size UTF-16 buffer. If you print it naïvely, you’ll get a dump of numbers, not a readable name.

We’ll fix that in a minute.

Initializing The Structure Correctly

Toolhelp functions require you to set dwSize to the size of the structure.

In C++ you often do something like:

  • zero the struct
  • set dwSize

In Rust, you can’t leave memory uninitialized. You typically start with a zeroed structure (or a default initializer provided by the bindings), then set:

  • entry.dwSize = size_of::<PROCESSENTRY32W>() as u32;

If dwSize is wrong, enumeration fails. This is one of those Win32 rules you can’t ignore.

Handling Mutability The Rust Way

Rust variables are immutable by default.

If the API is going to write into your process entry structure, the structure must be declared with mut. The snapshot handle doesn’t necessarily need to be mutable, but the entry does.

This is a small Rust difference that matters when you translate Win32 patterns into Rust.

Windows master developer badge 1

$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.

Converting UTF-16 Process Names Into Strings

The executable name in PROCESSENTRY32W is a UTF-16 buffer. Rust natural strings are UTF-8.

A common and practical approach is:

  1. Convert UTF-16 into a Rust String
  2. Trim at the first null terminator

For example (conceptually):

  • Convert the entire array with String::from_utf16_lossy(&entry.szExeFile)
  • Then cut it at the first '\0'

That gives you a normal readable process name.

A small Rust feature that helps here is variable shadowing. You can reuse the same variable name for each transformation step (raw → string → trimmed) without mutating the original binding. It keeps the code readable.

A Minimal Mental Model For The Final Loop

At a high level, the logic looks like this:

  • Take snapshot
  • Initialize entry, set dwSize
  • If Process32FirstW succeeds:
    • print PID + process name
    • loop while Process32NextW succeeds:

The “Rust work” is mostly about correctness at the boundaries:

  • setting structure sizes
  • using unsafe where required
  • converting UTF-16 to something printable

Why This Matters For TrainSec Students

If you do Windows Internals work, you end up writing small tools sooner or later. Sometimes it’s for debugging. Sometimes it’s for IR. Sometimes it’s for research or experiments.

Rust gives you a good balance:

  • Most of your code stays in safe Rust
  • The dangerous parts are constrained to explicit unsafe blocks
  • You still get direct access to the Win32 APIs you need

This example is intentionally simple, but it builds the foundation you need for anything bigger:

  • using the windows crate properly
  • choosing the right features
  • dealing with handles and structs
  • handling UTF-16 correctly
  • working comfortably with Win32 from Rust

Once you can do process enumeration cleanly, it’s a short step to building more advanced tooling.

Liked the content?

Subscribe to the free TrainSec knowledge library, and get insider access to new content, discounts and additional materials.

Keep Learning with TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals, malware analysis, and reverse engineering.Subscribe for free and continue learning with us:

https://trainsec.net/library

]]>
Windows Research with WinDBG – Live 4 hour masterclass with Pavel https://trainsec.net/library/windows-internals/live-windows-research-using-windbg/ Mon, 09 Mar 2026 15:26:24 +0000 https://trainsec.net/?p=10530 Many developers and researchers view WinDbg as the tool of last resort. Something crashes, nothing else helps, and eventually someone opens WinDbg and starts digging.

But that mindset misses the real power of the tool.

WinDbg is not just a debugger used in emergencies. It is one of the most powerful research tools available for understanding how Windows actually works. Microsoft engineers use it to debug the operating system itself. With the right setup and workflow, it becomes a microscope for exploring Windows internals.

On April 7th, 2026, I’ll be running a live 4-hour masterclass where we will use WinDbg specifically as a research platform for exploring Windows components.

This session is designed for Trainsec students who want to go beyond theory and develop practical techniques for investigating the system from the inside.

Windows Research with WINDBG with Pavel Yosifovich

Registration, syllabus and more: https://trainsec.net/windows-research-with-windbg-live-4h/

What we’ll cover

During the session we will explore several practical areas:

  • Configuring WinDbg for research work
  • Investigating user-mode components
  • Researching kernel-mode structures and behavior
  • Working with crash dumps and memory dumps
  • Extending WinDbg through scripting and automation

The focus will be on real investigative workflows and techniques that can be applied when studying Windows internals, reverse engineering components, analyzing system behavior, or troubleshooting complex issues.

Why this matters for Trainsec students

Many Trainsec courses dive deep into Windows internals, security research, reverse engineering, and malware analysis. WinDbg is one of the tools that ties these areas together.

If you know how to drive WinDbg effectively, you gain the ability to:

  • Inspect real system structures while Windows is running
  • Explore undocumented behavior
  • Understand crashes and unexpected system behavior
  • Validate hypotheses about how the OS actually works

In short, WinDbg turns documentation and theory into observable reality.

Special bonus for attendees

The ticket for the event is $49, but the admission works a bit differently than a typical webinar.

Every ticket also includes a $49 voucher that can be used toward any course in the Trainsec catalog.

In other words, if you are planning to take a Trainsec course anyway, the ticket effectively becomes store credit you can use later.

Event details

Windows Research with WinDbg – Live Masterclass
Date: April 7, 2026
Time: 10:00 AM – 2:00 PM (EDT)
Duration: 4 hours (live session)
Seats: Limited
Admission: $49 (includes a $49 Trainsec course voucher)

If you want to get more comfortable using WinDbg as a research tool rather than a last-resort debugger, this session will give you the workflows and techniques to start doing that.

Register now to reserve your seat.

Registration, syllabus and more: https://trainsec.net/windows-research-with-windbg-live-4h/

]]>
Windows TLS (Thread-Local Storage) Explained https://trainsec.net/library/windows-internals/windows-tls-thread-local-storage-explained/ Fri, 13 Feb 2026 14:36:13 +0000 https://trainsec.net/?p=10211 Thread-Local Storage (TLS) is one of those Windows concepts that sounds abstract until you hit a problem that’s hard to solve cleanly any other way.

In this post, I’ll explain what TLS is, where you’ve already been using it (whether you noticed or not), and then I’ll show a practical use case: avoiding a nasty recursion problem when hooking an allocation API and trying to log allocations.

What Thread-Local Storage Is

TLS is about storing information per thread, while keeping access uniform.

That means I can write code that says “get my TLS value,” and it will always return the value for the current thread. Another thread running the same code will read its own value, from its own storage, using the exact same access pattern.

The whole point is that each thread gets its own data, and threads don’t stomp on each other.

On Windows, you can think of TLS as an array of “slots” per thread:

  • Every thread has an array of slots.
  • An application can allocate a slot index (process-wide), and then every thread has a value for that slot (thread-specific).
  • The values are pointer-sized (8 bytes on x64). If you need more data, store a pointer to a structure you allocate elsewhere.

Windows guarantees at least 64 TLS slots, and in practice you can go higher (up to 1024).

Classic TLS You’re Already Using

You’ve seen TLS patterns in both the C runtime and the Windows API.

errno And Other CRT “Globals”

In old (and current) C code, errno is a global variable. In a multi-threaded process, that’s a data race waiting to happen. One thread calls (say) fopen, while another thread does some I/O, and suddenly your “last error” isn’t your last error anymore.

So modern CRT implementations make errno effectively thread-local. In practice, it’s accessed through a function (in Visual Studio you’ll see it as a macro calling an internal function), and that function returns the per-thread value.

The same idea applies to other classic CRT functions that need per-thread state, like strtok (which keeps internal state between calls). That state can’t safely be process-global in a multi-threaded world, so it ends up being thread-local.

GetLastError And The TEB

On the Windows side, GetLastError is another obvious example: it can’t be a single global variable either.

The value is stored per thread, and you can see it in the TEB (Thread Environment Block) in a debugger. Each thread has its own TEB, so each thread has its own “last error” value.

This is TLS in spirit: uniform access (“give me my last error”) with per-thread storage under the hood.

$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

TLS is not just “a programming convenience.” It’s a mechanism you’ll run into when you’re:

  • debugging multi-threaded behavior
  • interpreting per-thread state like last error values
  • building hooks / instrumentation without breaking the process
  • reasoning about how libraries keep thread-specific context

The key mental model is: uniform access, thread-specific storage. Once that clicks, a lot of Windows behavior becomes easier to explain—and certain “impossible” problems (like passing state into a hooked function with no extra parameters) become straightforward.

Keep Learning with TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals, malware analysis, and reverse engineering.Subscribe for free and continue learning with us:

https://trainsec.net/library

Liked the content?

Subscribe to the free TrainSec knowledge library, and get insider access to new content, discounts and additional materials.

]]>
Looking into Windows Access Masks https://trainsec.net/library/windows-internals/looking-into-windows-access-masks/ Fri, 13 Feb 2026 14:32:18 +0000 https://trainsec.net/?p=10200 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.

Windows master developer badge 1

$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.

]]>
How to Delete a File in Windows (and What “Delete” Really Means) https://trainsec.net/library/windows-internals/delete-a-file-in-windows/ Sun, 08 Feb 2026 12:05:30 +0000 https://trainsec.net/?p=10147 Insights from Pavel Yosifovich

How do you delete a file in Windows? looks like a beginner question, but it’s a great excuse to peel back the layers and see what Windows is really doing.

At the surface, you can just open File Explorer and delete a file. But if you’re studying Windows internals, the interesting part is what happens behind the scenes: which APIs are used, what flags matter, and how you can prove it with the right tooling.

How To Permanently Delete A Windows File From Explorer

In Explorer, there’s an important difference:

  • Delete sends the file to the Recycle Bin (it’s not “actually deleted” in the sense most people mean).
  • Shift+Delete deletes the file directly (skips the Recycle Bin).

So if your question is “how do I permanently delete a Windows file?” in the everyday Explorer sense (i.e., don’t send it to the Recycle Bin), Shift+Delete is the key.

Common Ways To Delete A File In Windows

There are plenty of tools you can use:

  • Explorer
  • The del command in a Command Prompt
  • PowerShell RemoveItem
  • A Windows API like DeleteFile
  • Shell APIs like SHFileOperation (more flexible — for example, deleting directories that contain files)

Different entry points… but internally, file deletion comes down to a small set of mechanisms.

The Two Ways Windows Actually Deletes A File

In reality, there are two ways to delete a file in Windows.

Delete A File By Opening It With Delete-On-Close

The first way is:

  1. Open the file with the “DeleteOnClose” flag (using CreateFile or a native API like NtOpenFile)
  2. Make sure you opened it with DELETE access (otherwise it won’t work)
  3. Close the handle (CloseHandle)
  4. Once the file object is closed, the file is deleted

This is a clean model: open the file with the right flag, then close the handle, and the deletion happens.

Delete A File By Setting Disposition Information

The second way is used when the file is already open and you decide you want to delete it:

  1. Open the file normally
  2. Call something like SetFileInformationByHandle
  3. Use a disposition information class such as FileDispositionInformation (or the extended version)
  4. Set the boolean that says “delete this file”
  5. The file is deleted once all handles to it are closed

If you’ve done any kernel driver work, this aligns with IRP_MJ_SET_INFORMATION being sent down to the file system driver.

$1300

$1040 or $104 X 10 payments

Windows Internals Master

Broadens and deepens your understanding of the inner workings of Windows.

How To See What’s Really Happening With ProcMon

To figure out what a tool is doing (what del does, what DeleteFile does, what anything does), you need visibility.

That’s where Process Monitor (ProcMon) from Sysinternals comes in.

A simple workflow:

  1. Open ProcMon
  2. Stop the capture (so you can configure things without drowning in noise)
  3. Clear existing events
  4. Add a filter like: Path contains test1.txt
  5. Start capture
  6. Perform the deletion
  7. Stop capture and inspect the relevant events

One thing you’ll notice right away: even typing in Command Prompt can trigger file activity, because of auto-complete. You may see a CreateFile with read-style access before you even run the delete command. That’s normal background behaviour — it’s not the deletion yet.

What The Del Command Does Behind The Scenes

When you actually run del test1.txt, ProcMon makes it very clear what’s happening.

You’ll see a CreateFile where:

  • Desired Access includes DELETE
  • Options include File Delete On Close

That tells you del is using the first mechanism:

  • Open the file with “DeleteOnClose”
  • Then close the handle
  • And the file is gone

It’s simple — and very effective.

What ‘DeleteFile’ Does Behind The Scenes

Next, I wrote a tiny program that just calls DeleteFile and passes in the filename (argv[1]). The deletion works, but the mechanism is different.

In ProcMon you’ll see:

  • a CreateFile with DELETE access
  • no “DeleteOnClose” option

So it has to use the other mechanism — and you can see it directly:

  • Set Disposition Information Ex
  • File Disposition: Delete

If you open the call stack in ProcMon, you can literally follow it:

  • The app calls DeleteFile
  • DeleteFile calls NtSetInformationFile
  • That goes to the kernel and down through the file system stack (via Filter Manager, etc.)

On this version of Windows, DeleteFile is implemented using the disposition information approach.

Windows master developer badge 1

$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.

Seeing IRP Major Function Codes With Advanced Output

If you enable Advanced Output in ProcMon, you can see the major function codes directly, which makes the mental model even cleaner:

  • IRP_MJ_CREATE when the file object is created/opened
  • IRP_MJ_CLOSE when the handle is closed
  • IRP_MJ_SET_INFORMATION when disposition information is set (including the delete disposition)

Once you see those three in context, file deletion stops being mysterious.

Take This Further

Now that you’ve got the model, you can apply it to anything:

  • Trace RemoveItem in PowerShell
  • Trace SHFileOperation
  • Trace whatever third-party tool you like

The point is: you don’t need to guess. You can verify exactly what’s happening.

Why This Matters For TrainSec Students

For TrainSec students, this example is important not because deleting a file is hard, but because it forces you to build a real mental model of Windows behaviour below the surface.

A lot of people learn Windows behaviour as a loose checklist of APIs. That approach hides the mechanics. In this deletion walkthrough, the mechanics are the whole lesson: there are two core deletion paths, access rights matter, and you can validate everything by watching the real operations (create, close, set-information) as they happen.

This is exactly the kind of thinking TrainSec is designed to train: stop treating Windows as a black box, and start reasoning about it based on what the system is actually doing.

Keep Learning With TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals.

Subscribe for free and continue learning with us: https://trainsec.net/library/.

]]>
When Process Hollowing Isn’t Process Hollowing https://trainsec.net/library/windows-internals/when-process-hollowing-isnt-process-hollowing/ Tue, 27 Jan 2026 14:37:20 +0000 https://trainsec.net/?p=9942

Process hollowing is usually described as creating a process in a suspended state, removing its original executable image, and replacing it with a different one. From the outside, the process appears normal, but the code running inside is not what you expect.

In this article, I explore a slightly different idea.

Instead of unmapping the original executable, we leave it in place. The original image stays mapped in memory, inactive but present. We then map a second executable into the same process and redirect execution to it. The result is a process that still looks legitimate when inspected at a high level, while actually running different code.

The flow looks like this:

First, an attacker process creates a target process in a suspended state. At this point, only the original image and NTDLL are mapped, and no user code has executed yet.

Next, memory is allocated inside the target process for a replacement executable. The executable is rebased to match the allocated address, avoiding the need for a full manual loader.

The rebased image is then mapped and copied into the target process, including headers and all sections. For simplicity, all memory is marked as execute, read, and write, even though this is not ideal from a stealth perspective.

After that, the Process Environment Block is updated so the loader sees the correct image base. The entry point is written into the suspended thread context, and the thread is resumed.

At this point, the original executable image is still present in memory, but it is dormant. The replacement image is what actually runs.

This approach has clear limitations. Rebasing modifies the executable on disk. Memory protections are overly permissive. Error handling is minimal. Still, it is a useful experiment for understanding how process creation, image loading, and execution really work on Windows.

If you are interested in detection, evasion, or low level Windows internals, this kind of hands on exploration is valuable. The code is not the point. The mechanics are.

Why This Matters for TrainSec Students

For TrainSec students, this example is important not because it is a perfect injection technique, but because it forces you to understand how Windows really works below the surface.

Many people learn process hollowing as a checklist of API calls. That approach often hides the real mechanics. In this variant, you are exposed to the actual building blocks: how a process is created, how images are mapped, how the PE structure is laid out in memory, how the loader relies on the PEB, and how execution flow is controlled through thread context.

This kind of understanding is critical for several roles.

For malware researchers, it shows why small changes in loading behavior can break assumptions made by static and dynamic analysis tools.

For detection and EDR engineers, it highlights why relying only on classic hollowing patterns is not enough. The original image is still present, memory layout looks mostly normal, and execution happens elsewhere.

For reverse engineers, it provides a concrete example of how loaders, rebasing, and section mapping interact, which helps when debugging crashes, unpacking malware, or analyzing custom loaders.

For exploit developers and low level Windows developers, it reinforces the idea that Windows APIs are only part of the story. The real behavior depends on internal structures like the PEB, NT headers, and thread state.

TrainSec courses focus on building this mental model. Once you understand these internals, techniques like hollowing, injection, and evasion stop being magic tricks and start becoming engineering problems you can reason about, improve, and detect.

This is exactly the gap TrainSec aims to close.

Keep Learning with TrainSec

This content is part of the free TrainSec Knowledge Library, where students can deepen their understanding of Windows internals, malware analysis, and reverse engineering.Subscribe for free and continue learning with us: https://trainsec.net/library

Liked the content?

Subscribe to the free TrainSec knowledge library, and get insider access to new content, discounts and additional materials.

]]>
How Malware Really Works (What Most People Miss) https://trainsec.net/library/malware-analysis/how-malware-really-works-what-most-people-miss/ Sun, 28 Dec 2025 12:02:41 +0000 https://trainsec.net/?p=9651 Insights from a discussion between Yaniv Hoffman and Uriel Kosayev

In a recent discussion between Yaniv Hoffman and me, a core idea came back again and again: strong defense in cybersecurity starts with understanding the attacker’s mindset. Malware analysis, EDR evasion, and modern attack chains cannot be learned only from theory or from a single defensive angle. They require thinking across roles and understanding how real attackers operate in practice.

This approach is at the heart of how I teach and research malware, and it is highly relevant for students and researchers learning at TrainSec Academy.

Becoming the Enemy to Defend Against It

I explained that effective defenders must learn to think like attackers. To detect and stop real threats, you need to understand why attackers choose certain techniques, how they bypass security tools, and where defenders usually make assumptions.

Malware analysis sits between blue and red teams. By reversing malware and studying real tradecraft, defenders learn how attackers reuse code, copy techniques from known ransomware groups, and adapt quickly. This is not theory. It is exactly how attackers learn from each other in the real world.

For TrainSec students, this means that malware analysis is not just about reading indicators or signatures. It is about understanding intent, design decisions, and abuse of legitimate system behavior.

Simple Techniques Are Often the Most Dangerous

One of the strongest points raised in the discussion is that attackers often succeed by using very simple methods. Instead of advanced shellcode or complex loaders, many attacks rely on trusted, signed system tools that defenders ignore.

I demonstrated how built in Windows utilities can be abused to fully compromise Active Directory by dumping the NTDS database. No custom malware, no suspicious API calls, and no obvious indicators. From an EDR point of view, everything looks like a normal administrative action.

This highlights a critical lesson for security researchers and SOC analysts: if you only look for complex behavior, you will miss the attacks that matter most.

EDR Internals: Research & Development

This hands-on workshop is designed to give cybersecurity professionals, malware researchers, and detection engineers a rare opportunity to explore how modern Endpoint Detection and Response (EDR) solutions truly work, and how to both research and build them from the ground up.

Modern Malware Trends Researchers Must Understand

The discussion also covers how malware has changed in recent years:

  • Phishing has become more targeted and realistic, including voice based attacks.
  • Attackers increasingly use AI to generate and adapt malware.
  • More malware is written in LLVM based languages like Rust, which makes static detection harder.
  • Evasion techniques now focus on bypassing both antivirus and EDR by abusing performance and detection limits.
  • Living off the land techniques are becoming a default approach, not an exception.

For TrainSec students, these trends explain why learning internals, operating systems, and detection logic is essential. Tools change, but attacker thinking patterns stay consistent.

The Skills That Matter Before Malware Analysis

  1. Understand why you want to do it and what motivates you.
  2. Learn to think from multiple perspectives, attacker and defender.
  3. Build strong fundamentals: how computers work, how memory and CPU interact, and how networks communicate.
  4. Invest time and patience. There are no shortcuts.

This philosophy aligns directly with the learning paths at TrainSec, where students are guided step by step from fundamentals to advanced research topics.

$1000

$700 or $73.5 x 10 installments

The MAoS Bundle is your fast track to mastering malware analysis. From core concepts to hands-on reverse engineering, this bundle gives you the real-world skills needed to dissect malware with confidence. Perfect for aspiring analysts, SOC pros, and red teamers preparing to dive into advanced content like the MAoS book.

Why This Matters for TrainSec Students

This discussion reflects how TrainSec approaches cybersecurity education. Courses are designed to teach how systems really work, how attackers abuse them, and how defenders can respond with informed detection and prevention.

For students and researchers, this mindset helps bridge the gap between theory and real world security work. It prepares them not only to detect threats, but to understand them deeply and adapt as attackers evolve.

Credits and Thank you

I appreciate Yaniv Hofman for hosting me for this great discussion. Check out his youtube channel here:

https://www.youtube.com/@yanivhoffman

]]>
You Can’t Sleep in the AI Era: The Brutal Reality of Securing Modern Data Centers https://trainsec.net/library/soc-and-dfir/reality-of-securing-modern-data-centers/ Thu, 25 Dec 2025 09:19:09 +0000 https://trainsec.net/?p=9625 A summary and perspective from the panel discussion at the “Data Centers & Cloud 2025” conference.

In a recent panel of industry experts convened at the annual Data Centers & Cloud 2025 event, we explored how data‑centres and cloud infrastructures are preparing for the AI era and why the sleep‑at‑night factor may be under threat for many organizations.

Setting the Scene

As AI and data‑intensive workloads move to the fore, traditional data‑centre and cloud infrastructure are under unprecedented pressure. The panel, which included cloud and cyber‑security leaders from government, healthcare and enterprise, examined how legacy installation, hybrid models, regulation, infrastructure scaling, and the human factor combine to create a volatile mix.

My Key Takeaways (as Uriel Kosayev)

  1. “You can breach any data‑centre … and any user.”
    Even the most hardened data‑centre or the most privileged user account is not immune. AI brings tremendous potential but also heightened responsibility. If we don’t manage access and permissions correctly, the whole system can unravel.
  2. The cloud‑transition is harder than it sounds.
    Many organizations still operate substantial on‑premise installations. In practice, the hybrid model becomes a bridge but also a risk vector. Maintaining parallel operations on‑premise and in the cloud adds complexity and creates new attack surfaces.
  3. From defeat‑preparation to damage‑limitation? That’s not good enough.
    Too many organizations have moved from “we will defend and win” to “we anticipate we’ll be attacked and aim to limit damage”. That signals a weaker stance akin to buying a car without a full inspection.

Other Panel Highlights

  • Tali Shitrit emphasized that although dedicated hardware installs will continue in the short term, the long‑term strategic direction must be cloud‑centric especially as AI workloads scale.
  • Orna Granot stressed data sovereignty: keeping all sensitive data in Israel.
  • Moshe Praver noted hyperscalers’ advantage but warned of availability risks in crisis scenarios.
  • Alex Peleg highlighted that security focus shifts from hardware protection to entity protection in cloud environments.

EDR Internals: Research & Development

This hands-on workshop is designed to give cybersecurity professionals, malware researchers, and detection engineers a rare opportunity to explore how modern Endpoint Detection and Response (EDR) solutions truly work, and how to both research and build them from the ground up.

Why This Matters

  • Hybrid complexity = hybrid risk.
  • AI workloads demand new infrastructure scale.
  • Identity becomes the critical vulnerability point.
  • Organisational mindset must return to proactive defence, not passive acceptance.

Recommendations

  • Avoid “lift‑and‑shift” cloud migration; start with identity/permissions design.
  • Build monitoring and incident response that is hybrid‑aware.
  • Evaluate cloud providers for sovereignty, regional availability, and crisis resilience.
  • Test application entry points aggressively, especially those exposed through AI‑augmented services.
  • Shift to continuous offensive testing: red‑teaming, penetration testing, identity audits.

Final Words

In the AI‑era, the data‑centre is no longer a simple physical facility. It is a dynamic hybrid ecosystem of compute, cloud services, accelerators, and identity flows. Security leaders cannot sleep soundly but with the right architecture and mindset, they can stay ahead of the threat curve.

Source & Attribution

Based on a panel discussion at Data Centers & Cloud 2025 and the original Hebrew article published by PC.co.il.

]]>