<:: Introduction
Due to the recent outbreak of COVID-19, we have seen a large spike in online courses, most colleges actually making them mandatory. Most schools are faced with an issue of how to actually administer tests while students are at home and ensure that they aren’t cheating, which is where ‘secured’ browsers come into play.
Secured browsers are not new, they have been around for quite some time and are usually used in school settings when a computer is required to take a test. In these settings, it is very hard for a student to cheat, and hard for them to use an external device such as their phone since in class there will be an actual proctor there too. Of course, when you’re taking this at home, this isn’t an issue.
I should add as a disclaimer before I continue that I don’t really care for cheating, if I really wanted to cheat while using this software I would just open my laptop or open my phone, this little excursion was purely for the purpose of reverse-engineering and seeing how the software worked. This all started because I opened the program and it forced closed all of my applications, which really annoyed me since I am a busy person and have a lot of work which needs to be saved frequently, so here I went reversing the software.
This article will be split into three sections, one for each respective module and then a final one for my own code and solution to the issues that will be mentioned.
<:: ETS Main Module
A brief glance at the main executable for the ETS Online Testing Browser will quickly show you that it is a pretty basic WinForm applications, which makes sense if you’ve ever opened it. The first thing I do is always check the imports/exports of the application. Seeing nothing notable in the exports, you can take a look inside of the imports and see a lot of functions which will be useful to look into later such as EmptyClipboard
, ShellExecuteA
, InternetConnectA
. There are of course lot more functions which they likely use to mess with you and ‘secure’ their testing suite, but it’s not worth going over all of them when you can just completely disable it.
A quick look at the main method of the application, WinMain
, you will probably notice that they stuffed a lot more than they should have into this function just based on the size of its stack.
Thankfully, the developers of the browser weren’t really thinking much about security since they very helpfully left in log strings after just about every function call telling you exactly what they’re doing.
The first part of the WinMain
function you will see where they parse the command line, accepting arguments such as url
, log
, errorlog
, nocheck
, and institutioncode
. I’m not too worried about these though, as these don’t really determine the security.
if ( cmd_arg_size > 1 )
{
v8 = 1;
while ( 1 )
{
if ( v8 == 1 )
{
strcpy_s(&Dst, 0x1FFEu, *(const char **)(cmd_args + 4));
}
else
{
strcat_s(&Dst, 0x1FFEu, " ");
strcat_s(&Dst, 0x1FFEu, *(const char **)(cmd_args + 4 * v8));
}
if ( _mbsnbicmp(*(const unsigned __int8 **)(cmd_args + 4 * v8), "/url", 4u)
|| strlen(*(const char **)(cmd_args + 4 * v8)) <= 4 )
{
if ( _mbsnbicmp(*(const unsigned __int8 **)(cmd_args + 4 * v8), "/log", 4u) )
{
if ( _mbsnbicmp(*(const unsigned __int8 **)(cmd_args + 4 * v8), "/errorlog", 9u) )
{
if ( _mbsnbicmp(*(const unsigned __int8 **)(cmd_args + 4 * v8), "/nocheck", 8u) )
{
if ( !_mbsnbicmp(*(const unsigned __int8 **)(cmd_args + 4 * v8), "/Institutioncode", 0x10u)
&& strlen(*(const char **)(cmd_args + 4 * v8)) > 0x10 )
{
strcpy_s(byte_4BB0C0, 0x64u, (const char *)(*(_DWORD *)(cmd_args + 4 * v8) + 16));
}
Continuing on, you can see where they register and create their main window, which is, for now, a loading window while they initialize their ‘security’. ETS has an interesting method of just storing files in the Windows temporary storage, captured via GetTempPathA
. If you cared, the loading screen is just a html file renamed to a bin file.
Just going a little bit further, we actually see an interesting call to LoadLibrary. I should preface this with the fact that this application is a standalone exe, no installer or anything, so seeing a LoadLibrary call is interesting. Back-tracing to the function that gets the name of the file to pass to LoadLibrary, we see something very… interesting, to say the least.
result = (void *)GetTempPathA(0x104u, &Buffer);
if ( result )
{
strcpy_s((char *)lpFileName, SizeInBytes, &Buffer);
strcat_s((char *)lpFileName, SizeInBytes, "TestSecurity.");
strcat_s((char *)lpFileName, SizeInBytes, Src);
strcat_s((char *)lpFileName, SizeInBytes, ".dll");
DeleteFileA(lpFileName);
v4 = FindResourceA(0, (LPCSTR)0x74, "BIN");
v5 = v4;
v6 = LoadResource(0, v4);
v7 = LockResource(v6);
v8 = SizeofResource(0, v5);
result = CreateFileA(lpFileName, 0xC0000000, 3u, 0, 2u, 0x80u, 0);
v9 = result;
if ( result )
{
WriteFile(result, v7, v8, &NumberOfBytesWritten, 0);
result = (void *)CloseHandle(v9);
}
}
return result;
}
This function returns the handle of the file created while storing the name in the passed reference lpFileName
, which is used in LoadLibrary. We can just go to our temp folder and copy this TestSecurity.x.x.x.dll to reverse it later.
As we go along further we can see that every function is uses in relation to security is actually dependent on this dll, using GetProcAddress
to call exports from the dll, since it is not actually imported by the Windows loader.
At this point, we should try to debug the application a little bit in real-time. Launching the browser in a Windows VM greets you with a friendly message:
Taking a look back in IDA and doing a quick string search, we can see this message box is only triggered when a handy function called IsVirtualMachine
returns true.
v3 = GetProcAddress(hModule, "IsVirtualMachine");
if ( v3 )
result = ((int (__stdcall *)(int, int))v3)(a2, a3);
else
result = 0;
return result;
Since we can’t really go much further without bypassing this, we open up TestSecurity.dll
.
<:: Security Module
Taking the same approach as the main executable, we can open up this security module and take a look at the exports. We are instantly greeted with just about every security subroutine that the main module calls.
By now, you should be able to see exactly where this is going. Many of these functions are named pretty well in what they do, and you should be able to formulate a way to bypass them with ease. One thing that interested me however was the fact that the low-level keyboard/mouse hooks are exported.
Back-tracing where they set basic Windows hooks, we find more strings that helpfully describe exactly what the function is doing, such as closing all of your existing applications and enabling message callbacks to send to the main application, nothing a simple return instruction can’t fix!
Unfortunately, this is one of the only functions not exported by the dll, so we have to get the address manually, which is nothing but a minor annoyance.
By now, we have enough information to bypass their entire security system with ease.
<:: Solution
Writing the solution takes about ten minutes max, and most of that is defining your imports for whatever hooking library you want to use (if you choose to use one).
Something important you have to keep in mind is that you will have to hook and hijack the security module as soon as possible to ensure that none of the security methods are executed before you disable the ones you think will be a problem. To do this you have a couple options, you can either inline hook directly after the security module is loaded, or you can hook a function after the security module is loaded, or just hook LoadLibrary.
I chose to hook a function called briefly after the library is loaded. The function itself is not so important, but the location and time of execution is. We place this hook immediately on application startup, and within that hook we can initialize our security hooks.
PVOID original_getwindowsversion = NULL;
INT __cdecl Hook_GetWindowsVersion(PCHAR dest, size_t size_of_bytes)
{
// Initialize our hooks on Security.dll
Hooks::Security::Init();
// We do this here since this is executed right after the security module is actually loaded in via `LoadLibrary`
return static_cast<INT(__cdecl*)(PCHAR, size_t)>(original_getwindowsversion)(dest, size_of_bytes);
}
The security hooks are pretty straightforward, no need to do anything fancy for our purpose, just disable them pretty much.
Truly, the most important part of this injection is timing, since it needs to be injected ASAP. You could also always start the browser in a suspended state, inject, and then resume it if you are having trouble loading it in.
Doing a double-check inside of the main process, we can find the function they use to send security violations/errors to the server:
The function itself returns a boolean which signifies if the POST request was successful or not. We just hook this and always return true without actually sending the request, meaning that if an error does happen to occur and it tries to send the report to the server, it will think that it did without actually doing it.
We can easily test if our solution works by injecting it into the client on a VM and seeing if it still displays the error. Thankfully, we are confident in our code here and it works just as expected:
<:: PS
Thank you very much for reading my second article! I was intending for my second article to be over AAL again, but I have been much too busy with my other work to actually spend the time reversing their Java methods (well, I have, I just haven’t had the time or motivation to write the massive article that will follow them). If you have any suggestions on things I should write then please feel free to message me and let me know.
I should also mention that the test I had to take on this browser for my school wasn’t graded at all and was in-fact just a personality type test, it just pissed me off that they closed all my applications without asking for any permissions.
Thanks for reading! – Sebastien
very nice and interesting