Skip to content

Instantly share code, notes, and snippets.

@PAndaContron
Last active July 18, 2022 02:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PAndaContron/c1aee1bd3014754f227e5561283a1546 to your computer and use it in GitHub Desktop.
Save PAndaContron/c1aee1bd3014754f227e5561283a1546 to your computer and use it in GitHub Desktop.
GoogleCTF 2022 Appnote.txt Challenge Writeup

For this challenge, we get what looks like a zip archive called dump.zip with the hint "Every single archive manager unpacks this to a different file...". Unzipping normally gives us a single file called hello.txt which doesn't contain anything useful.

However, running binwalk dump.zip shows us another file called hi.txt, along with many other files named flag00 to flag18. Each flagNN is repeated several times. Binwalk also tells us that all of the files have the same compressed/uncompressed size, indicating that they're too small to be compressed, and that each flagNN is only 1 byte, indicating that each one probably represents a single character of the flag.

Running strings dump.zip suggests, based on looking at hello.txt, that each file's contents are stored immediately after the filename followed by a PK:

V~uK)
hello.txtThere's more to it than meets the eye...
V~uK)
hello.txtPK
hi.txtFind a needle in the haystack...
hi.txtPK
flag00aPK
flag00PK
flag00bPK
flag00PK
flag00cPK
flag00PK
...

Using this pattern, we can see that there are 36 files with each flagNN filename, and that each flagNN has the same 36 files. This suggests that those 36 characters are the characters used in the flag, and that some other information will tell us which of the 36 files is the right one for each character. Since zip files also store information about directory structure, it seems likely that that's what we need.

The root directory of a zip archive is called the "central directory", and it's identified by a 4-byte "end of central directory" magic number, 50 4b 05 06. (It's frequently written in little endian as 0x06054b50, but that's a little confusing because it's not the order the bytes show up in in the file). Our file dump.zip actually has around 20 end of central directory signatures. Zip files allow this, since it used to be common at one point to modify a zip file by adding a new central directory instead of changing the old one. Programs that read zip files will find the last end of central directory signature and use that one, as explained here.

Since we have around 20 central directories, and our flag files are numbered 0 to 18, it stands to reason that each character in the flag is represented by one central directory, which points to one of the flag files. Therefore, we just want to unzip each of the central directories. Since the standard unzip utility will use the last end of central directory signature, we can just delete each one starting from the end and call unzip in order to unzip all of them. The attached file sol.py does this, with comments explaining the process in more detail. This gives us a single file for each character, and putting those together gives us the flag.

#!/usr/bin/env python3
# The `dump.zip` file contains multiple central directory structures,
# each of which points to only a single file in the archive.
# By default, zip utilities/parsers scan from the end of the file to find the *last*
# end of central directory signature, and use that central directory.
# By deleting that signature from the last central directory,
# we can make the regular Linux `unzip` utility read the second-to-last central directory instead.
# We can repeat this process to get all of the files in all of the central directories extracted.
from os import system
# Read the zipfile and reverse it, so we can substitute starting from the end
with open('dump.zip', 'rb') as f: zf = f.read()[::-1]
while True:
# Overwrite the signature with zeroes
# Since we do this before unzipping the first time,
# we miss the first central directory,
# but that one doesn't contain anything important
zfnew = zf.replace(b'\x06\x05\x4b\x50', b'\0\0\0\0', 1)
# If nothing was replaced, there are no central directories left
if zfnew == zf: break
zf = zfnew
# Write the modified zip file out
with open('dump-ed.zip', 'wb') as f: f.write(zf[::-1])
# Unzip the file with the Linux `unzip` utility
# The `-o` flag overwrites existing files without asking,
# just in case we've previously extracted some
# We redirect the output because the program complains very loudly about what we're doing,
# even though it still works
system('unzip -o dump-ed.zip >/dev/null 2>&1')
# The glob will put the files in alphabetical order,
# which is the correct order in this case
system('cat flag*')
# Newline
print()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment