Showing posts with label windows. Show all posts
Showing posts with label windows. Show all posts

Thursday, May 29, 2025

Toast Notifications from PowerShell or C++

I’m currently working on a project that involves sending alerts and notifications to users on Windows 11 systems.

During development, I learned that--for local testing purposes--it’s possible to generate toast notifications using built-in PowerShell functionality. Specifically, the ToastNotificationManager and CreateToastNotifier APIs make it straightforward to display dead simple, native notifications without any external dependencies.

$body = 'Hello from PowerShell! Behold, a toast notification.'

$toastXml = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText01)

$toastXml.SelectSingleNode('//text[@id="1"]').InnerText = $body

$appId = 'App'

$toast = [Windows.UI.Notifications.ToastNotification]::new($toastXml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)

Of course, you can also set up toast notifications with C++ in a Win32 shell environment, too. But Windows will only send toast notifications for apps that have both a shortcut in the start menu, and an AppUserModelID property within that shortcut!

To do this, we can also use a PowerShell script to:

1: Create a Windows shortcut .lnk file
2: Set the AppUserModelID property on that shortcut
3: Save it to disk

First, we set up our shortcut path, the target binary path, and define an AppUserModelID, then use PowerShell's built-in .NET to include functionality for interop services and COM objects.

So, we instantiate a new COM object using the correct interface GUID (which you can find on Pinvoke.net), and create a pointer to it with var link = (IShellLinkW)new ShellLink();. Next, we cast it to IPropertyStore so we can set properties: var store = (IPropertyStore)link; followed by store.SetValue(ref key, ref pv);. Then we set up the required COM structs — PROPERTYKEY to identify the property, and PROPVARIANT to hold the value. And once all the properties are set, we save the shortcut to disk via (IPersistFile)link;.


$ShortcutPath = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\ToastyApp.lnk"
$TargetPath = "C:\Path\To\App.exe"
$AppUserModelID = "App.ID"

Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
class ShellLink {}

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
interface IShellLinkW {
    void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);
    void GetIDList(out IntPtr ppidl);
    void SetIDList(IntPtr pidl);
    void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszName, int cchMaxName);
    void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
    void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszDir, int cchMaxPath);
    void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
    void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszArgs, int cchMaxPath);
    void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
    void GetHotkey(out short pwHotkey);
    void SetHotkey(short wHotkey);
    void GetShowCmd(out int piShowCmd);
    void SetShowCmd(int iShowCmd);
    void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszIconPath, int cchIconPath, out int piIcon);
    void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
    void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
    void Resolve(IntPtr hwnd, int fFlags);
    void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}

