At the University of Twente there was a pilot program for digital testing using Pearson MyLabsPlus in the Respondus Lockdown Browser. This browser is supposed to stop students from using their laptops for other things besides the test.

It does this by minimizing all applications, disabling a bunch of things and maximizing itself. It also forces you to shut down software such as Skype and Steam.

After the test, a friend mentioned he had received a notification from WhatsApp web during the test.
Surely enough, I could easily reproduce this with a few lines of JS using the notification API.

This got me thinking if you could show other things on top of Respondus Lockdown Browser.
I literally copied some code for making a transparent always-on-top window in Java, added a Thread.sleep (so it would open after I started Respondus) and I was in business.

A little more copy-pasting and a dozen lines of Swing and I had a transparent WolframAplha client sitting on top of my lockdown browser.

I’ve become convinced Respondus is selling snake oil, pure snake oil. To drive the point home, another friend demonstrated you could run Respndus Lockdown Browser in a virtual machine.

Time-line of events:

10 June 2016

13:45 - 15:45 Math exam

Intensive hacking

23:08 University of Twente and Respondus notified

12 June 2016, UT says they will bring it up with respondus

Respondus never even responded.

Below is the result of my copy-pasting. I didn’t even bother to rename anything.

importjava.awt.*;importjava.awt.event.*;importjavax.swing.*;importstaticjava.awt.GraphicsDevice.WindowTranslucency.*;importjava.awt.image.BufferedImage;importjavax.imageio.ImageIO;importcom.wolfram.alpha.WAEngine;importcom.wolfram.alpha.WAException;importcom.wolfram.alpha.WAImage;importcom.wolfram.alpha.WAPod;importcom.wolfram.alpha.WAQuery;importcom.wolfram.alpha.WAQueryResult;importcom.wolfram.alpha.WASubpod;publicclassTranslucentWindowDemoextendsJFrame{privatestaticStringappid="wolfram app id";privateWAEngineengine=newWAEngine();privateJLabelpicLabel=newJLabel();publicvoidrequestWA(Stringinput){WAQueryquery=engine.createQuery();query.setInput(input);try{// For educational purposes, print out the URL we are about to send:System.out.println("Query URL:");System.out.println(engine.toURL(query));System.out.println("");// This sends the URL to the Wolfram|Alpha server, gets the XML result// and parses it into an object hierarchy held by the WAQueryResult object.WAQueryResultqueryResult=engine.performQuery(query);if(queryResult.isError()){System.out.println("Query error");System.out.println(" error code: "+queryResult.getErrorCode());System.out.println(" error message: "+queryResult.getErrorMessage());}elseif(!queryResult.isSuccess()){System.out.println("Query was not understood; no results available.");}else{// Got a result.System.out.println("Successful query. Pods follow:\n");for(WAPodpod:queryResult.getPods()){if(!pod.isError()&&!pod.getTitle().equals("Input")&&!pod.getTitle().equals("Input interpretation")){System.out.println(pod.getTitle());System.out.println("------------");for(WASubpodsubpod:pod.getSubpods()){for(Objectelement:subpod.getContents()){if(elementinstanceofWAImage){WAImagewaimg=(WAImage)element;waimg.acquireImage();System.out.println(waimg.getFile());System.out.println("");BufferedImagemyPicture=ImageIO.read(waimg.getFile());picLabel.setIcon(newImageIcon(myPicture));SwingUtilities.invokeLater(newRunnable(){publicvoidrun(){revalidate();repaint();}});return;}}}System.out.println("");}}// We ignored many other types of Wolfram|Alpha output, such as warnings, assumptions, etc.// These can be obtained by methods of WAQueryResult or objects deeper in the hierarchy.}}catch(Exceptione){e.printStackTrace();}}publicTranslucentWindowDemo(){super("TranslucentWindow");setUndecorated(true);getRootPane().putClientProperty("Window.shadow",Boolean.FALSE);getContentPane().setBackground(Color.WHITE);setLayout(newFlowLayout());engine.setAppID(appid);engine.addFormat("image");//requestWA("integrate x^2");addMouseListener(newMouseAdapter(){@OverridepublicvoidmouseEntered(MouseEventme){setOpacity(0.55f);}@OverridepublicvoidmouseExited(MouseEventme){setOpacity(0.1f);}});setSize(300,200);setLocationRelativeTo(null);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//Add a sample button.finalJButtonbutton=newJButton("Solve");finalJTextFieldtextfield=newJTextField(20);ActionListenersubmitListener=newActionListener(){publicvoidactionPerformed(ActionEventae){requestWA(textfield.getText());}};button.addActionListener(submitListener);textfield.addActionListener(submitListener);//add(button);add(textfield);add(picLabel);}publicstaticvoidmain(String[]args){try{Thread.sleep(10000);}catch(InterruptedExceptione){}// Determine if the GraphicsDevice supports translucency.GraphicsEnvironmentge=GraphicsEnvironment.getLocalGraphicsEnvironment();GraphicsDevicegd=ge.getDefaultScreenDevice();finalRectanglerect=gd.getDefaultConfiguration().getBounds();//If translucent windows aren't supported, exit.if(!gd.isWindowTranslucencySupported(TRANSLUCENT)){System.err.println("Translucency is not supported");System.exit(0);}//JFrame.setDefaultLookAndFeelDecorated(true);// Create the GUI on the event-dispatching threadSwingUtilities.invokeLater(newRunnable(){@Overridepublicvoidrun(){TranslucentWindowDemotw=newTranslucentWindowDemo();// Set the window to 55% opaque (45% translucent).tw.setOpacity(0.1f);tw.setAlwaysOnTop(true);// Display the window.//tw.pack();tw.setLocation((int)rect.getMaxX()-400,(int)rect.getMaxY()-300);tw.setVisible(true);}});}}

I’m not sure it’s wise to release a “weaponized” version of my “exploit”, but I think anyone who can figure out how to compile and run this code and its dependencies knows enough to do the copy-pasting.