Description
Download the ZIP archive and recover the hidden flag. The challenge name is a pun: MAC stands for Modification, Access, Creation - the three NTFS timestamp types. The flag is not inside any file's content; it is encoded in the file metadata.
Setup
Download and unzip the archive.
wget <url>/b1g_mac.zipunzip b1g_mac.zipls -RSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Survey the archive contents
ObservationThe name 'B1g MAC' points at NTFS MAC timestamps rather than file content. So the thing to examine is the archive structure and the bundled executable, to see how metadata could be hiding the flag.After unzipping you will find main.exe (a 32-bit Windows PE binary) and a test/ folder containing 18 BMP images: 9 originals (Item01.bmp through Item09.bmp) and 9 copies (Item01 - Copy.bmp through Item09 - Copy.bmp). Run strings on the binary to get a first impression, then load it in Ghidra to read the source-level logic.bashfile main.exebashstrings main.exe | grep -i decodebashls test/Expected output
main.exe: PE32 executable (console) Intel 80386, for MS Windows
What didn't work first
Tried: Run strings on the BMP image files looking for embedded ASCII text that might be the flag.
The BMP files hold nothing but raw pixel data, so strings returns noise. The flag is not in file content at all: it is in the last-write timestamp metadata of each Copy file, stored in the ZIP central directory. Inspecting content is the wrong layer.
Tried: Use steghide or zsteg on the BMP images to look for steganographic payloads hidden in the pixel data.
steghide and zsteg probe pixel-level channels: LSB planes, DCT coefficients, palette tricks. Neither reads NTFS Extra Fields from a ZIP. The data here is in filesystem metadata, not pixel values, so stego tools find nothing.
Learn more
The challenge name tells you exactly where to look. In NTFS, every file carries three high-precision timestamps: Modification, Access, and Creation (MAC times). The FILETIME structure stores time as 100-nanosecond intervals since 1 January 1601, giving it sub-microsecond resolution. That precision is far more than wall-clock accuracy requires, so the spare low-order bits are usable as a covert data channel.
Step 2Understand the encoding in main.exe
Observationmain.exe is a 32-bit Windows PE shipped alongside 18 BMP files: 9 originals and 9 copies. The executable is presumably writing the flag into the copies' metadata, so reversing it in Ghidra should reveal the exact scheme.Ghidra reveals two key functions: _hideInFile (the encoder, called normally) and _decode (a dormant decoder that is never called). The encoder iterates over every other file in the test/ folder, takes two consecutive bytes of the flag, and stores them in the two least-significant bytes of each file's last-write time: timestamp = (timestamp & 0xffff0000) | (byte1 << 8) | byte2. The decoder reverses that operation. There are two ways to get the flag: redirect execution to _decode in a debugger, or parse the timestamps out of the ZIP central directory on Linux.bash# Option A: use a Windows debugger (x32dbg)bash# Set breakpoint at the call to _listdir inside mainbash# When hit, overwrite EIP with the address of _decode (0x00401AFE)bash# Resume - the program prints the flagWhat didn't work first
Tried: Run main.exe normally on Linux under Wine, hoping it prints the flag on its own.
main.exe only calls _hideInFile at runtime. The _decode function is present in the binary but nothing on the normal path ever invokes it, so running the program re-encodes files or exits quietly and never prints the flag. Either redirect execution to _decode in a debugger, or pull the timestamps out of the ZIP yourself.
Tried: Search the Ghidra decompiler output for a hardcoded flag string using the string search.
The flag is never a literal in the binary; it is rebuilt at runtime from bytes read out of file timestamps. Ghidra's string search and defined-strings window will not show it, because it was never stored as a constant.
Learn more
A ZIP file stores per-file metadata in its Central Directory. When the archive was created on Windows with NTFS, each entry received an NTFS Extra Field (tag 0x000a) containing three 64-bit FILETIME values: LastWriteTime, LastAccessTime, and CreationTime. Those fields are part of the ZIP on-disk structure, so you can read them on any OS without running main.exe at all.
The encoder overwrites only the low 16 bits of LastWriteTime, leaving the upper 48 bits intact so the timestamp still looks plausible. Extracting the low two bytes of that 8-byte field and interpreting them as two ASCII characters recovers two flag characters per BMP copy file. With 9 copy files you get 18 bytes, which is exactly the length of
picoCTF{M4cTim35!}.Step 3Extract the flag from the ZIP timestamps (Linux, no Windows required)
ObservationGhidra confirms the flag bytes live in the low 16 bits of each Copy file's LastWriteTime FILETIME, inside the ZIP's NTFS Extra Field (tag 0x000a). Those raw extra bytes can be parsed straight out with Python's zipfile module, so no Windows and no debugger are needed.The cleanest cross-platform approach parses the ZIP central directory directly. Each central directory entry for a Copy .bmp file contains an NTFS Extra Field; skip 12 bytes past the start of that field (2-byte tag, 2-byte field size, 4-byte reserved, 2-byte attribute tag, 2-byte attribute size) to reach the 8-byte LastWriteTime. Take the low 16 bits of that value and undo the encoder's (byte1 << 8) | byte2: bits 8 through 15 hold the first character of the pair, bits 0 through 7 hold the second. Append both to the flag string. Repeat for all 9 copy files in alphabetical order.pythonpython3 - <<'EOF'pythonimport zipfile, structbashflag = ''bashwith zipfile.ZipFile('b1g_mac.zip') as z:bashcopies = sorted(n for n in z.namelist() if 'Copy' in n and n.endswith('.bmp'))bashfor name in copies:bashextra = z.getinfo(name).extrabash# find NTFS extra field tag 0x000abashi = 0bashwhile i < len(extra):bashtag, size = struct.unpack_from('<HH', extra, i)bashif tag == 0x000a:bash# skip 4-byte reserved + 2-byte attr tag + 2-byte attr size = 8 bytesbashlw = struct.unpack_from('<Q', extra, i + 4 + 8)[0]bashflag += chr((lw >> 8) & 0xff)bashflag += chr(lw & 0xff)bashbreakbashi += 4 + sizepythonprint(flag)bashEOFThe script prints
picoCTF{M4cTim35!}. The title confirms the mechanic: MAC = Modification, Access, Creation times, and "B1g" is a playful spelling of "Big" referencing the Big Mac sandwich, tying the pun together.What didn't work first
Tried: Use unzip -v or zipinfo to read the timestamps, then parse the displayed date strings for the hidden bytes.
unzip -v and zipinfo print timestamps as human-readable dates, which round away the sub-second precision where the flag bytes live. You need the raw 64-bit FILETIME from the NTFS Extra Field, which means parsing the extra bytes yourself, as the Python script does.
Tried: Read the original Item01.bmp through Item09.bmp files instead of the Copy files when extracting timestamps.
The encoder only touches the Copy files; the originals keep unaltered timestamps that carry no data. The script filters for names containing 'Copy' because those are the 9 files whose last-write times were overwritten with flag bytes.
Learn more
Python's
zipfilemodule exposes the rawextrabytes for each central directory entry. The NTFS Extra Field layout is: 2-byte tag (0x000a), 2-byte field size, 4-byte reserved, then one or more attribute records each with a 2-byte attribute tag, 2-byte attribute size, and the data. Attribute tag 1 holds three 8-byte FILETIME values in order: LastWriteTime, LastAccessTime, CreationTime. Reading the first 8-byte value and extracting its bottom two bytes gives the two hidden ASCII characters.If you prefer not to parse raw bytes, the debugger route on Windows is equally valid: open main.exe in x32dbg, set a breakpoint right before the call to
_listdirinside the encode path, and when the breakpoint fires manually set EIP to the address of_decode. Resuming execution causes the binary to print the flag.
Interactive tools
- File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Image Metadata ViewerRead EXIF, XMP, JPEG comments, and PNG tEXt / iTXt / zTXt chunks from images entirely in the browser. Highlights flag-like values.
Flag
Reveal flag
picoCTF{M4cTim35!}
The flag is 18 characters, matching the 9 copy BMP files times 2 bytes each. MAC in the title stands for Modification, Access, Creation - the three NTFS timestamp fields used to hide the data.