CSAW CTF 2020 Quals - Crypto Challenges - LostMyPlaintext

Writeups for some of the crypto challenges from this years CSAW CTF.

Perfect Secrecy:

For this challenge we're given two images:



The challenge description implies both images were encrypted with a one-time pad (hence "perfect secrecy") however it also says that the same key was used on both images. In other words:

  • image1 = originalImage1 XOR key
  • image2 = originalImage2 XOR key

Meaning that if we XOR the two images we get:

  • image1 XOR image2 = originalImage1 XOR key XOR originalImage2 XOR key
  • or
  • image1 XOR image2 = originalImage1 XOR originalImage2

We can use the PIL python module to do this but personally I just threw the images into this website: https://online-image-comparison.com

And got the following result:

The flag is the base64 encoded string on the image, all we gotta do is decode it.

FLAG: flag{0n3_t1m3_P@d!}


For this challenge we get the following files:




Mr. Jock, TV quiz PhD., bags few lynx
Two driven jocks help fax my big quiz.
Jock nymphs waqf drug vex blitz
Fickle jinx bog dwarves spy math quiz.
Crwth vox zaps qi gym fjeld bunk
Public junk dwarves hug my quartz fox.
Quick fox jumps nightly above wizard.
Hm, fjord waltz, cinq busk, pyx veg
phav fyx bugs tonq milk JZD CREW
Woven silk pyjamas exchanged for blue quartz.
The quick onyx goblin jumps over the lazy dwarf.
Foxy diva Jennifer Lopez wasn’t baking my quiche.
he said 'bcfgjklmnopqrtuvwxyz'
Jen Q. Vahl: bidgum@krw.cfpost.xyz
Brawny gods just flocked up to quiz and vex him.
Emily Q Jung-Schwarzkopf XTV, B.D.
My girl wove six dozen plaid jackets before she quit.
John 'Fez' Camrws Putyx. IG: @kqBlvd
Q-Tip for SUV + NZ Xylem + DC Bag + HW?....JK!
Jumbling vext frowzy hacks pdq
Jim quickly realized that the beautiful gowns are expensive.
J.Q. Vandz struck my big fox whelp
How razorback-jumping frogs can level six piqued gymnasts!
Lumpy Dr. AbcGQVZ jinks fox thew
Fake bugs put in wax jonquils drive him crazy.
The jay, pig, fox, zebra, and my wolves quack!
hey i am nopqrstuvwxzbcdfgjkl
Quiz JV BMW lynx stock derp. Agh! F.
Pled big czar junks my VW Fox THQ
The big plump jowls of zany Dick Nixon quiver.
Waltz GB quick fjords vex nymph
Cozy lummox gives smart squid who asks for job pen.
Few black taxis drive up major roads on quiet hazy nights.
a quick brown fx jmps ve th lzy dg
Bored? Craving a pub quiz fix? Why, just come to the Royal Oak!

From the title and description we conclude this must be related to the classic cipher bifid. Understanding how this cipher works shouldn't be particularly hard, however notice that the number of solves was quite low especially considering it's a 50 point challenge. This was because there is a bit of guessing involved from this point onward. Among the ramblings file we see that there are a few lines that are 26 characters long (if we remove spaces and punctuation) and have exactly one of each letter in them. The Bifid cipher uses keys that are 25 characters long and contain exactly one of each letter (normally with 'j' being omitted). So I first thought that one these lines must be the correct key (after removing spaces and such of course) but this is not the case. This is where I'd guess most people got stuck. With a hint that was posted later it became clear that all apparent keys (the lines with the 26 different letters) where used (not just one) in the order they appear in the ramblings. This means that to get the original message we need to decrypt the message we where given using the last key in the ramblings file, then decrypt the result using the second to last key and so on. Here's the first script I wrote to do so:

from pycipher import Bifid

message = "snbwmuotwodwvcywfgmruotoozaiwghlabvuzmfobhtywftopmtawyhifqgtsiowetrksrzgrztkfctxnrswnhxshylyehtatssukfvsnztyzlopsv"

