flag.txt seems to be an encrypted flag in hex format. This is a reverse challenge anyway, so let’s head over to Ghidra.
Ghidra
This is the decompiled main function. Variables are renamed for readability.
int main(int argc,char **argv)
{
char jumble_result;
byte shift;
int is_valid;
long in_FS_OFFSET;
int while_idx;
int i;
char input_key [100];
undefined _;
char output [104];
long _stack_canary;
_stack_canary = *(long *)(in_FS_OFFSET + 0x28);
if (argc < 2) {
printf("USAGE: %s [KEY]\n",*argv);
is_valid = 1;
}
else {
strncpy(input_key,argv[1],100);
_ = 0;
while_idx = 0;
while( true ) {
is_valid = valid_char(input_key[while_idx]);
if (is_valid == 0) break;
if (while_idx == 0) {
jumble_result = jumble();
shift = (byte)(jumble_result >> 7) >> 4;
output[0] = (jumble_result + shift & 0xf) - shift;
}
else {
jumble_result = jumble();
shift = (byte)((int)jumble_result + (int)output[while_idx + -1] >> 0x37);
output[while_idx] =
((char)((int)jumble_result + (int)output[while_idx + -1]) + (shift >> 4) & 0xf) -
(shift >> 4);
}
while_idx = while_idx + 1;
}
for (i = 0; i < while_idx; i = i + 1) {
output[i] = output[i] + 'a';
}
if (while_idx == 100) {
is_valid = strncmp(output,
"lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmad dhngbgbhobgnofjgeaomadbidl"
,100);
if (is_valid == 0) {
puts("You got the key, congrats! Now xor it with the flag!");
is_valid = 0;
goto LAB_001009ea;
}
}
puts("Invalid key!");
is_valid = 1;
}
LAB_001009ea:
if (_stack_canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return is_valid;
}
So the program takes an input of 100 characters, do some transformation and compare with the target string. The valid_char function restricts the input characters to be only one of 0123456789abcdef. The jumble function does some computation to initial input.
char jumble(char param_1)
{
byte ZERO;
char local_c;
local_c = param_1;
if ('`' < param_1) {
local_c = param_1 + '\t';
}
ZERO = (byte)(local_c >> 7) >> 4;
local_c = ((local_c + ZERO & 0xf) - ZERO) * '\x02';
if ('\x0f' < local_c) {
local_c = local_c + '\x01';
}
return local_c;
}
Note: I don’t know why when decompiling
main, there arejumble()calls without arguments.
Attack
The solution is simple. Since we have the decompiled code, we can just implement our version of jumble and all the other transformation with guessing mechanism. I don’t know why but I struggled through this.
After getting the key, xor the key with flag.txt and get the real flag.
The following is my solution script
target = [ord(x) - ord('a') for x in "lfmhjmnahapkechbanheabbfjladhbplbnfaijdajpnljecghmoafbljlaamhpaheonlmnpmaddhngbgbhobgnofjgeaomadbidl"]
valid_input = "0123456789abcdef"
output = [0] * 100
key = [0] * 100
def jumble(c: str) -> int:
assert len(c) == 1
local_c = ord(c)
if (ord('`') < local_c): local_c = local_c + ord('\t')
somevar = (local_c >> 7) >> 4
local_c = ((local_c + somevar & 0xf) - somevar) * 0x02
if (0xf < local_c): local_c = local_c + 1
return local_c
for i in range(100):
for c in valid_input:
if i == 0:
a = jumble(c)
shift = (a >> 7) >> 4
res = (a + shift & 0xf) - shift
if (res == target[0]):
print(f"Found key[0] = {c}")
output[i] = res
key[i] = c
break
else:
a = jumble(c)
shift = a + output[i-1] >> 0x37
res = (a + output[i-1]) + (shift >> 4) & 0xf - (shift >> 4)
if (res == target[i]):
print(f"Found key[{i}] = {c}")
output[i] = res
key[i] = c
break
final_key = ''.join(key)
print(f"Key = {final_key}")
with open("flag.txt", "r") as f:
flag_enc = f.read()
flag_xor = int(flag_enc, 16) ^ int(final_key, 16)
flag_str = hex(flag_xor)[2:]
flag = "".join([chr(int(flag_str[i:i+2], 16)) for i in range(0, len(flag_str), 2)])
print(f"Flag = {flag}")
Note: Some writeups use
ltraceoutput as an oracle to bruteforce the key. That’s also a good strategy if you don’t want to deal with the logic within the program.