I wrote this advanced program to only work on my computer but I think I might have made a mistake somewhere, as I can’t even confirm my own identity.
Category: reversing
Solver: t0b1
Flag: HTB{Id3nt1ty_c0nf1rmat1on}
Writeup
In this challenge we get a Windows executable. We open it up in Ghidra to see what it does.
The main
function is printing Starting to confirm identity...
and then calls the RegOpenKeyExA
function with Control Panel\Desktop
as the argument.
local_1e8 = RegOpenKeyExA((HKEY)0x80000001,s_Control_Panel\Desktop_00405090,0,0x20019,(PHKEY)local_204);
This is a Windows function to open the specified Windows registry section. The (HKEY)0x80000001
value means it is opening the HKEY_CURRENT_USER
.
Afterwards Ghidra shows some weird code.
local_1f4 = 1;
local_8 = 0;
pcVar1 = (code *)swi(3);
(*pcVar1)();
return;
This doesn’t seem right and so we look at the corresponding Assembly code.
00401086 c7 85 10 MOV dword ptr [EBP + local_1f4],0x1
fe ff ff
01 00 00 00
00401090 c7 45 fc MOV dword ptr [EBP + local_8],0x0
00 00 00 00
00401097 cc INT3
The INT3
instruction is typically used by debugger to temporarly replace an instruction in a running program in order to set a code breakpoint.
In a normal program run, this wouldn’t do anything and just continue running instead.
As this is kinda suspicious, we go through the following instructions and find the following flow.
We find the first jump at 0x40109f
that just jumps over some bunch of code. Afterwards we find a CMP
instruction, looks at an address very far up in the stack.
This will most likely be 0
and so the JZ
(jump if zero) will be executed, skipping the next bunch of code.
All of this is only some obfuscation to confuse Ghidra and disguise the rest of the function.
Thus we patch the binary at 0x401086
to a JMP LAB_004010cc
or eb 44
in hex. That way we will skip this bunch of code which is useless anyway and help Ghidra in decompiling.
This helps, and Ghidra now shows the complete code.
Instead of the weird code, we now find the code reading a value from the registry section opened before.
local_1e8 = RegQueryValueExA(local_204,s_WallPaper_004050a8,(LPDWORD)0x0,(LPDWORD)0x0,aBStack288, &local_208);
This time it actually reads a value, which is the WallPaper
key. The complete registry path is thus HKEY_CURRENT_USER\Control Panel\Desktop\WallPaper
.
This key contains the path to the desktop wallpaper of the current user.
The code then has some anti-debugger checks.
BStack504 = IsDebuggerPresent();
if (BStack504 != 0) {
exit(1);
}
And later on we find this check as well.
pvStack508 = GetCurrentProcess();
CheckRemoteDebuggerPresent(pvStack508,&iStack512);
if (iStack512 != 0) {
exit(1);
}
However, as we execute the binary locally, we can just patch the exit
call with a NOP
and circumvent those checks.
The code performs several operations on the read registry value. First it locates the last \
in the string.
lastBackslash = strrchr((char *)aBStack288,'\\');
It then locates the last first .
in the string. If a dot is found, the dot will be overwritten with a NULL
byte, effectively terminating the string early.
pcStack492 = strchr((char *)pBStack480,0x2e);
Then an important check is executed. The string after the last .
is compared with the string \proof
. If that string matches, the confirmation will succeed and we will probably get the flag.
iVar2 = strcmp(pcStack492,s_\proof_0040504c);
Now you might be thinking, how we could make the check pass and trick the functions we mentioned earlier, to maybe not find the \
after the .
.
However all of this is not necessary here. As we disabled all anti-debugger checks, we can simply attach a debugger, halt before the call to strcmp
and overwrite the string in the memory.
The reason why we can’t just invert the jump in the binary can be found in the image above. The pcStack492
variable which holds the string between the last \
and first .
is used again in line 95. Without the right content, we would not receive the correct flag.
Halting before the strcmp
and changing the memory does reveal the correct flag.