Last active
December 13, 2020 22:43
-
-
Save ariankordi/afbd2506bac01b9d7c68eb06cfac21fa to your computer and use it in GitHub Desktop.
mitmproxy script for cheating on my tests LMAOOOOO
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from mitmproxy import ctx | |
from notifypy import Notify | |
import json | |
# represents items but only with answers, populated in response method | |
current_assessment_items = [] | |
# temporary | |
# indicates if the question ids are in order so we can identify by publishing answer | |
#questions_in_order = None | |
# response handler mostly populates assessment items to refer to | |
def response(flow): | |
# temp: when server replay is enabled, use a dummy socket.io server instead of the real one because mitmproxy can't replay websocket yet | |
#if ctx.options.server_replay: | |
#flow.response.text = flow.response.text.replace('wss://proctorsockets.educationincites.com/cbt', 'wss://socketio-chat-h9jt.herokuapp.com') | |
# if response is assessment response (w data hopefully) | |
if flow.request.path == '/api/getAssessment' and flow.request.method == 'POST': | |
ctx.log.info('"getAssessment" response seen. loading assessment items') | |
assessment_full = json.loads(flow.response.content) | |
ctx.log.info('assessment: "{0}"'.format(assessment_full[0]['assessment']['description'])) | |
global current_assessment_items | |
# set assessment items, this fails if the response isn't perfect | |
current_assessment_items = assessment_full[0]['assessment']['item'] | |
del(assessment_full[0]['assessment']['item']) | |
# iterate through assessment items to remove unnecessary keys | |
for k, item in enumerate(current_assessment_items): | |
# standard can be a HUGE item | |
#try: | |
del(current_assessment_items[k]['standards']) | |
del(current_assessment_items[k]['metadata']) | |
# a lot of these items don't have to exist | |
#except KeyError: | |
# pass | |
#current_assessment_items.append({ | |
# "item number" although there is also an "id" attribute which appears to be the same thing | |
# also starts at 1, not zero | |
# nvm just realized this is stupid and useless | |
#"item_no": item['itemNo'], | |
# stem text also isn't necessary | |
# limit to 500 bytes because uhhhh | |
# 'stem_text': item['stem']['text'][:500], | |
#'stem_text': 'dummy', | |
# list of dicts with strings "identifier" and "text" | |
# 'answer_choices': item['answerChoices'], | |
# contains a list of strings representing the keys of correct answer choices | |
# most of the time just a single answer | |
# 'correct_responses': item['correctResponse'], | |
#}) | |
# patch out test locking in js | |
elif 'questionsCtrl' in flow.request.path: | |
# replace js in response because this is likely the questionctrl | |
# multiple replacements just for good measure | |
# actually idk if all of these even work please don't hurt me. | |
# set occurrences of "testLockingEnabled = true" with false | |
flow.response.text = flow.response.text.replace('testLockingEnabled = true', 'testLockingEnabled=0') | |
# stub handleVisibilityChange function | |
flow.response.text = flow.response.text.replace('handleVisibilityChange =', 'handleVisibilityChange=function(){return};function foo(){//') | |
# comment out CountdownToLock calls | |
flow.response.text = flow.response.text.replace('rs.CountdownToLock\(', '//') | |
# stub CountdownToLock function | |
flow.response.text = flow.response.text.replace('CountdownToLock =', 'CountdownToLock=function(){return};function foo(){//') | |
# replace occurrences of Locked (localstorage item) | |
flow.response.text = flow.response.text.replace('"Locked"', '"Paused"') | |
# replace occurrences of lockTest (socket io message) | |
flow.response.text = flow.response.text.replace('lockTest', 'foo') | |
# replace occurrences of isAssessmentLocked (localstorage item) | |
flow.response.text = flow.response.text.replace('isAssessmentLocked', 'paused') | |
# i can't think of anything else for now, sorry | |
# patch instructions template to notify that test locking is disabled | |
elif 'instructions' in flow.request.path: | |
# um. | |
flow.response.text = flow.response.text.replace('{{loc.ONLINE_TEST_CLIENT_TEST_LOCK_ON}}', '<span style=color:#e6a7ff>actually don't worry about it. you don't have to worry anymore. you're in good hands</span>') | |
# gets answer status on proctor socket io, and test lock mitigation? | |
def websocket_message(flow): | |
# get the latest message | |
message = flow.messages[-1] | |
#print(flow.server_conn.address) | |
# ignore if message is not from client | |
if not message.from_client: | |
return | |
#ctx.log.info("-> {}".format(message.content)) | |
# see if message is from proctor service so we can parse it | |
#if not '/cbt,' in message.content: | |
# return | |
# idk why i chose this length. just | |
if len(message.content) < 5: | |
return | |
#message_split = message.content.split('/cbt,') | |
message_split = message.content.split('[', 1) | |
message_json = [] | |
try: | |
# if message is empty then ignore | |
if not len(message_split[1]): | |
return | |
# hopefully load message | |
try: | |
message_json = json.loads('[' + message_split[1]) | |
# probably a lazy solution i'm sorry for doing this | |
except json.decoder.JSONDecodeError: | |
ctx.log.error('decode error???? huh... ignoring for now...') | |
return | |
# lazy workaround, i'm so sorry, i'm not sane at this point | |
except IndexError: | |
return | |
#print(message_json) | |
# finally handle payloads, index 0 is the name of the function and 1 is the data | |
# handler for change question payload | |
if message_json[0] == 'studentChangeQuestion': | |
item_no = message_json[1]['itemNo'] | |
# if this is the first question (questions in order hasn't been set yet) | |
# then identify if questions will be in order from here i guess | |
#elif questions_in_order is None and item_no == 1: | |
# questions_in_order = True | |
#else: | |
# questions_in_order = False | |
ctx.log.info('changed question (item #{})'.format(item_no)) | |
item = current_assessment_items[item_no - 1] | |
try: | |
ctx.log.info('"{}"'.format(item['stem']['text'][:140])) | |
except KeyError: | |
ctx.log.info('(tried to get stem text, ran into keyerror)') | |
#ctx.log.info(str(current_assessment_items[item_no - 1])[:120]) | |
# correct response as key in answer choices | |
correct_response_key = int(item['correctResponse'][0]) - 1 | |
# get an always answer identifier that's always a letter | |
answer_identifier = { | |
# select answer identifier from our correct response key | |
0: 'A', | |
1: 'B', | |
2: 'C', | |
3: 'D', | |
# todo make this translate into letters without this huge statement | |
4: 'E', | |
5: 'F' | |
}.get(correct_response_key) | |
if 'answerChoices' in item: | |
answer = item['answerChoices'][correct_response_key] | |
# hack to remove html markup from answer text i guess | |
answer_text = answer['text'].replace(' ', '').replace('<p>', '').replace('</p>', '') | |
ctx.log.info('aaaAAA here answee ' + str(answer)) | |
answer_message = '{answer_identifier}. {answer_text}'.format( | |
# this is inaccurate; on one test it's numbers, on one it's letters, the official client will always display letters | |
#answer_identifier=answer['identifier'], | |
answer_identifier=answer_identifier, | |
# try to truncate answer text, idk | |
answer_text=answer_text[:70] + ('...' if len(answer_text) > 70 else ''), | |
) | |
else: | |
# no answer choices so make up an answer without the text | |
answer_message = 'the answre iS ' + answer_identifier | |
ctx.log.info('WTF NO ANSWER CHOICES (WTF?) ok i tried anyway i think thei dentifier is ' + answer_identifier) | |
notification = Notify() | |
try: | |
notification.title = 'here uR answer for "{}" (#{})'.format(item['stem']['text'][:40].replace(' ', '').replace('<p>', '').replace('</p>', '') + ('...' if len(item['stem']['text']) > 40 else ''), item_no) | |
except KeyError: | |
notification.title = 'answer #{}.'.format(item_no) | |
notification.message = answer_message | |
notification.send() | |
ctx.log.info('{start}{answer_message}{end}'.format(answer_message=answer_message, start='\033[1m', end='\033[0m')) | |
# handle publishcbtanswer if questions are in order | |
#elif message_json['0'] == 'publishCBTAnswer' and questions_in_order == True: | |
# ansewewre = | |
# message_temp = 'todo TODO ' | |
# ctx.log.info() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment