From 9feb845d3cb86c69509d3b15b1fdead1fc71e4ec Mon Sep 17 00:00:00 2001 From: Stanislav Mykhailenko Date: Tue, 31 Jan 2023 15:27:10 +0200 Subject: [PATCH] Initial commit --- .gitignore | 131 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 23 +++++++++ UNLICENSE | 24 +++++++++ main.py | 15 ++++++ payload.py | 25 +++++++++ requirements.txt | 10 ++++ tg.py | 105 +++++++++++++++++++++++++++++++++++++ 7 files changed, 333 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 UNLICENSE create mode 100644 main.py create mode 100644 payload.py create mode 100644 requirements.txt create mode 100644 tg.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f32609 --- /dev/null +++ b/.gitignore @@ -0,0 +1,131 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +token.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c97b4c --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Computer kill switch + +This program is a PC kill switch Telegram bot. It is a project for New Generation. + +## Security notice +This program provides no access control, so anyone knowing the bot username is able to use it. + +## Features +- disabling and re-enabling keyboard and mouse input +- locking the screen +- formatting partitions +- playing text-to-speech messages + +## Limitations +- input lock resets if Control-Alt-Delete is pressed +- volumes used by some software at the time of request cannot be formatted +- works on Windows only + +## Usage +Before starting the bot, its token must be put into token.txt file with no newlines. Administrator privileges are required so that the bot is able to disable input and format partitions. Once the bot is started, send /start to its account on Telegram to see what can be done. + +## Licensing +All code in this repository is Unlicensed, see UNLICENSE. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/main.py b/main.py new file mode 100644 index 0000000..8186aa3 --- /dev/null +++ b/main.py @@ -0,0 +1,15 @@ +# Project: Telegram PC kill switch bot +# Author: Stanislav Mykhailenko +# License: Unlicense + +import ctypes, sys +from tg import startBot + +def main() -> None: + if ctypes.windll.shell32.IsUserAnAdmin(): + startBot() + else: + ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) + +if __name__ == '__main__': + main() diff --git a/payload.py b/payload.py new file mode 100644 index 0000000..a7ac5b9 --- /dev/null +++ b/payload.py @@ -0,0 +1,25 @@ +# Project: Telegram PC kill switch bot +# Author: Stanislav Mykhailenko +# License: Unlicense + +# This file contains the payloads + +import ctypes, os, pyttsx3 + +def enableInput(): + ctypes.windll.user32.BlockInput(False) + +def disableInput(): + ctypes.windll.user32.BlockInput(True) + +def lockScreen(): + ctypes.windll.user32.LockWorkStation() + +def formatVolumes(volumes): + for volume in volumes: + os.system('format ' + volume + ' /y') + +def playMessage(message): + engine = pyttsx3.init() + engine.say(message) + engine.runAndWait() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ea0e637 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +anyio==3.6.2 +certifi==2022.12.7 +h11==0.14.0 +httpcore==0.16.3 +httpx==0.23.3 +idna==3.4 +python-telegram-bot==20.0 +pyttsx3==2.90 +rfc3986==1.5.0 +sniffio==1.3.0 diff --git a/tg.py b/tg.py new file mode 100644 index 0000000..2b21472 --- /dev/null +++ b/tg.py @@ -0,0 +1,105 @@ +# Project: Telegram PC kill switch bot +# Author: Stanislav Mykhailenko +# License: Unlicense + +# This file contains Telegram interactions + +from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update +from telegram.ext import Application, CommandHandler, ContextTypes, ConversationHandler, filters, MessageHandler +from payload import * +import logging +from threading import Thread + +logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO +) +logger = logging.getLogger(__name__) + +ACTION, FORMAT, MESSAGE = range(3) + +def getToken(): + with open(os.path.realpath(os.path.dirname(__file__)) + '/' + 'token.txt', 'r') as file: + return file.read() + + +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + reply_keyboard = [["Enable input", "Disable input", "Lock screen", "Format volumes", "Play message"]] + + await update.message.reply_text( + "Choose your action:", + reply_markup=ReplyKeyboardMarkup( + reply_keyboard, one_time_keyboard=True, input_field_placeholder="Choose your action" + ), + ) + + return ACTION + +async def action(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + user = update.message.from_user + + match update.message.text: + case "Enable input": + logger.info("Got a request from %s to enable input.", user.first_name) + enableInput() + await update.message.reply_text("Input enabled.", reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + case "Disable input": + logger.info("Got a request from %s to disable input.", user.first_name) + disableInput() + await update.message.reply_text("Input disabled.", reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + case "Lock screen": + logger.info("Got a request from %s to lock screen.", user.first_name) + lockScreen() + await update.message.reply_text("Screen locked.", reply_markup=ReplyKeyboardRemove()) + return ConversationHandler.END + case "Format volumes": + logger.info("Got a request from %s to format volumes.", user.first_name) + await update.message.reply_text("Please type a space-separated list of the volumes you want to format.", reply_markup=ReplyKeyboardRemove()) + return FORMAT + case "Play message": + logger.info("Got a request from %s to play a message.", user.first_name) + await update.message.reply_text("Please type the message you want to play.", reply_markup=ReplyKeyboardRemove()) + return MESSAGE + +async def format(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + user = update.message.from_user + + logger.info("Got a request from %s to format volumes %s.", user.first_name, update.message.text) + Thread(target=formatVolumes,args=(update.message.text.split(),)).start() + await update.message.reply_text("Command to format the volumes sent.") + + return ConversationHandler.END + +async def message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + user = update.message.from_user + logger.info("Got a request from %s to play message %s.", user.first_name, update.message.text) + Thread(target=playMessage,args=(update.message.text,)).start() + await update.message.reply_text("Command to play the message sent.") + + return ConversationHandler.END + +async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + user = update.message.from_user + logger.info("User %s canceled the conversation.", user.first_name) + await update.message.reply_text("Request cancelled.", reply_markup=ReplyKeyboardRemove()) + + return ConversationHandler.END + +def startBot(): + application = Application.builder().token(getToken()).build() + + conv_handler = ConversationHandler( + entry_points=[CommandHandler("start", start)], + states={ + ACTION: [MessageHandler(filters.Regex("^(Enable input|Disable input|Lock screen|Format volumes|Play message)$"), action)], + FORMAT: [MessageHandler(filters.TEXT & ~filters.COMMAND, format)], + MESSAGE: [MessageHandler(filters.TEXT & ~filters.COMMAND, message)], + }, + fallbacks=[CommandHandler("cancel", cancel)], + ) + + application.add_handler(conv_handler) + + application.run_polling()