This is a low-resolution version of the logo of Windows XP by Microsoft, protected by copyright

Windows hardening (part 2)

Notes published the
19 - 24 minutes to read, 4778 words
Categories: security windows
Keywords: administrator file association hardening security windows

After writing my notes about hardening a windows system I also remembered another trick, that I currently have not used as much as maybe I should have: changing file associations.

Change file associations

Double-clicking on a file from explorer has different effects that depend on the file extension. There are different file types, like .bat, .cmd, .js, and .reg and a double-click does not open those files in a text editor, but their content gets parsed and executed.

From a usability perspective, it’s not so good, as a single action (double-clicking on an icon), it has a different effect depending on the file type, even for text files (and most people do not know by heart the whole list of file extensions that get executed, myself included). From a security perspective, it’s also not so good, as, accustomed to double-clicking a file to see it’s content, a user might execute something by accident.

Some file managers, like caja (a file manager mainly for non-Windows systems), permit to change the default behavior. Unfortunately, Windows Explorer does not have such an option.

It is possible to change the default file association for all those file types, for example with

  $editor = "`"C:\Program Files\Notepad++\notepad++.exe`"" # also notepad.exe would be OK
  # files that also happens to be text, some like "Microsoft.PowerShellScript.1" already open by default in the text editor
  $filetypes = @("batfile", "cmdfile", "regfile", "JSFile", "htafile", "WSFFile", "VBSFile", "VBEFile");
  foreach ($filetype in $filetypes) {
    $key = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\open\command)
    Set-ItemProperty -Path "$key" -name '(Default)' -Value "$editor `"%1`""
  # other interesting entries: "comfile", "Msi.Package", "exefile"

After applying those changes, double-clicking on foo.bat will open the file with the text editor, and not execute the content. Also from the right-click menu, the open option, will invoke the editor, while Run as administrator will execute the file as it used to.

For example, from powershell, it is possible to execute the file as earlier: & .\foo.bat, but & .\foo.reg will open it with the associated editor. It’s unclear to me why such a difference exists and how to make the behavior consistent, but it might not be that important.

If we want to execute somehow those file types from explorer (instead of opening a command or PowerShell prompt), it might be better to rename the default open key and add a new one

  $editor = "`"C:\Program Files\Notepad++\notepad++.exe`"" # also notepad.exe would be OK
  # files that also happens to be text, some like "Microsoft.PowerShellScript.1" already open by default in the text editor
  $filetypes = @("batfile", "cmdfile", "regfile", "JSFile", "htafile", "WSFFile", "VBSFile", "VBEFile");
  foreach ($filetype in $filetypes) {
    $key_run = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\Run);
    if (Test-Path $key_run) {
    $key = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\open);
    Rename-Item -Path "$key" -NewName "run";
    $key = (Join-Path $key command);
    New-Item $key;
    Set-ItemProperty -Path "$key" -name '(Default)' -Value "$editor `"%1`"";
  # other interesting entries: "comfile", "Msi.Package", "exefile"

And an even better approach might be to reuse the possibly already existing "edit" option from the context menu instead of using the same editor for everything

  $editor = "`"C:\Program Files\Notepad++\notepad++.exe`"" # also notepad.exe would be OK
  # files that also happens to be text, some like "Microsoft.PowerShellScript.1" already open by default in the text editor
  $filetypes = @("batfile", "cmdfile", "regfile", "JSFile", "htafile", "WSFFile", "VBSFile", "VBEFile");
  foreach ($filetype in $filetypes) {
    $key_run = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\Run);
    if (Test-Path $key_run) {
    $key_open = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\open);
    Rename-Item -Path "$key_open" -NewName "Run";
    $key_open_command = (Join-Path $key_open command);
    New-Item -Force $key_open_command;
    Set-ItemProperty -Path "$key_open_command" -name '(Default)' -Value "$editor `"%1`"";

    $key_edit_command = (Join-Path (Join-Path Registry::HKEY_CLASSES_ROOT $filetype) shell\edit\command);
    if (Test-Path $key_edit_command) {
      Copy-Item -Path "$key_edit_command" -Destination "$key_open"
  # other interesting entries: "comfile", "Msi.Package", "exefile"

This way, the default action to execute the file is saved in the context menu entry Run, while double-clicking on the file will open it with notepad++, or the default program for editing those files.

Similar changes can be made for other file types like .exe, .com, .msi and so on.

Why I did not use it in practice

Unfortunately, these settings do not control only the behavior of the file manager, but also the default behavior in many more contexts.

If a program tries to execute, for example, foo.cmd, it will now open it with the chosen editor, which is not the intended action.

It is possible to fix those issues by declaring with which program the script needs to be executed, for example, cmd /c myscript.cmd. As such commands might be buried inside programs, where the user has no chance to adapt them, it is not always possible to revert the unintended side effects.

Therefore, on a system where there is not much control over which software gest installed, changing default file associations might not be a good idea.

On the other hand, from a security perspective, it would be a big win if the user must make a distinct action for executing some code. It would not be possible anymore to execute some code accidentally while thinking to open a file.

Testing this approach further

Unfortunately, I do not use Windows as my primary system, and even if, my usage is hardly comparable to the usage of must Windows users, as I tend to disable some programs, I avoid installing a lot of software, and I hardly use an office suite, because I prefer simple text files.

The biggest advantage of this approach is that it can help a lot with social engineering attacks.

With those settings applied, instead of teaching people not to open anything they do not know, an attacker needs to convince someone to "execute", and not simply "open" a file. This is not impossible, but harder to exploit. And it is also easier to teach as a possible security issue: "Do not execute unknown program."/"Do not execute programs not approved by the administration."

How to fix a broken program

In scripting languages, like a .bat, .cmd or .ps1 file, instead of opening the file, you should invoke explicitely with what you want to execute it:

 foo.bat -> cmd /c foo.bat
 foo.cmd -> cmd /c foo.cmd
 foo.ps1 -> powershell -File foo.ps1
 foo.reg -> regedit foo.reg
 foo.msi -> msiexec /i foo.msi

In C (and languages that provides C- keybindings), it is possible to open and execute external files and program through a function, and they will behave like double-clicking in explorer.exe.

The functions are ShellExecute*.

HINSTANCE ShellExecuteA(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd);
HINSTANCE ShellExecuteW(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);


so if you are executing a .bat file from C with (just using ShellExecuteW as an example, similar adaptations needs to be done for the other variants)

ShellExecuteW(NULL, L"open", L"C:\\foo.bat", NULL, NULL, SW_SHOWNORMAL);


ShellExecuteW(NULL, NULL, L"C:\\foo.bat", NULL, NULL, SW_SHOWNORMAL);

then the behavior of these programs depends on how the users open by default a .bat file, and will break with this hardening settings.

The correct approach would be to state that we want to execute the .bat file, and not do what the user normally does:

ShellExecuteW(NULL, L"open", L"cmd.exe", L"/C C:\\foo.bat", NULL, SW_SHOWNORMAL);

The solution is indeed similar to script files, but without source code, it’s hard to impossible to change a broken program.

At least requesting a fix, if the program is still maintained, should be easy.

For testing purposes, it is possible to create a simple wrapper around ShellExecute. (NOTE: For simplicity and brevity, I used the ANSI version to avoid compiler flags fiddling between the Microsoft compiler and GCC, using the WIDE version is left as an exercise for the reader.)

Currently a CMakeLists.txt like

cmake_minimum_required(VERSION 3.5)

project(shellexecute LANGUAGES C)
add_executable(shellexecute main.c)

is sufficient.

Notice that this is not production-ready code, as at least the parameter handling has known bugs, and if ShellExecuteA fails, some information except the error code could be provided to stderr.

#include <stdio.h>
#include <string.h>
#include <windows.h>

void help(const char* argv0) {
    puts("Usage:\n %s -operation <op> -parameters <param> -directory <dir> <file>\n\nAll parameters are optional\n", argv0);

int main(int argc, const char* const* argv) {
    if (argc % 2 != 0){
        puts("Wrong number of parameters!\n\n");
        return 1;
    const char* operation = NULL;
    const char* const file = argv[argc-1];
    const char* parameters = NULL;
    const char* directory = NULL;
    for(int i = 1; i != argc-1; i+=2) {
        if        (strcmp(argv[i], "-operation") == 0) {
            operation = argv[i+1];
        } else if (strcmp(argv[i], "-parameters") == 0) {
            parameters = argv[i+1];
        } else if (strcmp(argv[i], "-directory") == 0) {
            directory = argv[i+1];
        } else  {
            printf("Unknown parameter: %s\n\n", argv[i]);
            return 1;
    int res = (int) ShellExecuteA(NULL, operation, file, parameters, directory, SW_SHOWNORMAL);
    return res >= 32 ? 0 : res;

and try to see the difference between shellexecute foo.bat and shellexecute -parameters "/C foo.bat" cmd.exe.

Without those hardening settings, both commands should behave the same. With the hardening policy, the first should open the text editor, while the second will still parse and execute the script.

The same holds for shellexecute foo.reg and shellexecute -parameters "foo.reg" regedit.exe.

This, of course, works as long as the registry key open for exefile is unchanged. If this gets changed to showing some information about the executable instead of executing it, invoking ShellExecute* will show the information about the executable file.

There is no way about it, except for using another API.

The CreateProcess* family functions are those responsible for creating processes, and they do not depend on file types and program associations.

BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,  LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);
BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);

A simple wrapper, again, for simplicity and brevity, around the ANSI API, and again, without proper error handling:

#include <windows.h>
#include <stdio.h>

int main(int argc, char* const* argv) {
    if (argc != 2) {
        printf("Usage: %s [CommandLine]\n", argv[0]);
        return 1;

    STARTUPINFO si = {};
    char* cmdline = argv[1];
    if (!CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        return 1;
    WaitForSingleObject(pi.hProcess, INFINITE);

An example usage would be createprocess "cmd /c foo.cmd" or createprocess "regedit foo.reg", and it will execute the binary, regardless of what the user preference is.

Change file association for exefile

This is a bold move, as if some internal component of Windows uses the default file association for executing files, it might make the system unstable.

Of course, this is a limited issue if we change the file associations for single users, but I’m changing the default file association system-wide.

For simplicity, like to .cmdfile, I’ve associated notepad as the default program, but it might make sense to associate another program that explains how to execute the binary file, or why the file does not get executed with a double-click.

What broke, and what kept working

I began testing a minimal system installed in VirtualBox.

On login, following programs opened in notepad

  • Onedrive

  • Vboxtray

  • SecurityHealthSystray

I know, as I do it all the time, that Onedrive can be forcefully disabled, and I just learned that SecurityHealthSystray has no side effect apart from the system tray. So they are not necessary for the system to work correctly.

VBoxTray is actually necessary for some guest additions, and I was able to start it by hand (from cmd).

Notice that other programs in the system tray, like battery manager, appears normally.


  • win+r program.exe opens program.exe with notepad

  • Clicking on "Start" menu entries opens the selected program with notepad

  • "Change date, time, or number formats" as well as other "predefined programs" open rundll32 with notepad

  • from the control panel, trying to change adobe flash settings opens notepad

  • "ctrl+shift+esc" opens notepad instead of the task manager

  • starting task manager from the tray opens notepad

  • Chocolatey failed to install some packages (like google chrome and skype, the first exits with an error code, the second seems to hang)

  • Setups using squirrel will open notepad, even if the installation process ends successfully

  • Opera and Microsoft Teams install correctly, but on startup, notepad opens and that’s it, probably the launcher uses internally ShellExecute

  • installers that want to do something after reboot open notepad

  • PowerToys seems to have some issues, as it does not find net.core, even if it starts correctly

Notice that it seems to be impossible to start an executable directly from Firefox and Thunderbird, and this is normally a good thing, because we do not want to execute something downloaded the same way we open files, but we want to do a separate action.

What still worked

  • alt+f4 for invoking the dialog to shut down windows

  • win+e opens the file manager

  • double click on a file that is associated with a program, opens the selected file with the program (and not the program in notepad as I feared)

  • "run as administrator" works

  • "open file location" works (and does not open explorer.exe with notepad as I feared)

  • 7z menu entries seems to work

  • windows settings (new interface) opens normally, while old settings not always

  • updates seem to work/they got installed

  • win+r services.msc works (as it is not an *exe)

  • win+r %APPDATA% (and other paths) works, and also win+r shell:startup

  • Opening Microsoft Edge (the non-chrome based version, need to test see how the new behaves)

  • opening explorer from the taskbar works (but not explorer.exe in cmd or win+r or from file manager), the same does not hold for other pinned programs in the taskbar (I learned that effectively the file manager shortcut does not link to explorer.exe).

  • Icons like recycle bin, user home directory, "this pc" and "network" all work correctly

  • Most options from control panel can be opened without issues (as probably they do not invoke separate binaries, except for flash settings)

  • ctrl+alt+del, except for selecting task manager, which opens notepad

  • "Open PowerShell window here" works

  • calculator works (it is an UWP program, that behind the scenes executes a .exe file)

  • execute executable from task manager opens notepad

  • print opens the print dialog

  • virtual box guest additions (service started, mouse integration, shared folders, …​. the clipboard integration did not work until VBoxTray started)

  • properties dialog when right-clicking

  • "Create shortcut"

  • the run option appears and also works on .lnk files that point to executables

  • installing cygwin and starting programs from the Cygwin shell

  • Most chocolatey packages I tried to install installed correctly (7zip, adobereader, chocolatey, chromium, cmake, eraser, Firefox, intellijidea-community, libreoffice-fresh, mingw, msys2, neovim, notepadplusplus, open-shell, Opera, powertoys, qtcreator, sumatrapdf, sysinternals, teamviewer, thunderbird, vim, vlc, WhatsApp, zoom), and unless otherwise noted (like Opera) they opened correctly. I did not test extensively their functionalities.

  • qtcreator (invoking mingw compiler for compiling c and C++ programs, executing compiled programs and debugging)

  • PowerToys starts after logging in, the difference is that it uses the task scheduler instead of keys under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

Notice that most of the issues I had are also present when changing the file association for a single user, as most of those programs are started from the user context.

So, all in all, the system seems to work, except for some programs at startup and some windows utilities integrated into File Explorer. Hopefully, those issues get fixed, or at least I’ll be able to find a workaround.

Currently, the biggest limitation is the inability to start programs from the Start menu, as the "run" entry does not appear, and the fact that most startup programs will fail.

Alternate start menu

I tried using Open Shell, as maybe it provides some customization possibilities, this is what I’ve noticed:

  • clicking a menu entry of the last used programs opens notepad

  • right-clicking a menu entry does not show the custom run option

  • after pinning a program, the custom run option is shown

  • right-clicking the search result provides the custom run option

  • after clicking on "all program", there is the custom run option

I did not find any settings that could help me to always display the run option, or execute run instead of open if present. Maybe a feature request would get accepted, the current "fix" is not showing last used programs.

All other menu entries (pc locations, run, "help and support") seems to work correctly

Programs Startup issues

After installing Open-Shell, I noticed when rebooting the PC that it continued to work. This was strange since VBoxTray is executed at startup the same way. In both cases, it is a .exe file and they both have a registry entry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

By looking more closely I noticed the value for Open-Shell Start Menu is a REG_SZ, while VBoxTray has a REG_EXPAND_SZ.

Adapting the entries and converting them all to REG_SZ did not make any difference, unfortunately, it means I’ll have to search further.

Just after writing my notes down, I notice that I have a notepad window with the content of "StarMenu". Thus OpenShell gets executed by other means.

I feared there was no way around it, but after testing different programs I noticed that also PowerToys are executed at startup!

After a closer look, I noticed that it gets started through the task scheduler.

It is probably an overkill, but it works. I’ve deleted the registry entry for vboxtray and added it as a login task, and all integrations with VirtualBox worked again, withtou starting the progrma manually.

Would those hardening settings have helped?

To understand if such an approach can help, or is just a theoretical improvement from the security perspective, I’ve looked at how the most famous viruses spread.

As I do not have so many first-hand experiences with viruses, I searched for "famous" computer viruses.

The analysis is simple, as it is obvious that those mitigations will only help against specific threats: when the user thinks to open a file, but instead executes an executable.

Some of the described attacks could have been mitigated by blacklisting common double extensions or by showing file extension.

Preventing an attack might not be entirely possible, as the attacker can always convince, through social engineering, the user to rename the file.

For example, supposing file extensions are hidden but we have deployed the double-extension blacklist the attacker could claim that some windows version has problems visualizing the attachment, and the user should rename it without the file extension. Thus, if the attached file is Windows would display an error, but if the user renames it to foo.exe (the .exe is not shown in the File Manager), it gets executed without issues.

Also notice that instead of opening notepad, as I did for testing, it is possible to execute another program that shows some information about the executable, a help message that explains how to execute the file, provide an option to call the IT department, something like Windows Smartscreen but without sending executables to Microsoft, etc.

With this settings, the attacker needs to convince the user to left-click the executable, and click on run to view the file. This is a much higher bar than simply double-clicking (and eventually renaming) a file looking like a PDF and claiming that it is a very important document.

Of course if, for example, the mail client of web browser provides the option to open directly files, and uses internally Createprocess* instead of ShellExecute, this changes won’t help much. (and since both Firefox and Thunderbird open a downloaded executable with notepad, I’m happy that they do not blindly call Createprocess*) Nevertheless it reduces the attack surface, and it might be possible to convince the authors of those programs, if they exist, that executing files from unknown sources is a security hazard, and that in this context, CreateProcess is normally not what the user wants.


From Wikipedia:

CryptoLocker typically propagated as an attachment to a seemingly innocuous e-mail message, which appears to have been sent by a legitimate company. A ZIP file attached to an email message contains an executable file with the filename and the icon disguised as a PDF file, taking advantage of Windows' default behavior of hiding the extension from file names to disguise the real .EXE extension.

The attack would have been prevented. The user should have been convinced by the attacker to click on run, which is for executing files, and not for viewing content.


From Wikipedia:

On the machine system level, ILOVEYOU relied on the scripting engine system setting (which runs scripting language files such as .vbs files) being enabled and took advantage of a feature in Windows that hid file extensions by default, which malware authors would use as an exploit.

Just like CryptoLocker, this attack would have been prevented. Also because this is not a .exe file, but a .vbs file, it might be harder for the user to recognize that this is a file that would get executed (in the case file extensions are visible). AFAIK changing the default file associations for .vbs files had no unintended consequences in my environment.


From F-Secure:

Propagation (Peer-to-Peer)

The worm will look up form the Windows' Registry the value containing the users Kazaa shared folder, and it will copy itself to that location with a filename composed from the following list:


And extensions chosen from:



Propagation (email)


Attachments are composed combining the following names:


with the following extensions:


The ZIP file itself is not harmful when double-clicked. Inside the zip you have a copy of the worm, sharing the same file name as the .zip. For example, contains message.exe.

.bat, .cmd, .exe have been exaustively covered in this article.

.pif and .scr should be handled just like the first three formats (this is already covered in the article about disabling common double-extensions), even if I’m wondering if there are legitimate use-cases left and if maybe those files should completely get disabled.

The .zip file extension should not be touched, as those files are not executed. Also in the case of MyDoom, this file is innocuous, it’s the contained file, which is an executable, that is harmful, and will not get executed.

Thus also this worm would not have infected a hardened Windows system so easily.

Storm Worm

Again, for propagating the Also this attacker convinces the victim to open an attachment, which is not a document but an executable.

Similar to the previous viruses, the user needed to explicitly execute it.

Anna Kournikova

This virus relied on Windows explorer hiding the file extension.


Contrary to the other viruses, Slammer used a system vulnerability, those hardening settings would not have prevented an infection

On the bright side, if the offending program (SQL Server Monitor) gets fixed, there is no need to do anything.

Still, a robust system must be able to prevent attacks that take advantage of (possibly) unknown vulnerabilities, as buffer overflows.

Mitigation techniques like address space layout randomization (ASLR), are, finally, enabled by default on Windows 10, while on older Windows version one had to enable it manually.


Like Slammer, and contrary to the other viruses, Stuxnet used different System vulnerabilities, I believe that those hardening settings would not have stopped it’s spreading.

Code Red

Code Red, like slammer, attacked a running program through a buffer overflow.


This virus, as far as I understood, abused the macro system of office documents.

Those hardening guides would thus not have helped against this virus.


This program took advantage of a buffer overflow.


It seems that this approach would have avoided the diffusion of different worldwide viruses in their current form.

Other attacks would have come through unchanged, those that relied on

  • system vulnerabilities

  • vulnerabilities of a running program

  • embedded scripts inside documents

Alternative approaches

This journey began because I dislike the default behavior of the file manager, which does not provide the option not to execute programs on a double-click, while another file manager (mainly in the GNU/Linux World) permits it.

There are alternative file managers, like explorer++, which does not permit (yet) to change this particular behavior.

I did not like this approach for the same reason I do not like the approaches to replace notepad.exe.

It has consequences that are not always easy to foresee.

Just look at all the issue changing the default file association for .exe has.

My guess is that replacing explorer.exe with something else will probably have all the same issues. And what if a future Windows update breaks the program? Granted, most of those issues are also present when changing the file association, but at least it’s easy to oversee the necessary changes.

Those are the same reason I’ve never tried Open Shell before, but being unable to start a program from the start menu makes this hardening configuration a no-go for most use-cases.

I hope that changing file association is seen as a configuration that should be supported, and as it should be "easy" to fix most programs, it does not encounter much resistance.

While arguing that third party programs break other programs tend to give as a response that the fault is on the other side, and it might not always be wrong.

Another option might be that Microsoft realizes the potential benefit of such a configuration for the file manager has, without affecting (for the sake of compatibility) other programs. This would be a win-win situation, as most viruses rely on social engineering will have a much harder time, and nearly no program needs any update or hack to continue to work.


Maybe changing the default file association is not the right way to go, even if the system seems usable.

Programs that rely on starting automatically might have a hard time (like the guest additions of Virtualbox), so it might not be a viable approach unless the end-user has some time to fiddle with single program settings.

Replacing the file manager, just like using open-shell for having a working start menu, is probably a more stable approach. But it won’t save all those attacks that manage to convince the end-user to open an attachment where the mail client (or browser) gives the possibility to open a file directly, which most do.

I was also hoping for a solution that does not require installing any additional software, as I did not want to add new functionalities.

I would thus not recommend for less tech-savvy people to change the association of .exefile as it has some issues (most notably startup programs) while changing file associations for scripts should not be too problematic (as programs generally do not rely that much on those), and most issues should be easily solvable upstream and downstream.

After all, I’m glad that everything nearly worked, I’m going to see what happens and how projects react to such bug reports.

Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.