LabyREnth Challenge Three

Synopsis - Watching You Watching Me Watching You

Here is the third of the LabyREnth Windows track challenges. In this challenge we are taunted by the Pokémon character, Squirtle, who says each time we fail to enter the correct password another of their kind dies.

LRENTH-CH3-FirstRun


Walk-Through

Static Analysis

As we have seen so far from the first two challenges done, there is typically a comparison function used to confirm the password. Let's open this executable in IDA and see if we can find the function where the password comparison is done.

After opening the file in IDA (and the analysis completes) here is what the main function looks like after scrolling out. So many decisions to choose from!

LRENTH-CH3-Decisions

Zooming in on the first one we see:

Good place to start, and upon entering the function at address 4010EE we see the text "Everytime you type the password wrong a"... which looks just like the beginning of the error message we saw when originally running the program. The password comparison check is more than likely here. Looking above that we see a call to strncmp at 4010CE which is a string comparison function.

PwComparison

The two values bring checked are pushed on to the stack at 4010C9 and 4010CD. The one at 4010CD looks to be what we entered (see lea eax [ebp+Buf]). Looking at the other one we see lea eax, [ebp+Str2] at 4010C6 loading the address of the string into EAX. So our current working hypothesis is that this holds the password value. Tracing this backwards we see that Str2 is set at 401093 and the value of xmm0 is set via the instruction movq xmm0, ds:qword_414194 at 401080. Double clicking on the qword, we see an 8 character string (highlight the hexadecimal number and hit r in IDA to see the string representation).

Pwd

Now, remember that strncmp relies on the array of characters being null terminated and the number of ASCII values in a qword is 8. cerrocni is exactly 8 characters. There is no null terminating character. Paying close attention to the location of variables on the stack we notice that Str2 is a byte pointer at -10 and (dealing with Little Endian) the next variable on the stack is var_8. At address 401098 the value of ax is placed into this word. The value of ax is set at 401088. Following ds:word_41419C we find the value t. So the password should be incorrect. Taking one last look at the parameters for strncmp we notice that the first value passed in is the MaxCount of 0Ah or 10. This jives with the 9 characters of incorrect plus one more to account for the null termination character.


Dynamic Password Confirmation

Let's test what we have learned during static analysis.

CorrectPwButWait

Woohoo... huh? While we entered the correct password, it appears that we failed a number of anti-analysis tricks. The checks vary between wanting detection and not to be detected. Remember the staircase graphic of the main method, those are probably these checks.

Notice the flag looks like it is contained in a file that gets created called answer.jpg (if we are to believe what is written).


Static Analysis - Take 2

Inspecting the graph in IDA, there are 10 staircases each with their own stair case.

InsideStairCase
The first staircase inside the larger staircase.

Looking at the conditions across the top (after the initial condition) it looks like the makers of the CTF had some fun and added additional routes to other "failure" messages depending on the outcome of two rand calls.

Rand
Example of the call to rand

We will ignore these and focus on the first decision in each set of stair cases.


FindWindowW Check

So after the password check, the next check is looking for applications whose GUI Window name contains one of the following:

  • OLLYDBG
  • QWidget
  • WinDbgFrameClass
  • ID

FindWindowW
FindWindowW checks

For this one, if none are identified, then it takes the stair case of rand checks and (provided none of them randomly fail the check) it comes out successfully.


PEB Debugger Check

The next check, looks to see if a debugger is attached to the process by inspecting FS[30]+2

FS30+2

This is a well documented anti-debugging trick. Here the address of the PEB is moved into EAX then 2 is added to acquire the BeingDebugged flag. This is copied to al and then a comparison is done to see if the value is 0 or 1 (a 1 says that a debugger is attached).

Upon no debugger being detected, the execution flows through the rand steps like the prior check.

The trick with this one is that Squirtle wants you to run him in a debugger. We'll need to remember this for later.


GetTickCount

Here Squirtle checks to see if you've been at this for awhile.

GetTickCount

Checking MDSN[1] GetTickCount "Retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days."

The comparison here is done against 0xFFFFF which is 1048575 in base 10. Converting this to minutes we have a total of 17.4762 (1048575÷1000÷60) minutes.