ramblings = ['MrJockTVquizPhDbagsfewlynx', 'Twodrivenjockshelpfaxmybigquiz', 'Jocknymphswaqfdrugvexblitz', 'Ficklejinxbogdwarvesspymathquiz', 'Crwthvoxzapsqigymfjeldbunk', 'Publicjunkdwarveshugmyquartzfox', 'Quickfoxjumpsnightlyabovewizard', 'Hmfjordwaltzcinqbuskpyxveg', 'phavfyxbugstonqmilkJZDCREW', 'Wovensilkpyjamasexchangedforbluequartz', 'Thequickonyxgoblinjumpsoverthelazydwarf', 'FoxydivaJenniferLopezwasntbakingmyquiche', 'hesaidbcfgjklmnopqrtuvwxyz', 'JenQVahlbidgumkrwcfpostxyz', 'Brawnygodsjustflockeduptoquizandvexhim', 'EmilyQJungSchwarzkopfXTVBD', 'Mygirlwovesixdozenplaidjacketsbeforeshequit', 'JohnFezCamrwsPutyxIGkqBlvd', 'QTipforSUVNZXylemDCBagHWJK', 'Jumblingvextfrowzyhackspdq', 'Jimquicklyrealizedthatthebeautifulgownsareexpensive', 'JQVandzstruckmybigfoxwhelp', 'Howrazorbackjumpingfrogscanlevelsixpiquedgymnasts', 'LumpyDrAbcGQVZjinksfoxthew', 'Fakebugsputinwaxjonquilsdrivehimcrazy', 'Thejaypigfoxzebraandmywolvesquack', 'heyiamnopqrstuvwxzbcdfgjkl', 'QuizJVBMWlynxstockderpAghF', 'PledbigczarjunksmyVWFoxTHQ', 'ThebigplumpjowlsofzanyDickNixonquiver', 'WaltzGBquickfjordsvexnymph', 'qwertyuioplkjhgfdsazxcvbnm', 'Cozylummoxgivessmartsquidwhoasksforjobpen', 'zyxwvutsrqponmlkjihgfedcba', 'Fewblacktaxisdriveupmajorroadsonquiethazynights', 'aquickbrownfxjmpsvethlzydg', 'BoredCravingapubquizfixWhyjustcometotheRoyalOak']

keys = []
for r in ramblings[::-1]:
	if len(r) == 26:

def main():
	ct = message
	for key in keys:
		ct = Bifid(key,5).decipher(ct)
	print (ct)

if __name__ == "__main__":



I first replaces the first 'X' with a 'J' as that seems like what would be intended and sent the result to the server:

I then tried to replace the "X"s with spaces and send everything in lower case, and this got me the flag:

Final solver:

from pycipher import Bifid
from pwn import *

message = "snbwmuotwodwvcywfgmruotoozaiwghlabvuzmfobhtywftopmtawyhifqgtsiowetrksrzgrztkfctxnrswnhxshylyehtatssukfvsnztyzlopsv"

