A look into Exam.net's cheat prevention

Published: 2022-04-08 - Updated: 2022-08-03

Introduction

Exam.net is an exam taking tool. It features a wide variety of features allowing teacher to smoothly administer and monitor exams.

The software has different security levels, that allow teachers to lockdown students computers to prevent cheating. It supports all major operating systems (Windows, ChromeOS, iOS, MacOS)

Security levels

Exam.net has two different security levels you can choose from depending on the situtation.

Allow any browser

This is the default setting allowing students to easily take the exam without the need to install external software as it works in any modern browser.

This mode will alert the teacher if the student tries to switch tab or open an external program.

High-security mode

This mode requires the student to install external software to enter exam. The software that needs to be installed depends on the platform.

Platform Software
Windows Safe Exam Browser (SEB)
iOS, macOS The Exam.net app
Chromebook The Exam.net app

While SEB is open source software (some parts are proprietary), the Exam.net app is proprietary. Both of these software do more intrusive lockdowns.

SEB for example has a wide amount of checks it does before it allows you to enter the exam. Such as virtual machine detection, known instant messaging and VoIP software. If any of these get detected you will be advised to close the specified software.

How does it work?

As usual, most exam solutions have flaws allowing students to cheat. Exam.net is no exception, there are ways to bypass both security-modes. But first we have understand how it works.

Allow any browser

This mode uses client-side javascript to detect if you are inside the exam or not. Both Firefox and Chrome have so called events and functions, which can be used within websites. For example the following Javascript code, will alert the user if the website were to lose focus.

document.addEventListener('blur', function(e) { 
    alert("Window lost focus!")
})

Exam.net listens on a whole bunch of these events, their code looks something like this. Their code is obfuscated (intentionally made confusing). To be honest, I don't know exactly what it does.

detectFocusLoss: function(e) {
    window.addEventListener("blur", (function(n) {
        window.K$.iOS && window.K$.iOS.isNative() || t || (n.currentTarget && n.currentTarget.className && n.currentTarget.className.split(" ").find((e=>r.includes(e))) ? console.log("Allowing whitelisted blur/focusout") : e(n))
    }
    ));
    let n = "hidden";
    function i(e) {}
    n in document ? document.addEventListener("visibilitychange", i) : (n = "mozHidden")in document ? document.addEventListener("mozvisibilitychange", i) : (n = "webkitHidden")in document ? document.addEventListener("webkitvisibilitychange", i) : (n = "msHidden")in document ? document.addEventListener("msvisibilitychange", i) : "onfocusin"in document ? document.onfocusin = document.onfocusout = i : window.onpageshow = window.onpagehide = window.onfocus = window.onblur = i,
    void 0 !== document[n] && document[n]
},

But I would guess it adds a listener for the following events:

On top of that it also checks the following variables if they are true or not.

So what if we have make a script that blocks the website from trying to create these eventListeners. We can use something like TamperMonkey, to make these scripts load before the actual website is. I came up with the following script (Download). The script has only been tested to work on Firefox, but may work on others aswell.

// ==UserScript==
// @name         exam.net - anti-focus
// @namespace    http://tampermonkey.net/
// @version      v0.1
// @description  Prevent exam.net seeing if you are inside of the exam or not.
// @author       urof
// @match        *://exam.net/*
// @icon         none
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const blocklist = [
        "blur",
        "resize",
        "visibilitychange",
        "webkitvisibilitychange",
        "onresize",
        "webkitfullscreenchange",
        "mozvisibilitychange",
        "msvisibilitychange"
    ]

    var oldAEL = Element.prototype.addEventListener // Store the previous event adder before it's hijacked

    const Elements = [window, document]

    // Block any attempts of creating a eventListener that's listed in the blocklist
    for(const e of Elements) {
        e.addEventListener = Element.prototype.addEventListener = function (name, func, capture) {
            if(!blocklist.includes(name)) {
                oldAEL.apply(this, arguments)
            }
        }
    }

    // Prevent the website forcing the user to enter fullscreen
    document.documentElement.requestFullscreen = function() { return true }

    Object.defineProperty(document, 'visibilityState', { configurable: false, writable: false, value: 'visible' });
    Object.defineProperty(document, 'hidden', { configurable: false, writable: false, value: false });
})();

High-security mode

Compared to the previous setting, this one requries a dedicated program to be installed. In our case (Windows) this software will be Safe Exam Browser (SEB).

The first thing I looked at was how SEB and Exam.net communicate with eacother (how does Exam.net verify that we are using SEB?). We can use a tool like mitmproxy to be able to decrypt the encrypted HTTPS requests.

In the requests going to Exam.net, I noticed the following "unique" header fields. They use a custom user-agent, funnily enough their agent contains EXAM_#THISISNOTVERYSECRET#_NET. So, atleast they're a bit selfaware.

The x-safeexambrowser-configkeyhash and x-safeexambrowser-requesthash are unique for every URL, meaning they're pretty much different for each request sent. They're also only send with requests starting with https://exam.net/

user-agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 SEB/3.3.2 (x64) EXAM_#THISISNOTVERYSECRET#_NET
x-safeexambrowser-configkeyhash: bca2f86865f65ca683fc75114bd439f4ecb777b356ef8c34e6ab0f0336971565
x-safeexambrowser-requesthash: d63e8f800159b96b6b31b98e7184bcad2ef5ea09a6608bcdd0e81e38ead8558a

So, I thought what happens if I send invalid hashes, but still the correct user agent. I used the Modify Header Value extension with the following configuration

Surprisingly enough, I was seen as a high security user in the teacher's overview. Which means that they don't initially or under the exam verify the hashes, but I personally believe that they may be verified after the exam has ended. Also, I would assume that they log absolutely everything. So, if for example the hashes above were to pop up alot in the logs, they would start to investigate.

So I wondered to myself, how are these generated? SEB has documentation of the generation of these, but it all kinda went over my head and I honestly don't feel like spending more time on this.

Questions or comments? contact me!