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 1
Survey the archive contentsObservationI noticed the challenge name 'B1g MAC' hints at NTFS MAC timestamps rather than file content, which suggested examining the archive structure and the included executable to understand how metadata could conceal 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 contain only raw pixel data - strings outputs noise, not flag characters. The flag is not inside file content at all; it is encoded in the last-write timestamp metadata of each Copy file stored in the ZIP central directory. Inspecting file content is the wrong layer entirely.
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 steganography channels (LSB planes, DCT coefficients, palette tricks). Neither tool reads NTFS Extra Fields from a ZIP archive. The challenge hides data in file system metadata, not in image pixel values, so stego tools return no useful output.
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 2
Understand the encoding in main.exeObservationI noticed main.exe is a 32-bit Windows PE binary included alongside 18 BMP files (9 originals and 9 copies), which suggested the executable encodes the flag into the Copy files' metadata and that reverse engineering it in Ghidra would reveal the exact encoding 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 exists in the binary but is never invoked by the normal execution path. Running it normally re-encodes files (if they exist) or exits silently; it never prints the flag. You must either redirect execution to _decode in a debugger or extract timestamps directly from the ZIP.
Tried: Search the Ghidra decompiler output for a hardcoded flag string using the string search.
The flag is not stored as a literal string in the binary - it is reconstructed at runtime by reading bytes from file timestamps. Ghidra's string search and defined-strings window will not surface the flag value because it was never embedded as a constant in the executable.
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 3
Extract the flag from the ZIP timestamps (Linux, no Windows required)ObservationI noticed that the Ghidra analysis confirmed flag bytes are stored in the low 16 bits of each Copy file's LastWriteTime FILETIME field inside the ZIP's NTFS Extra Field (tag 0x000a), which suggested parsing those raw extra bytes directly with Python's zipfile module to recover the flag without needing Windows or a debugger.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 16 bytes past the start of that field to reach the 8-byte LastWriteTime. Take bytes at offsets 0 and 1 (the two lowest bytes, stored little-endian), swap them (byte 1 is the high character, byte 0 is the low character), and 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 & 0xff)bashflag += chr((lw >> 8) & 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 both display timestamps in human-readable date format (e.g. 2019-03-12 14:23:10), which rounds or discards the sub-second precision where the flag bytes are stored. You need direct access to the raw 64-bit FILETIME value from the NTFS Extra Field, which requires 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 modifies the Copy files - the originals are left with unaltered timestamps that carry no flag data. The Python script filters for names containing 'Copy' specifically 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
- StegallDrop any file and Stegall runs every applicable steg technique in parallel: LSB sweeps, bit planes, spectrograms, polyglot carving, metadata, whitespace decode, and a 6-layer base/ROT/XOR/zlib cascade. Recursively unpacks results and surfaces flag matches.
- 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.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
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.