Looks like Squirtle wants the machine to be running for more than 17 and half minutes (rounded) for it to be happy.


Sleep and GetTickCount

Squirtle calls GetTickCount again except this time it calls Sleep prior to calling GetTickCount. The response is subtracted by the value stored from the prior call to GetTickCount. That in turn is compared to 0xFF

SleepTickCount

According to MSDN[2] the parameter passed to Sleep is in milliseconds. The difference between the two GetTickCounts will always be at least 0x3e8 which is larger than 0xFF so this check will always fail.


IsDebuggerPresent

The next toll gate makes a call to IsDebuggerPresent; which, according to MSDN[3] "Determines whether the calling process is being debugged by a user-mode debugger."

IsDebuggerPresent

Squirtle expects this to be false.


CheckRemoteDebuggerPresent

Here Squirtle is making use of another Windows API call to see whether or not it is being debugged. The call this time is to CheckRemoteDebuggerPresent

CheckRemoteDebuggerPresent

One of the parameters to CheckRemoteDebuggerPresent is the handle to a process. This function calls GetCurrentProcess to get its own process handle and passes that to CheckRemoteDebuggerPresent API call.

Expected value is false (0).


Hardware Check - Processor Count

Next Squirtle starts checking the hardware the process is running on versus looking for debuggers. The thought here is that reverse engineers commonly use virtual machines and those machine are often given the bare essentials to run. The first of the hardware checks is counting the number of processors on the machine or more accurately "The number of logical processors in the current group"[4]

Processor Count Check

The steps after the Windows API call seem a little different from the others. The cmp instruction will set the carry flag to 1 if the source (2 in this case) is greater than the destination ([ebp+SystemInfo.dwNumberOfProcessers]).

Stepping though it:

  1. 401150 : Set the Carry Flag to 1 if source is greater than destination else set it to 0
  • 401154 : Set EAX equal to (EAX + the Carry Flag) - EAX
  • If reading this in Intel like syntax it would be: DEST ← (DEST – (SRC + CF))[^n]
  • If the Carry Flag is 1 then EAX will be 1 else it will be 0
  • 401165 : Takes the two's complement of EAX and stores that into EAX
  • If EAX is 1 it will become -1

Passing this check requires that the Carry Flag not be set, so 2 or more logical processors are needed.


Hardware Check - GlobalMemoryStatusEx

The next hardware check done is to see the amount of RAM the machine has. The Windows API call used here is GlobalMemoryStatusEx

GlobalMemoryStatusEx

Here I'm going to make a deduction about what I believe is going on after the call to GlobalMemoryStatusEx and the values are pushed onto the stack for the __aulldiv function.

  1. 40217D is a call to __aulldiv which appears to be a division function.
  • 402182 copies the quotient into var_4A94
  • 402188 copies the remainder into var_4A90
  • 40218E checks to see if the remainder is is zero
  • 402195 takes a jump if the remainder is greater than 0
  • aka, more than enough RAM
  • 402197 takes a jump if the remainder is less than 0
  • Not enough RAM
  • 402199 gets executed if the remainder is 0 and compares the quotient against 0x100000
  • 4021A3 takes the jump if the prior cmp check was not below 0x100000
  • Passes the RAM check

Hardware Check - Am I in a VM?

This check is a little different from the other hardware checks. This one tries to detect if the underlying machine is actually a virtual machine or not. The instruction we want to look at here is the cpuid call at 40117B

Cpuid

Here is how it works:

  1. 401169 : EAX is set to 1
  • This parameter is used by cpuid
  • "This returns the CPU's stepping, model, and family information in EAX (also called the signature of a CPU), feature flags in EDX and ECX, and additional feature info in EBX."[5]
  • 401177 : The address of [ebp+var_10] is loaded into EDI
  • 40117B : cpuid is called
  • 401188 - 401190 : copies the values of EAX, ESI, ECX, and EDX into a contiguous memory space starting at EDI
  • 401193 : copies the value of what came from ECX into EAX
  • When looking at how to read ECX, bit at position 31 is the hypervisor bit. The value is 1 when running in a hypervisor and "0 on a real CPU, but also with some hypervisors"[6]
  • 401196 : Takes the value of EAX and preforms an arithmetic shift to the right.
  • The most significant bit is what is used to fill as the bits are shifted to the right.
    • The hypervisor bit is in the most significant bit spot
  • 40119E : ANDs the value in EAX setting it to either 1 if FF or leaving it as a 0

