Perplexed
https://play.picoctf.org/practice/challenge/458?category=3&page=1

Challenge Overview
We were provided with a binary named
perplexed
. Our task was to reverse engineer it, understand its password validation logic, and extract the correct input that would pass the check.
Step 1: Initial Reconnaissance
We begin by inspecting the binary with the file
command:
└─$ file perplexed
perplexed: ELF 64-bit LSB executable, x86–64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86–64.so.2,
BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped
The output confirms:
It's a 64-bit ELF binary
It is dynamically linked
It's not stripped (we may see symbol info)
We give it execute permissions and then run it
└─$ ./perplexed
Enter the password: password
Wrong :(
Step 2: Static Analysis using Binary Ninja
Opening the binary in Binary Ninja, we find two main functions:
main()
– handles input/outputcheck()
– performs validation of the input
Decompiled Code
int64_t check(char* arg1)
{
if (strlen(arg1) != 0x1b) // Must be exactly 27 characters
return 1;
int64_t var_58;
__builtin_memcpy(&var_58,
"\xe1\xa7\x1e\xf8\x75\x23\x7b\x61\xb9\x9d\xfc\x5a\x5b\xdf\x69\xd2\xfe\x1b\xed\xf4\xed\x67\xf4",
0x17); // 23-byte hardcoded binary pattern
int var_1c_1 = 0; // Byte index in user input
int var_20_1 = 0; // Bit index in current byte
for (int i = 0; i <= 0x16; i++) { // Loop through 23 bytes
for (int j = 0; j <= 7; j++) { // Loop through 8 bits in each byte
if (!var_20_1)
var_20_1 += 1;
int rax_17 = (arg1[var_1c_1] & (1 << (7 - var_20_1))) > 0;
if (rax_17 != ((*(char*)(&var_58 + i) & (1 << (7 - j))) > 0))
return 1;
var_20_1 += 1;
if (var_20_1 == 8) {
var_20_1 = 0;
var_1c_1 += 1;
}
if (var_1c_1 == strlen(arg1))
return 0;
}
}
return 0;
}
Step 3:Understanding the check
Function Logic
check
Function LogicIt validates the bitstream of the 27-byte password (
0x1b = 27
).A hardcoded 23-byte array is used as a bitmask reference.
The user input is treated bit by bit (not byte-by-byte).
It compares 184 bits from the user input (23×8) against this hardcoded bitstream.
Example:
Let’s say the first character of input is 'p'
.
'p'
→ ASCII112
→ Binary:01110000
The function compares certain bits of
'p'
to the first bits of the hardcoded array.
Step 4: Map bits → reverse the check
For each bit in the stored 23-byte sequence:
Decide which bit position in the result buffer it maps to.
Remember:
You skip bit 0 (MSB)
You fill bits 1 through 7 of each byte
After reaching 7 bits, move to next byte
So the filling order for result[0] will be bits 6 → 0 (because you're filling from MSB down, but skipping 7th bit)
Repeat this for 184 bits.
stored_bit = [ 0xe1,0xa7,0x1e,0xf8,0x75,0x23,0x7b
,0x61,0xb9,0x9d,0xfc,0x5a,0x5b,0xdf
,0x69,0xd2,0xfe,0x1b,0xed,0xf4,0xed
,0x67,0xf4]
result = [0]*28 # Result Buffer
# rev eng logic
var_20_1 = 0
index = 0
for i in range(0,23):
for j in range(0,8):
if var_20_1 ==0:
var_20_1=1
temp = ( stored_bit[i] & 1 << ( 7 - j )) > 0;
if temp: #change bit when it is one
result[index] |= (1 << (7-var_20_1));
var_20_1+=1
if var_20_1==8:
var_20_1=0
index+=1
if index==27:
print(completed)
# print he rev eng flag
print("".join([chr(x) for x in result]))
After running the script we got out flag

Last updated