Every Bit Counts
Decompile the program with ghidra
we got a super long if-else
block in main function. It takes 0x34 bytes of input, if every position of input meet the requirements, we found the flag.
A simple way to figure out the correct input is to mimic the check process. We only look into those != 0
part of check, translate it to |=
for corresponding positions. There are 233 of non-zero check by greap
.
unsigned char param[0x34] = {0};
param[0x24] |= 0x10;
param[0x2f] |= 0x20;
param[0x20] |= 0x20;
param[0x2b] |= 4;
param[8] |= 1;
param[0x2e] |= 4;
param[0x30] |= 0x10;
...
In case of missing bytes, we place palceholders for those unset.
for (int i = 0; i < sizeof(param); ++i) {
if (param[i] == 0)
printf("+");
else
printf("%c", param[i]);
}
In this way we got an incomplete output.
But the missing part is easy to guess, change the first 3 chars to “CTF”, here comes the flag.
./every_bit_counts CTFlearn{w0w_you_f0und_My_fl@g_y0u_Ar3_so_much_n1c3}
Wow you found my flag!
Here is an interesting solution from CTFLearn community.
#/usr/bin/env python3
import sys
import time
pwd = bytearray('.' * 52, 'utf8')
with open('every_bit_counts', 'rb') as f:
f.seek(0x486)
while True:
code = f.read(11)
if code != b'\x48\x8b\x45\xf0\x48\x83\xc0\x08\x48\x8b\x08': # MOV+ADD+MOV
break
code = f.read(3) # ADD (optional) + MOVSX
if code == b'\x48\x83\xc1':
offset = int.from_bytes(f.read(1), 'big')
code = f.read(3)
else:
offset = 0
code = f.read(3) # AND (3 or 6 bytes)
mask = code[2]
if mask == 0x80:
f.read(3)
code = f.read(9) # CMP + JN/JNZ
if code[4] == 0x85: # JNZ
pwd[offset] &= ~mask
else:
pwd[offset] |= mask
print(pwd.decode('utf8'), end='\r')
time.sleep(0.02)
sys.stdout.flush()
print()