Skip to main content

Investigative Reversing 0

  • Description: We have recovered a binary and an image. See what you can make of it. There should be a flag somewhere.
  • Difficulty: Hard

🔎 Solution

In this challenge, we are provided with 2 files: a mystery executable and an image file.

mystery: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=34b772a4f30594e2f30ac431c72667c3e10fa3e9, not stripped

To analyze the mystery executable, I used Ghidra. Upon decompiling the main function, I identified the following logic:

  • It reads 26 bytes from a file named flag.txt.
  • These 26 bytes are written to the end of mystery.png, but not in a straightforward way:
    • Bytes 0-5 are written as-is
    • Bytes 6-14 are each incremented by 5 before being written
    • Byte 15 is modified by subtracting 3 before storing in local_29
    • Bytes 16-25 are written as-is
void main(void)

{
FILE *__stream;
FILE *__stream_00;
size_t sVar1;
long in_FS_OFFSET;
int local_54;
int local_50;
char local_38 [4];
char local_34;
char local_33;
char local_29;
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
__stream = fopen("flag.txt","r");
__stream_00 = fopen("mystery.png","a");
if (__stream == (FILE *)0x0) {
puts("No flag found, please make sure this is run on the server");
}
if (__stream_00 == (FILE *)0x0) {
puts("mystery.png is missing, please run this on the server");
}
sVar1 = fread(local_38,0x1a,1,__stream);
if ((int)sVar1 < 1) {
/* WARNING: Subroutine does not return */
exit(0);
}
puts("at insert");
fputc((int)local_38[0],__stream_00);
fputc((int)local_38[1],__stream_00);
fputc((int)local_38[2],__stream_00);
fputc((int)local_38[3],__stream_00);
fputc((int)local_34,__stream_00);
fputc((int)local_33,__stream_00);
for (local_54 = 6; local_54 < 0xf; local_54 = local_54 + 1) {
fputc((int)(char)(local_38[local_54] + '\x05'),__stream_00);
}
fputc((int)(char)(local_29 + -3),__stream_00);
for (local_50 = 0x10; local_50 < 0x1a; local_50 = local_50 + 1) {
fputc((int)local_38[local_50],__stream_00);
}
fclose(__stream_00);
fclose(__stream);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

With this logic, we can reverse the process to recover the flag from the image file:

  • Read the last 26 bytes from mystery.png.
  • Recover the original bytes:
    • Bytes 0-5: unchanged
    • Bytes 6-14: subtract 5 from each to get original values
    • Byte 15: add 3 to recover local_29
    • Bytes 16-25: unchanged
  • Reconstruct the flag using the structure local_38[0..3] + local_38[6..14] + local_29 + local_38[16..25]. This effectively rebuilds almost all of the original local_38 buffer.

Finally, I wrote a simple Python script to automate this extraction and reconstruction process.

def extract_flag_from_mystery():
with open("mystery.png", "rb") as f:
data = f.read()
flag_bytes = data[-26:]

recovered = bytearray()
recovered.extend(flag_bytes[0:4]) # byte 0 → 3
recovered.append(flag_bytes[4]) # byte 4
recovered.append(flag_bytes[5]) # byte 5

# byte 6 → 14
for i in range(6, 15):
recovered.append((flag_bytes[i] - 5) % 256)

# byte 15
recovered.append((flag_bytes[15] + 3) % 256)

# byte 16 → 25
recovered.extend(flag_bytes[16:26])
print(recovered.decode())

extract_flag_from_mystery()

After running the script, we successfully recovered the flag.

🚩Flag

picoCTF{f0und_1t_fb51c821}