ramblings = ['MrJockTVquizPhDbagsfewlynx', 'Twodrivenjockshelpfaxmybigquiz', 'Jocknymphswaqfdrugvexblitz', 'Ficklejinxbogdwarvesspymathquiz', 'Crwthvoxzapsqigymfjeldbunk', 'Publicjunkdwarveshugmyquartzfox', 'Quickfoxjumpsnightlyabovewizard', 'Hmfjordwaltzcinqbuskpyxveg', 'phavfyxbugstonqmilkJZDCREW', 'Wovensilkpyjamasexchangedforbluequartz', 'Thequickonyxgoblinjumpsoverthelazydwarf', 'FoxydivaJenniferLopezwasntbakingmyquiche', 'hesaidbcfgjklmnopqrtuvwxyz', 'JenQVahlbidgumkrwcfpostxyz', 'Brawnygodsjustflockeduptoquizandvexhim', 'EmilyQJungSchwarzkopfXTVBD', 'Mygirlwovesixdozenplaidjacketsbeforeshequit', 'JohnFezCamrwsPutyxIGkqBlvd', 'QTipforSUVNZXylemDCBagHWJK', 'Jumblingvextfrowzyhackspdq', 'Jimquicklyrealizedthatthebeautifulgownsareexpensive', 'JQVandzstruckmybigfoxwhelp', 'Howrazorbackjumpingfrogscanlevelsixpiquedgymnasts', 'LumpyDrAbcGQVZjinksfoxthew', 'Fakebugsputinwaxjonquilsdrivehimcrazy', 'Thejaypigfoxzebraandmywolvesquack', 'heyiamnopqrstuvwxzbcdfgjkl', 'QuizJVBMWlynxstockderpAghF', 'PledbigczarjunksmyVWFoxTHQ', 'ThebigplumpjowlsofzanyDickNixonquiver', 'WaltzGBquickfjordsvexnymph', 'qwertyuioplkjhgfdsazxcvbnm', 'Cozylummoxgivessmartsquidwhoasksforjobpen', 'zyxwvutsrqponmlkjihgfedcba', 'Fewblacktaxisdriveupmajorroadsonquiethazynights', 'aquickbrownfxjmpsvethlzydg', 'BoredCravingapubquizfixWhyjustcometotheRoyalOak']

keys = []
for r in ramblings[::-1]:
	if len(r) == 26:

def main():
	ct = message
	for key in keys:
		ct = Bifid(key,5).decipher(ct)
	#print (ct)
	ct = ct.lower().replace("x"," ")
	ct = "j"+ct[1:24]+'x'+ct[25:]

	r = remote("crypto.chal.csaw.io", 5004)

if __name__ == "__main__":

FLAG: flag{t0ld_y4_1t_w4s_3z}


For this challenge we only get a server connection that asks for input, encrypts it with either AES ECB Mode or AES CBC Mode, gives back the corresponding ciphertext and asks us to guess which mode was used. If we get correctly it repeats this process, if not we lose the connection. First let's take a look at how AES ECB encryption works and at how AES CBC encryption works:

AES ECB Mode - Encryption:

AES CBC Mode - Encryption:

