Find the Easy Pass

FIRST TAKE

We’re provided with just a single file, EasyPass.exe. It’s an exe file, so on Linux we’ll need to run it through wine:

wine EasyPass.exe

main screen

When we enter a test password, we get a modal indicating the wrong password was used:

wrong password

DISASSEMBLY

Normally, these would by my go-to tools for analyzing an exe:

  • ILSpy
  • BinaryNinja
  • Ghidra

ILSpy

ILSpy didn’t work at all.

BinaryNinja

BinaryNinja is my favourite for doing small, simple programs, so I started with that. However, after taking a look at the disassembled code, it’s clear that BinaryNinja did a terrible job on this one (for example, there aren’t even any call instructions 👀)

Ghidra

In Ghidra, we start a fresh project, choose a directory, then use the Code Analysis tool, loading EasyPass.exe

🏆 Ghidra can be really annoying to use, but in this case it vastly outperformed BinaryNinja.

Since I don’t see any main function, a good place to start is by looking for recognizable strings. Use Search > For Strings… to open a tool for this. All of the recognizable text on the window (and modal) contains the word password, so let’s search for that:

finding strings

Enter Password exists only in the .data segment - it is apparently not referenced in the code at all (which makes sense, since it’s fully static: we’ll see that same text regardless of what happens at runtime.)

Wrong Password! is actually referenced in the code. Here’s the data:

wrong password string data

Now if we right-click on the address and use References > Show References to Address, we see that it’s referenced at .code address 0x454144:

assembly showing message modal

We see two spots where a string “message” is being moved into EAX then calling the same function: the function call is clearly to something that shows the modal window, so I’ve renamed it to showModal in Ghidra (right-click, Edit Function).

So what determines which message get’s shown? We can see in the above image that the message is chosen by that JNZ (Jump if Nonzero) instruction at 0x454136. This makes the code jump to the specified address if the ZF (Zero Flag) in the status register is not set, which is almost always due to some kind of comparison (CMP) instruction 🤔

The CMP instruction that sets (or doesn’t set) the ZF bit would be very close to the JNZ. This is because pretty much all comparison operators use this same ZF bit, so it must be between the JNZ call and whatever “if” statement, “while” loop, or any other conditional logic preceded the JNZ.

However, when we look for the CMP instruction (or something like it), we don’t actually find it nearby. Instead, we see a CALL instruction - this might be the true source of the CMP. Conspicuously, there are two registers (EAX and EDX) being loaded onto the stack right before the CALL instruction, indicating that these are arguments to the function call.

On a hunch, we can label the function being called as checkPassword:

assembly password check logic

To check if our hunch is correct, we’ll run it in something like radare2 or gdb.

DYNAMIC ANALYSIS

Normally, we could just load the binary into gdb or radare directly; this challenge uses an .exe, so we’ll have to use Wine to accomplish this:

winedbg --gdb EasyPass.exe

This will load the binary through are more-or-less familiar GDB interface:

loading gdb

☝️ Remember: our hunch is that the CALL instruction at 0x454131 might be some kind of password-checking function. Our goal here is to examine what arguments the were placed on the stack before the CALL instruction.

Let’s set a breakpoint at 0x454131 then run the program:

break *0x454131
continue

The window appears and we once again enter some random “test” password:

wrong password 2

As soon as we click Check Password, the breakpoint trips:

breakpoint tripped

We can examine the registers using x. To make gdb assume that the register is actually a string (thus, a pointer to the beginning of a C-style string), we can use the /s format identifier. To tell gdb what to examine, we provide either a memory address, or tell it to read an address directly out of a register:

x /s 0x1442460
x /s $eax

test password found

Nice! It’s the password we entered 👍 Now let’s check the other argument, which should be in edx:

x /s 0x1443688
x /s $edx

true password found

😁 Alright! That looks like it might be the real password. To proceed with the program and guess another password, let’s proceed past the breakpoint using c or continue:

continue

We can try the password fortran!:

right password

🍰 Confirmed - that is the password! We can now enter the flag as HTB{fortran!}.


Thanks for reading

🤝🤝🤝🤝
@4wayhandshake