Dynamic Confirmation - Take 2

Running the executable again but in a debugger, let's force the Squirtle to be "happy"!

Steps

This is just one of many ways to overcome the hurdles and getting the correct answers.jpg to be created.

Follow Along Note: Set "DLL can move" to false using CFF Explorer to get the memory addresses to match.

  1. Modify the underlying VM (using Virtual Box) so that it is running with
  • At least 2 logical processors
    • Keeps the GetSystemInfo processor check from failing
  • 4GB of RAM
    • Keeps the GlobalMemoryStatusEx from failing
  • For the first GetTickCount check, the machine had already been running for more than Squirtle's required time but the result could be manipulated directly in the debugger if needed.
  • Load Squirtle into a debugger (using x64dbg[7] as it avoids the first anti-debugging check)
    • Avoids the FindWindowW check
  • Locate the sleep call parameter @ 401A12 and change the value to 0x0 and nop the remaining bytes.
    • Not Sleeping
  • This avoids the second GetTickCount tripwire.
  • Saved the patched version of the file
    • Patching
      • Click Patch File and give it a name
  • Load the patched version of the file into the debugger
  • Then prep the program for execution by:
  1. Setting a breakpoint @ 401C13 and when tripped during execution, change the value of EAX to 0
    • Corrects the detection from IsDebuggerPresent
  • Setting another breakpoint @ 401DD4 and when tripped during execution, change the value of EAX to 0

    • Corrects the detection from CheckRemoteDebuggerPresent
  • Setting another breakpoint @ 402362 and when tripped during execution, change the value of EAX to 0

    • Corrects the VM detection from cpuid
  • Or create a script to do step 7 for you

      bpc
      bp 401c13
      bp 401DD4
      bp 402362
      run
      mov eax, 0
      run
      mov eax, 0
      run
      mov eax, 0
      run
    
    • Load the above script into the script pane in x64dbg and restart the debug process if needed (Ctrl-F2)
    • Select the first instruction in the script and click spacebar
    • It will execute, remember to enter the password when prompted
  • And shazam! A successful run!

    • SuccessfulRun
  • And looks like a correctly formatted jpg has been dropped right next to the patched executable.

  • Answer.jpg


Deciphering Zeros and Ones

Now we just need to get the ASCII representation of these bytes. We could OCR it but not all ORC programs are created equal. This is short enough, let's copy them manually into a python script to interpret the results.

#!/usr/bin/env python

binarry = [
0b01010000,0b01000001,
0b01001110,0b01111011,
0b01010100,0b01101000,
0b00110011,0b01011111,
0b00100100,0b01110001,
0b01110101,0b01101001,
0b01110010,0b01110100,
0b01001100,0b00110011,
0b01011111,0b00100100,
0b01110001,0b01110101,
0b01000000,0b01100100,
0b01011111,0b01110111,
0b01000000,0b01111010,
0b01011111,0b01100010,
0b01001100,0b01110101,
0b01100110,0b01100110,
0b01101001,0b01001110,
0b01100111,0b01111101
]

answer = []

for x in binarry:
  answer.append(chr(int(str(x))))

print ''.join(answer)

Upon executing the script, the Flag is revealed!

PAN{[email protected][email protected]_bLuffiNg}


Closing Remarks

This challenge provided an opportunity for us to work around a number of anti-analysis tricks in order to get the required result. Multiple tricks were used from patching the binary to running a script in the debugger to change the output of the program to give us the jpeg. Taking the values from the picture we wrote a short python script to interpret them for us to get the Flag. Additionally, the makers of this CTF were kind enough to include links to papers for more info about some of the checks done. Highly recommend readying through them to gain additional insight!


  1. GetTickCount ↩︎

  2. Sleep ↩︎

  3. IsDebuggerPresent ↩︎

  4. System_Info ↩︎

  5. Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z ↩︎

  6. Processor Info and Feature Bits ↩︎

  7. x64dbg ↩︎

comments powered by Disqus