Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT NachrichtenDNA-Computer rechnet ohne Strom und bricht Tempo-Rekord(23.09.2026 um 13:39 Uhr)
IT NachrichtenApple arbeitet an Fitnesstracker ohne Display(23.09.2026 um 12:37 Uhr)
IT NachrichtenDiese Serien scheiterten mit Staffel 2(23.09.2026 um 13:20 Uhr)
IT NachrichtenDNA-Computer rechnet ohne Strom und bricht Tempo-Rekord(23.09.2026 um 13:39 Uhr)
IT NachrichtenApple arbeitet an Fitnesstracker ohne Display(23.09.2026 um 12:37 Uhr)
IT NachrichtenDiese Serien scheiterten mit Staffel 2(23.09.2026 um 13:20 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

I Built a Command-Line Spy++ to Log Windows Messages. Here's How.

If you've ever done serious Windows development, you've probably used or at least heard of Spy++. It's a classic tool, bundled with Visual Studio, that lets you see the torrent of window messages flying around an application in real-time.…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

If you've ever done serious Windows development, you've probably used or at least heard of Spy++. It's a classic tool, bundled with Visual Studio, that lets you see the torrent of window messages flying around an application in real-time. It's an indispensable utility for debugging stubborn UI problems.



But it has one major limitation that always bothered me: you can't easily export the message stream.



You can watch messages go by, but you can't save them to a file for later analysis, comparison, or to use as the basis for an automation script. I wanted a tool that could do just that—a "Spy++ for the terminal."



So, I built NotSpy++. This is the story of how it works, the problems I solved along the way, and where it could go next.






The Goal: A "Message Logger"



The mission was simple:




  1. Target any running application by its Process ID (PID).

  2. Hook the process and start capturing its window messages.

  3. On exit (Ctrl+C), dump all captured messages to a file.

  4. Offer both a high-performance binary format and a human-readable JSON format.






How It Works: The Architecture



You can't just "read" messages from another process. Windows security boundaries prevent that. To get inside, you need a legitimate mechanism to execute your own code within the target's memory space. For this, we use the Win32 API's global hooks.



The architecture has two main components:




  1. The Controller (NotSpy.exe): This is the user-facing command-line tool. It finds the target process, sets up the communication channel, and installs the hook.

  2. The Hook DLL (Hook.dll): This is a small dynamic library that Windows automatically injects into the target application's process when the hook is set. This DLL contains the code that actually runs alongside the target, capturing its messages.



The flow looks like this:




[ You ] ---> Runs `NotSpy64.exe <PID>`
|
+-----> [ NotSpy64.exe (Controller) ]
| 1. Creates a Shared Memory block.
| 2. Gets a handle to the Hook DLL and the address of the hook procedure within it.
` 3. Calls SetWindowsHookEx(..., hookProcAddress, dllHandle, threadId)
|
V
[ Target Process (e.g., notepad.exe) ]
| 1. The Windows OS sees the hook, finds `Hook64.dll` on disk, and loads it into Notepad's process space.
| 2. The Hook Procedure inside the DLL starts executing on every message.
| 3. It writes the captured message data into the Shared Memory block.
`--------------------------------------------------+
|
<-- Reads data from Shared Memory on exit <--+









The Communication: Shared Memory



The hook DLL is running inside the target process, and the controller is running in its own. How do they talk? Standard output (printf, cout) from the DLL won't go to your console.



The classic solution is Shared Memory. The controller uses CreateFileMapping and MapViewOfFile to create a block of memory that can be accessed by both processes. The DLL gets a handle to this same memory block and writes the MessageInfo structs directly into it.




// From Shared.h - The data structure we pass back and forth
const int MAX_MESSAGES = 100000; // Buffer for 100k messages

struct MessageInfo {
char type; // 'P' for Pre-processing, 'R' for Result (post-processing)
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
LRESULT result; // Only used for post-processing messages
};

struct SharedData {
int count; // How many messages have we captured?
MessageInfo messages[MAX_MESSAGES];
};









Hurdles & Evolutions



Building this wasn't a straight line. Here are some of the key challenges that shaped the final program.






Challenge 1: The 32-bit vs. 64-bit Divide



This was the first major roadblock. I compiled my tool as 64-bit and tried to spy on a 32-bit application. It failed silently. SetWindowsHookEx returned NULL, and GetLastError() wasn't much help. Why?



The Golden Rule: The architecture of your injected DLL must match the architecture of the target process.




  • A 32-bit process can only load 32-bit DLLs.

  • A 64-bit process can only load 64-bit DLLs.



My 64-bit Controller was trying to use a hook that would require loading a 64-bit DLL into a 32-bit process. Windows correctly forbids this.



The Solution: Compile everything twice. The project now produces two full sets of binaries:





  • NotSpy32.exe & Hook32.dll for spying on 32-bit targets.


  • NotSpy64.exe & Hook64.dll for spying on 64-bit targets.



The C++ source code is identical for both; we just use the different Visual Studio command prompts to compile for each architecture.



At this point i could already log Messages from the target process, but i wanted to get more info from the stream




Hook installed. Capturing messages...
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3943046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3943062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3944031,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3944062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3945046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3945062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3946046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3946062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3947046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3947062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":14,"message_name":"MSG_ID_14","time":3947359,"wParam":0},
{"hwnd":656982,"lParam":50126892,"message_id":13,"message_name":"MSG_ID_13","time":3947359,"wParam":7},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3948046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3948046,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3949046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3949078,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3950046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3950062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3951046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3951062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3952046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3952062,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":14,"message_name":"MSG_ID_14","time":3952375,"wParam":0},
{"hwnd":656982,"lParam":50126892,"message_id":13,"message_name":"MSG_ID_13","time":3952375,"wParam":7},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3953031,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3953031,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3954031,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3954046,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3955046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3955046,"wParam":0},
{"hwnd":919308,"lParam":50131860,"message_id":70,"message_name":"MSG_ID_70","time":3955937,"wParam":0},
{"hwnd":329592,"lParam":50131860,"message_id":70,"message_name":"MSG_ID_70","time":3955937,"wParam":0},
{"hwnd":656982,"lParam":50131860,"message_id":70,"message_name":"MSG_ID_70","time":3955937,"wParam":0},
{"hwnd":919308,"lParam":50131860,"message_id":71,"message_name":"MSG_ID_71","time":3955937,"wParam":0},
{"hwnd":329592,"lParam":50131860,"message_id":71,"message_name":"MSG_ID_71","time":3955937,"wParam":0},
{"hwnd":656982,"lParam":50131860,"message_id":71,"message_name":"MSG_ID_71","time":3955937,"wParam":0},
{"hwnd":919308,"lParam":7720,"message_id":28,"message_name":"MSG_ID_28","time":3955937,"wParam":1},
{"hwnd":329592,"lParam":7720,"message_id":28,"message_name":"MSG_ID_28","time":3955937,"wParam":1},
{"hwnd":656982,"lParam":7720,"message_id":28,"message_name":"MSG_ID_28","time":3955937,"wParam":1},
{"hwnd":656982,"lParam":0,"message_id":134,"message_name":"MSG_ID_134","time":3955937,"wParam":1},
{"hwnd":656982,"lParam":0,"message_id":6,"message_name":"MSG_ID_6","time":3955937,"wParam":1},
{"hwnd":329592,"lParam":656982,"message_id":647,"message_name":"MSG_ID_647","time":3955937,"wParam":23},
{"hwnd":656982,"lParam":-1073741809,"message_id":641,"message_name":"MSG_ID_641","time":3955937,"wParam":1},
{"hwnd":329592,"lParam":1073741824,"message_id":641,"message_name":"MSG_ID_641","time":3955937,"wParam":1},
{"hwnd":919308,"lParam":1073741824,"message_id":641,"message_name":"MSG_ID_641","time":3955937,"wParam":1},
{"hwnd":656982,"lParam":0,"message_id":642,"message_name":"MSG_ID_642","time":3955937,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":7,"message_name":"WM_SETFOCUS","time":3955937,"wParam":0},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3956046,"wParam":2},
{"hwnd":656982,"lParam":0,"message_id":127,"message_name":"MSG_ID_127","time":3956046,"wParam":0},
{"hwnd":656982,"lParam":49480547,"message_id":132,"message_name":"MSG_ID_132","time":3956109,"wParam":0},
{"hwnd":656982,"lParam":49415011,"message_id":132,"message_name":"MSG_ID_132","time":3956109,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956109,"wParam":656982},
{"hwnd":656982,"lParam":48890728,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":48890728,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":48104301,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":48104301,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":47514482,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":47514482,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":47055733,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":47055733,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":46465915,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":46465915,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":45482882,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":45220741,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956125,"wParam":656982},
{"hwnd":656982,"lParam":45089670,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":44827527,"message_id":132,"message_name":"MSG_ID_132","time":3956125,"wParam":0},
{"hwnd":656982,"lParam":33554433,"message_id":32,"message_name":"MSG_ID_32","time":3956140,"wParam":656982},









Challenge 2: Making the Output Truly Useful



A raw binary dump is fast but unreadable. For debugging, I needed something I could actually look at. This led to adding a JSON output option.



When the user runs NotSpy.exe 1234 --json, the program sets a flag. On exit, instead of just dumping the messages.dat file, it iterates through the captured MessageInfo structs and builds a beautiful JSON array using the fantastic nlohmann/json single-header library. This also includes mapping the integer message codes (like 512) to their string names (WM_MOUSEMOVE) for maximum readability.



This gives the user the best of both worlds:





  • messages.dat: Super-fast, low-overhead binary capture. Perfect for performance-critical logging or for a replay tool to read.


  • messages.json: Human-readable, detailed output. Perfect for manual inspection and analysis.




[
{
"type": "P",
"hwnd": 131362,
"message_id": 512,
"message_name": "WM_MOUSEMOVE",
"wParam": 0,
"lParam": 12583546,
"decoded_params": {
"x": 602,
"y": 192
}
},
{
"type": "R",
"hwnd": 131362,
"message_id": 132,
"message_name": "WM_NCHITTEST",
"wParam": 0,
"lParam": 12583546,
"result": 1,
"decoded_params": {
"x_screen": 602,
"y_screen": 192,
"result_name": "HTCLIENT"
}
}
]









How to Use It



The project is available on GitHub: https://github.com/qwerty32123/NotSpy



You can either build it yourself or grab a pre-compiled version from the Releases page.



1. Build (from source):

You'll need Visual Studio's command-line tools.




REM --- For 32-bit ---
cl /LD src\Hook.cpp src\Hook.def user32.lib /Fe:Hook32.dll
cl /EHsc /Iinclude src\NotSpy.cpp /Fe:NotSpy32.exe user32.lib gdi32.lib

REM --- For 64-bit ---
cl /LD src\Hook.cpp src\Hook.def user32.lib /Fe:Hook64.dll
cl /EHsc /Iinclude src\NotSpy.cpp /Fe:NotSpy64.exe user32.lib gdi32.lib






2. Run:

Find the PID of your target app from Task Manager. Make sure to check its architecture (in the Details tab, right-click the columns header, select "Architecture").




# Spy on a 64-bit app and save to binary (default)
NotSpy64.exe 12345

# Spy on a 32-bit app and save to JSON
NotSpy32.exe 9876 --json






Now, just use the target application. Move the mouse, click, type—all the messages are being logged. When you're done, hit Ctrl+C in the terminal to stop and save the file.






The Future: What's Next?



This tool is functional, but it's also a great foundation for more features. Here are some ideas I've been thinking about:




  • Message Filtering: Allow the user to specify which messages to include or exclude (e.g., --ignore=WM_MOUSEMOVE) to reduce log file size.

  • Real-time Streaming: Instead of saving on exit, stream the JSON output to the console in real-time.

  • A Simple TUI: Use a library like ncurses or FTXUI to build a Text-based User Interface that looks more like the classic Spy++.

  • An "All-in-One" Launcher: This is the big one. A single launcher (NotSpy.exe) that detects the target process's architecture and automatically runs the correct NotSpy32.exe or NotSpy64.exe helper process. This would eliminate the need for the user to manually check the target's architecture, vastly improving usability.



This project was a fantastic dive into the depths of the Win32 API, and it resulted in a tool I've already found truly useful. If any of these ideas sound interesting, I'd love to see contributions!



Check out the full source code and releases on GitHub:

https://github.com/qwerty32123/NotSpy



Thanks for reading

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I Built a Command-Line Spy++ to Log Windows Messages. Here's How.

Thematisch verwandte Begriffe: Built, CommandLine, Windows, Messages · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-19438 | Improper Limitation of a Pathname to a Restricted Directory ('Path Trave…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick