Find the Easy Pass
2025-05-22
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
When we enter a test password, we get a modal indicating the wrong password was used:
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:
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:
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
:
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) theZF
bit would be very close to theJNZ
. This is because pretty much all comparison operators use this sameZF
bit, so it must be between theJNZ
call and whatever “if” statement, “while” loop, or any other conditional logic preceded theJNZ
.
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
:
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:
☝️ Remember: our hunch is that the
CALL
instruction at0x454131
might be some kind of password-checking function. Our goal here is to examine what arguments the were placed on the stack before theCALL
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:
As soon as we click Check Password
, the breakpoint trips:
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
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
😁 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!
:
🍰 Confirmed - that is the password! We can now enter the flag as HTB{fortran!}
.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake