Introduction
This write-up details the process of reverse engineering a binary to extract the required passwords for a challenge. Using Binary Ninja, Ghidra, and Python, we analyzed the exam() function and the xor() function to uncover the three required passwords.
Examining the exam() Function
The exam() function follows a sequence of password validation steps:
First Password Check:
The program reads input and compares it to a hardcoded string:
if (strcmp(rax, "PasswordNumeroUno") != 0)If the input does not match PasswordNumeroUno, the program exits.
Second Password Check:
The binary stores a reversed version of the second password in memory:
reverse(&var_1c, "0wTdr0wss4P", 0xb);Reversing this string gives "P4sswordTw0".
If the input does not match this, the program exits.
Third Password Check:
This step involves an XOR operation:
__builtin_memset(&s, c: 0, n: 0x11);
xor(&s, &t2, 0x11, 0x13);The xor() function transforms t2 (a 12-byte string) into a 17-byte value using the XOR key 0x13.
The transformed value is compared against user input.
If the input does not match, the program exits.
Understanding XOR and Memory Impact
How XOR Works
XOR (exclusive OR) is a bitwise operation that follows these rules:
0 ⊕ 0 = 0
1 ⊕ 0 = 1
0 ⊕ 1 = 1
1 ⊕ 1 = 0
This means XORing the same value twice will return the original value:
original = ord('A') # ASCII 65
key = 0x13
encoded = original ^ key # Encrypt
decoded = encoded ^ key # Decrypt
print(chr(decoded)) # Output: 'A'
In our binary, each byte is XORed with 0x13, which scrambles and later reconstructs the password.
Memory Impact
The binary initializes s with 17 bytes set to zero before XORing with t2:
__builtin_memset(&s, c: 0, n: 0x11);
Since t2 only has 12 bytes, the remaining bytes are pulled from memory beyond t2, potentially containing leftover data.
Reverse Engineering the xor() Function
The xor() function is structured as follows:
while (result_1 < arg3) {
*(arg1 + result_1) = *(arg2 + result_1) ^ arg4;
result_1 += 1;
}
It loops for 17 bytes, XORing each byte from t2 with 0x13.
Since t2 is 12 bytes long, the remaining 5 bytes are read from adjacent memory.
We dumped this memory section and found the extra bytes: \x7f222\x13.
Extracting t2 and Decoding the Final Password
From Binary Ninja, we extracted t2:
G{zawR}wUz}r
and the additional 5 bytes:
\x7f222\x13
Using Python, we decoded the password by XORing each byte with 0x13:
data = b"G{zawR}wUz}r\x7f222\x13"
xor_key = 0x13
decoded = bytes([b ^ xor_key for b in data])
print(decoded.decode())
This resulted in:
ThirdAndFinal!!!
Thus, the final password is ThirdAndFinal!!!.
Conclusion
By disassembling the binary, analyzing memory, and applying XOR decoding, we successfully extracted all three passwords required for the challenge:
PasswordNumeroUno
P4sswordTw0
ThirdAndFinal!!!
This process showcased key reverse engineering techniques, including static analysis, memory inspection, and binary manipulation.