Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Last active December 13, 2020 22:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ariankordi/afbd2506bac01b9d7c68eb06cfac21fa to your computer and use it in GitHub Desktop.
Save ariankordi/afbd2506bac01b9d7c68eb06cfac21fa to your computer and use it in GitHub Desktop.
mitmproxy script for cheating on my tests LMAOOOOO
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&apos;t worry about it. you don&apos;t have to worry anymore. you&apos;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('&nbsp;', '').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('&nbsp;', '').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