[ComImport]
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore {
    void GetCount(out uint cProps);
    void GetAt(uint iProp, out PROPERTYKEY pkey);
    void GetValue(ref PROPERTYKEY key, out PROPVARIANT pv);
    void SetValue(ref PROPERTYKEY key, ref PROPVARIANT pv);
    void Commit();
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct PROPERTYKEY {
    public Guid fmtid;
    public uint pid;
}

[StructLayout(LayoutKind.Explicit)]
struct PROPVARIANT {
    [FieldOffset(0)]
    public ushort vt;
    [FieldOffset(8)]
    public IntPtr pszVal;

    public static PROPVARIANT FromString(string value) {
        var pv = new PROPVARIANT();
        pv.vt = 31; // VT_LPWSTR
        pv.pszVal = Marshal.StringToCoTaskMemUni(value);
        return pv;
    }
}

public static class ShellLinkHelper {
    static readonly Guid PKEY_AppUserModel_ID_fmtid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");
    const uint PKEY_AppUserModel_ID_pid = 5;

    public static void CreateShortcut(string shortcutPath, string exePath, string appId) {
        var link = (IShellLinkW)new ShellLink();
        link.SetPath(exePath);

        var store = (IPropertyStore)link;
        var key = new PROPERTYKEY() { fmtid = PKEY_AppUserModel_ID_fmtid, pid = PKEY_AppUserModel_ID_pid };
        var pv = PROPVARIANT.FromString(appId);

        store.SetValue(ref key, ref pv);
        store.Commit();

        var file = (IPersistFile)link;
        file.Save(shortcutPath, false);
    }
}
"@ -Language CSharp

# Call helper from PowerShell
[ShellLinkHelper]::CreateShortcut($ShortcutPath, $TargetPath, $AppUserModelID)
Write-Host "Shortcut created at $ShortcutPath with AppUserModelID = $AppUserModelID"

With our shortcut and AppID properly set up, we can use the following C++ for a bare bones Toast Notification test. After compiling our C++ program below, we will return to the .lnk shortcut we created with PowerShell at %APPDATA%\Microsoft\Windows\Start Menu\Programs\ToastyApp.lnk, to make one small change--configuring its properties to point to wherever our compiled C++ binary is.

To ensure the following C++ code compiles, you will need to open Visual Studio Community and click Projects -> Properties -> Linker -> Input and manually add "runtimeobject.lib" to your additional dependencies.

Additionally, this build only compiles using the ISO C++17 Standard. C++17 is mandatory for building the code below. You may configure your project to use the standard within the C/C++ -> Language selector in the same Project Properties dialogue as mentioned above.


#include <windows.h>
#include <wrl/client.h>
#include <wrl/wrappers/corewrappers.h>
#include <windows.ui.notifications.h>
#include <winrt/base.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.UI.Notifications.h>
#include <string>
#include <iostream>
#include <shobjidl.h>
#pragma comment(lib, "Shell32.lib")
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace winrt;
using namespace winrt::Windows::Data::Xml::Dom;
using namespace winrt::Windows::UI::Notifications;

int main() {
    RoInitialize(RO_INIT_SINGLETHREADED);

    // Set AppUserModelID
    SetCurrentProcessExplicitAppUserModelID(L"Your.App.ID");

    // Create Toast Notifier
    auto toastNotifier = ToastNotificationManager::CreateToastNotifier(L"Your.App.ID");

    // Create XML content
    XmlDocument toastXml;
    try {
        std::wstring xmlString = L"Hello from C++!";
        toastXml.LoadXml(xmlString);
    }
    catch (const hresult_error& ex) {
        std::wcerr << L"Failed to load XML: " << ex.message().c_str() << std::endl;
        RoUninitialize();
        return 1;
    }

    // Create Toast Notification
    auto toast = ToastNotification(toastXml);

    // Show Toast
    toastNotifier.Show(toast);


    RoUninitialize();
    return 0;
}

Source code for Toast Notifications on Windows 11.

Friday, May 16, 2025

A Security Trilemma

Playing around with writing malware proof-of-concepts, running red and blue team simulations in my computer lab against Windows Home edition, I feel sort of bad for Windows Home users.

Such users probably constitute the majority of Microsoft's userbase. And most security mitigations for that edition are not exactly effective against attackers.

Commercial-grade versions of Windows and commercial-grade security products are a different story in some circumstances. Commercial editions of Windows include a lot of nice mitigations and security features. But I think it's kind of an economic trilemma.

You have three potential strategies for security--and a few different potential tradeoffs. You can only optimize for two out of three.

  • If it's cheap and convenient, it won't be secure.
  • If it's cheap and secure, it won't be convenient.
  • If it's secure and convenient, it won't be cheap.

There are certainly exceptions to this model, though. For example, think about open-source, end-to-end encrypted messaging apps. Some of those feel like very unlikely tail distributions, where, to some extent, the solutions provide all of the above: they're cheap, secure, and convenient.

Monday, March 10, 2025

Subshells in Powershell

Previously, I wrote a post about how it's possible to create a "subshell" in Windows analogous to the subshell feature available in Bash on Linux—because Microsoft Windows doesn't actually have native subshell capability the same way that Linux does. The script below is an improvement on the same previous method of using the .NET System.Diagnostics trick. But this new version correctly redirects the standard output:

$x = New-Object System.Diagnostics.ProcessStartInfo
$x.FileName = "cmd.exe"
$x.Arguments = "/c echo %PATH%"
$x.UseShellExecute = $false
$x.RedirectStandardOutput = $true  
$x.EnvironmentVariables.Remove("Path")
$x.EnvironmentVariables.Add("PATH", "C:\custom\path")
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $x
$p.Start() | Out-Null
$output = $p.StandardOutput.ReadToEnd()
$p.WaitForExit()
Write-Output $output

Real-World Example

$customPath2 = "C:\custom\path\2"

$data = @{
    Path = $customPath2  
    Timestamp = Get-Date
    ProcessID = $PID  
}

$x = New-Object System.Diagnostics.ProcessStartInfo
$x.FileName = "cmd.exe"
$x.Arguments = "/c echo %PATH%"
$x.UseShellExecute = $false
$x.RedirectStandardOutput = $true
$x.RedirectStandardError = $true

$data["SubshellError"] = $stderr

$x.EnvironmentVariables.Remove("Path")
$x.EnvironmentVariables.Add("PATH", $customPath2)

$p = New-Object System.Diagnostics.Process
$p.StartInfo = $x
$p.Start() | Out-Null

$output = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd() 
$p.WaitForExit()

$data["SubshellOutput"] = $output
$data["SubshellError"] = $stderr

$data
$data

Name                           Value
----                           -----
ProcessID                      11852
Path                           C:\custom\path\2
SubshellOutput                 C:\custom\path\2...
SubshellError
Timestamp                      3/10/2025 7:05:01 PM

Tuesday, August 29, 2023

Life of a Windows Process

In a previous post, I covered a bit about how Windows Processes are initialized. But how does process creation work in Windows? Let's explore a bit further into Windows processes.

Thursday, August 24, 2023

Subshells in Linux (and Windows)

Or rather, subshells in Bash and Powershell. A subshell functions as a sort of isolated environment for executing commands, creating a subprocess or child process within the parent shell.

Wednesday, August 23, 2023

Portable Executable Format and Structured Exception Handling

The Portable Executable (PE) file format is the native file format for executable and binary files in the Microsoft Windows ecosystem.

Tuesday, August 22, 2023

Processes and Call Stacks

In Windows, our process information looks something like this.

Wednesday, August 16, 2023

Windows Process Initialization

Most code on Windows runs in user-space. This means that, when we first run a program, it needs to perform some rituals to successfully callback into the kernel.

Tuesday, August 15, 2023

Windows

From "user space and system space":

Windows gives each user-mode application a block of virtual addresses. This is known as the user space of that application. The other large block of addresses, known as system space or kernel space, cannot be directly accessed by the application.

More or less everything in the user space talks to NTDLL.DLL to make appropriate calls to hand off work to the Windows kernel, effectively context-switching. While some other software calls are diverted to libraries such as:

  • MSVCRT.DLL: the standard C library
  • MSVCP*.DLL: the standard C++ library
  • CRTDLL.DLL.: library for multithreaded support
  • All code that runs in kernel mode shares a single virtual address space. Therefore, a kernel-mode driver isn't isolated from other drivers and the operating system itself. If a kernel-mode driver accidentally writes to the wrong virtual address, data that belongs to the operating system or another driver could be compromised. If a kernel-mode driver crashes, the entire operating system crashes.

    Windows Architecture Overview


    User Space:
    • System Processes
      • Session manager
      • LSASS
      • Winlogon
      • Session Manager
    • Services
      • Service control manager
      • SvcHost.exe
      • WinMgt.exe
      • SpoolSv.exe
      • Services exe
    • Applications
      • Task Manager
      • Explorer
      • User apps
      • Subsystem DLLs
    • Environment Subsystems
      • Win32
      • POSIX
      • OS/2
    Kernel Space:
    • Kernel Mode
      • Kernel mode drivers
      • Hardware Abstraction Layer (HAL)
    • System Threads
      • System Service Dispatcher
      • Virtual Memory
      • Processes and Threads
    • Security
      • Security Reference Monitor
    • Device & File Systems
      • Device & File System cache
      • Kernel Drivers
      • I/O manager
      • Plug and play manager
      • Local procedure call
      • Graphics drivers
    • Hardware Abstraction Layer (HAL)
      • Hardware interfaces

    Windows Ecosystem

    OK, so of course the real question is, how do we interact with Windows ecosystem to actually do things? Like other software ecosystems, we have some set of libraries which we can use to implement functions which return values. Consider the CreateFileA API. Per Microsoft's documentation, here is the prototype for this interface:

    HANDLE CreateFileA(
      [in]           LPCSTR                lpFileName,
      [in]           DWORD                 dwDesiredAccess,
      [in]           DWORD                 dwShareMode,
      [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
      [in]           DWORD                 dwCreationDisposition,
      [in]           DWORD                 dwFlagsAndAttributes,
      [in, optional] HANDLE                hTemplateFile
    );

    A file name, access, share mode, security attributes (optional), a disposition, flags, and a template (optional). We'll also use printf and scanf to read some inputs. First we'll get a file path, and then a name for our new file. We'll concatenate the two into a full path, and call it with hFile on the CreateFileA API. And we'll define a constant to point to the content we wish to write to our text file.

    We'll use FormatMessageA, as listed in Microsoft's documentation, to obtain possible error messages in case of failure. And check for errors against the WriteFile API with our if(!WriteFile statement - that is, if our write fails, let us know that it failed, close our handle, and return a fail status. Else, if our file has been created, close our handle and let us know by printing a message and the conjoined fullPath of our file, then exit cleanly with 0:

    #include <stdio.h>
    #include <windows.h>
    
    int main() {
        char path[MAX_PATH];
        char filename[MAX_PATH];
        HANDLE hFile;
        DWORD bytesWritten;
    
        // Get user input for path and filename
        printf("Enter the path: ");
        scanf("%s", path);
    
        printf("Enter the filename: ");
        scanf("%s", filename);
    
        char fullPath[MAX_PATH];
        snprintf(fullPath, sizeof(fullPath), "%s\\%s", path, filename);
    
        hFile = CreateFileA(fullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    
        if (hFile == INVALID_HANDLE_VALUE) {
            DWORD error = GetLastError();
            LPVOID errorMsg;
            FormatMessageA(
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                error,
                0, // Default language
                (LPSTR)&errorMsg,
                0,
                NULL
            );
            printf("Failed to create the file: %s\n", (char*)errorMsg);
            LocalFree(errorMsg);
            return 1;
        }
    
        const char* content = "Noted";
        if (!WriteFile(hFile, content, strlen(content), &bytesWritten, NULL)) 
    {
            printf("Failed to write to the file.\n");
            CloseHandle(hFile);
            return 1;
        }
    
        CloseHandle(hFile);
    
        printf("File created successfully: %s\n", fullPath);
    
        return 0;
    }
    

    Just a Prologue

    After compiling, we can test and observe this to make some observations about Windows system behavior. The prologue, stack unwinding, and it's use of undocumented calls, which happen abstracted and hidden away from the user.

    C:\Users\User\Downloads>.\createfile.exe
    Enter the path: C:\Users\User
    Enter the filename: ts
    File created successfully: C:\Users\User\text.txt

    For example, when we first run our program, we immediately observe calls to NTDLL, which negotiates a thread and begins the work of running and executing our file. We can see this here:

    0, ntdll.dll!RtlUserThreadStart

    After hitting the first return, we can pull a stack trace to see our thread has now unwound a bit, and we've initiated contact with the kernel at KERNEL32.DLL, which is home to x64 function calls.

    0, ntdll.dll!NtWaitForWorkViaWorkerFactory+0x14
    1, ntdll.dll!RtlClearThreadWorkOnBehalfTicket+0x35e
    2, kernel32.dll!BaseThreadInitThunk+0x1d
    3, ntdll.dll!RtlUserThreadStart+0x28

    During this time, we see multiple calls to LdrpInitializeProcess which initialize the structures in our process. Then we see our BaseThreadInitThunk, a similar kernel mode callback like LdrInitializeThunk, and a call to RtlNtImageHeader to get the image headers for our process.

    Skipping forward a bit, later, when we enter our path and filename, those values are moved into the registers, like so. And following this, many cmp comparisons are made, checking the path to see that it is ok:

    mov rbx,qword ptr ss:[rsp+70] | __pioinfo
    mov rsi,qword ptr ss:[rsp+78] | Users\\User\n\n 

    After a very long dance handling the file path, we finally see assembly calls involving our filename emerge. The filename is effectively loaded into a register like so:

    push rbx                        | rbx:&"ts\n\nsers\\User\n\n"
    sub rsp,20                      |
    mov rbx,rcx                     | rbx:&"ts\n\nsers\\User\n\n"
    lea rcx,qword ptr ds:[<_iob>]   | 
    cmp rbx,rcx                     | 
    jb msvcrt.7FFF040306F5          |
    lea rax,qword ptr ds:[7FFF04088 |
    cmp rbx,rax                     | rbx:&"ts\n\nsers\\User\n\n"
    ja msvcrt.7FFF040306F5          |

    Much later on when our file is created, we see that this file creation likely could have been logged by Event Tracing For Windows.

    call createfile.7FF60B1C6D00    |
    jmp createfile.7FF60B1C860C     |
    sub r10d,2                      |
    mov rcx,qword ptr ds:[r13]      | rcx:"ts", [r13]:"ts"
    lea rbx,qword ptr ds:[r13+8]    | [r13+8]:EtwEventWriteTransfer+260

    And after many assembly instructions later, we finally see our text get the lea, load effective address, containing our message for the text file we're writing. "Noted":

    call rax                        |
    mov eax,1                       |
    jmp createfile.7FF60B1C1743     |
    lea rax,qword ptr ds:[7FF60B1D1 | 00007FF60B1D104F:"Noted"
    mov qword ptr ss:[rbp+300],rax  |
    mov rax,qword ptr ss:[rbp+300]  |

    And a syscall for NtWriteFile:

    mov r10,rcx                     | NtWriteFile
    mov eax,8                       |
    test byte ptr ds:[7FFE0308],1   |
    jne ntdll.7FFF055AEE55          |
    syscall                         |
    ret                             |

    And lastly, our call to closeHandle:

    mov rax,qword ptr ds:[<CloseHandle>] | rax:CloseHandle
    call rax                             | rax:CloseHandle

    Though, much more happens - this is the gist of it.

    Most of the stuff in the Microsoft API is well documented. Some of the code is even partially compatible with Unix systems. But other things in the Microsoft ecosystem however, are not officially documented. Microsoft gives us some public APIs. Some of which are just wrappers that call undocumented features under the hood. In a future post, we'll use an undocumented API to talk to the Windows kernel.

Thursday, June 22, 2023

Agent Tesla Spearphishing

More .NET malware analysis. In this post, we'll be analyzing another spearphishing email, this time masquerading as a mathematics paper exploiting CVE-2017-11882. And we'll generate some Yara rules for detecting it.

Sunday, June 11, 2023

Sysmon Custom Templates for Event Tracing

A few days ago I learned it's possible to modify Window's Sysmon to enable tailored Windows Event Tracing. By simply providing Sysmon with an XML ruleset, we can generate custom alerts and automatically filter for and tag particular events. For example, the following rule would alert us of proxy code execution using .NET's C# compiler, csc.exe:

<!-- MITRE ATT&CK TECHNIQUE: Obfuscated Files or Information: Compile After Delivery -->
<Rule name="Attack= T1127.001,Technique=Trusted Developer Utilities Proxy Execution ,Tactic=Defnse Evasion,DS=Process: Process Creation,Level=4,Alert=CSC Suspicious Location,Risk=60" groupRelation="and">
<Image condition="image">csc.exe</Image>
<CommandLine condition="contains any">\AppData\;\Windows\Temp\</CommandLine>

The above is a snippet from the default template published by @SwiftOnSecurity. But various orgs have made their own forks. And it can be further modified per use-case. A super useful customization for malware analysis, forensics, and anything involving Windows.

Saturday, January 21, 2023

Mm .. Malware Analysis

TL;DR: Analysis of malspam potentially targeting an organization. C#/.NET binary using KoiVM, process hollowing, and abusing vulnerable procexp152.sys driver.

Using Python To Access archive.today, July 2025

It seems like a lot of the previous software wrappers to interact with archive.today (and archive.is, archive.ph, etc) via the command-line ...