Notice how in ECB our plaintext is simply divided in a number of blocks of the same length and encrypted block by block. Given a plaintext block, the AES algorithm by itself, will always produce the same ciphertext as long as the same key is used and in AES ECB mode the same key is used for each block. Consider the following example:

  • Let's say we want to encrypt this plaintext: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
  • (that's 32 'A's)
  • And we use AES ECB mode with a block size of 16 bytes
  • We'd first encrypt a block of 16 'A's and then a second block of 16 'A's
  • Since we're using the same key for both blocks and we're encrypting the exact same plaintext in each block (16 'A's) we'll end up with two encrypted blocks that look exactly the same.
  • So our 32 byte ciphretext will be the concatenation of two indentical 16 byte sequences

This means if we find repeating patterns (with lenght equal to the block size used) in our ciphertext it is very likely that the AES mode used was ECB. Note that because an initialization vector and XOR operations are used in CBC, this mode is very very unlikely to produce such patterns.

Something important to note about this challenge is that the first answer the server is expecting is always 'ECB' (it would close the connection if we answered 'CBC'). Knowing the first mode used by the server will always be ECB we can send large strings of 'A's and look for repeating patterns in order to figure out the block size. I first tried sending 32 'A's and didn't get any pattern in the response (considering the standard size for and AES block is 16 bytes this was a little strange). But it turns out that when sending 64 'A's we in fact got a repating pattern of 32 bytes meaning the server is using 32 byte blocks. Knowing all of this we can write a script that repeatedly sends a plaintext of 64 'A's, looks for repeating blocks in the corresponding ciphertexts and answers back with 'ECB' if it finds repeating blocks and 'CBC' if it doesn't:

import binascii

from pwn import *


pt = "A"*64

r = remote("crypto.chal.csaw.io", 5001)

def AES_ECB_Score(ct):
	ct = binascii.unhexlify(ct)
	blocks = [ct[i:i + BLOCK_SIZE] for i in range(0, len(ct), BLOCK_SIZE)]
	score = len(blocks) - len(set(blocks))
	return score

while True:
	r.recvuntil("Enter plaintext:")


	r.recvuntil("Ciphertext is: ")

	ct = r.recvline()[1:-1]

	print (ct)

	if AES_ECB_Score(ct) > 0:
		print ("sending 'ECB'")

		print ("sending 'CBC'")
	print (str(i+1) + " correct answers")


Now this script would always crash after sending arround 176 plaintexts and the server didn't seem to be printing the flag after a given number of correct answers.

A fellow team member thought that since the server always used ECB on the first plaintext, CBC on the second and so on that maybe the modes used where a binary encoding of the flag, as in every 'ECB' answer represents a 0 and every 'CBC' answer represents a 1 (or the other way arround). Turns out this is exactly what was happening.

New solver:

import binascii

from pwn import *


pt = "A"*64

r = remote("crypto.chal.csaw.io", 5001)

def AES_ECB_Score(ct):
	ct = binascii.unhexlify(ct)
	blocks = [ct[i:i + BLOCK_SIZE] for i in range(0, len(ct), BLOCK_SIZE)]
	score = len(blocks) - len(set(blocks))
	return score

bits0 = ""
bits1 = ""

for i in range(0,176):
	r.recvuntil("Enter plaintext:")


	r.recvuntil("Ciphertext is: ")

	ct = r.recvline()[1:-1]

	print (ct)

	if AES_ECB_Score(ct) > 0:
		print ("sending 'ECB'")
		bits0 += "0"
		bits1 += "1"
		print ("sending 'CBC'")
		bits0 += "1"
		bits1 += "0"
	print (str(i+1) + " correct answers")

print (binascii.unhexlify(hex(int(bits0,2))[2:].encode()))
print (binascii.unhexlify(hex(int(bits1,2))[2:].encode()))

FLAG: flag{ECB_re@lly_sUck$}


For this challenge we the following server code:

#!/usr/bin/env python3
import struct
import hashlib
import base64
import flask

# flag that is to be returned once authenticated
FLAG = ":p"

# secret used to generate HMAC with
SECRET = ":p".encode()

app = flask.Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def home():
    return """
This is a secure and private note-taking app sponsored by your favorite Nation-State.
For citizens' convenience, we offer to encrypt your notes with OUR own password! How awesome is that?
Just give us the ID that we generate for you, and we'll happily decrypt it back for you!

Unfortunately we have prohibited the use of frontend design in our intranet, so the only way you can interact with it is our API.


        Adds a new note and uses our Super Secure Cryptography to encrypt it.

        :author: your full government-issued legal name
        :note: the message body you want to include. We won't read it :)

        :id: an ID protected by password  that you can use to retrieve and decrypt the note.
        :integrity: make sure you give this to validate your ID, Fraud is a high-level offense!

        View and decrypt the contents of a note stored on our government-sponsored servers.

        :id: an ID that you can use to retrieve and decrypt the note.
        :integrity: make sure you give this to validate your ID, Fraud is a high-level offense!

        :message: the original unadultered message you stored on our service.

@app.route("/new", methods=["POST"])
def new():
    if flask.request.method == "POST":

        payload = flask.request.form.to_dict()
        if "author" not in payload.keys():
            return ">:(\n"
        if "note" not in payload.keys():
            return ">:(\n"

        if "admin" in payload.keys():
            return ">:(\n>:(\n"
        if "access_sensitive" in payload.keys():
            return ">:(\n>:(\n"

        info = {"admin": "False", "access_sensitive": "False" }
        info["entrynum"] = 783

        infostr = ""
        for pos, (key, val) in enumerate(info.items()):
            infostr += "{}={}".format(key, val)
            if pos != (len(info) - 1):
                infostr += "&"

        infostr = infostr.encode()

        identifier = base64.b64encode(infostr).decode()

        hasher = hashlib.sha1()
        hasher.update(SECRET + infostr)
        return "Successfully added {}:{}\n".format(identifier, hasher.hexdigest())

@app.route("/view", methods=["POST"])
def view():

    info = flask.request.form.to_dict()
    if "id" not in info.keys():
        return ">:(\n"
    if "integrity" not in info.keys():
        return ">:(\n"

    identifier = base64.b64decode(info["id"]).decode()
    checksum = info["integrity"]

    params = identifier.replace('&', ' ').split(" ")
    note_dict = { param.split("=")[0]: param.split("=")[1]  for param in params }

    encode = base64.b64decode(info["id"]).decode('unicode-escape').encode('ISO-8859-1')
    hasher = hashlib.sha1()
    hasher.update(SECRET + encode)
    gen_checksum = hasher.hexdigest()

    if checksum != gen_checksum:
        return ">:(\n>:(\n>:(\n"

        entrynum = int(note_dict["entrynum"])
        if 0 <= entrynum <= 10:

            if (note_dict["admin"] not in [True, "True"]):
                return ">:(\n"
            if (note_dict["access_sensitive"] not in [True, "True"]):
                return ">:(\n"

            if (entrynum == 7):
                return "\nAuthor: admin\nNote: You disobeyed our rules, but here's the note: " + FLAG + "\n\n"
                return "Hmmmmm...."

            return """\nAuthor: {}
Note: {}\n\n""".format(note_dict["author"], note_dict["note"])

    except Exception:
        return ">:(\n"

if __name__ == "__main__":

Basically we have a /new endpoint that let's us send an 'author' name and a 'note' to the server and gives us back and ID for said note, essentially a base64 encoding of something that looks like this...


...and an integrity value. We can then send both of these back to the server through the /view endpoint and read our note. From the following part of the server code...

            if (note_dict["admin"] not in [True, "True"]):
                return ">:(\n"
            if (note_dict["access_sensitive"] not in [True, "True"]):
                return ">:(\n"

            if (entrynum == 7):
                return "\nAuthor: admin\nNote: You disobeyed our rules, but here's the note: " + FLAG + "\n\n"
                return "Hmmmmm...."

...we can tell that we can get the flag if we produce a valid note that contains the following:

  • &admin=True&access_sensitive=True&entrynum=7

Obviously, the code won't allow us to just create a note with this string.

        payload = flask.request.form.to_dict()
        if "author" not in payload.keys():
            return ">:(\n"
        if "note" not in payload.keys():
            return ">:(\n"

        if "admin" in payload.keys():
            return ">:(\n>:(\n"
        if "access_sensitive" in payload.keys():
            return ">:(\n>:(\n"

The vulnerability lies on the use of SHA1 to create the integrity value:

        hasher = hashlib.sha1()
        hasher.update(SECRET + infostr)
        return "Successfully added {}:{}\n".format(identifier, hasher.hexdigest())

SHA1 is a hash function know to be vulnerable to Length Extension Attacks. Now there are a few implementation nuances to take into consideration if you wish to write code from scratch to do this so we're gonna go full script kiddy here and use haspumpy. The idea here is, we send some random note to the server, take the corresponding ID, decode it, add whatever data we want (in this case '&admin=True&access_sensitive=True&entrynum=7<'), encode it, and send it to the server along side the corresponding integrity hash value. To do this we also need to know the lenght of SECRET but we can just brute force it.


import base64
import hashpumpy
import requests

url = "http://crypto.chal.csaw.io:5003" 
new = url + "/new"
view = url + "/view"

def sendNotes(name,note):
	s = requests.Session()
	r = s.post(new, data = {"author" : name, "note" : note})
	return r.text[len("Successfully added "):]

def recvNotes(id,integrity):
	s = requests.Session()
	r = s.post(view, data = {"id" : id, "integrity" : integrity})
	return r.text

def main():
	id,integrity = sendNotes("Lost","MyPlaintext").split(":")
	integrity = integrity[:-1]
	original = base64.b64decode(id)
	for i in range(0,256):
		integ, id = hashpumpy.hashpump(integrity,original,"&admin=True&access_sensitive=True&entrynum=7",i)
		id = base64.b64encode(id.decode('ISO-8859-1').encode('unicode-escape'))
		r = recvNotes(id,integ)
		print ("Number of requests sent > " + str(i+1))
		if "flag" in r:
			print (r)

if __name__ == "__main__":

FLAG: flag{h4ck_th3_h4sh}


For this challenge we get a file containing the following:

   Problem Text:

While digging through system logs, Morpheus discovered machines on the local
network transmitting the following base64-encoded ciphertexts to an IP address
known to be under enemy control:






















Upon further investigation, the following script was found:

#!/usr/bin/env python2

import os

import Crypto.Cipher.AES
import Crypto.Util.Counter

from Messager import send

KEY = os.environ['key']
IV = os.environ['iv']

secrets = open('/tmp/exfil.txt', 'r')

for pt in secrets:
    # initialize our counter
    ctr = Crypto.Util.Counter.new(128, initial_value=long(IV.encode("hex"), 16))

    # create our cipher
    cipher = Crypto.Cipher.AES.new(KEY, Crypto.Cipher.AES.MODE_CTR, counter=ctr)

    # encrypt the plaintext
    ciphertext = cipher.encrypt(pt)

    # send the ciphertext

Unfortunately, the environment variables used for KEY and IV are no longer recoverable
and the file /tmp/exfil.txt has been deleted.

Use your knowledge of how AES in CTR mode work to decrypt the ciphertexts and find the flag.'''

Before we get into it I should point out that in concept this is the exact same challenge as cryptopals 20.

In the file we get a bunch of ciphertexts that were all encrypted with AES CTR (a usually pretty safe way to encrypt things). The problem here is that the same key and nonce pair was used to encrypt all of them. First let's get a basic idea of how CTR works.

The point of CTR mode is to transform a block cipher into a stream cipher. It achieves this by encrypting successive values of a nonce (an arbitrary number used only once) instead of the plaintext, and then XORing the result with the plaintext. Since the ciphertexts we have were encrypted with the same key and nonce pair, if we concatenate them we can decrypt the result through frequency analysis, as if it were encrypted using repeating key XOR.


import base64
import binascii
import os
import string

from Crypto.Cipher import AES
from math import ceil


ctBase64 = ['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']

freqs = {
      'A': 0.0651738,
      'B': 0.0124248,
      'C': 0.0217339,
      'D': 0.0349835,
      'E': 0.1241442,
      'F': 0.0197881,
      'G': 0.0158610,
      'H': 0.0492888,
      'I': 0.0558094,
      'J': 0.0009033,
      'K': 0.0050529,
      'L': 0.0331490,
      'M': 0.0202124,
      'N': 0.0564513,
      'O': 0.0596302,
      'P': 0.0137645,
      'Q': 0.0008606,
      'R': 0.0497563,
      'S': 0.0515760,
      'T': 0.0729357,
      'U': 0.0225134,
      'V': 0.0082903,
      'W': 0.0171272,
      'X': 0.0013692,
      'Y': 0.0145984,
      'Z': 0.0007836,
      ' ': 0.1918182

valid = string.ascii_uppercase + string.ascii_lowercase + " "

def singleByteXor(s,byte):
	return "".join(chr(s[i]^byte) for i in range(0,len(s)))

def score(s):
	sc = 0
	for c in s:
		if c in valid:
			sc += freqs[c.upper()]
	return sc

def xorStrings(data0, data1):
	ret = b"".join(binascii.unhexlify(hex(data0[i]^data1[i])[2:].encode().rjust(2,b'0')) for i in range(min(len(data0),len(data1))))
	return ret

def decrypt(ct,key,nonce):
	aes = AES.new(key, AES.MODE_ECB)
	keystream = b"".join(aes.encrypt(nonce+chr(ord(str(counter))-48).ljust(8,'\x00').encode()) for counter in range(ceil(len(ct)/BLOCK_SIZE)))
	return xorStrings(ct,keystream)

def getBlocks(ciphertexts, keySize):
	ret = []
	for i in range(keySize):
		block = b""
		for j in range(len(ciphertexts)):
			if i < len(ciphertexts[j]):
				block += bytes([ciphertexts[j][i]])
	return ret

def getKey(blocks):
	key = b""
	for block in blocks:
		maxScore = 0
		keyGuess = b""
		for i in range(0,256):
			curTry = singleByteXor(block,i)
			curScore = score(curTry)
			if curScore > maxScore:
				maxScore = curScore
				keyGuess = bytes([i])
		key += keyGuess
	return key

def main():
	ciphertexts = []
	for ct in ctBase64:

	keySize = len(max(ciphertexts, key=len))
	blocks = getBlocks(ciphertexts, keySize)
	keyGuess = getKey(blocks)
	for i in range(len(ciphertexts)):
		print (xorStrings(ciphertexts[i],keyGuess))

if __name__ == "__main__":


b"What is real? How do you define real? If you're talKing about what you can feel, what yox can smEll, what you Eanataste and see, then rea? is  :mply electrical signaAs inter~retEd by your brain."
b"Neo, sooner or later you're going to realize, just As I did, that there's a difference bhtween kNowing the patN,  nd walking the path."
b'The flag is: 4fb81eac0729a -- The flag is: 4fb81eac\x10729a -- The flag is: 4fb81eac0729a -  The flAg is: 4fb81eaE07s9a -- The flag is: 4fb8beac0da9a -- The flag is: 4fO81eac07<9a'
b'Message 86831. Test message 86831. Test message 868\x131. Test message 86831. Test message 56831. TEst message 86\x1e31o Test message 86831. Te t me  age 86831. Test messaJe 86831  TeSt message 86831.'
b'I am the Architect. I created the Matrix. I have beEn waiting for you. You have many que~tions aNd though the Vro"ess has altered your co=scio& ness, you remain irre[ocably fumaN. Ergo, some of my answNrs you wizl :nDerstand, som  of them you wilJ no;. Concurrentky, Phide 8oTr jirs! quEst on m5y be thi m st per;xne!t, y=u fay or 9ay no  r alizei i5 is . s& thE D ss it7e  vao= '
b'Attack at dawn. Use the address 37.9257 10.2036 193\x19.283 - Do not reply to this message.-Attack At dawn. Use tNe  ddress 37.9257 10.2036 b939.ak3 - Do not reply to tEis messoge.'
b'Have you ever had a dream Neo, that you were so surE was real? What if you were unable tb wake fRom that dream\x19 H.w would you know the di5fere=0e between the dream wBrld, anj thE real world?'
b'Which brings us at last to the moment of truth, wheRein the fundamental flaw is ultimateay expreSsed, and the gno,aly revealed as both be4inni=4 and end. There are tZo doors  ThE door to your right leaOs to the Eou=cE and the sal3ation of Zion. TNe d or to your lbft Keals #aBk xo t=e MAtr x, t; her anh t  the e!u o) you  s{ecies.'
b'Message 64023. Test message 64023. Test message 640\x123. Test message 64023. Test message ;4023. TEst message 64\x1623o Test message 64023. Te t me  age 64023. Test messaJe 64023  TeSt message 64023.'
b'Unfortunately, no one can be told what the Matrix iS. You have to see it for yourself. Teis is yOur last chancC. \x00fter this, there is no \'urni=4 back. You take the bAue pill" thE story ends, you wake u[ in your tedoaNd believe wh$tever you want tI be#ieve. You tale tOe zedapHll  yo  stAy  n Wo:derland  a!d I sh f y u ho% dnep thetrabbi  h*le go s.'
b'The Matrix is older than you know. I prefer countinG from the emergence of one integral lnomaly To the emergenEe .f the next, in which ca e th:  is the sixth version\x03'
b'The Matrix is a system, Neo. That system is our eneMy. But when you\'re inside, you look lround, What do you seC? \x03usiness men, teachers, ?awye! , carpenters. The verT minds af tHe people we are trying _o save. Bct :nTil we do, th se people are stOll . part of thas syTtee,  nE tdat 8akeS t!em o!r enemy" Y u haveoeo :nder!taed, mos  of t<es  peop)e  re n 8 ;eadY ]  ee s+p 0ggd- hAnD len- af Hh c a&   otN+ufed  6  -oAel sTaQ dep "E n  on A E \'8Sxe:  -ha  t EEr il# f g=& <O TrEttc  Htm'
b'The flag is: 4fb81eac0729a -- The flag is: 4fb81eac\x10729a -- The flag is: 4fb81eac0729a -  The flAg is: 4fb81eaE07s9a -- The flag is: 4fb8beac0da9a -- The flag is: 4fO81eac07<9a'
b'Sentient programs. They can move in and out of any Software still hard-wired to their sy~tem. ThAt means that Gny.ne we haven\'t unpluggedsis p<\'entially an Agent. In^ide the.MatRix, they are everyone aEd they ars n  One. We have 6urvived by hidinA fr m them, by rrnniIg nro, Uhea, b t tHeyiare  he gategee?ers. T\'ty .re g\'aroing al8 the 0oo7s, th y  re h  d ng AlEotoe m y?i wi m  mEaow  hot Oo*`ert*rsl5S r8 scm  n  Xs "oNcO to --W   o fiR T  )Ea.'
b'Zion Keys: 8 - F - A - Q - 1 - Z - R - Z - B - Z - r - R - R. Repeat: 8 - F - A - Q - 1   Z - R \r Z - B - Z - t -aR - R. Repeat: 8 - F - \x12 - Qs~ 1 - Z - R - Z - B - w - R - \\ - r'
b"I won't lie to you, Neo. Every single man or woman Who has stood their ground, everyone zho has Fought an agenR h s died. But where they ;ave 52iled, you will succeeI."
b'Please. As I was saying, she stumbled upon a solutiOn whereby nearly 99% of all test subgects acCepted the proAra, as long as they were g:ven 2schoice, even if they Zere onlw awAre of that choice at a Eear-unconeci uS level. Whil  this answer funEtio!ed, it was oevioRslq f4nEaminta9ly Fla>ed,  hus cremti!g the  ehe=wise\x7fcoetradic ory s-st mic a+om ly t\'-tiif LeO; rncn c\' d l i t Thsaa e` tTee}ys  msi T lr. Ir" ,etYos  SeIt re#9R dtthe E:Og& M   di5e 5 m!NS >tyc i/  <c EcOeN fo!lE     x 5    .; e t$ 5t: gw5 $  E                                                                                                                                                                                                 '
b"I've seen an agent punch through a concrete wall. MEn have emptied entire clips at them lnd hit Nothing but aiT. \x18et their strength and t;eir  #eed are still based iC a worlj thAt is built on rules. BeHause of t~atc They will nev r be as strong oT asofast as you dan Ee."
b'Everything that has a beginning has an end. I see tHe end coming. I see the darkness sprhading. i see death...\x06an% you are all that stand  in ;:s way.'
b"Test message 10592. Test message 10592. Test messagE 10592. Test message 10592. Test mes~age 105\x192. Test messaAe p0592. Test message 1059a. Te ' message 10592. Test @essage ?059\x12. Test message 10592."
b'As you adequately put, the problem is choice. But wE already know what you are going to io, don\'T we? Already o c n see the chain reactio=: th6schemical precursors tEat signol tHe onset of an emotion, Oesigned sfec&fIcally to ove7whelm logic and Teas n. An emotioi thFt as  lSeahy b9indIngiyou  o the semp#e and  svi us t utc: she =s goi:g 1o dieean% the=)  s nOtA&n` yi0 /$n e&.<o Stnt =t  HSp   I ei   O  euibt <s nEia) OxEan d  T6i;n, s\\%Ul  Nio"\x7fl  t<e ;OI 4e  f 0o   /ReEtOse \'tSe-(:; i ;0ey  r 4e - e :   3 ,$T:b'

By sending one of the "flags" from the output to the server we get the actuall flag.

FLAG: flag{m1ss1on_acc00mpl11shheedd!!}